Errors and Exception Handling

 

As software quality improves, error handling becomes increasingly important in providing safe and trustworthy programs that users feel they can rely on. In this section we look at the standard predicates Visual Prolog provides, giving you control over the errors and exceptions that may occur when your application is running. This includes trapping run-time errors and controlling user interruption.

If you look in Visual Prolog's error-message file (PROLOG.ERR on the DOS-related platforms, "PDCProlog.err" in UNIX), you'll see all the error numbers applicable to both compile-time and run-time problems. All numbers above and including 10000 are reserved for user program exit codes, and you may modify and distribute the error message file if required. Additionally, in the include directory you'll find the ERROR.CON include file, containing constant declarations for all error codes. To guard against future changes, use this file for error codes rather than hard-coding numbers into your application.

1. Exception Handling and Error Trapping

The cornerstone of error and exception handling is the trap predicate, which can catch run-time errors as well as exceptions activated by the exit predicate. You can also use this mechanism to catch signals, such as that generated by Ctrl-Break in the textmode platforms, as well as a kind of "block exit" mechanism.

(1) exit/0 and exit/1

A call to exit has an effect identical to a run-time error.

    exit                                         /* (no arguments) */
    exit(ExitCode)
                                            /* (i) */

exit without an argument is equivalent to exit(0). If the call to exit is executed in a direct or indirect subgoal of a trap, the ExitCode will be passed to the trap.

The behavior of an untrapped exit depends on the platform. The VPI event handlers do their own trapping, and an exit will be caught here resulting in an error message.

An untrapped call of the exit predicate on the textmode platforms results in program termination, and the OS return code ('ErrorLevel' in the DOS-related operating systems, '$?' in UNIX sh) will be set to the value used in the call. The maximum value a process can exit with is 254; 255 is reserved for Visual Prolog's system call, but no checks are performed.

(2) trap/3

trap, which takes three arguments, carries out error trapping and exception handling. The first and the last arguments to trap are predicate calls, and the second argument is a variable; it takes this format:

    trap(PredicateCall, ExitCode, PredicateToCallOnError)

For example, consider the call:

    trap(menuact(P1, P2, P3), ExitCode, error(ExitCode, P1)), ...

If an error occurs during execution of menuact--including all further called subgoals--an error code will be returned in the variable ExitCode, and the error-handling predicate error will be called. trap will then fail on return from error. If menuact returns successfully, evaluation will continue after the trap, which will no longer be effective.

Before calling the error predicate, the system resets the stack, global stack, and trail to the values they had before the goal specified in the trap (menuact in the example above) was called. This means that you can use a trap to catch memory overflows, but you shouldn't rely on big memory consuming operations such as database updates to be in either a complete or unaltered state - a heap-full error may occur anytime.

If Break is enabled on the textmode platforms, and a Break occurs (because the user pressed Ctrl-Break during execution of a predicate with a surrounding trap), the trap will catch the Break and return 0 in the ExitCode variable.

Example: catching file-not-open

/* Program ch11e12.pro */

(3) errormsg/4

You can use the errormsg predicate to access files that are structured the same as Visual Prolog's error-message file.

    errormsg(File name, ErrorNo, ErrorMsg, ExtraHelpMsg) /* (i,i,o,o) */

A typical use of errormsg is in error-trapping predicates to obtain an explanation of an error code, as illustrated below.

    PREDICATES
        error(integer)
        main
        /*....*/

    CLAUSES
        error(0) :- !. % discard break.
    error(E) :-
            errormsg("prolog.err", E, ErrorMsg, _),
            write("¡¬nSorry; the error¡¬n", E, " : ", ErrorMsg),
            write("¡¬nhas occurred in your program."),
            write("¡¬nYour database will be saved in the file error.sav"),
            save("error.sav").

    GOAL
        trap(main, ExitCode, error(Exitcode)).

2. Error reporting

Visual Prolog includes several compiler directives that you can use to control run-time error reporting in your programs. These directives allow you to select the following:

whether code should be generated to check for integer overflows.

the level of detail in reporting run-time errors.

whether code should be generated for stack overflow checking.

You can place these compiler directives at the top of your program, or choose them from the Compiler Options dialog.

(1) errorlevel

Visual Prolog has a mechanism to locate the source position where a run-time error occurs. To do this, it generates code before predicate calls to save the source code position where executions are actually performed. The level of error reporting and the storing of source positions are selected by the errorlevel compiler directive. The syntax is:

    errorlevel = d

where d is one of 0, 1, or 2, representing the following levels:

0  This level generates the smallest and most efficient code. No source positions are saved. When an error occurs, just the error number is reported.

1  This is the default level. When an error occurs, the Visual Prolog system displays its origin (module name and include file, if applicable). The place where the error was detected within the relevant source file is also displayed, expressed in terms of the number of bytes from the beginning of the file.

2  At this level, certain errors not reported at level 1, including stack overflow, heap overflow, trail overflow, etc., are also reported. Before each predicate call code is generated to store the source position.

When a source position are reported, the source program can be loaded into the editor, and you can activate the Edit | Go To Line Number menu item, where you can enter the position number and the cursor will move to the place where the error occurred.

In a project, the errorlevel directive in each module controls that module's detail of error reporting. However, if the errorlevel directive in the main module is higher than that of the other modules, the system might generate misleading error information.

If, for example, an error occurs in a module compiled with errorlevel = 0, which is linked with a main module compiled with errorlevel set to 1 or 2, the system will be unable to show the correct location of the error--instead, it will indicate the position of some previously executed code.

For more information about projects, refer to "Modular Programming" in the chapter 17.

(2) lasterror/4

Hand in hand with trap and errormsg goes lasterror. It returns all relevant information about the most recent error, and looks like this:

    lasterror(ErrNo,Module,IncFile,Pos)              /* (i,i,i,i) */

where ErrNo is the error number, Module is the source file name, IncFile is the include file name, and Pos is the position in the source code where the error occurred. However, the program must be compiled with an errorlevel greater than 1 in order for the information to be relevant in case of memory overflow. For ordinary errors, an errorlevel of 1 is sufficient.

The primary aim of lasterror is to ease debugging when subgoals are trapped, but it may equally well form the basis of a cause-of-death indicator in commercially distributed software. Using lasterror, your code can provide the user with a quite sober error message in addition to exact information about what happened.

3. Handling Errors from the Term Reader

When you call consult or readterm and a syntax error occurs in the line read, the predicates will exit with an error. The syntax error could be any one of the following:

A string is not terminated.

A symbol is placed where an integer is expected.

Upper-case letters are used for the predicate name.

A symbol is not surrounded by double quotes.

Etc.

When consult was originally introduced in Visual Prolog, it was not meant to be used for reading user-edited files: It was designed to read back files that were saved by the save predicate. In order to make it easier to consult user-created files, we have introduced the two predicates readtermerror and consulterror. You can call these to obtain information about what went wrong in readterm or consult, respectively.

If the errors from consult and readterm are caught by the trap predicate, consulterror and readtermerror allow you to inspect and possibly edit the cause of the syntax error.

(1) consulterror/3

consulterror returns information about the line containing a syntax error.

consulterror(Line, LinePos, Filepos),               /* (o,o,o) */

Line is bound to the line that has the syntax error, LinePos is bound to the position in the line where the syntax error was found, and FilePos is bound to the position in the file where the line was read.

/* Program ch11e13.pro */

(2) readtermerror/2

readtermerror returns information about the readterm-read line containing a syntax error.

    readtermerror(Line, LinePos),                         /* (o,o) */

Line is bound to the line that has the syntax error, and LinePos is bound to the position in the line where the syntax error was found.