logo
Powered by QM on a Rpi server
Home
About OpenQM
Sales and Downloads
Help and Support
About
Login

KnowledgeBase 00108: Exceptions

Last updated: 22 Jul 2016
Applies to: All versions
Search  
Top level index       Full Index Search Tips
Previous article     Next article

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.


Trapping Exceptions

The QMBasic construct used to trap exceptions is a TRY/CATCH block.

   TRY 
      statement(s)
   CATCH exception
      statement(s)
   END 
When 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' 
   END 
This 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' 
   END 
In 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 exception
Whereas 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, data
If 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:

@EXCEPTION
Holds the name of the exception being caught.
@EXCEPTION.DATA
Contains the optional data element passed from the THROW statement. If no data was passed, this will be a null string.
@EXCEPTION.ORIGIN
A dynamic array in which field 1 is the name of the program in which the exception was thrown, field 2 is the line number or, if the program has no symbol table, -1.

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.


Exception Names

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.


Exception Groups

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' 
   END 
where 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.


Unhandled Exceptions

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.


Related Articles

None.



Please tell us if this article was helpful
Very     Slightly     Not at all
Comments
Email (optional)