Everybody who tries Factor has run into this situation:
\ layout see
An unhandled error was caught:
Parsing <interactive>:1
\ layout see
^
"Not a number"
:s :r :c show stacks at time of error
:get ( var -- value ) accesses variables at time of error
:error starts the inspector with the error
:cc starts the inspector with the error continuation
Oops! You forgot to use the right vocabulary -- if you know which one, otherwise its another call to
apropos
:
USE: gadgets-layouts
\ layout see
IN: gadgets-layouts : layout
dup gadget-relayout? [
f over set-gadget-relayout? dup layout* dup
layout-children
] when drop ;
This is somewhat irritating, and the error message ("Not a number") is not helpful at all. It arises because of how the Factor parser works; first it looks up each token in the vocabulary search path, and if it does not find a word named by this token, it attempts to parse it as a number.
Now after my latest changes, this situation is handled more gracefully:
\ layout see
An unhandled error was caught:
Parsing :1
\ layout see
^
"No word named layout found in current vocabulary search path"
The following restarts are available:
0 :res Use the word IN: gadgets-layouts : layout
:s :r :c show stacks at time of error
:get ( var -- value ) accesses variables at time of error
:error starts the inspector with the error
:cc starts the inspector with the error continuation
0 :res
IN: gadgets-layouts : layout
dup gadget-relayout? [
f over set-gadget-relayout? dup layout* dup
layout-children
] when drop ;
After the restart is invoked, the relevant vocabulary is automatically added to the search path, and parsing continues. If more than one word with the given name is defined in the dictionary, a number of restarts are offered, one for each possible vocabulary.
Here is how it works behind the scenes:
The
condition ( error restarts -- restart )
word signals a recoverable error. The
error
parameter is the actual error message; it is wrapped inside a special condition object, together with the restarts parameter (more on it in a second) and the current continuation. The restarts parameter is an association list, associating a string description of a restart to an object. If the user chooses to restart from this error, the object associated with the chosen restart will be pushed on the stack, and execution will resume after the point where
condition
was called.
Here is an example:
: restart-test
"This is broken" {
{ "Indeed" t }
{ "I disagree" f }
} condition
"You choose " write . ;
restart-test
Running this example will yield the following error:
An unhandled error was caught:
"This is broken"
The following restarts are available:
0 :res Indeed
1 :res I disagree
:s :r :c show stacks at time of error
:get ( var -- value ) accesses variables at time of error
:error starts the inspector with the error
:cc starts the inspector with the error continuation
Invoking either restart using
0 :res
or
1 :res
will rewind execution and push a boolean true or false on the stack. The portion of the word definition after the call to
condition
now executes; it prints the received boolean on the stack.
0 :res
You choose t
The problem with this approach is that any exception handlers further up the chain might have already closed streams and other external resources before the restart is invoked. In this case the restart will not function properly. What CL does is not execute any clean up handlers before the user picks a restart; this involves two trips up the catch stack, one to collect restarts and possibly present a debugger, and another one to call the relevant cleanup hooks, if any, depending on what restart is chosen. I'll investigate this further and pick a good solution. In any case I don't intend to use this feature as extensively as CL uses restarts, so I hope to avoid hairy situations.