[PREVIOUS][UP]

Runtime Error Handling

About Neo/NST exceptions

Neo/NST runtime error handling is modelled after the familiar C++/Java throw-catch scheme: an operation can "throw" an "exception" that then propagates in the execution hierarchy until a suitable handler "catching" the exception is found. In Neo/NST, a handler can be an entire NST unit or a code block inside a prog_unit. After the exception was handled, processing continues behind the handler. If no handler was found, the exception is propagated up to the top-level (usually the Neo interactive loop) and appears as a run-time error message.

NST exception types are identified by string names with a simple hierarchical syntax. In particular, prog_units can be used to throw and catch exceptions, including exceptions thrown from prog-unit #imported library functions.

How to catch NST exceptions

A NST runtime exception can be "thrown" from any point within the NST execution sequence. After an exception has been thrown, NST will seek a suitable exception handler to "catch" (=handle) the exception. If the throw point was inside a prog_unit (possibly from within an #import function), the first handler candidate is the nearest chain of catch blocks of source lines that follows after the throw point in the prog_unit:

  ...
  catch (Accept1) { ... handle exceptions matching Accept1 ... }
  catch (Accept2) { ... dito, for Accept2 ...}
  ...
  catch (Acceptk) { ... }

The arguments Accepti specify the exceptions that can be "caught" (handled) by a particular catch block (cf. below).

If the throw point is some other, "opaque" NST unit, the first handler candidate is the nearest suitably designated (cf. below) NST "handler unit" in the NST execution sequence behind the throw point.

In both cases, the search follows the same general pattern: when the current candidate does not handle the exception (cf. below), or the search hits the border of the current invocation scope (= prog function or container unit) before a candidate is found, the search "propagates" to the invocation point (=prog function call or container) at the next outer level and continues recursively from there.

If the exception remains unhandled along all the way, the search will finally end at the outermost calling level, issueing a runtime error message there.

How to define exception handlers with the prog_unit

 Handling of an exception that has propagated into the NST execution chain needs an NST exception handler unit. This is a special type of unit that does nothing under normal exec (and adapt, ctrl, load, save calls) and becomes only active when it is reached by a (matching) exception.

The prog_unit offers the simplest way to implement NST exception handlers. This requires that the entire "main" portion of the prog_unit source code consists of a single catch block chain only (additional function definitions may be present). Such prog_units have no directly executable code and will act as exception handler for those exceptions that are handled by their catch chain.

When needed, exception handlers can also be implemented in C, using the system functions described in Section
System routines below.

How to select exceptions

Each exception is identified by a hierarchical string name pcErr = "ident1:ident2:...identk" where each identj may consist of alphanumeric characters, (including the underscore) or a single '*' (acting as a don't care symbol).

Each catch block argument Accepti is a constant string with the same syntax (e.g., Accept1 = "badop:array"). When an argument matches in all of its ident-fields a corresponding initial portion of the current exception's pcErr (e.g., Accept1 would match "badop:array:anything" and "badop:array", but not "badop"), the code of the associated catch block will be executed. When this happens, the current exception is considered as "handled" and normal processing continues with the first statement after the chain (a "break" statement can be used to leave the chain before all statements of a block are executed).

A catch block can also re-instate the exception by which it was triggered by a parameterless call of throw(). This leaves the code block immediately and lets the search resume at the next block in the chain.

Each field in pcAccept can also be the wildcard character '*' to allow more flexible matching.

The two implicitly defined prog unit char arrays

   char errtype[];
   char errtext[];

contain always the full type string plus an (optionally specified) description text of the most recent exception. The convenience routine

   int errmatch(char *pcAccept)

with binary return value allows to test whether a particular accept string matches the current exception and may be useful inside a catch block.

How to throw a NST exception

Whenever an error situation occurs, one may wish to throw an exception to trigger appropriate action. A NST exception can be thrown in three different contexts:
  In all three situations, the type of the exception must be identified with its hierarchical string name

   pcErr = "ident1:ident2:...identk"

which is passed as the main argument to one of the three routines
 


which are provided for the three contexts (1)-(3) above. The additional (optional) parameters are processed as in a printf statement (i.e., the format string coming first) to assemble an (optional) print message for the exception (stored in the errtext variable and issued when the exception is not caught).

The first call initiates the search for a suitable exception handler, first among the available catch blocks in the current prog_unit, then, if the exception remained uncaught, the exception is propagated up to the NST calling level (thereby terminating the prog_unit execution).

Similarly, any call of (2) inside the implementation of a #import function will cause an immediate return to its invocation point at the prog_unit calling level, after which subsequent processing will continue as if the exception had been caused with a call of throw(...) at this point.

The third call (3) is only important when implementing new NST units at the C (or C++) level. In contrast to (1) and (2), here the return must be explicitly specified, i.e., (3) must always be used in the form

   return nst_throw(pcErr, ...);

[this causes returning the special value NST_EXCEPTION which tells the enclosing NST exec routine to initiate the search for a suitable handler].

Predefined exception types

Currently, the following exception types are in use:

Bad argument errors:

badarg:value       bad argument value
badarg:index       bad index
badarg:object      bad object value
badarg:array       bad argument array
badarg:array:dim   bad argument array

Attempts at bad operations:

badop:div0         bad operation: division by 0
badop:index        illegal array index
badop:array        illegal operation with array
badop:object       illegal operation on object

Detected bad result outcomes

badres:singular     singular matrix
badres:array        error during array math calculation
badres:nomem        not enough memory
badres:noconv       no convergence within given limits

System routines

Implementing a specialized exception handler as a native NST unit is very simple: declare the unit as an exception handler by a call of the system routine nst_mark_as_exception_handler(unitptr u). Then, NST will call this unit only when it is encountered along the propagation path of an exception (therefore, an exception handler's exec routine never needs to check that an exception is present). The unit can inquire about the type of exception, the causing unit and a possibly associated error message with the system routines

   char *nst_errtype();  // returns pointer to private exception type string
   char *nst_errtext();  // returns pointer to private error message
   unitptr *nst_errunit()                // ptr to unit that caused the exception
   void     nst_errtrace(FILE *fp)       // prints call chain
   char    *nst_errmatch(char *pcAccept) // tests if pcAccept matches exception type

The last routine returns a pointer to a copy of the entire  pcErr string in case of a match, and NULL otherwise Therefore, a NST handler can use simple code such as

   if (nst_errmatch("badop:array")) { ... handle badop:array exceptions }
   else if (nst_errmatch("badop")) { ... handle any other badop error}
   else return nst_throw(NULL); // pass exception unchanged on to other handlers

When the unit finally returns a value different from NST_EXCEPTION, NST considers the exception as handled (whatever the unit actually might have done; only the return value counts). When it instead returns NST_EXCEPTION (which is what the nst_throw() function does) he exception will be further propagated.

At the implementation level of an #import function, pointers to the current contents of errtype and errtext can be obtained with

   char *nst_prog_errtype();
   char *nst_prog_errtext();

Further remarks

The NST exception handling mechanism is entirely separate from the exception handling at the level of the C++ implementation language that may be used to implement NST units.

It should also only be used for NST runtime situations, not when units are created or destroyed, since during such operations the NST execution queue is reconfigured and NST exceptions thrown in such situations may fail to propagate to all prospective handlers.


[PREVIOUS][UP]