Exception and Error Handling

CATCH and THROW

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.

Example implementation

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.

SP@

( -- addr ) returns the address corresponding to the top of data stack.

SP!

( 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 SP@.

RP@

( -- addr ) returns the address corresponding to the top of return stack.

RP!

( 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 RP@.


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.

Example use

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
;

Wordset

: >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 DEFERred 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 DEFERred 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.

Extending CATCH and THROW

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.

ABORT and ABORT"

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.

Defining Error/Throw codes

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 DEFERred. 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 DEFERred 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.

System Error Handling

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 TYPEs 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.