<< Home | About Forth | About TurboForth | Download | Language Reference | Resources | Tutorials | YouTube >>


CATCH and THROW

"Any action might fail. To keep a real-time system in continuous operation, the designer must be able to specify a recovery action to be taken as the effective replacement for the remainder of the action which was not successfully completed."
I.C. Pyle - "The ADA Programming Language" 1981.

Many languages do not cater for exceptions at all. Those that do include Common Lisp, LOGO, ADA, and (I believe) C. As of ANSI pre-release report BASIS13, so does Forth, with the Error Handling Wordset (it should be called the Exception Handling Wordset).

This is a long overdue recognition of the simple fact that bears a good deal of repetition. Programs go wrong. Setting aside Goedel's Therorem and the 2nd law of thermodynamics there are understandable reasons for this. Programmers make mistakes. The amount of testing a program requires increases much faster than the size of the program. Specifications are incomplete. Programs have to interact with humans, a major source of fallibility. Hardware wears out. One in a million occurrences occur. The list is endless

A programmers focus is mostly on algorithms and data structures. Once these are understood a program can be made to run. This is the smallest part of programming. Turning a working program into a secure, reliable, stable program is the harder task. Too often this is underrated. One can use the best theories of structured programming, trapping erroneous input before it is used, apply rigourous testing and design specification systems such as SSADM and still a program will bomb out. Perhaps not straightaway, but it will.

Exceptions are a major problem. Those things that almost never happen, but when they do need special handling. Things like running out of space on disk. Or a valve failing on a piece of machinery, or a group of data items conspiring to make the top of stack zero just before attempting to divide. The trouble is that catering for these can turn an elegant program into a morass of flags and conditionals. And, given enough paths, you will take a wrong turn eventually. Correctness is especially important in process control. When a word processor trashes your hard disk it may cause heartache or expense, but when a plane tumbles from the sky it costs lives. I do not think this can be overstated.

When an exception occurs we need to detect it, executes an appropriate exception handling code and then continue the program. Of course we may not wish to continue from the same place that we left off. If we failed to open a file it would be silly to try to write to it. This may mean jumping out of a deeply nested subroutine to the word that initiated the file access, such as a menu very near the top level routine. This is where exception handling words such as CATCH and THROW useful.

CATCH takes the execution token (CFA) of a Forth word as an argument. It then executes this word. Assuming that the word THROW is not executed during the execution of this word, CATCH places a zero on top of the stack. This indicates that an exception did not arise during the execution of the word.

Throw takes a number N from the stack. If N is zero it does nothing. if then is nonzero it jumps out of subroutine after subroutine until it finds a CATCH. Once it has found a CATCH it adjusts the data stack and places the number and on the top, to indicate that an exception has occurred. The adjustment it makes to the data stack is this; it ensures that the same number of items are on the stack as before the CATCH was executed, not including the execution token that CATCH used. The value of any stack item that may have been altered between the CATCH and the THROW is unspecified and must be assumed to be invalid. Execution then continues just after the CATCH. Catches can be nested.

Throw's stack action may seem a little strange. In fact it is making the best of a bad job. It can't do nothing to the stack. The number of items on the stack would be unknown. It can't clear the stack. Obviously. Throw cannot know the stack action of the word that CATCH executes, but the programmer can. So it sets it to the depth that was before, and makes it the programmer's responsibility to drop any invalid items.

It is possible, but inappropriate, to make a distinction between system generated exceptions, such as divide by zero error and disk full error, and program detected exceptions, such as a valve failure. Both are equally damaging to a program. This suggests that CATCH and THROW should be incorporated into the heart of the Forth system. This is neat, as it means the default action of say, /MOD can be to abort with an error message if top of stack is zero, but the action can be altered by intercepting the THROW with a programmer defined CATCH. It also means that there be no need for a test in THROW for THROW without CATCH, as any THROW without a CATCH will be handled by the system CATCH.

CATCH and THROW are very unstructured, both in terms of flow control (i.e. the return stack) and data flow (i.e. the data stack). Therefore certain disciplines should be adopted in their use.

Firstly every exception should be uniquely identifiable. This means the number passed to THROW should be an exception number. For readability exception numbers should be defined as constants with meaningful names. Any CATCH, with the exception of the top level CATCH, should only perform one exception handling routine for one specified exception. If it receives an exception other than the specified one it should THROW it to the next level CATCH. Exception handling should be done by CATCH, never by THROW. This allows greater reusability of code. For the same reason throws should be done at the lowest possible level in the code, and catches at the highest, while still allowing a program to recover gracefully from an exception. CATCH and THROW should only be used where they are completely justifiable, and not just an easy way of jumping out of nested loops. Search your heart before using them.

A finished program should never invoke the system CATCH. It should report a serious system malfunction, give instructions to the user to contact the system designer and dump any information that may be useful in debugging. As a sweetener it should offer the user a temp and fault finders fee, claim the ball any contact the designer. This will also encourage the programmer to ensure that this error handler need never be invoked. I suggest that system exceptions be given negative numbers, and program to find one's positive numbers, to avoid conflict.

The implementation is highly system specific. For those with gemForth for the Atari ST the one given will work. Others will, I am afraid, have to figure it out for themselves. This version saves the data stack pointer on the return stack, and the return stack pointer in the variable 'CATCH. The previous value of 'CATCH is also stashed, to allow nesting. If there is no THROW, CATCH restores the variable and leaves a zero. Throw restores the return stack to just after the catch, using the value in 'CATCH, restores the 'CATCH to its former value and the data stack to its former depth. It leaves its argument on top of stack. That the definitions are very short means that there is no reason why any implementation of ANSI Forth should not include the error handling word set. Given its usefulness any Forth producer should be encouraged to include it. I shall certainly press for its inclusion in FANSI.

Gordon Charlton, Forthwrite FigUK, Issue 59 (April 1991).

9th October 2015


<< Home | About Forth | About TurboForth | Download | Language Reference | Resources | Tutorials | YouTube >>