Exceptions are used to escape from method calls under unusual circumstances. For example, a robust numerical application may wish to provide an alternate means of solving a problem under unusual circumstances such as ill conditioning. Exceptions bypass the ordinary way of returning from methods and may be used to skip over multiple callers until a suitable handler is found.
There are two aspects to indicating errors using exceptions - how the error is indicated at the point where it occurs. This is usually referred to as throwing the exception. The other aspect of exceptions is how the error message is handled, which is referred to as catching the exception.
Exceptions are explicitly raised by raise statements. The raise statement specifies an expression, which is evaluated to obtain the exception object.
add_if_positive(i:INT) is if i < 0 then raise "Negative value:" + i + "\n"; end; end; |
In the example above, the object happens to be a string that indicates the problem. In general, the exception object must provide enough information for the error handling mechanism. Since the error handling mechanism can discriminate between different objects of different types, it is standard practice to use the type of the exception object to indicate the type of the error that occurred.
Exceptions are passed to higher contexts until a handler is found and the exception is caught. Exceptions are caught using protect statements. The protect statement surrounds a piece of code, and provides an appropriate method of handling any exceptions that might occur when executing that piece of code.
protect foo; when $STR then #ERR + "An error in foo!:" + exception.str; when INT then #ERR + "INT error=" + exception; -- 'exception' of type INT else -- Some other error handling end; |
When there is an uncaught exception in a protect statement, the system finds the first type specifier listed in the 'when' lists which is a supertype of the exception object type. The statement list following this specifier is executed and then control passes to the statement following the protect statement.
In the protect clause, the exception raised may be referred to by the built in expression 'exception'[1], which refers to the exception object. The type of the exception object can be used to categorize the exception and to discriminate between exceptions when they are actually caught. In fact, the when clauses may be viewed as a typecase (see unnamedlink) on the exception object.
[1] In fact, you can look at the tail half of the protect as a typecase on the exception object.
No statements may follow a raise statement in a statement list because they can never be executed.
If there is no else clause in a protect statement, and none of the types in the when branches matches the type of the exception object, then the exception is passed to the next higher protect statement
Exceptions can be significantly slower than ordinary routine calls, so they should be avoided except for truly exceptional (unexpected) cases. Using exceptions to implement normal control flow may be tempting, but should be avoided. For instance, in the STR_CURSOR class, we can make use of exceptions for parsing. It might be tempting to write code like the following
test_bool:BOOL is protect current_state ::= save_state; b ::= get_bool; restore_state(current_state); when STR_CURSOR_EX then return(false); end; return(true); end; |
The above code determines whether a boolean is present in the string by trying to read one and treating an error state as evidence that there is no boolean. While it is perfectly correct code, this is an example of what you should not do. The implementation of a function should not rely on exceptions for its normal functioning. Doing so is extremely inefficient and can result in an unnecessarily complicated flow of control.
The alternative to using exceptions is to use a sticky error flag in the class, as is done by IEEE exceptions and the current FILE classes. This has problems such as the fact that the outermost error is logged, not the most immediate one, and it is very easy to forget to test for the error. However, this method has a much lower overhead and is suitable in certain cases.