Epic 11 will give us exception handling - the ANS Forth “EXCEPTION” word-set and also the ANS Forth “EXCEPTION-EXT” word-set - although the user should note that as of 2017 “EXCEPTION-EXT” is no longer optional and now considered mandatory, so “EXCEPTION-EXT” is itself exceptional, it would seem.

AntForth is not shy! It includes both!

Note that ANS Forth’s exception handling centres around the THROW, CATCH, ABORT and ABORT" words, which differ from many traditional implementations with words like ALERT, (ALERT), ESCAPE, EXCEPT etc.

Planning

Nothing too remarkable, just the usual cycle of /bmad-create-story, /mad-dev-story and /bmad-code-review finishing off with a retro /bmad-bmm-retrospective.

CATCH and THROW

The CATCH word sets up an exception handling context: any exceptions that are thrown (by the THROW keyword) within the exception context are handled by the code that immediately follows it.

If the exception handling code immediately follows CATCH how do we tell it what code to execute in the exception context - i.e. the code we want to run that might throw ?

We use an execution token, which is Forth’s equivalent of a pointer to a function. It’s the address of a word’s implementation. If you’re thinking “that sounds like the Code Field Address of the word” then you’d be right (for an indirect threaded Forth like AntForth).

The usual approach is something like this:

: safe/ ( n1 n2 -- n1/n2 | 0 error-code )
  ['] / catch ;  \ Executes / , catches error if n2 is 0

: test-division
  10 0 safe/ ?dup IF
    ." Error occurred: " . cr \ Handles error (e.g., prints -50, etc.)
  ELSE
    ." Result: " . cr        \ Executes if no error
  THEN ;

Let’s see it in action:

Catch exception handler

We get the xt for the builtin / word and use CATCH to execute it. If that code throws an exception (for example, attempted division by zero) then whatever value was thrown goes in TOS execution immediately resumes with the word following CATCH. Otherwise if no exception was thrown (or if a 0 was explicitly thrown) CATCH pushes a zero onto TOS and continues.

?DUP duplicates TOS only if it is non-zero, so it’s only going to DUP if an exception was thrown. If an exception was thrown, ?DUP duplicates the error code so that we can both test it with IF and print it out with .. But if an exception wasn’t thrown TOS contains zero, so we don’t ?DUP, IF consumes the 0 in TOS and the . in the ELSE clause prints the value of the division operation, which was underneath TOS.

Here’s what it looks like when the exception isn’t thrown:

Catch exception handler no exception

THROW simply takes an error code from TOS and throws it:

: check-range ( n -- )
  dup 10 > IF -99 throw THEN ; \ Throws -99 if n > 10

Note that that includes error code 0. The -1 error code is special, and performs the action of the ABORT word (more details below). Similarly -2 is special, and performs the action of the ABORT" (“abort-quote”) word, more details below.

Rethrowing

Exception handlers nest, so it is quite acceptable to handle an exception, do some cleanup, and then re-throw the exception to a higher level exception handler:

: managed-word
  ['] risky-word catch
  dup IF \ If an error happened
    ." Cleaning up..." cr
    throw \ Rethrow the original error
  THEN ;

ABORT

ABORT (or -1 throw) immediately quits execution, and, crucially clears the data and return stacks.

: PROCESS-DATA ( n -- )
  DUP 100 > IF
    DROP
    CR ." Value too high, aborting."
    ABORT
  THEN
  . ;

50 PROCESS-DATA   \ Output: 50 ok
150 PROCESS-DATA  \ Output: Value too high, aborting. ok

ABORT”

ABORT" is a compile-time word that parses a string (ending with a "). At run-time, it removes a value from TOS and if it’s not zero it displays the string that was defined, then follows up with the usual ABORT sequence (clearing the stacks).

Error codes

Here’s a complete list of AntForth’s error codes. Values between -1 and -255 are reserved for the ANS Forth standard assignments 9.3.4.

Values between -4095 and -256 are reserved for use by AntForth itself.

Positive non-zero values are available for custom user error codes.

Error code Where Meaning
-1 Standard ABORT
-2 Standard ABORT"
-3 Standard Stack overflow
-4 Standard Stack underflow
-5 Standard Return Stack overflow
-6 Standard Return Stack underflow
-7 Standard DO loops nested too deep
-8 Standard Dictionary overflow
-9 Standard Invalid memory address
-10 Standard Division by zero
-11 Standard Result out of range
-12 Standard Argument type mismatch
-13 Standard Undefined word
-14 Standard Interpreting a compile-only word
-15 Standard Invalid FORGET
-16 Standard Attempt to use zero-length string as a name
-17 Standard Pictured numeric output string overflow
-18 Standard Parsed string overflow
-19 Standard Definition name too long
-20 Standard Write to a read-only location
-21 Standard Unsupported operation
-22 Standard Control structure mismatch
-23 Standard Address alignment exception
-24 Standard Invalid numeric argument
-25 Standard Return stack imbalance
-26 Standard Loop parameters unavailable
-27 Standard Invalid recursion
-28 Standard User interrupt
-29 Standard Compiler nesting
-30 Standard Obsolescent feature
-31 Standard >BODY used on non-CREATE definition
-32 Standard Invalid name argument
-47 Standard Compilation word list deleted
-48 Standard Invalid postpone
-49 Standard Search order overflow
-50 Standard Search order underflow
-51 Standard Compilation word list changed
-52 Standard Control flow stack overflow
-53 Standard Exception stack overflow
-56 Standard QUIT
-57 Standard Exception sending/receiving char
-58 Standard [IF], [ELSE] or [THEN] exception
-258 System Bad operand (asm)
-259 System Nested CODE (asm)
-260 System CODE needs name (asm)
-261 System END-CODE without CODE (asm)
-262 System LABEL must precede opcodes (asm)
-263 System JR out of range (asm)
-264 System Too many labels (asm)
-265 System Too many fixups (asm)
-266 System EQU outside CODE (asm)
-267 System Bare integer (asm)
-268 System Unresolved label (asm)
-269 System Already fixed (asm)
-270 System Not in CODE (asm)
-271 System Bad Displacement range (asm)
-272 System Bad bit range (asm)