## Chapter 16The Message and Error Systems

16.1.1 Reporting messages

16.1.3 Message tokens
16.1.4 Message parameters
16.1.5 Parameter references

16.2.2 Reporting errors
16.2.3 When to report an error

16.2.6 Deferred error reporting
16.2.7 Error message parameters

There is a general need for application programs to provide the user with informative messages about:

• What they do — for example, during long operations it is helpful if the user is kept informed of what a program is doing.
• What results have been obtained — for example, the notification of the final results from a procedure, or of some intermediate results that would help the user respond to further prompts.
• What errors have occurred — for example, errors which lead to the user being prompted to provide more sensible input to a program, or fatal errors which cause an application to stop.

This chapter describes two subroutine libraries which can be used for this purpose. They are:

MSG
— Message Reporting System.
ERR
— Error Reporting System.

These are fully described in SUN/104.

### 16.1 MSG — Message reporting system

The obvious way to produce messages in Fortran programs is by WRITE and PRINT statements. It is possible to construct the text of a message from various components, including numbers, formatted in a CHARACTER variable using the internal WRITE statement. The resulting message may then be displayed. However, it is sometimes difficult to format numerical output in its most concise form. To do this in-line each time a message is sent to the user would be very inconvenient and justifies the provision of a dedicated set of subroutines. These considerations led to the production Message Reporting System.

#### 16.1.1 Reporting messages

The primary message reporting subroutine is MSG_OUT. It has a calling sequence of the form:

CALL MSG_OUT(PARAM, TEXT, STATUS)

where PARAM is a character string giving the name of the message, TEXT is a character string giving the message text, and STATUS is the integer global status value.

It sends the message string, TEXT, to the standard output stream. This will normally be the user’s terminal, but is the log file for a batch job. The maximum message length is 200 characters. If it exceeds this, it is truncated with an ellipsis, i.e. ‘…’, but no error results.

Here is an example of using MSG_OUT:

CALL MSG_OUT(’EXAMPLE_MSGOUT’, ’An example of MSG_OUT.’, STATUS)

It is sometimes useful to add blank lines for clarity. MSG_BLANK does this:

CALL MSG_BLANK(STATUS)

Messages can also be stored in a character variable, rather than being output. MSG_LOAD does this.

#### 16.1.2 Conditional message reporting

It is sometimes useful to have varying levels of message output which may be controlled by the user. This is achieved by MSG_OUTIF:

CALL MSG_OUTIF(MSG__NORM, ’MESS_NAME’, ’A conditional message’, STATUS)

Here, the first argument is the ‘priority’ associated with the message. It has three possible values, which represent filter levels:

MSG__QUIET
— always output message, regardless of output filter setting.
MSG__NORM
— output message if current output filter is set to either MSG__NORM or MSG__VERB.
MSG__VERB
— output message only if current output filter is set to MSG__VERB.

(I know this looks wrong on a first reading, but ‘quiet’ messages are messages that are output in ‘quiet’ mode, and are therefore the loudest!) The default is MSG__NORM. It may be modified by MSG_IFSET, for example:

CALL MSG_IFSET(MSG__QUIET, STATUS)

#### 16.1.3 Message tokens

Applications often need to include the values of Fortran variables in output messages. This is done in the Message System using tokens embedded in the message text. For example, a program which measures the intensity of an emission line in a spectrum can output its result by:

CALL MSG_SETR(’FLUX’, FLUX)
CALL MSG_OUT(’EXAMPLE_RESULT’,
:             ’Emission flux is ^FLUX (erg/cm2/A/s).’, STATUS)

Here, MSG_SETR assigns the result, stored in the REAL variable FLUX, to the message token named ‘FLUX’. The token string is then included in the message text by prefixing it with the up-arrow, ‘${}^{}$^’, escape character. The usual set of similar routines is provided to handle the other standard data types.

An additional feature of the MSG_SETx routines is that calls using an existing token name will result in the value being appended to any previously assigned token string. Here is the previous example written to exploit this feature:

FUNITS = ’ erg/cm2/A/s’
CALL MSG_SETR(’FLUX’, FLUX)
CALL MSG_SETC(’FLUX’, FUNITS)
CALL MSG_OUT(’EXAMPLE_RESULT’,
:             ’Emission flux is ^FLUX.’, STATUS)

Note that repeated calls append values with no separator, and hence a leading space is needed in the FUNITS string to separate the flux value and its units in the expanded message.

Sometimes a specific format is required; for example, the message may form part of a table. MSG_FMTx is a set of subroutines of the form:

CALL MSG_FMTx(TOKEN, FORMAT, VALUE)

where FORMAT is a valid Fortran 77 format string which can be used to encode the supplied value, VALUE. As for MSG_SETx, x corresponds to each of the five standard Fortran 77 data types — D, R, I, L and C.

Sometimes it is necessary to include the message token escape character, ‘${}^{}$^’, literally in a message. When the character is not the last in a message string, it can be included literally by duplicating it. When it is immediately followed by a blank, or is at the end of the MSG_OUT text, it is included literally. Escape characters and token names will also be output literally if they appear within the value assigned to a message token; i.e. message token substitution is not recursive.

#### 16.1.4 Message parameters

In calls to MSG_OUT and MSG_LOAD, the message name is the name of a message parameter which is associated with the message text. This name should be no more than 15 characters long and may be associated with a message specified in the interface file. When the message parameter is specified in the interface file, this text is used in preference to that given in the argument list.

Here is an example of using MSG_OUT:

CALL MSG_OUT(’RD_TAPE’, ’Reading tape.’, STATUS)

This will generate the message:

If the message parameter, ‘RD_TAPE’, is associated with a different text string in the interface file, e.g.

message RD_TAPE
text ’The program is currently reading the tape, please wait.’
endmessage

then the output message would be the one defined in the interface file:

The program is currently reading the tape, please wait.

This enables ADAM applications to support foreign languages.

#### 16.1.5 Parameter references

We may need to refer to a program parameter in a message. There are two kinds of reference required:

• The keyword.
• The name of an object, device, or file.

We can include references of these kinds by prefixing parameter names with an escape character of which there are three: ‘^’, ‘%’, ‘$’. To include a parameter’s keyword, prefix its name with a ‘%’ character, as in: CALL MSG_OUT(’ET_RANGE’, ’%ET parameter is ignored’, STATUS) If the keyword of parameter ET is ‘EXPOSURE_TIME’, the output generated by this call is: EXPOSURE_TIME parameter is ignored To include the name of an object, device, or file associated with a parameter, prefix its name with a ‘$’ character, as in:

CALL MSG_OUT(’CREATING’, ’Creating \$DATASET’, STATUS)

If parameter DATASET is associated with an object called SWP1234, this would produce the output:

Creating SWP1234

Sometimes a parameter name is contained in a variable. In order to use it in a message, a message token can be associated with its name. For example:

CALL MSG_SETC(’PAR’, PNAME)
CALL MSG_OUT(’PAR_UNEXP’, ’%^PAR=0.0 unexpected’, STATUS)

In this example, the escape sequence ‘${}^{}$^’ prefixes the token name, ‘PAR’. The sequence ‘${}^{}$^PAR’ gets replaced by the contents of the character string contained in the variable PNAME; this, being prefixed by ‘%’, then gets replaced in the final message by the keyword associated with the parameter.

#### 16.1.6 Getting the conditional output level

Routine MSG_IFSET sets the filter level for conditional message output. Routine MSG_IFGET also sets it, but gets its value from the parameter system:

CALL MSG_IFGET(PNAME, STATUS)

where PNAME is the parameter name. Always use the same name, MSG_FILTER, for this purpose. The three acceptable strings are:

QUIET
— representing MSG__QUIET;
NORMAL
— representing MSG__NORM;
VERBOSE
— representing MSG__VERB.

Any other value will result in an error report and the status value being set to MSG__INVIF.

### 16.2 ERR — Error reporting system

Although the Message System could be used for reporting errors, there are a number of reasons why a separate facility should be provided:

• The Message System uses the inherited status scheme, so MSG_OUT and MSG_LOAD will not execute if STATUS has an error value. Consequently, it cannot report information about an earlier error.
• In a program consisting of many subroutine levels, each routine which has something informative to say about an error should be able to contribute to the information that the user receives. This includes:
• The routine which first detects the error, as this will probably have access to information which is hidden from higher level routines.
• The chain of routines between the main program and the routine in which the error originated. Some of these will usually be able to report on the context in which the error occurred, and so add relevant information which is not available to routines at lower levels.

This can lead to several error reports arising from a single failure.

• It is not always necessary for an error report to reach the user. For example, a routine may decide that it can safely handle an error detected at a lower level without informing the user. In this case, error reports associated with the error should be discarded, and this can only happen if the output of error messages to the user is deferred.

These considerations have led to the design and implementation of a set of routines which form the Error Reporting System.

#### 16.2.1 Inherited status checking

The recommended method of indicating when errors have occurred in Starlink software is to use an integer status value in each subroutine argument list. This inherited status argument, say STATUS, should always be the last argument and every routine should check its value on entry. The ADAM Error Strategy is as follows:

• The routine returns without action if STATUS is input with a value other than SAI__OK.
• The routine leaves STATUS unchanged if it completes successfully.
• The routine sets STATUS to an appropriate error value and outputs an error message if it fails to complete successfully.

Note that it is often useful to use a status argument and inherited status checking in routines which ‘cannot fail’. This prevents them executing, possibly producing a run-time error, if their arguments contain rubbish after a previous error. Every piece of software that calls such a routine is then saved from making an extra status check. Furthermore, if the routine is later upgraded it may acquire the potential to fail, and so a status argument will subsequently be required. If a status argument is included initially, existing code which calls the routine will not need changing.

#### 16.2.2 Reporting errors

The routine used to report errors is ERR_REP. It has a calling sequence of the form:

CALL ERR_REP(PARAM, TEXT, STATUS)

where PARAM is the error message name, TEXT is the error message text, and STATUS is the inherited status. These arguments are similar to those used in the Message System routine MSG_OUT.

The error message name, PARAM, should be a globally unique identifier for the error report with the form:

routn_message

for routines in an application, or:

fac_routn_message

for routines in a subroutine library. In the former case, routn is the name of the application routine from which ERR_REP is being called, and message is a sequence of characters uniquely identifying the error report within that routine. In the latter case, fac_routn is the full name of the routine from which ERR_REP is being called, and message is a sequence of characters unique within that routine. These naming conventions are designed to ensure that each error report made within a complete software system has a unique error name associated with it.

Here is a simple example of error reporting where part of the application code detects an invalid value of some kind, sets STATUS, reports the error, and then aborts:

IF (<invalid value>) THEN
STATUS = SAI__ERROR
CALL ERR_REP(’ROUTN_BADV’, ’Value is invalid.’, STATUS)
GO TO 999
END IF
...
999  CONTINUE
END

This sequence of three operations:

(1)
Set STATUS to an error value.
(2)
Report an error.
(3)
Abort.

is the standard response to an error condition and should be adopted by all software which uses the Error System.

Note that ERR_REP differs from MSG_OUT in that ERR_REP will execute regardless of the input value of STATUS. Although the Starlink convention is for routines not to execute if their status argument indicates a previous error, the Error System routines obviously cannot behave in this way if their purpose is to report these errors.

Message tokens can be used in ERR_REP in the same way as in MSG_OUT.

#### 16.2.3 When to report an error

In the following example, part of an application makes a series of routine calls:

CALL ROUTN1(A, B, STATUS)
CALL ROUTN2(C, STATUS)
CALL ROUTN3(T, Z, STATUS)

*  Check the global status.
IF (STATUS .NE. SAI__OK) GO TO 999
.
999   CONTINUE
END

Each routine uses the inherited status strategy and reports errors by calling ERR_REP. If an error occurs within any of them, STATUS will be set to an error value and inherited status checking by all subsequent routines will cause them not to execute. Thus, it becomes unnecessary to check for an error after each routine call, and a single check at the end of the sequence of calls is all that is required to handle correctly any error condition that may arise. Because an error report will already have been made by the routine that failed, it is usually sufficient simply to abort if an error arises in a sequence of routine calls.

It is important to distinguish the case where a called subroutine sets STATUS and makes its own error report, as above, from the case where STATUS is set explicitly as a result of a directly detected error, as in the previous example. If the error reporting strategy is to function correctly, then responsibility for reporting the error must lie with the routine which modifies the status argument. The golden rule is therefore:

If STATUS is explicitly set to an error value, then an accompanying call to ERR_REP must be made.

Unless there are good documented reasons why this cannot be done, routines which return a bad status value and do not make an accompanying error report should be regarded as containing a bug1.

#### 16.2.4 Setting and defining status values

Normally, set status values to the global constants SAI__OK and SAI__ERROR. However, when writing subroutine libraries it is useful to have a larger number of error codes available and to define these in a separate include file. The naming convention:

fac__ecode

should be used for the names of error codes where fac is the three-character facility prefix and ecode is up to five alphanumeric characters of error code name. Note the double underscore used in this naming convention. The include file should be referred to by the name fac_ERR, e.g.

INCLUDE ’SGS_ERR’

where in this case the facility name is SGS (Simple Graphics System). These symbolic constants should be defined at the beginning of every routine which requires them, prior to the declaration of any subroutine arguments or local variables.

The purpose of error codes is to enable the status argument to indicate that an error has occurred by having a value which is not equal to SAI__OK. By using a set of pre-defined error codes the calling routine is able to test the returned status to distinguish between error conditions which may require different action. Generally, it is not necessary to define a large number of error codes which would allow a unique value to be used every time an error report is made. It is sufficient to be able to distinguish the important classes of error which may occur. Examples of existing software can be consulted as a guide in this matter.

Software from outside a package which defines a set of error codes may use that package’s codes to test for specific error conditions arising within that package. However, with the exception of the SAI__ codes, it should not assign these values to the status argument. To do so could cause confusion about which package detected the error.

#### 16.2.5 The content of error messages

The purpose of an error message is to be informative and it should therefore provide as much relevant information about the context of the error as possible. It should not be misleading or contain irrelevant information. Particular care is necessary when reporting errors from routines which might be called by a wide variety of software. They should not make unjustified assumptions. For example, in a routine that adds two arrays, the report:

!! Error adding two arrays.

would be preferable to:

!! Error adding two images.

if the same routine could be called to add two spectra!

Normally it is adequate to report an error when is first detected, followed by a further report from the ‘top-level’ routine. Only include routine names in error reports if they appear in documentation.

#### 16.2.6 Deferred error reporting

The Error System can defer the output of a message to the user, and this allows the final delivery of error messages to be controlled. This is done by the routines ERR_MARK, ERR_RLSE, ERR_FLUSH and ERR_ANNUL. This section describes their functions and how they are used.

The purpose of deferred error reporting can be illustrated by the following example. Consider a routine, HELPER, which detects an error during execution. It reports the error to its caller, giving as much contextual information as it can. It also returns an error status, enabling the caller to react appropriately. However, what may be considered an ‘error’ in HELPER, e.g. an ‘end of file’ condition, may be considered by the caller to be something that can be handled without informing the user, e.g. by terminating its input. Thus, although HELPER will always report the error, it is not always necessary for the associated error message to reach the user. Deferred error reporting enables programs to handle such errors internally.

Suppose HELPER reports an error. At this point, the error message may, or may not, have been received by the user — this will depend on the environment and on whether the caller deferred the error report. HELPER should not ensure delivery of the message to the user; its responsibility ends when it aborts, and responsibility for handling the error condition passes to the caller.

Suppose HELPER is called by HELPED which defers error messages so it can decide how to handle errors. It does this by calling ERR_MARK before HELPER. This ensures that subsequent error messages are deferred and stored in an ‘error table’. It also starts a new ‘error context’ which is independent of previous error messages or tokens. Routine ERR_RLSE returns to the previous context, whereupon any messages in the new error context are transferred to the previous context. In this way, no existing error messages can be lost through deferral. ERR_MARK and ERR_RLSE should always be called in matching pairs and can be nested.

After deferring the error messages, HELPED can handle the error condition in one of two ways:

• by calling ERR_ANNUL, which ‘annuls’ the error, deleting any deferred error messages in the current context and resetting STATUS to SAI__OK. This causes the error condition to be ignored.
• by calling ERR_FLUSH, which ‘flushes out’ the error, sending any deferred error messages in the current context to the user and resetting STATUS to SAI__OK. This notifies the user that a problem has occurred, but allows the application to continue anyway.

When an application finally ends, the value of the status argument will reflect whether or not it finished with an error condition. At this point, any remaining error messages will be delivered automatically to the user.

#### 16.2.7 Error message parameters

In calls to ERR_REP, the error name is the name of a message parameter which is associated with the error message text. Like the message parameters used in MSG_OUT and MSG_LOAD, those used in ERR_REP may be associated with an error message specified in the interface file as well as in the argument list.

1For historical reasons there are still many routines in ADAM which set a status value without making an accompanying error report — these are gradually being corrected. If such a routine is used before it has been corrected, then the strategy outlined here is recommended. It is advisable not to complicate new code by attempting to make an error report on behalf of the faulty subroutine. If appropriate, please tell the relevant support person about the problem.