Don't call it a comeback
(I've been here for years!) Here's a preview of coming attractions:
No, this is not running on Windows Mobile.
Nor does it share a single line of code with the current Pocket Scheme project.
posted at: 19:41 | path: /pscheme | permanent link to this entry
Pocket Scheme is hibernating
Last summer's projects never materialized, so nothing at all took place in this codebase. Witness the silence herein.
Realistically, I don't foresee anything happening in this codebase any time soon, which is a pity, as the guts of pscheme are still pretty embarrassing in many places. (Sure, it works. You just don't want to know how it works.) The problem is that I no longer use the CE platform, either personally or professionally.
Of my projects that are currently under active development*, one of them-- yes, yet another Scheme implementation, on another platform-- could conceivably donate some R5RS- and R6RS-enabling technology to Pocket Scheme, were I ever to resume developing this. So I'm hesitant to come out and declare Pocket Scheme as completely dead. Too, one of last summer's projects might possibly return to life, in which case I'd find myself again hacking CE. Otherwise, though, I should publicly declare this project as frozen. If some other crazy person wanted to take it over, they'd have my blessing as well as all of the help that I could offer.
*(Not being coy. I just don't care to hype things that don't yet work.
At an appropriate time I'll post something here.)
posted at: 13:52 | path: /pscheme | permanent link to this entry
Hygienic Cat Macros
(From the never-to-be-created icanhasche
posted at: 23:20 | path: /pscheme | permanent link to this entry
I've received enough reports of Pocket Scheme working on various WM-powered smartphones
to decide to claim Smartphone support.
I do this with some trepidation, since I have no Smartphone test coverage other than these anecdotal reports.
If a class of Smartphone-only bugs emerges, it's going to be just as much fun
as debugging Japanese-only memory manager flaws was in 1999....
posted at: 13:55 | path: /pscheme | permanent link to this entry
Planning a 1.4 release
Despite the "2.0: currently under development" claim on the front page, no development has taken place on Pocket Scheme since November 2006, the date of the last posting here. (Note to self: update front page.) And now I find myself needing some new features in the summer timeframe for a new project. So the 1.x codebase will receive yet another facelift.
In for certain:
The new project needs other long overdue Pocket Scheme features,
but I'm not confident that I can get enough test coverage on them to put them into a public release.
posted at: 18:04 | path: /pscheme | permanent link to this entry
I've completed a first cut at a proof of concept for a direct-threaded interpreter, implementing a amazing language capable of generating any list of the digits 1, 2, and 3. (Just look out, world. Next week: the digit 4!) One version of the interpreter runs on x86, where I could use inline assembly in my C compiler to manipulate the callstack while leaving the majority of the implementation in C: call this version the proof of concept of a proof of concept. The other version runs on my target processor, the ARM, where my compiler doesn't support any sort of inline assembly; there, the entire interpreter core is in assembly, calling back into the C-language pscheme kernel for consing and so forth. This offers a strong motivation to keep the VM small.
In the VM I described a couple of weeks ago, the FRAME and CONT registers can be unified,
CONT being just a couple more fields in the frame.
I was led astray by not originally realizing that even the topmost computation has a continuation,
and that my VM should make that outermost continuation explicit.
Oddly, I realized this immediately upon waking one morning.
posted at: 19:34 | path: /pscheme | permanent link to this entry
The 2.0 VM
I've spent the last couple of weeks hand-compiling Scheme programs, working out the details of the VM for the next release. I've ended up with a VM strongly reminiscent of the CEK abstract machine.
While this is a work in progress, it'll do me good to write it up here.
One advantage of keeping the activation chain separate from the process callstack is having Windows system calls interoperate nicely with reentrant continuations. In Pocket Scheme 1.x, FFI wndprocs and the interpreter share a common stack, which means that any windows running in the Scheme thread (i.e. any windows with a wndproc written in Scheme) must take care never to reenter themselves accidentally.
A frame is a multipurpose structure, responsible for saving the values of local variables, saving temporary computation results, and finally passing a set of parameters to a procedure. A frame starts life with a FRAME instruction, and has its elements set with FRAMESET instructions. It may stay in FRAME for a while, possibly as the tail of a chain of frames as created by subsequent FRAME operations, but eventually it will move into the ENV chain via PUSHENV, CALL, or TAILCALL. A frame ends its life via RETURN or POPENV, both of which immediately recycle the frame for subsequent FRAME calls, or a CALL or TAILCALL that makes it unreachable.
A frame is recycled by returning it to a pool from which subsequent FRAME instructions will reallocate it.
Operations that break the stack access pattern of a frame will mark it as nonrecyclable,
in which case the frame does not return to the pool, but instead is left for eventual GC.
The CLOSURE instruction will mark every frame in the current ENV as nonrecyclable.
Likewise, the procedure
will mark every frame in both ENV and CONT as nonrecyclable,
so that those frames last for the life of the reified continuation.
For this reason, the compiler attempts to elide CLOSURE operations wherever possible
(e.g. tail-calls to procedures defined via
let, but not
Issue: if VAR-ARITY has extended the environment, RETURN should recycle two frames.
let; otherwise, it is a Scheme
I always write JUMP with a symbolic offset, just as I do JUMPFALSE and CLOSURE. A LABEL pseudo-instruction gives a name to the target.
Closing over the environment marks every frame in the current environment as nonrecyclable.
I always write LITERAL with the explicit datum, just as I do GLOBDEF, GLOBSET, and GLOBREF.
The intent is for all -ARITY instructions eventually to emit procedure metadata that is interpreted by CALL
instead of performing the check inline.
This would allow a better error message in the case of a mismatch in a tail call,
allow a direct jump to the callsite for a
and for calls to statically determined sites,
would allow arity and type checks to be hoisted higher in the calltree.
Happy autumn equinox! I'm back.
1.3.2 publishes the registry access routines that I described last May, and corrects the bugs reported against 1.3.1, including a couple of problems in writing non-Unicode text output that have slept undetected since 1.2's release last year. (Thank you, George.)
1.3.x still doesn't support first-generation Pocket PC devices, which I suspect will now join Handheld PCs as an abandoned platform. This means that I no longer have test coverage for grayscale displays and non-ARM processors. (Actually, the Scheme engine itself works fine on old devices, but it never displays any output. Fixing this would involve experimenting with the HTML viewer on the PPC 2000 platform until it does the right thing there— and yet doesn't break every other platform's output.)
posted at: 16:49 | path: /pscheme | permanent link to this entry
Absolutely nothing has happened on the pscheme front for the last two months, and I foresee nothing else happening for quite a little while yet. The days are very long, and I am very busy.
We will return to life in the autumn, when I should have a chance to investigate how I broke PPC2000 support in 1.3, continue work on replacing my lame little Editor, and recover the progress I had made on a FFI to Microsoft's COM.
Have a good summer.
posted at: 08:34 | path: /pscheme | permanent link to this entry
New registry glue
New in the forthcoming 1.3.2, scheduled for release in late June: registry access.
w32:reg-key root path → key
Opens a key in the registry. The root can be an object returned by another call to
or one of the symbols
which correspond respectively to HKEY_LOCAL_MACHINE (HKLM),
and HKEY_CLASSES_ROOT (HKCR).
The path is a string that uses backslashes to separate parent and child keys
"Software\\Goetter\\Pocket Scheme" in Scheme syntax).
Returns a key object that can be passed to other registry routines.
w32:reg-key-add! key name
Adds a new subkey with the given name.
w32:reg-key-delete! key name
Removes the subkey with the given name.
w32:reg-key-names key → list of strings
Returns a list of the names of the subkeys of the given key.
w32:reg-value key name → value
Returns the value stored under the name in the key, or
#f if no such value exists in the key.
The type of the value will be a number, a string, a list of strings, or a raw vector,
depending on whether the registry value contains type
REG_DWORD, REG_SZ, REG_MULTI_SZ, or REG_BINARY.
w32:reg-value-names key → list of strings
Returns a list of the names of the values stored in the key.
w32:reg-value-alist key → association list
Returns an alist of every name and value stored in the key.
w32:reg-value-set! key name value
Saves the value under the given name in the key. Works like the converse of
with the exception of REG_MULTI_SZ, which I lazily didn't implement.
w32:reg-value-delete! key name
Removes the value with the given name.
posted at: 12:55 | path: /pscheme | permanent link to this entry
Progress to 1.3.1
I had hoped to release 1.3.1 today, but I'm tired and out of time. Instead I'll offer a brief status report of what has turned out to be the memory manager sub-release.
The D-S-W bug fell easily. Pocket Scheme has enormous conses (hmmm... that sounds a bit rude), a misfeature which I look forward to correcting in 2.0. I've been using what otherwise would be wasted padding space to track various attributes of different types: closed state of ports, read-only state of strings, various object ownership flags, even variable lookup address memoization. Since vectors and continuations didn't yet use that space, I now use it to track the additional state needed for marking a variable-sized node (per the Thorelli variant of D-S-W, q.v. Jones and Lins Garbage Collection, p. 84) With this change, the mark phase no longer recurses, and hence no longer blows the Scheme thread's runtime stack.
The objects that we're marking occupy three separate heaps: the cons heap, of fixed-sized cons cells; a vector heap, for storing strings and vectors; and a continuation heap, for saving pieces of the runtime stack that embody the current continuation. Prior to 1.3.1, only the cons heap adhered to the configured (Tools - Configure - Memory) size limit, with the other two heaps being allowed to grow without bound. Furthermore, on hibernation we would release memory from the cons heap, but would never do anything with the vector or continuation heaps. Hence a deeply recursing program could hog all of the device's memory, then would refuse to release it gracefully, eventually necessitating that the system kill pscheme. Like the D-S-W recursion bug, this is a problem that only became manifest in 1.2, when such deep recursion became possible through runtime stack juggling.
Anyway, Pocket Scheme now abides fully by the configured heap size limit, which specifies the sum of the limits of the three types of heap. Furthermore, it releases memory from the continuation heap much more aggressively than before. There is some fun new UI with slider bars to configure the three heaps' share, too. (Ideally, this would auto-configure as the program runs, but at present I need to know at startup the limit of each particular heap. In 2.0 we'll make these heaps two or three subsections of a single size-constrained area.) Since we're now trying to run three heaps out of the space formerly hosting just one, an advertised-as-1-megabyte heap offers less capacity than it once did. I've bumped the minimum default heap to 2 megs for now, and have made a note to look into our stack consumption at some point... the last time I looked at it carefully was in 1999, and that was on a MIPS device.
Two work items remain before I can release 1.3.1. Presently I accumulate data for the transcript window in a temporary file \Temp\Scheme Transcript.htm. On WM5, unlike previous releases, the entire filesystem is hosted in flash NVRAM, which is both relatively slow to write and has a limited number of writes in its lifetime. So in the interest of minimizing wear and tear to the PDA, I'm moving the transcript into a true RAM buffer (which is where the temporary file would have been hosted, pre WM5). Also, while in the garbage collector I noticed some dodgy assumptions about how we mark "extension" objects (e.g. the windows from w32, or the connections from tcp), which I need to review while GC is still paged into my mind, so to speak.
There's a lot of talk about the "2.0" vaporscheme in this entry.
Partly driven by distaste of pscheme's current performance,
partly as a procrastination tactic to avoid actually grovelling the pointer-reversing graph traversal code,
I sat down and hacked out much of the object layout for the 2.0 release,
reducing conses to 8 bytes apiece.
I can't implement any of this until 2.0, however,
because it depends on my reworking the evaluator extensively.
Oh, speed the day.
posted at: 23:22 | path: /pscheme | permanent link to this entry
There is so much left to do, but this must suffice. It works. I have defeated the wily code generator of my C compiler, which cheerfully reorders elements on the runtime stack so as to frustrate my garbage collector. Yea, verily, it works.
By dint of immense strength of will I left the achingly lame Pocket Scheme Editor alone. I'll fix that when I rewrite it in the next release. Similar strength of will kept my code generic to WM2003. There is nothing in 1.3 that expects even WM2003SE, let alone WM5. Again, for the next release. And I left out the debugging support that I've been fussing with over the winter, and I didn't dive into the D-S-W bug, and, and, and ....
Smart Mode's pretty nice. A few more ideas for it
Now to catch up on other parts of my life.
posted at: 16:58 | path: /pscheme | permanent link to this entry
The Iota discovery
It took me a day to locate a suitably burly debugger, install it, and build pscheme with it. This entailed installing an almost-expired beta of VS 2005 (which, incidentally, looks like a very nice product—I'll save my pennies for a copy), then installing a matching WM5 SDK, and then endless fussing: first to get pscheme to build; then to get the debugger talking to the device, working around bugs in the beta; then to make the iota bug repro; and finally to capture the bug. A productive 24 hours, really, in the same sense than running ten miles on a stationary treadmill is running a great distance.
Turns out that I didn't break anything: the bug's been there for the last year, since I released 1.2. (Longer, really. I wrote the code in 2003.) And the underlying flaw in the garbage collector that creates the bug has been there since the last millenium.
The pscheme garbage collector uses the pointer-reversing D-S-W (Deutsch-Schorr-Waite) algorithm to perform the mark phase of a mark-sweep in a constrained amout of stack. (This design originated from running on a very old version of Windows CE that limited its applications to 58Kb of staack.) However, I was lazy on that day in 1998 and didn't D-S-W when marking vectors (and continuations, which in pscheme terms are implemented as a sort of degenerate vector), simply recursing over each element instead. Also, since 1.2 pscheme has used chained continuations to allow it to evaluate arbitrarily complex expressions in a constrained runtime stack: whenever it exhausts its runtime stack, it saves the current continuation, then resets the stack, restoring the previous continuation as necessary to resume computation from that point. Finally, debug builds drastically reduce the size of these stack segments in order to exercise the chaining code.
Taken all together, a call to Iota on a debug build generates a large number of stack-segment continuations. And those continuations contain references to other continuations in the stack, which in turn refer to others, and so on, and so on. The garbage collector calls itself recursively when marking this sequence. And the garbage collector is the only part of pscheme that recurses without chaining continuations. So eventually a few thousand stack segments build up, and pscheme simple-mindedly tries to mark them recursively. Boom, stack fault.
The right fix for this is to finish the job that I started in 1998 and do the D-S-W pointer reversing trick within vectors and continuations. That, however, may have to wait until after 1.3. Short term, I'll turn off the debug-mode stack-swap exercising code that was creating a new continuation with every Kb of runtime stack, and I'll add some extra stack discipline to the mark phase so that it fails more gracefully in the face of (iota 0 1000000) or whatever.
Speaking of stack discipline, here's a happy discovery:
WM5 lets exception handlers capture stack faults!
In previous releases of Windows CE,
any stack fault in a thread would terminate the entire application,
exception handling notwithstanding.
posted at: 22:09 | path: /pscheme | permanent link to this entry
I can feel the undertow sucking me back to the Real World even as I type, so I'd better write this up before I drown. [Ed.: I fell asleep last night immediately after typing that.]
1.3 basically works. I've spent the last couple of days fiddling with the interface, trying to polish what passes for the Pocket Scheme user experience, as well as how we manage multiple script-type operations on a platform that does not provide a good generic way to do so. Those two goals have at times worked at cross purposes. For example, I revoked some fairly involved soft input panel manipulation when I found that it confused CE's cross-application SIP manipulation, especially when another instance of pscheme was running a background script.
Presently I'm looking at the following:
(iota 0 5000)now crashes the interpreter thread. This one's going to be a lot of fun to chase, since I don't have a working CE debugger right now, and the bug doesn't manifest itself on my console development platform.
After that, I have to finish coding DWIM, aka Input→Smart Mode, and add a couple more paren-balancing options to the context menu. My current notion of DWIM entails:
Singing yourself to sleep
Or, how I exercised the hibernation code in the version of pscheme under development:
(load "w32.dll") (define p (w32:foreign-procedure "coredll.dll" "PostMessageW" '(w32api bool handle dword dword))) (p w32:*host-hwnd* #x03ff 0 0)
That magic number in the last line is the value of the (poorly named) WM_HIBERNATE message, broadcast by the system in low memory scenarios. I built a thunk to call the PostMessage API, used that thunk to simulate the broadcast of WM_HIBERNATE, then sat back and watched the fireworks.
posted at: 15:09 | path: /pscheme | permanent link to this entry
A preliminary 1.3 release
For the brave and the bold, I have finished a preliminary cut at 1.3. To install it, start with an installation of 1.2.2 on a WM 2003, 2003SE, or WM5 device, then unzip this archive into your installation directory thereon.
Again, this version only supports WM 2003, WM 2003 SE, and WM 5.0.
The user interface has changed a fair bit, a result of both my accommodating limitations of the HTML viewer control and my attempting to improve the overall user experience. The changes are mostly obvious upon inspection.
Not yet implemented:
Not yet exercised:
(display 'something)(newline)(read-char)doesn't put echoed input on its own line.
Known annoyances that 1.3 will have to endure:
One solution to both the flashing transcript window and the moving edit window
involves abandoning the TTY metaphor,
i.e., the conceit that your entered expression and the system's response to it
appear at the bottom of the screen,
scrolling up to make room for new data as necessary.
If instead new items appear at the top,
with older data scrolling down,
then the edit control could be at the top of the screen,
where it wouldn't move on SIP state change,
and the transcript window below it,
not flashing on update because it now starts with the most recent entries.
Two problems immediately occur to me, however.
First, separating the editable input pane and the command buttons and keys
forces the user's eyes to scan greater distances while editing,
passing the noise in the transcript pane
(though View→Input→Full Screen could help).
Second, scrolling down doesn't work so well if the standard program output
is interlaced with the transcript. That program output must appear top to bottom
and left to right, in the order of the English language.
posted at: 07:50 | path: /pscheme | permanent link to this entry
1.3 works, sort of
Happy times! My initial cut at 1.3 is now REPLing merrily on WM 5.0.
Unfortunately, the control that I'm now using for per-REPL-pass output
(output displayed in real time,
as opposed to the static transcript of the entire session)
doesn't have the same concurrency rigor
as the control that I used in the last version.
Cross-thread activations are confusing it something fierce.
I have a number of possible solutions that I cooked up during yesterday's long commute,
but they'll have to wait for tonight.
Right now the sun is shining.
posted at: 13:12 | path: /pscheme | permanent link to this entry
The new runtime
In the same sense that 1.2 was about changing the I/O model, 1.3 is about changing the runtime window hosting model. The existing hosting model has been in place since 1998, when it seemed simply the easiest way to get something running at the time; since then, it's been extended, patched, and twisted into something that I no longer wish to maintain. Fortunately, I now have the opportunity to replace that model: partly because the current model uses htmlctrl in a way incompatible with that control on WM 5.0, and partly because I'm dropping support for the Handheld PC platform, thus sparing me a lot of testing and a blizzard of #ifdef statements.
1.2, and indeed every version since 0.1.0, uses two threads of operation. The primary thread creates the user interface windows and receives notifications from the operating system of system memory exhaustion, application shutdown, and the like. A secondary thread interprets all Scheme code, leaving the primary thread free to respond to user and system actions in a timely manner even as an evaluation (let loop (forever) (loop)) runs. This dual-thread design makes Pocket Scheme a well-behaved application on the device.
To evaluate an expression, the user enters it into the lower pane of the application, then taps the Eval button. In 1.2, the UI thread takes the expression, echoes it to its output window, copies it to a common memory buffer, sets some flags to indicate that it's busy in an evaluation, then signals the Scheme thread, which has been asleep awaiting that signal. The Scheme thread makes a private copy of that data, then signals back to the UI thread that it may resume. The UI thread now runs in an "eval busy" mode, hiding the old eval input window, as the Scheme thread evaluates the submitted data as an s-expression. The Scheme thread emits output by sending text data to the appropriate output window. Should the Scheme thread need input from the user, it sends a notification to the UI thread, then goes to sleep awaiting a response; the UI thread receives the notification and changes its mode from "eval busy" to "input visible," displaying a new pane for user input. After the user enters data as requested, the UI thread signals the Scheme thread again, which in turn copies the data from the input window, then notifies the UI thread that it has finished with that data. Once Scheme has finished evaluating its expression, it prints the evaluated value to the output window, then goes back to sleep, notifying the UI thread that it has finished evaluation. The UI thread then moves from "eval busy" back into "eval ready" mode, displaying once again the eval input pane and awaiting a new expression from the user. What a modal mess.
Features of the 1.2 design (perhaps "design" is overstating it):
As I've said before, 1.2 does not work on WM 5.0 because of its reliance on the htmlctrl as an output window that it continually updates with new output. 1.3 fixes this by splitting this output function into two windows: a per-evaluation output window that displays any current output in real time, and a per-session transcript window that displays the results of all prior computations, updated with every pass through the REPL. To respond in real time, the new output window cannot use htmlctrl. The new transcript window, however, can continue to leverage htmlctrl for that control's text formatting benefits.
The 1.3 release unifies the data paths and synchronization methods between the UI and Scheme threads by making the Scheme thread drive the REPL, with the UI thread in every case responding to a notification from the Scheme thread. The UI thread creates some windows, or promises of windows, then hands them to the Scheme thread. The Scheme thread then runs something similar to the following loop:
(with-input-from-window THE-INPUT-WINDOW (lambda () (with-output-to-window THE-OUTPUT-WINDOW (lambda () (let loop (let ((value (eval (read (THE-EVAL-INPUT-WINDOW))))) (if (not (void? value)) (display value (THE-TRANSCRIPT-WINDOW)))) (loop))))))The UI thread satisfies any read request from an input window in the same manner. The transcript window only updates in response to a direct write request, at which time it displays all of the previous REPL pass, using data that the other three window I/O channels have logged to it.
Contrasting the 1.3 redesign with 1.2:
An additional asset of the new design is the ease with which I can either reduce or extend the number of windows. All UI modality now operates as a function of the current active windows, which in turn is a function of the pending requests from the Scheme thread. A lightweight program runtime will lack the eval input and transcript windows, while a better debugger would have at least one additional input window along with multiple output windows.
As of this writing,
the new REPL is in place,
along with the new windows for input, output, and transcript,
and all of the management logic coordinating those windows.
With the Scheme thread now always active,
I've lost the old means (an error code, returned from an evaluation pass)
by which the REPL UI knew to retain syntactically invalid expressions in the input pane
for editing and possible resubmission,
so I'm experimenting with some new ideas—
perhaps making errors on input ports call an optional function on that port,
or perhaps generalizing the current GC announcement mechanism
into a general hosting metainformation announcement mechanism.
I also need to make the styled transcript window
work on old-model Pocket PCs,
which had a more primitive HTML output control.
Finally, I need to think more about displaying error text,
which presently takes place via another text stream interleaved into the output text
(and displayed in a nice error-flag red in the transcript window).
posted at: 05:40 | path: /pscheme | permanent link to this entry
(begin (display "Hello, world") (newline))
This entry serves to break a bottle of champagne over the bow of the new Pocket Scheme development log.
The preceding entries come from the main Pocket Scheme page,
which will become more static as this blog thingy develops further.
posted at: 18:39 | path: /pscheme | permanent link to this entry
After three weeks of bidding on ebay, I've finally landed an inexpensive WM5 device on which to test 1.3. (Testing! What an idea!) This bodes well for my releasing 1.3 sooner rather than later.
Recent, I spent an evening playing with
a port of SLIME
Particularly sexy is SLIME's willingness to connect to a running Lisp on another host.
After some fiddling, I found that I could run a daemon Scheme48 on a FreeBSD box,
interacting with it through a SLIME running on my Windows workstation;
whereupon I thought: if Scheme48 on my BSD server, why not Pocket Scheme on my docked PPC?
Unfortunately, a docked PPC isn't TCP-addressible via ActiveSync from the host,
and SLIME's substrate depends on the debugger-in-Emacs being able to call the remote Scheme via TCP,
though it could work via WiFi, Ethernet, or Bluetooth.
It's still an inspiring thought.
posted at: 00:00 | path: /pscheme | permanent link to this entry
Some background on the WM5 bug:
Pocket Scheme uses an instance of the system HTML viewer control (essentially, the guts of Pocket IE repackaged for use by third-party applications) for its output pane. As PIE wasn't intended for dynamic content like the continually scrolling and extended content of the output pane, pscheme must convince PIE to display an incomplete document, much in the same manner that PIE displays the first part of a large web page while downloading the rest of that page over a slow link. There is no official interface by which to do this, so it breaks with nearly every new release of a Pocket PC operating system.
Why do I persevere with abusing the HTML viewer control like this?
Because the code for the control is in ROM,
so using it reduces the total code size of Pocket Scheme;
and the scrolling-TTY model for a Lisp REPL has a long history from Lisps on other devices,
not to mention being convenient when viewing a sequence of actions.
I could implement my own output pane, or else port some subset
of a rich control like Scintilla,
but that would inflate my code size and introduce plenty of new bugs.
Alternately, I could change the model of the output pane from the current scrolling TTY
to something easier to build with the HTML viewer:
perhaps a series of pages, one per evaluated expression,
with the user seeking through those pages via the History buttons;
or perhaps just showing the results of the last evaluation,
with a separate command displaying a summary of all evaluations made.
That would be easier to model with the HTML viewer
because I wouldn't be trying to make it scroll on command.
posted at: 00:00 | path: /pscheme | permanent link to this entry