Program Handling of Errors

GT.M provides the error handling facilities described in the M standard. In addition, GT.M provides a number of extensions for error handling. Both are discussed in the following sections. The following table summarizes some of the tools, which are then described in more detail within the context of various techniques and examples.

Summary of GT.M Error-Handling Facilities

EXTENSION

EXPLANATION

OPEN/USE/CLOSE EXCEPTION

Provides a deviceparameter specifying an XECUTE string or entryref that GT.M invokes upon encountering a device-related exception condition.

MUMPS -list ZLINK :"-list"

Creates a listing file of all the errors detected by the compiler and detects syntax errors. Useful in the process of re-editing program to correct errors.

ZGoto

Provides for removing multiple levels from the M invocation stack.

ZMESSAGE

Creates or emulates arbitrary errors.

$STACK

Contains the current level of M execution stack depth.

$STACK()

Returns values describing aspects of the execution environment.

$ECODE

Contains a list of error codes for "active" errors; these are the errors that have occurred, but have not yet been cleared.

$ESTACK

Contains an integer count of M virtual machine stack levels that have been activated and not removed, since the last time $ESTACK was NEW'd.

$ETRAP

Contains a string value that GT.M invokes when an error occurs during routine execution.

$QUIT

Indicates whether the current block of code was called as an extrinsic function or a subroutine.

$ZCSTATUS

Holds the value of the status code for the last compilation performed by a ZCOMPILE, ZLINK or auto-ZLINK.

$ZEDIT

Holds the value of the status code for the last edit session invoked by a ZEDIT command.

$ZEOF

Holds the value '1' (TRUE) if the last READ on the current device reached end-of-file, otherwise holds a '0' (FALSE).

$ZERROR

Contains a string supplied by the application, typically one generated by the code specified in $ZYERROR.

$ZLEVEL

Contains current level of DO/EXECUTE nesting ($STACK+1).

$ZMESSAGE()

Translates a UNIX/GT.M condition code into text form.

$ZSTATUS

Contains the error condition code and location of last exception condition occurring during routine execution.

$ZTRAP

Contains an XECUTE string or entryref that GT.M invokes upon encountering an exception condition.

$ZYERROR

Contains an entryref to invoke when an error occurs; typically used to maintain $ZERROR.

$ECODE

The value of $ECODE is a string that may reflect multiple error conditions. As long as no error has occured, the value of $ECODE is equal to the empty string.

$ECODE contains a list of errors codes for "active" errors - the error conditions which are not yet resolved. If there are no active errors, $ECODE contains the empty string. The value of $ECODE can be SET.

The most recent error in $ECODE appears first, the oldest last. If the error is defined by the M standard, the code starts with an "M", GT.M error codes including those provided by OS services start with "Z", and application defined codes must start with "U". Every code is separated by a coma (,) and there is always a coma at the beginning and at the end of a list. GT.M provided codes are those reported in $ZSTATUS, interpreted by $ZMESSAGE() and recognized as arguments to ZMESSAGE command. When GT.M supplies a standard error code in $ECODE, it also supplies a corresponding 'Z' code.

[Note] Note

See “$ECode” for a detailed description of $ECODE.

Example (setting $ECODE):

SET $ECODE="" ;sets $ECODE to the empty string
SET $ECODE=",M20," ;an ANSI M standardized error code
SET $ECODE=",U14," ;user defined error code
SET $PIECE($ECODE,",",2)="Z3," ;insert a non-ANSI error code
SET $PIECE($ECODE,",",$LENGTH($ECODE,",")+1)="An..," ;append        

Standard Error processing affects the flow of control in the following manner. Detection of an error causes GOTO implicit sub-routine. When $ECODE="", the implicit subroutine is $ETRAP and QUIT:$QUIT "" QUIT. Otherwise the implicit subroutine is $ETRAP followed by TROLLBACK:$TLEVEL and then QUIT:$QUIT "" QUIT.

The QUIT command behaves in a special fashion while the value of $ECODE is non-empty. If a QUIT command is executed that returns control to a less nested level than the one where the error occurred, and the value of $ECODE is still non-empty, first all normal activity related to the QUIT command occurs (especially the unstacking of NEWed variables) and then the current value of $ETRAP is executed. Note that, if $ETRAP had been NEWed at the current or intervening level, the unstacked value of $ETRAP is executed.

SETting $ECODE to an invalid value is an error. SETting $ECODE to a valid error behaves like detection of error. SETting $ECODE="" does not cause a change in the flow, but effects $STACK(), subsequent $QUITs and errors.

[Note] Note

To force execution of an error trap or to flag a user-defined error ("U" errors), make the value of $ECODE non-empty:

SET $ECODE=",U13-User defined error trap,"

[Note] Note

The value of $ECODE provides information about errors that have occurred since the last time it was reset to an empty string. In addition to the information in this variable, more detailed information can be obtained from the intrinsic function $STACK. For more information, see the section on “$STack()”.

$ZSTATUS Content

$ZSTATUS contains a string value specifying the error condition code and location of the last exception condition that occurred during routine execution.

[Note] Note

For further details, see “$ZStatus”.

$ZERROR and $ZYERROR

After an error occurs, if $ZYERROR is set to a valid entryref that exists in the current environment, GT.M invokes the routine at that entryref with an implicit DO before returning control to M code specified by a device EXCEPTION, $ETRAP or $ZTRAP. It is intended that the code invoked by $ZYERROR use the value of $ZSTATUS to select or construct a value to which it SETs $ZERROR.

If $ZYERROR is empty, $ZYERROR="unprocessed $ZERROR, see $ZSTATUS".

If there is a problem with the content of $ZYERROR or if the execution of the code it invokes, GT.M sets $ZERROR=$ZSTATUS for the secondary error and terminates the attempt to use $ZYERROR. During code evoked by $ZYERROR, the value of $ZERROR is the empty string.

$ETRAP Behavior

If, at the time of any error, the value of $ETRAP is non-empty, GT.M proceeds as if the next instruction to be excuted were the first one on "the next line" and the code on that next line would be the same as the text in the value of $ETRAP. Furthermore, GT.M behaves as if the line following "the next line" looks like:

QUIT:$QUIT "" QUIT

When SET assigns a value to $ETRAP, the new value replaces the previous value, and if $ZTRAP was not empty (in control), the value of $ZTRAP becomes equal to the empty string without being stacked.

Nesting $ETRAP and using $ESTACK

When you need to set up a stratified scheme where one level of subroutines use one error trap setting and another more nested subroutine uses a different one; the more nested subroutine must NEW $ETRAP. When $ETRAP is NEWed, its old value is saved and copied to the current value. A subsequent SET $ETRAP=<new-value> then establishes the error trapping code for the current execution level.

The QUIT command that reverts to the calling routine causes the NEWed values to be unstacked, including the one for $ETRAP.

If an error occurs while executing at the current execution level (or at an execution level farther from the initial base stack frame), GT.M executes the code from the current $ETRAP. Unless a GOTO or ZGOTO in $ETRAP or any code it invokes redirects the flow of execution, when the execution of the $ETRAP code completes, control reverts to the implicit QUIT command, which returns to the routine that invoked the code that encountered the error. At this time, the QUIT reinstates any prior value of $ETRAP.

While at the more nested execution level(s), if an error occurs, GT.M executes the code from the current $ETRAP. After the QUIT to a less nested level, GT.M invokes the code from the now current $ETRAP. The current $ETRAP may be different from the $ETRAP at the time of the error due to unstacking. This behavior continues until one of the following possible situations occur:

  • $ECODE is empty. When the value of $ECODE is equal to the empty string, error processing is no longer active, and normal processing resumes.

  • A QUIT reaches an execution level where the value of $ETRAP is empty ($ZTRAP might be non-empty at that level). When the values of both $ZTRAP and $ETRAP are equal to the empty string, no error trapping is active and the QUIT repeats until it unstacks a $ETRAP or $ZTRAP.

  • The stack is reduced to an empty state. When there is no previous level left to QUIT into, GT.M returns to the operating system level shell. A frame that is in direct mode stops the process by putting the user back into the Direct Mode shell.

When dealing with stratified error trapping, it is important to be aware of two additional intrinsic variables: $STACK and $ESTACK. The values of both of these variables indicate the current execution level. The value of $STACK is an "absolute" value that counts from the start of the GT.M process, whereas the value of $ESTACK restarts at zero (0) each time $ESTACK is NEWed.

It is often beneficial to NEW both $ETRAP and $ESTACK a the same time.

$ZTRAP Behavior

If, at the time of any error, the value of $ZTRAP is non-empty, GT.M uses the $ZTRAP contents to direct execution of the next action.Refer to the $ZTRAP section in Chapter 8: “Intrinsic Special Variables.

By default, execution proceeds as if the next instruction to be executed were the first one on "the next line", and the code on that next line would be the same as the text in the value of $ZTRAP. Unless $ZTRAP or any code it invokes issues a GOTO or ZGOTO, after GT.M has executed the code in $ZTRAP, GT.M attempts to execute the line with the error again. When a value is assigned to $ZTRAP, the new value replaces the previous value. If the value of $ETRAP is a non-empty one, $ETRAP is implicitly NEWed, and the value of $ETRAP becomes equal to the empty string; this ensures that at most one of $ETRAP and $ZTRAP is not the empty string. If the environment variable gtm_ztrap_new evaluates to Boolean TRUE (case insensitive string "TRUE", or case insensitive string "YES", or a non-zero number), $ZTRAP is NEWed when $ZTRAP is SET; otherwise $ZTRAP is not stacked when it is SET.

Other than the default behavior, $ZTRAP settings are controlled by the environment variable gtm_ztrap_form as described in the following table.

gtm_ztrap_form

$ZTRAP and EXCEPTION Behavior

code

Content is code executed after the error; in the absence of GOTO, ZGOTO, or QUIT, execution resumes at the beginning of the line containing the error - note that the default behavior tends to create an indefinite loop.

entryref

Content is an entryref to which control is transferred by an implicit GOTO

adaptive

If content is valid code treat it as described for "code", otherwise attempt to treat it as an entryref

popentryref

Content is entryref - remove M virtual stack levels until the level at which $ZTRAP was SET, then GOTO the entryref; the stack manipulation occurs only for $ZTRAP and not for EXCEPTION

popadaptive

If content is valid code treat it as described for code, otherwise attempt to treat it as an entryref used as described for popentryref

Although the "adaptive" and "popadaptive" behaviors permit mixing of two behaviors based on the current value of $ZTRAP, the $ZTRAP behavior type is selected at process startup from gtm_ztrap_form and cannot be modified during the life of the process.

[Note]

Like $ZTRAP values, invocation of device EXCEPTION values, with the exception noted, follow the pattern specified by the current gtm_ztrap_form setting.

Differences between $ETRAP and $ZTRAP

The activation of $ETRAP and $ZTRAP are the same, however there are a number of differences in their subsequent behavior.

For subsequent errors the then current $ZTRAP is invoked, while with $ETRAP, behavior is controlled by the state of $ECODE. This means that when using $ZTRAP, it is important to change $ZTRAP, possibly to the empty string, at the beginning of the action in order to protect against recursion caused by any errors in $ZTRAP itself or in the code it invokes.

If there is no explicit or implicit GOTO or ZGOTO in the action, once a $ZTRAP action completes, execution resumes at the beginning of the line where the error occurred, while once a $ETRAP action completes, there is an implicit QUIT. This means that $ZTRAP actions that are not intended to permit a successful retry of the failing code should contain a GOTO, or more typically a ZGOTO. In contrast, $ETRAP actions that are intended to cause a retry must explicitly reinvoke the code where the error occurred.

For QUITs from the level at which an error occurred, $ZTRAP has no effect, where $ETRAP behavior is controlled by the state of $ECODE. This means that to invoke an error handler nested at the lower level, $ZTRAP actions need to use an explicit ZMESSAGE command, while $ETRAP does such invocations implicitly unless $ECODE is SET to the empty string.

$ZTRAP Interaction With $ETRAP

It is important to be aware of which of the trap mechanisms is in place to avoid unintended interactions, and aware of which conditions may cause a switch-over from one mode of error handling to the other.

When a SET command assigns a value to either $ZTRAP or $ETRAP, GT.M examines the value of the other error handling variable. If the other value is non-empty, GT.M executes an implicit NEW command that saves the current value of that variable, and then assigns that variable to the empty string, then makes the requested assignment effective.

For example, re-setting $ETRAP is internally processed as:

NEW:$LENGTH($ZTRAP) $ZTRAP $ETRAP SET $ETRAP=code        

Whereas, SET $ZTRAP=value is internally processed as:

NEW:$LENGTH($ETRAP) $ETRAP SET:$LENGTH($ETRAP)="" SET $ZTRAP=value

Note that, after saving the prior value, GT.M ensures the superseded $ETRAP or $ZTRAP implicitly gets the value of the empty string. As a result, at most one of the two error handling mechanisms can be effective at any given point in time.

If an error handling procedure was invoked through the $ETRAP method, and the value of $ECODE is non-empty when QUITing from the level of which the error occurred, the behavior is to transfer control to the error handler associated with the newly unstacked level. However, if the QUIT command at the end of error level happens to unstack a saved value of $ZTRAP (and thus cause the value of $ETRAP to become empty), the error handling mechanism switches from $ETRAP-based to $ZTRAP-based.

[Note] Note

At the end of an error handling procedure invoked through $ZTRAP, the value of $ECODE is not examined, and this value (if any) does not cause any transfer to another error handling procedure. However, if not cleared it may later trigger a $ETRAP unstacked by a QUIT.

Choosing $ETRAP or $ZTRAP

Making a choice between the two mechanisms for error handling is mostly a matter of compatibility. If compatibility with existing GT.M code is important, and that code happens to use $ZTRAP, then $ZTRAP is the best effort choice. If compatibility with code written in MUMPS dialects from other vendors is important, then $ETRAP or a non-default form of $ZTRAP probably is the better choice.

When no pre-existing code exists that favors one mechanism, the features of the mechanisms themselves should be examined.

Almost any effect that can be achieved using one mechanism can also be achieved using the other. However, some effects are easier to achieve using one method, and some are easier using with the other.

If the mechanisms are mixed, or there is a desire to refer to $ECODE in an environment using $ZTRAP, it is recommended to have $ZTRAP error code SET $ECODE="" at some appropriate time, so that $ECODE does not become cluttered with errors that have been successfully handled.

[Note] Note

A device EXCEPTION gets control after a non-fatal device error and $ETRAP/$ZTRAP get control after other non-fatal errors.

Example 1: Returning control to a specific execution level

The following example returns control to the execution level "level" and then to an error processing routine "proc^prog".

With $ZTRAP: Set $ZTRAP="ZGOTO "_level_":proc^prog"

With $ETRAP: Set $ETRAP="Quit:$STACK>"_level_" Do proc^prog"

Note that, ZGOTO can be used with $ETRAP and $STACK with $ZTRAP. Alternatively if $ESTACK were NEWed at LEVEL:

Set $ETRAP="Quit:$ESTACK>0 Do proc^prog"

Example 2: Ignoring an Error

With $ZTRAP: Set $ZTRAP="Quit"

With $ETRAP: Set $ETRAP="Set $ECODE="""" Quit"

Note that, while it is not necessary to SET $ECODE="" when using $ZTRAP it is advisable to do it in order to permit mixing of the two mechanisms.

Example 3: Nested Error Handlers

With $ZTRAP: New $ZTRAP Set $ZTRAP=...

With $ETRAP: New $ETRAP Set $ETRAP=...

[Note] Note

In both cases, QUITting to a lower level may effectively make the other mechanism active.

Example 4: Access to "cause of error"

With $ZTRAP: If $ZSTATUS[...

With $ETRAP: If $ECODE[...

[Note] Note

The value of $ZSTATUS reflects only the most recent error, while the value of $ECODE is the cumulative list of all errors since its value was explicitly set to empty. Both values are always maintained and can be used with either mechanism.

Error Processing Cautions

$ETRAP and $ZTRAP offer many features for catching, recognizing, and recovering from errors. Code within an error processing subroutines may cause its own errors and these need to be processed without causing an infinite loop (where an error is caught, which, while being processed causes another error, which is caught, and so on).

During the debugging phase, such loops are typically the result of typographical errors in code. Once these typographical errors are corrected, the risk remains that an error trapping subroutine was designed specifically to deal with an expected condition; such as the loss of a network connection. This then creates an unexpected error of its own, such as:

  • a device that had not yet been opened because the loss of network connectivity occured sooner than expected

  • an unexpected data configuration caused by the fact that an earlier instance of the same program did not complete its task for the same reason

[Note] Note

It is important to remain aware of any issues that may arise within an error trapping procedure, and also of the conditions that might cause the code in question to be invoked.

$ETRAP is recursively invoked if it invokes a GOTO or a ZGOTO and the error condition persists in the code path and the code SETs $ECODE="". $ZTRAP is recursively invoked if the error condition persists in the code path.

Input/Output Errors

When GT.M encounters an error in the operation of an I/O device, GT.M executes the EXCEPTION deviceparameter for the OPEN/USE/CLOSE commands. An EXCEPTION deviceparameter specifies an action to take when an error occurs in the operation of an I/O device. The form of the EXCEPTION action is subject to the gtm_ztrap_form setting described for $ZTRAP, except that there is never any implicit popping with EXCEPTION actions. If a device has no current EXCEPTION, GT.M uses $ETRAP or $ZTRAP to handle an error from that device.

GT.M provides the option to:

  • Trap or process an exception based on device error.

  • Trap or process an exception based on terminal input.

An EXCEPTION based on an error for the device applies only to that device, and provides a specific error handler for a specific I/O device.

The CTRAP deviceparameter for USE establishes <CTRL-n> where 0<=n<=31 as an interrupting signal. When GT.M encounters <CTRL-n> with CTRAP enabled, GT.M executes the EXCEPTION deviceparamenter, or, $ETRAP or $ZTRAP if the device has no current EXCEPTION. <CTRL-C> is unique among <CTRL> characters, in that the OS recognizes it as an out-of-band signal and delivers it immediately; GT.M recognizes other <CTRL> character when they appear duing a READ.

Example:

GTM>ZPRINT ^EP12
EP12    WRITE !,"THIS IS ",$TEXT(+0)
        SET $ECODE="";this only affects $ETRAP
        SET $ETRAP="GOTO ET"
        ;N $ZT S $ZT="W !,"CAN'T TAKE RECIPROCAL OF 0"",*7"
        USE $P:(EXCEPTION="D BYE":CTRAP=$C(3))
        WRITE !,"TYPE <CTRL-C> TO STOP"
LOOP    FOR DO
        . READ !,"TYPE A NUMBER: ",X
        . WRITE ?20,"HAS RECIPROCAL OF: ",1/X
        . QUIT
ET      . WRITE !,"CAN'T TAKE RECIRPOCAL OF 0",*7
        . SET $ECODE=""
        QUIT
BYE     WRITE !,"YOU TYPED <CTRL-C> YOU MUST BE DONE!"
        USE $P:(EXCEPTION="":CTRAP="")
        WRITE !,"$ZSTATUS=",$ZSTATUS
        ZGOTO 1
GTM>DO ^EP12
THIS IS EP12
TYPE <CTRL-C> TO STOP
TYPE A NUMBER: 1 HAS RECIPROCAL OF: 1
TYPE A NUMBER: 2 HAS RECIRPOCAL OF: .5
TYPE A NUMBER: 3 HAS RECIPROCAL OF: .33333333333333
TYPE A NUMBER: 4 HAS RECIPROCAL OF: .25
TYPE A NUMBER: HAS RECIPROCAL OF:
CAN'T TAKE RECIPROCAL OF 0
TYPE A NUMBER:
YOU TYPED <CTRL-C> YOU MUST BE DONE!
$ZSTATUS=150372498,LOOP+1^EP12,%GTM-E-CTRAP,Character trap $C(3) encountered
GTM>

This routine prompts the user to enter a number at the terminal. If the user enters a zero, GT.M encounters an error and executes $ETRAP (or $ZTRAP). The action specified reports the error and returns to prompt the user to enter a number. With $ZTRAP, this is very straightforward. With $ETRAP, some care is required to get the code to resume at the proper place. The CTRAP deviceparameter establishes <CTRL-C> as a trap character. When GT.M encounters a <CTRL-C>, GT.M executes the EXCEPTION string whcih transfers control to the label BYE. At the label BYE, the routine terminates execution with an error message. Using the EXCEPTION deviceparameter with CTRAP generally simplifies $ETRAP or $ZTRAP handling.

$ZSTATUS allows the routine to find out which trap character GT.M encountered. When a routine has several character traps set, $ZSTATUS provides useful information for identifying which character triggered the trap, and thereby allows a custom response to a specific input.