Previous Up Next

14.2  Errors

Error handling is one particular use of events. The main property of error events is that they have a culprit goal, i.e., the goal that detected or caused the error. The error handler obtains that goal as an argument.

The errors that the system raises have numerical identifiers, as documented in appendix C. User-defined errors have atomic names, they are the same as events. Whenever an error occurs, the ECLiPSe system identifies the type of error, and calls the appropriate handler. For each type of error, it is possible for the user to define a separate handler. This definition will replace the default error handling routine for that particular error—all other errors will still be handled by their respective handlers. It is of course possible to associate the same user defined error handler to more than one error type.

When a goal is called and produces an error, execution of the goal is aborted and the appropriate error handler is invoked. This invocation of the error handler is seen as replacing the invocation of the erroneous goal:

For errors that are classified as warnings the second point is somewhat different: if the handler succeeds, the goal that raised the warning is allowed to continue execution.

Apart from binding variables in the erroneous goal, error handlers can also leave backtrack points. However, if the error was raised by an external or a built-in that is implemented as an external, these choicepoints are discarded.3

14.2.1  Error Handlers

The predicate set_event_handler/2 is used to assign a procedure as an error handler. The call

set_event_handler(ErrorId, PredSpec)

sets the event handler for error type ErrorId to the procedure specified by PredSpec, which must be of the form Name/Arity.

The corresponding predicate get_event_handler/3 may be used to identify the current handler for a particular error. The call

get_event_handler(ErrorId, PredSpec, HomeModule)

will, provided ErrorId is a valid error identifier, unify PredSpec with the specification of the current handler for error ErrorId in the form Name/Arity, and HomeModule will be unified with the module where the error handler has been defined. Note that this error handler might not be visible from every module and therefore may not be callable.

To re-install the system’s error handler in case the user error handler is no longer needed, reset_event_handler/1 should be used. reset_error_handlers/0 resets all error handlers to their default values.

To enable the user to conveniently write predicates with error checking the built-ins

error(ErrorId, Goal)
error(ErrorId, Goal, Module)

are provided to raise the error ErrorId (an error number or a name atom) with the culprit Goal. Inside tool procedures it is usually necessary to use error/3 in order to pass the caller module to the error handler. Typical error checking code looks like this

increment(X, X1) :-
        ( integer(X) ->
            X1 is X + 1
        ;
            error(5, increment(X, X1))
        ).

The predicate current_error/1 can be used to yield all valid error numbers, a valid error is that one to which an error message and an error handler are associated. The predicate error_id/2 gives the corresponding error message to the specified error number. To ease the search for the appropriate error number, the library util contains the predicate

util:list_error(Text, N, Message)

which returns on backtracking all the errors whose error message contains the string Text.

The ability to define any Prolog predicate as the error handler permits a great deal of flexibility in error handling. However, this flexibility should be used with caution. The action of an error handler could have side effects altering the correctness of a program; indeed it could be responsible for further errors being introduced. One particular area of danger is in the use of input and output streams by error handlers.

14.2.2  Arguments of Error Handlers

An error handler has four optional arguments:

  1. The first argument is the number or atom that identifies the error.
  2. The second argument is the culprit (a structure corresponding to the call which caused the error). For instance, if, say, a type error occurs upon calling the second goal of the procedure p(2, Z):
     p(X, Y) :- a(X), b(X, Y), c(Y).
    
    the structure given to the error handler is b(2, Y). Note that the handler could bind Y which would have the same effect as if b/2 had done the binding.
  3. The third argument is only defined for a subset of the existing errors. If the error occurred inside a tool body, it holds the caller module, otherwise it is identical to the fourth argument.4
  4. The fourth argument is the lookup module for the culprit goal. This is needed for example when the handler wants to call the culprit reliably, using a qualified call via :/2.

The error handler is free to ignore some of these arguments, i.e., it can have any arity from 0 to 4. The first argument is provided for the case that the same procedure serves as the handler for several error types—then it can distinguish which is the actual error type. An error handler is just an ordinary Prolog procedure and thus within it a call may be made to any other procedure, or any built in predicate; this in particular means that a call to throw/1 may be made (see the section on the catch/3 predicate). This will work “through” the call to the error handler, and so an exit may be made from within the handler out of the current catch-block (i.e., back to the corresponding call of the catch/3 predicate). Specifying the predicates true/0 or fail/0 as error handlers will make the erroneous predicate succeed (without binding any further variables) or fail respectively.

The following two templates are the most common for error handlers. The first simply prints an error message and aborts:

my_error_handler(ErrorId, Goal, ContextModule) :-
        printf(error, "Error %w in %w in module %w%n",
                [ErrorId,Goal,ContextModule]),
        abort.

The following handler tries to repair the error and call the goal again:

my_error_repair_handler(ErrorId, Goal, ContextModule, LookupModule) :-
        % repair the error
        ... some code to repair the cause for the error ...
        % try call the erroneous goal again
        LookupModule : Goal @ ContextModule.

14.2.3  User Defined Errors

The following example illustrates the use of a user-defined error. We declare a handler for the event Invalid command and raise the new error in the application code.

% Command error handler - output invalid command, sound bell and abort
command_error_handler(_, Command) :-
        printf("\007\nInvalid command: %w\n", [Command]),
        abort.

% Activate the handler
:- set_event_handler('Invalid command', command_error_handler/2).

% top command processing loop
go :-
        writeln("Enter command."),
        read(Command),
        ( valid_command(Command)->
            process_command(Command),
            go
        ;
            error('Invalid command',Command)  % Call the error handler
        ).

% Some valid commands
valid_command(start).
valid_command(stop).

3
This is necessary because the compiler recognises simple predicates as deterministic at compile time and so if a simple predicate were to cause the invocation of a non-deterministic error handler, the generated code might no longer be correct.
4
Note that some events are not errors but are used for different purposes. In those cases the second and third argument are sometimes used differently. See Appendix C for details.

Previous Up Next