CATCH
and THROW
form the basis of all VFX Forth
error handling. The following description of CATCH
and
THROW
originates with Mitch Bradley and is taken from
an ANS Forth standard draft.
CATCH
and THROW
provide a reliable mechanism for
handling exceptions, without having to propagate exception
flags through multiple levels of word nesting. It is similar
in spirit to the "non-local return" mechanisms of many other
languages, such as C's setjmp() and longjmp(), and
LISP's CATCH and THROW. In the Forth context,
THROW
may be described as a "multi-level EXIT
",
with CATCH
marking a location to which a THROW
may
return.
Several similar Forth "multi-level EXIT
"
exception-handling schemes have been described and used in
past years. It is not possible to implement such a scheme
using only standard words (other than CATCH
and
THROW
), because there is no portable way to "unwind"
the return stack to a predetermined place.
THROW
also provides a convenient implementation
technique for the standard words ABORT
and ABORT"
,
allowing an application to define, through the use of
CATCH
, the behavior in the event of a system ABORT
.
This sample implementation of CATCH
and THROW
uses the non-standard words described below. They or their
equivalents are available in many systems. Other
implementation strategies, including directly saving the value
of DEPTH
, are possible if such words are not available.
|
( -- addr ) returns the address corresponding to the top of data stack. |
|
( addr -- ) sets the stack pointer to addr, thus restoring the stack depth to the same depth that existed just before addr was acquired by executing |
|
( -- addr ) returns the address corresponding to the top of return stack. |
|
( addr -- ) sets the return stack pointer to addr, thus restoring the return stack depth to the same depth that existed just before addr was acquired by executing |
nnn USER HANDLER 0 HANDLER ! \ last exception handler
: CATCH ( xt -- exception# | 0 ) \ return addr on stack
SP@ >R ( xt ) \ save data stack pointer
HANDLER @ >R ( xt ) \ and previous handler
RP@ HANDLER ! ( xt ) \ set current handler
EXECUTE ( ) \ execute returns if no THROW
R> HANDLER ! ( ) \ restore previous handler
R> DROP ( ) \ discard saved stack ptr
0 ( 0 ) \ normal completion
;
: THROW ( ??? exception# -- ??? exception# )
?DUP IF ( exc# ) \ 0 THROW is no-op
HANDLER @ RP! ( exc# ) \ restore prev return stack
R> HANDLER ! ( exc# ) \ restore prev handler
R> SWAP >R ( saved-sp ) \ exc# on return stack
SP! DROP R> ( exc# ) \ restore stack
\ Return to the caller of CATCH because return
\ stack is restored to the state that existed
\ when CATCH began execution
THEN
;
The VFX Forth implementation is similar to the one described above, but is not identical.
If THROW
is executed with a non zero argument, the effect
is as if the corresponding CATCH
had returned it. In
that case, the stack depth is the same as it was just before
CATCH
began execution. The values of the i*x stack
arguments could have been modified arbitrarily during the
execution of xt. In general, nothing useful may be done with
those stack items, but since their number is known (because
the stack depth is deterministic), the application may
DROP
them to return to a predictable stack state.
Typical use:
: could-fail \ -- char
KEY DUP [CHAR] Q =
IF 1 THROW THEN
;
: do-it \ a b -- c
2DROP could-fail
;
: try-it \ --
1 2 ['] do-it CATCH IF
( -- x1 x2 ) 2DROP ." There was an exception" CR
ELSE
." The character was " EMIT CR
THEN
;
: retry-it \ --
BEGIN
1 2 ['] do-it CATCH
WHILE
( -- x1 x2 ) 2DROP ." Exception, keep trying" CR
REPEAT ( char )
." The character was " EMIT CR
;
: >ep \ x --
Push a cell item onto the exception stack.
: ep> \ -- x
Pop a cell item from the exception stack.
defer o_ABORT \ i*x -- ; R: j*x --
The exception handler of last resort. Clears the stacks and
calls the DEFER
red word QUIT
.
: CATCH \ i*x xt -- j*x 0|i*x n 9.6.1.0875
Execute the code at XT with an exception frame protecting it.
CATCH
returns a 0 if no error has occurred, otherwise
it returns the throw-code passed to the last THROW
.
: THROW \ k*x n -- k*x|i*x n 9.6.1.2275
Throw a non-zero exception code n back to the last CATCH
call. If n is 0, no action is taken except to DROP
n.
If n is non-zero and no previous CATCH
has been
performed, there is an exception frame error, and O_ABORT
is performed which finally calls the DEFER
red word
QUIT
.
: ?THROW \ k*x flag throw-code -- k*x|i*x n
Perform a THROW
of value throw-code if flag is non-zero.
The CATCH
and THROW
mechanism can be extended by
the user if additional information needs to be preserved.
By default the following pointers are preserved - data stack,
return stack, float stack, local frame and current object.
Additional information is saved and restored by user-defined
words as follows.
The save word must return n, the number of cells saved
on the exception stack. The restore word must consume
n and restore n items from the return stack. The words
>EP ( x -- )
and EP> ( -- x )
are used to
push and pop items respectively to and from the exception
stack.
variable v1
variable v2
: MySave \ -- n ; number of cells
v1 @ >ep v2 @ >ep 2
;
: MyRestore \ n -- ; number of cells
drop ep> v2 ! ep> v1 !
;
The new save and restore words are activated by the word
EXTENDS-CATCH
and the default action is restored by
DEFAULT-CATCH
. See below.
: extends-catch \ xt-save xt-restore --
Sets the new save and restore actions of CATCH
and
THROW
.
: default-catch \ --
Restores the default actions of CATCH
and THROW
.
These words are built on top of CATCH
and THROW
.
defer ABORT \ i*x -- ; R: j*x -- ; error handler
Empty the data stack and perform the action of QUIT
, which
includes emptying the return stack, without displaying a
message.
: ABORT" \ Comp: "ccc<quote>" -- ; Run: i*x x1 -- | i*x ; R: j*x -- | j*x 9.6.2.0680
If x1 is true at run-time, display the following string and
perform ABORT
, otherwise do nothing. This is handled by
performing -2 THROW
after setting the variable
'ABORTTEXT
.
As of VFX Forth v3.6, the user definable mechanism has changed.
In order to simplify the construction and allocation of error
messages and references, they can be constructed automatically.
Use ERRDEF
and #ERRDEF
as shown below to construct
messages for error handlers. These messages are created as
/ERRDEF
structures which are also used for messages that
can be internationalised. Note also that this structure and
mechanism may be subject to change to cope with
internationalisation, which is documented in a separate
chapter of the manual.
ErrDef ScrewUp "Oh bother, something went wrong"
defines a constant called SCREWUP
associated with a
string. The constant SCREWUP
can be passed to ERR$
to retrieve the address and length of the string. The value
of the constant is generated from the contents of variable
NEXTERROR
.
999 #ErrDef Snafu "Situation Normal, All ****ed Up"
defines a constant called SNAFU
of value 999 and an
associated text message.
When assigning error codes, please note that the ANS specification reserves error codes -255..-1 for ANS defined error messages. Error codes in the range -4095..-256 are reserved for use by VFX Forth iteself. Applications may use other codes. Please do not use error codes 0..499 as these are reserved by VFX Forth for optional system extensions. By default, automatically assigned error codes start at 501.
The error system relies on a data structure /ERRDEF
which follows a constant value for the error number. The
/ERRDEF
structure contains a link to the previous
ERRDEF
or #ERRDEF
definition, a message identifier
which is 0 for non-databased strings in the ISO Latin1
coding, the address of the text, and the length of the text
in bytes. The text is followed by two zero bytes, and the
text is long aligned. The /ERRDEF
structure is a
subset of the /TEXTDEF
structure described in the
internationalisation chapter. This chapter also includes a
discussion of the concepts used for internationalisation. )
Error messages are linked into the chain used for all
application strings that can be internationalised. This chain
is anchored by the variable TEXTCHAIN
.
The words PARSEERRDEF
, ERR$
and .ERR
are
DEFER
red. PARSEERRDEF
creates the /ERRDEF
structure from the source text. It is the basis of error
defining words such as ERRDEF
. You can install
alternative versions of these words for internationalised
applications. In this context, #ERRDEF
and friends can
be used as the basis of any text handler that requires
translation. Note that PARSEERRDEF
can be modified so
that a message file is produced at compile time, and
ERR$
modified so that the message file is accessed at
run time. Similarly, providing that the application language
is correctly handled, the run time can access translated
messages in other languages, character sets and character
sizes. .ERR
is similarly DEFER
red and is used to
display the message. )
struct /errdef \ -- len ; DOES NOT include constant definition
int ed.link \ link to previous ERRDEF
int ed.id \ 0 or message ID
int ed.caddr \ address of text string to use
int ed.len \ length of text string to use in bytes
int ed.lenInline \ length of inline text string in bytes
end-struct
The previous kernel words GETERRORTEXT
and
GETERRORTEXTEX
that existed up to VFX Forth v3.3 have
been removed and are replaced by ERR$
, which has the
same stack effect.
defer ParseErrDef \ "<text>" -- ; create /ErrDef structure
Create a /ErrDef structure in the dictionary, parsing the
required text. The error number must already have been laid.
defer Err$ \ n -- addr/n len/0
Convert an error/message number to the address of the relevant
string. Addr is the start of the string, and len is
its length in bytes. If the string cannot be found, addr
is set to n and len is set to 0.
defer .Err \ caddr u --
Given the address and length in bytes of a message that may
have been internationalised, display it. The default action
is TYPE
.
variable NextError
Holds the value of the next application error number to be
allocated by ERRDEF
. Application error numbers are
positive and are incremented by ERRDEF
.
variable NextSysError
Holds the value of the next system error number to be
allocated by SYSERRDEF
. System error numbers are
negative and are decremented by SYSERRDEF
.
variable TextChain
The anchor for the chain of error and text messages that may
be internationalised.
: ErrStruct \ n -- struct|0 ; produce pointer to error structure
Given an error number n, return the address of the
/ERRDEF
error structure containing its details.
: .TextChain \ -- ; display all error messages
Display a list of all the error codes and messages defined by
ERRDEF
and #ERRDEF
and other text chain users.
: (Err$) \ throw#/msg# -- c-addr u | throw#/msg# 0
The default action of ERR$
.
: (ParseErrDef) \ "<text>" -- ; create /ErrDef structure
The default action of PARSEERRDEF
.
: #ErrDef \ n -- ; -- n ; used as throw/error codes
Define a constant and associated message in the form:
<n> #ERRDEF <name> "<text>".
Execution of <name>
returns <n>.
: ErrDef \ -- ; -- n ; used as throw/error codes
Define a constant and associated message in the form:
ERRDEF <name> "<text>"
Execution of <name>
returns the constant automatically
allocated from NEXTERROR
.
: SysErrDef \ -- ; -- n ; used as throw/error codes
Define a constant and associated message in the form:
SYSERRDEF <name> "<text>"
: #AnonErr \ n "<text>" -- ; create anonymous error text
Create an anonymous error definition that has no name, e.g.
WSAEWOULDBLOCK #AnonErr "WSAEWOULDBLOCK"
#AnonErr
is useful when dealing with operating
system return codes whose names are available from the
support DLL.
The VFX Forth kernel handles the display of error messages
using the word .THROW ( n -- )
which recognises two
classes of message. The first class consists of the messages
handled by ABORT" <text>"
. At present these cannot be
internationalised, and they are displayed by the deferred
word DOABORTMESSAGE
. The second class handles all
other error messages, and these are displayed by the deferred
word DOERRORMESSAGE
.
See also:
LINE# |
Current source input line number. |
'SourceFile |
Pointer to source include struct for current source file, or 0. |
SOURCE |
Return source line ; -- c-addr u |
CurrSourceName |
Return source file name ; -- c-addr u |
Other subsidiary words are also documented here.
: .ErrDef \ n --
Display the error/message number n and the associated message.
: ShowErrorLine \ -- ; display error line
Show the current source file and line number.
If LINE#
contains -1, no action is taken.
: ShowSourceOnError \ -- ; display pointer to error
Show the current text input line and a pointer to the error
location defined by the current value of >IN
.
If >IN
contains -1, no action is taken.
: .source-line \ --
Show the current source file, line and a pointer to the
source position.
defer DoAbortMessage \ c-addr --
The action of this word is used by the kernel to print the
text associated with an ABORT"
from the system. By
default it simply TYPE
s the counted string at c-addr,
and shows the offending source line. You can replace this
action at any time, using:
ASSIGN <xxx> to-do DoAbortMessage
Note that ABORT"
messages cannot be internationalised at
present because all these messages share a common throw code,
-2. DOABORTMESSAGE
is used by .THROW
below.
defer DoErrorMessage \ n --
A deferred word which handles the display of error messages
for the VFX Forth system's text interpreter. By default the
ERRDEF
mechanism above is used. DOERRORMESSAGE
is used by .THROW
below.
: .throw \ n -- ; show throw code n
Process the given THROW
code. Throw codes 0 and -1 are
silent by specification. Throw code -2 displays the string
set by the last ABORT" <string>"
and all other error
messages are searched for in the ERRDEF
chain. See
ERRDEF
and #ERRDEF
.
create zSysName \ -- zaddr
Returns the system name "VFX Forth" as a zero terminated
string.