If a word detects an error condition that it cannot handle, it can
throw
an exception. In the simplest case, this will terminate
your program, and report an appropriate error.
throw
( y1 .. ym nerror – y1 .. ym / z1 .. zn error ) exception “throw”
If nerror is 0, drop it and continue. Otherwise, transfer control to the next dynamically enclosing exception handler, reset the stacks accordingly, and push nerror.
fast-throw
( ... wball – ... wball ) gforth-experimental “fast-throw”
Lightweight throw
variant: only for non-zero balls, and
does not store a backtrace or deal with missing catch
.
Throw
consumes a cell-sized error number on the stack. There are
some predefined error numbers in Standard Forth (see errors.fs). In
Gforth (and most other systems) you can use the iors produced by various
words as error numbers (e.g., a typical use of allocate
is
allocate throw
). Gforth also provides the word exception
to define your own error numbers (with decent error reporting); a Standard
Forth version of this word (but without the error messages) is available
in compat/except.fs
. And finally, you can use your own error
numbers (anything outside the range -4095..0), but won’t get nice error
messages, only numbers. For example, try:
-10 throw \ Standard defined -267 throw \ system defined s" my error" exception throw \ user defined 7 throw \ arbitrary number
exception
( addr u – n ) gforth-0.2 “exception”
n is a previously unused throw
value in the range
(-4095...-256). Consecutive calls to exception
return
consecutive decreasing numbers. Gforth uses the string
addr u as an error message.
There are also cases where you have a word (typically modeled after
POSIX’ strerror
) for converting an error number into a string.
You can use the following word to get these strings into Gforth’s
error handling:
exceptions
( xt n1 – n2 ) gforth-1.0 “exceptions”
Xt ( +n -- c-addr u )
converts an error number in
the range 0<=n<n1 into an error message.
Exceptions
reserves n1 error codes in the range
n2-n1<n3<=n2. When (at some later point in time) the Gforth
error code n3 in that range is thrown, it pushes n2-n3
and then executes xt to produce the error message.
As an example, if the errno
errors (and the conversion using
strerror
) was not already directly supported by Gforth, you
could tie strerror
in as follows:
' strerror 1536 exceptions constant errno-base : errno-ior ( -- n ) \ n is the Gforth ior corresponding to the value in errno, so \ we have to convert between the ranges here. \ ERRNO is not a Gforth word, so you would have to use the \ C interface to access it. errno errno-base over - swap 0<> and ;
When you call a C function that can set errno
(with the C
interface, see C Interface), you can use one of the following
words for converting that error into a throw
:
?errno-throw
( f – ) gforth-1.0 “?errno-throw”
If f<>0, throws an error code based on the value of errno
.
?ior
( x – ) gforth-1.0 “?ior”
If f=-1, throws an error code based on the value of errno
.
Which of these you should use depends on how the C function indicates that an error has happened. When the system then catches a throw performed by one of these words, it produces the proper error message (such as “Permission denied”).
Note that the errno numbers are not directly used as throw codes (because the Forth standard specifies that positive throw codes must not be system-defined), but maps them into a different number range.
A common idiom to THROW
a specific err# if a
flag is true is this:
( flag ) 0<> err# and throw
Your program can provide exception handlers to catch exceptions. An
exception handler can be used to correct the problem, or to clean up
some data structures and just throw the exception to the next exception
handler. Note that throw
jumps to the dynamically innermost
exception handler. The system’s exception handler is outermost, and just
prints an error and restarts command-line interpretation (or, in batch
mode (i.e., while processing the shell command line), leaves Gforth).
The Standard Forth way to catch exceptions is catch
:
catch
( x1 .. xn xt – y1 .. ym 0 / z1 .. zn error ) exception “catch”
Executes
xt. If execution returns normally,
catch
pushes 0 on the stack. If execution returns through
throw
, all the stacks are reset to the depth on entry to
catch
, and the TOS (the xt position) is replaced with
the throw code.
catch-nobt
( x1 .. xn xt – y1 .. ym 0 / z1 .. zn error ) gforth-experimental “catch-nobt”
perform a catch that does not record backtraces on errors
nothrow
( – ) gforth-0.7 “nothrow”
Use this (or the standard sequence ['] false catch 2drop
)
after a catch
or endtry
that does not rethrow;
this ensures that the next throw
will record a
backtrace.
The most common use of exception handlers is to clean up the state when an error happens. E.g.,
base @ >r hex \ actually the HEX should be inside foo to protect \ against exceptions between HEX and CATCH ['] foo catch ( nerror|0 ) r> base ! ( nerror|0 ) throw \ pass it on
A use of catch
for handling the error myerror
might look
like this:
['] foo catch CASE myerror OF ... ( do something about it ) nothrow ENDOF dup throw \ default: pass other errors on, do nothing on non-errors ENDCASE
Having to wrap the code into a separate word is often cumbersome, therefore Gforth provides an alternative syntax:
TRY code1 IFERROR code2 THEN code3 ENDTRY
This performs code1. If code1 completes normally, execution
continues with code3. If there is an exception in code1 or
before endtry
, the stacks are reset to the depth during
try
, the throw value is pushed on the data stack, and execution
continues at code2, and finally falls through to code3.
try
( compilation – orig ; run-time – R:sys1 ) gforth-0.5 “try”
Start an exception-catching region.
endtry
( compilation – ; run-time R:sys1 – ) gforth-0.5 “endtry”
End an exception-catching region.
iferror
( compilation orig1 – orig2 ; run-time – ) gforth-0.7 “iferror”
Starts the exception handling code (executed if there is an
exception between try
and endtry
). This part has
to be finished with then
.
If you don’t need code2, you can write restore
instead of
iferror then
:
TRY code1 RESTORE code3 ENDTRY
The cleanup example from above in this syntax:
base @ { oldbase } TRY hex foo \ now the hex is placed correctly 0 \ value for throw RESTORE oldbase base ! ENDTRY throw
An additional advantage of this variant is that an exception between
restore
and endtry
(e.g., from the user pressing
Ctrl-C) restarts the execution of the code after restore
,
so the base will be restored under all circumstances.
However, you have to ensure that this code does not cause an exception
itself, otherwise the iferror
/restore
code will loop.
Moreover, you should also make sure that the stack contents needed by
the iferror
/restore
code exist everywhere between
try
and endtry
; in our example this is achived by
putting the data in a local before the try
(you cannot use the
return stack because the exception frame (sys1) is in the way
there).
This kind of usage corresponds to Lisp’s unwind-protect
.
If you do not want this exception-restarting behaviour, you achieve this as follows:
TRY code1 ENDTRY-IFERROR code2 THEN
If there is an exception in code1, then code2 is executed,
otherwise execution continues behind the then
(or in a possible
else
branch). This corresponds to the construct
TRY code1 RECOVER code2 ENDTRY
in Gforth before version 0.7. So you can directly replace
recover
-using code; however, we recommend that you check if it
would not be better to use one of the other try
variants while
you are at it.
To ease the transition, Gforth provides two compatibility files:
endtry-iferror.fs provides the try ... endtry-iferror
... then
syntax (but not iferror
or restore
) for old
systems; recover-endtry.fs provides the try ... recover
... endtry
syntax on new systems, so you can use that file as a
stopgap to run old programs. Both files work on any system (they just
do nothing if the system already has the syntax it implements), so you
can unconditionally require
one of these files, even if you use
a mix old and new systems.
restore
( compilation orig1 – ; run-time – ) gforth-0.7 “restore”
Starts restoring code, that is executed if there is an exception, and if there is no exception.
endtry-iferror
( compilation orig1 – orig2 ; run-time R:sys1 – ) gforth-0.7 “endtry-iferror”
End an exception-catching region while starting
exception-handling code outside that region (executed if there
is an exception between try
and endtry-iferror
).
This part has to be finished with then
(or
else
...then
).
Here’s the error handling example:
TRY foo ENDTRY-IFERROR CASE myerror OF ... ( do something about it ) nothrow ENDOF throw \ pass other errors on ENDCASE THEN
Programming style note:
As usual, you should ensure that the stack depth is statically known at
the end: either after the throw
for passing on errors, or after
the ENDTRY
(or, if you use catch
, after the end of the
selection construct for handling the error).
There are two alternatives to throw
: Abort"
is conditional
and you can provide an error message. Abort
just produces an
“Aborted” error.
The problem with these words is that exception handlers cannot
differentiate between different abort"
s; they just look like
-2 throw
to them (the error message cannot be accessed by
standard programs). Similar abort
looks like -1 throw
to
exception handlers.
ABORT"
( compilation ’ccc"’ – ; run-time f – ) core,exception-ext “abort-quote”
If any bit of f is non-zero, perform the function of -2 throw
,
displaying the string ccc if there is no exception frame on the
exception stack.
abort
( ?? – ?? ) core,exception-ext “abort”
-1 throw
.
For problems that are not that awful that you need to abort execution,
you can just display a warning. The variable warnings
allows
to tune how many warnings you see.
WARNING"
( compilation ’ccc"’ – ; run-time f – ) gforth-1.0 “WARNING"”
if f is non-zero, display the string ccc as warning message.
warnings
( – addr ) gforth-0.2 “warnings”
set warnings level to
0
turns warnings off
-1
turns normal warnings on
-2
turns beginner warnings on
-3
pedantic warnings on
-4
turns warnings into errors (including beginner warnings)