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


Handling Errors and Exceptions with 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.

Here's a nice article on CATCH and THROW.

Forth 83 has rather rudimentary error handling capabilities. There's ABORT and ABORT". Both cause the running program to stop and return control to the Forth system. That's not a particularly elegant solution. It would be nicer if the running program could deal with errors itself, or, in such an event that it can't, give in and stop the program. CATCH and THROW - part of the ANS Forth specification, permit this additional flexibility. It's probably easier to talk about THROW first, then we'll move on to CATCH.

THROW

When you detect an error in your program, you can use THROW to "throw an error". In practice, THROW is used with a number (on the stack) so that the type of error can be identified. Let's take an imaginary word, DIV which divides two numbers. You want to check for a division by zero, and if so, act upon it. First, the Forth 83 way:

   : DIV ( quotient divisor -- result ) dup 0= abort" DIV: Divide by 0 error." / ;

That's about the best you can do in Forth 83 without having to resort to passing flags to indicate if the division succeeded or not. The problem is that in the event of 0 being passed, the running program will stop. That's what ABORT and ABORT" does. Even worse, DIV can't tell us which word passed 0 to DIV in the first place, so it's not particularly useful.

Let's look at how we would trap errors using THROW:

   : DIV ( quotient divisor -- result ) dup 0= if 99 throw else / then ;

Here, if the divisor is 0, we "throw" error code 99 (which, in our program, means "divide by zero" - I chose 99 at random - it could be any value you like). But to where do we "throw" this 99? Or put it another way, who, or what is going to catch this error?

CATCH

CATCH will catch an error thrown by THROW. The critical difference is, this allows your program to gracefully handle the error situation (prompt the user to change disks if the disk is full, rather than just abort, causing the user to lose his magnus opus in your word processor application). Let's have a look at how we would use CATCH with our DIV example above.

   : test-div ( quotient divisor -- result )
     ['] div catch dup 0<> if
dup 99 = if
." Divide by zero error"
else
throw
then then ;

The stack signature for CATCH is as follows:

   CATCH ( ... xt -- 0|error_code )

What this means is, CATCH expects the execution token (xt) of the word you want to execute, in our example, DIV, to be on the stack. CATCH itself will then execute that word on your behalf. After DIV executes, CATCH can determine if control came back to CATCH via THROW or by a normal termination of the word. If the word terminated normally, CATCH puts a 0 on the stack (meaning that CATCH did not catch anything). If control came back to CATCH via THROW, the THROW code will be on the stack.

Thusly, in our example test-above we test the error code returned by CATCH. If it's 0 then DIV did not throw anything, everything worked. If the return code is not zero however, something went wrong in DIV. We then examine the code, and, if it's 99 we inidicate a divide by zero error.

If the error code is not 99 (which is the only thing that test-div is interested in) then something else went wrong (maybe the word / threw a different error of its own). All we know is, we're interested in error codes 0 and 99, and if aint either of them then it's "sombody elses problem". In this case, we can THROW the error again, which will cause it to be caught by the next higher CATCH in the chain (if there is one).

Implementation Code

The following code implements CATCH and THROW. It is also available on the Tools disk on block 37. It occupies 118 bytes of memory.

Aknowledgements

This article was inspired by an article presented by Gordon Charlton, which appeard in the UK Forth magazine, Forthwrite FigUK, Issue 59 (April 1991). The code presented here is also based on his code, modified for a 16-bit environment. The syntactic sugar is my own work, included because I thought it was rather neat, and made things a lot clearer, especially for newcomers to Forth. It seems that Mr. Charlton was somewhat prolific as a Forth coder and writer. See here for a list of his contributions to ForthWrite.

10th October 2015


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