|Powered by QM on a Rpi server|
KnowledgeBase 00108: Exceptions
This article was originally published as a Tip of the Week.
Developers moving to multivalue systems from other languages such as Java are used to trapping errors by use of exception handlers instead of ELSE clauses or returned status values. QM supports an exception handling mechanism that is broadly similar to that found in other languages and can trap user generated exception conditions or many internally detected situations that would normally cause an application to abort.
What is an Exception?
An exception is a named event, typically an error, that can be caught and handled by an application. Exceptions are thrown either by use of the THROW statement in an application program or automatically by QM as an alternative to the abort events normally raised by data type errors and similar failures.
The QMBasic construct used to trap exceptions is a TRY/CATCH block.
TRY statement(s) CATCH exception statement(s) ENDWhen this construct is used, the statements between the TRY and CATCH lines are executed. Normally, the program would then continue at the statement following the END that terminates this construct.
If any part of the processing in the TRY clause, including statements executed in subroutines called from there, throws the exception named in the CATCH clause, the program jumps to the CATCH clause, possibly discarding many layers of subroutine calls. Any discarded OO programming objects will execute the DESTROY.OBJECT subroutine in the usual way, if present. Once all lower level subroutines have been discarded, the statements in the CATCH clause are executed to handle the exception condition.
A single CATCH clause can list multiple exception names or there may be more than one CATCH clause to allow handling specific to each exception caught.
TRY statement(s) CATCH exception1, exception2 statement(s) CATCH exception3 statement(s) END
As an example, consider the following code that does not use exceptions:
IF NUM(VALUE) THEN TOTAL += VALUE END ELSE DISPLAY 'Non-numeric data' ENDThis code fragment tests whether the content of VALUE is numeric and, if so, accumulates a total. If the data is non-numeric, an error message is displayed.
Using exceptions, this same functionality could be achieved using
TRY TOTAL += VALUE CATCH SYS.PROGRAM.DATATYPE.NOT_NUMERIC DISPLAY 'Non-numeric data' ENDIn this simple example, the first method is probably easier to read but the value of exception handling grows as the code in the TRY clause becomes more complex. The SYS.PROGRAM.DATATYPE.NOT_NUMERIC exception name is an example of an exception related to internally detected errors and is discussed more fully below.
Throwing an Exception
An application can raise an exception using the THROW statement
THROW exceptionWhereas the exception name in the CATCH clause is a constant and may be written with or without quotes, the exception element of the THROW statement may be a quoted constant or an expression that constructs the exception name in some way.
There is an optional data element that can be appended to this statement
THROW exception, dataIf present, the data item passed can be examined in the exception handler to provide additional information.
Catching an Exception
The CATCH clause can determine more information about the exception by examination of three system variables:
A CATCH clause may validly throw a further exception, including re-throwing the same exception to allow it to be caught by further exception handlers on the call stack.
An exception name is case insensitive and follows the same general rules as other QMBasic names. Users should avoid using names that contain dollar signs or that begin with "SYS." as these are used internally.
The example above trapped an exception named SYS.PROGRAM.DATATYPE.NON_NUMERIC. Many of the situations that would normally cause a program to abort scan back down the program call stack to see if there is an exception handler for the particular event. If there is, an exception is raised, otherwise the normal abort procedure is followed.
There is a full list of system exception names in the QM Reference Manual.
The SYS.PROGRAM.DATATYPE.NUM_NUMERIC exception is a good example of the concept of exception groups.
A program could use specific exception handlers for many of the system exceptions, however, it is often more practical to create a handler that traps a whole group of related exceptions. An exception name is considered to be formed from a series of elements separated by periods. The SYS.PROGRAM.DATATYPE.NON_NUMERIC exception is formed from four levels of grouping. The earlier example that explicitly referenced the entire name could usefully be reduced to
TRY TOTAL += VALUE CATCH SYS.PROGRAM.DATATYPE DISPLAY 'Non-numeric data' ENDwhere the CATCH clause will now trap any exception name that begins with SYS.PROGRAM.DATATYPE such as SYS.PROGRAM.DATATYPE.UNASSIGNED.
This process of trapping larger groups of exceptions could be taken all the way back to catching the entire SYS group for all internally generated exceptions. There is a special exception name, SYS$ANY, that is a universal catch-all and traps any exception, user or system generated. Because the CATCH clauses are prioritised in the order in which they appear, it is essential that a handler for SYS$ANY is the final CATCH clause element in a TRY/CATCH block.
If an exception is thrown for which there is no handler, the normal action is to abort the program with an "unhandled exception" message.
As an alternative, a program can include a handler for SYS$UNHANDLED. When an exception is thrown, the system scans back down the call stack for the first handler for the specific exception or a group to which it belongs. If no handler is found, the first SYS$UNHANDLED handler, if any, found in the scan will be executed.
The relationship between SYS$ANY and SYS$UNHANDLED is important though few applications probably need both. The SYS$ANY handler will be trapped by the handler scan described above. The SYS$UNHANDLED handler is only executed if no specific handler or SYS$ANY handler is found, regardless of their relative positions in the call stack.
Aborts and Exceptions
Use of the ABORT statement in a QMBasic program will look for a handler for SYS.ABORT and, if found, will treat this as an exception.
Not all internally generated aborts are transformed to exceptions, especially those that relate to internal inconsistencies such as structural errors in data files. It is considered more important to trap these in a controlled manner to aid diagnosis. Even with a SYS$ANY exception handler, aborts can still occur and the ON.ABORT paragraph (which is very much like an exception handler) is still relevant.
If a program uses the EXECUTE statement with the TRAPPING ABORTS option, this is considered a firewall beyond which the exception handler search process will not pass. Because an unhandled exception will get transformed into an abort in the absence of a SYS$UNHANDLED handler, the program performing the EXECUTE will trap the error in exactly the same way as applications that do not use exceptions.