StahlOS Forth: Errors
In StahlOS Forth, there are a variety of errors that can be encountered. For example, if the
+ word is executed when the stack is empty, there's no reasonable way to continue, so an error occurs. Currently, all errors are handled with the
panic procedure (impl). This procedure prints the cause of the panic, the string
"panic!", then halts the CPU.
However, when a REPL is being run by the Forth system directly, killing the system whenever a typo occurs is definitely overkill, especially if the REPL is being used to repair a system already in an unhealthy state. A better error-handling mechanism is therefore needed.
In the last iteration of StahlOS (on amd64), each process' has associated with it a set of execution tokens for words that may need to be overwritten; namely,
QUIT. In practice, this wasn't useful, since every non-REPL process would use the same definitions for these words. Additionally, there's not a ton of free space left in the current process table, and I'd rather conserve it as much as possible.
Forth 2012 Exceptions
The Forth 2012 spec defines a mechanism for error handling. It's fairly straightforward; there are two words
CATCH takes the address of code to execute, and wraps it such that
THROW calls result in the execution completing.
: foo 1 2 3 THROW ; : bar 4 5 6 ; 7 8 9 ' foo CATCH .S ' bar CATCH .S
<4> 7 8 9 3 <8> 7 8 9 3 4 5 6 0
Notably, the specification of
THROW makes it unsafe to consume items on the data stack before
THROWing; the following reads from uninitialized memory.
: foo + 42 THROW ; 2 3 ' foo CATCH .S
With GForth on my machine, it prints
<3> 5 140418305966048 42 rather than the likely expected
<2> 5 42.
A third option would be to have a more Common Lisp-like handler mechanism, where the handler is passed to
CATCH as an execution token, and invoked at the point
THROW is, rather than unwinding before invoking the handler. This allows a handler to perform error recovery actions inline with the code.
RETURN-TO-CATCH, etc. would probably be defined as well. A hypothetical example:
: handler ." caught error" CR .S CONTINUE ; : foo + THROW ." after throw" CR ; 2 3 ' foo ' handler CATCH .S
The above would print:
caught error <1> 5 after throw <1> 5