Quintus Prolog Manual


(PREV) (NEXT)

g-19: Errors and Exceptions

g-19-1: Overview

Whenever the Prolog system encounters a situation where it cannot continue execution, it raises an exception. For example, if a built-in predicate detects an argument of the wrong type, it raises a type_error exception. The reference page description of each built-in predicate lists the kinds of exceptions that can be raised by that built-in predicate. The default effect of raising an exception is to terminate the current computation and then print an error message. After the error message, you are back at Prolog's top level. For example, if the goal

X is a/2

 

is executed somewhere in a program you get

! Type Error in argument 2 of is/2
! number expected, but a found
! goal:  A is a/2

| ?-

 

Particular things to notice in this message are:

!
This character indicates that this is an error message rather than a warning or informational message.
Type Error
This is the exception class. Every exception raised by the system is categorized into one of a small number of classes. The classes are listed in {manual(g-19-4)}.
goal:
The goal which caused the exception to be raised.

g-19-2: Raising Exceptions

You can raise exceptions from your own code using the built-in predicate

raise_exception(+ExceptionCode)

 

The argument to this predicate is the exception code; it is an arbitrary non-variable term of which the principal functor indicates the exception class. You can use the same exception classes as the system (see {manual(g-19-4)}), or you can use your own exception classes. Error messages like the one shown above are printed using the built-in predicate print_message/2. One of the arguments to print_message is the exception code. print_message can be extended, as described in {manual(g-20)}, so that you can have appropriate error messages printed corresponding to your own exception classes.

g-19-3: Handling Exceptions

It is possible to protect a part of a program against abrupt termination in the event of an exception. There are two ways to do this:

g-19-3-1: Protecting a Particular Goal

The built-in predicate on_exception/3 enables you to handle exceptions to a specific goal: on_exception(?ExceptionCode, +ProtectedGoal, +Handler) ProtectedGoal is executed. If all goes well, it will behave just as if you had written ProtectedGoal without the on_exception/3 wrapper. If an exception is raised while ProtectedGoal is running, Prolog will abandon ProtectedGoal entirely. Any bindings made by ProtectedGoal will be undone, just as if it had failed. Side-effects, such as asserts and retracts, are not undone, just as they are not undone when a goal fails. After undoing the bindings, Prolog tries to unify the exception code raised with the ExceptionCode argument. If this unification succeeds, Handler will be executed as if you had written

    ExceptionCode=<the actual exception code>,
    Handler

 

If this unification fails, Prolog will keep searching up the ancestor list looking for another exception handler. If it reaches Prolog's top level (or a break level) without having found a call to on_exception/3 with a matching ExceptionCode, an appropriate error message is printed (using print_message/2). ProtectedGoal need not be determinate. That is, backtracking into ProtectedGoal is possible, and the exception handler becomes reactivated in this case. However, if ProtectedGoal is determinate, then the call to on_exception/3 is also determinate. Setting up an exception handler with on_exception/3 is cheap provided that ProtectedGoal is an ordinary goal. Some efficiency is lost, in the current implementation, if ProtectedGoal is of the form (Goal1,Goal2) or (Goal1;Goal2). The ProtectedGoal is logically "inside" the on_exception/3 form, but the Handler is NOT. If an exception is raised inside the Handler, this on_exception/3 form will NOT be reactivated. If you want an exception handler which protects itself, you have to program it, perhaps like this:

recursive_on_exception_handler(Err, Goal, Handler) :-
    on_exception(Err, Goal,
        recursive_on_exception_handler(Err, Handler, Handler)).

g-19-3-2: Handling Unknown Predicates

Users can write a handler for the specific exception occurring when an undefined predicate is called by defining clauses for the hook predicate unknown_predicate_handler/3. This can be thought of as a "global" exception handler for this particular exception, because unlike on_exception/3, its effect is not limited to a particular goal. Furthermore, the exception is handled at the point where the undefined predicate is called. The handler can be written to apply to all unknown predicates, or to a class of them. The reference page contains an example of constraining the handler to certain predicates.

g-19-4: Error Classes

Exceptions raised by the Prolog system are called errors. The set of exception classes used by the system has been kept small. Here is a complete list:

Instantiation Error
An input argument is insufficiently instantiated.
Type Error
An input argument is of the type.
Domain Error
An input argument is illegal but of the right type.
Range Error
A value specified for an output argument is illegal.
Representation Error
A computed value cannot be represented.
Existence Error
Something does not exist.
Permission Error
Specified operation is not permitted.
Context Error
Specified operation is not permitted in this context.
Consistency Error
Two otherwise correct values are inconsistent with each other.
Syntax Error
Error in reading a term.
Resource Error
Some resource limit has been exceeded.
System Error
An error detected by the operating system.

The exception codes corresponding to these classes are:

     instantiation_error(Goal, ArgNo)
     type_error(Goal, ArgNo, TypeName, Culprit)
     domain_error(Goal, ArgNo, DomainName, Culprit, Message)
     range_error(Goal, ArgNo, TypeName, Culprit)
     representation_error(Goal, ArgNo, Message)
     existence_error(Goal, ArgNo, ObjectType, Culprit, Message)
     permission_error(Goal, Operation, ObjectType, Culprit, Message)
     context_error(Goal, ContextType, CommandType)
     consistency_error(Goal, Culprit1, Culprit2, Message)
     syntax_error(Goal, Position, Message, Left, Right)
     resource_error(Goal, Resource, Message)
     system_error(Message)


Most exception codes include a copy of the Goal which raised the exception. In general, built-in predicates which cause side-effects, such as the opening of a stream or asserting a clause into the Prolog database, attempt to do all error checking before the side-effect is performed. Unless otherwise indicated in the documentation for a particular predicate or error class, it should be assumed that goals which raise exceptions have not performed any side-effect.

g-19-4-1: Instantiation Errors

An instantiation error occurs when a predicate or command is called with one of its input arguments insufficiently instantiated. The exception code associated with an instantiation error is

instantiation_error(Goal, ArgNo)

ArgNo is a non-negative integer indicating which argument caused the problem. ArgNo=0 means that the problem could not be localized to a single argument. Note that the ArgNoth argument of Goal might well be a non-variable: the error is in that argument. For example, the goal

X is Y+1

 

where Y is uninstantiated raises the exception

instantiation_error(_2298 is _2301+1,2)

 

because the second argument to is/2 contains a variable.

g-19-4-2: Type Errors

A type error occurs when an input argument is of the wrong "type". In general, a "type" is taken to be a class of terms for which there exists a unary "type test predicate". Some types are built-in, such as atom/1 and integer/1. Some are defined in library(types), such as chars/1. The type of a term is the sort of thing you can tell just by looking at it, without checking to see how "big" it is. So "integer" is a type, but "non-negative integer" is not, and "atom" is a type, but "atom with 5 letters in its name" and "atom starting with 'x'" are not. The point of a type error is that you have obviously passed the wrong sort of argument to a command; perhaps you have switched two arguments, or perhaps you have called the wrong predicate, but it isn't a subtle matter of being off by one. Most built-in predicates check all their input arguments for type errors. The exception code associated with a type error is

type_error(Goal, ArgNo, TypeName, Culprit)

ArgNo
Culprit occurs somewhere in the ArgNoth argument of Goal.
TypeName
says what sort of term was expected; it should be the name of a unary predicate which is true of whatever terms would not provoke a type error. Some TypeNames recognized by the system include:
0
No type name specified
atom
atomic
callable
db_reference
integer
number
Culprit
is the actual term being complained about: TypeName(Culprit) should be false.

For example, suppose we had a predicate

date_plus(NumberOfDays, Date0, Date)

 

true when Date0 and Date were date(Y,M,D) records and NumberOfDays was the number of days between those two dates. You might see an error term such as

type_error(/* Goal     */ date_plus(27, date(18,mar,11), _235),
           /* Argno    */ 2,
           /* TypeName */ integer,
           /* Culprit  */ mar


g-19-4-3: Domain Errors

A domain error occurs when an input argument is of the right type but there is something wrong with its value. For example, the second argument to open/3 is supposed to be an atom which represents a valid mode for opening a file, such as 'read' or 'write'. If a number or a compound term is given instead, that is a type error. If an atom is given which is not a valid mode, that is a domain error. The main reason that we distinguish between type errors and domain errors is that they usually represent different sorts of mistake in your program. A type error usually indicates that you have passed the wrong argument to a command, whereas a domain error usually indicates that you passed the argument you meant to check, but you hadn't checked it enough. The exception code associated with a domain error is

domain_error(Goal, ArgNo, DomainName, Culprit, Message)

The arguments correspond to those of the exception code for a type error, except that DomainName is not in general the name of a unary predicate: it needn't even be an atom. For example, if some command requires an argument to be an integer in the range 1..99, it might use between(1,99) as the DomainName. With respect to the date_plus example under "Type Errors", if the month had been given as 13 it would have passed the type test but would raise a domain error. For example, the goal

open(somefile,rread,S)

 

raises the exception

domain_error(open(somefile,rread,_2490),2,'i/o mode',rread,'')

The Message argument is used to provide extra information about the problem. Some DomainNames recognized by the system include:

0
no domain name specified
atom
'atom or list'
atomic
between(L,H)
'something between L and H
built_in
callable
char
character
compiled
directory
declaration
db_reference
foreign
file
flag
imported_predicate
integer
interpreted
list
module
number
one_of(List)
a member of the set List
pair
predicate_specification
procedure
stream
term
value(X)
the value X
clause

g-19-4-4: Range Errors

A range error occurs when an output argument was supplied with an illegal value. This is similar to a type error or a domain error, except that it is a hint that a variable would be a good thing to supply instead; type and domain errors are associated with input arguments, where a variable would usually not be a good idea. The exception code associated with a range error is

range_error(Goal, ArgNo, TypeName, Culprit)

 

This has the same arguments as a type error. Most built-in predicates do not raise any range errors. Instead they fail quietly when an output argument fails to unify.

g-19-4-5: Representation Errors

A representation error occurs when your program calls for the computation of some well-defined value which cannot be represented. Most representation errors are some sort of overflow:

functor(T, f, 1000)             % maximum arity is 255
X is 16'7fffffff, Y is X+1      % 32-bit signed integers
atom_chars(X, L)                % if length of L > 1024

 

are all representation errors. Floating-point overflow is a representation error. The exception code for a representation error is

representation_error(Goal, ArgNo, Message)

ArgNo
identifies the argument of the goal which cannot be constructed.
Message
further classifies the problem. A message of 0 or '' provides no further information.

g-19-4-6: Existence Errors

An existence error occurs when a predicate attempts to access something which does not exist. For example, trying to compile a file which does not exist, erasing a database reference which has already been erased. A less obvious example: reading past the end of file marker in a stream is regarded as asking for an object (the next character) which does not exist. The exception code associated with an existence error is

existence_error(Goal, ArgNo, ObjectType, Culprit, Message)

ArgNo
index of argument of Goal where Culprit appears
ObjectType
expected type of non-existent object
Culprit
name for the non-existent object
Message
the constant 0 or '', or
some additional information provided by the operating system or other support system indicating why Culprit is thought not to exist.

For example, see('../brother/niece') might raise the exception

existence_error(see('../brother/niece'),
        1, file, '/usr/stella/parent/brother/niece',
        errno(20))

 

where the Message encodes the UNIX error "ENOTDIR" (some component of the path is not a directory). As a general rule, if Culprit was provided in the goal as some sort of context-sensitive name, the Prolog system will try to resolve it to an absolute name, as shown here, so that you can see whether the problem is just that the name was resolved in the wrong context.

g-19-4-7: Permission Errors

A permission error occurs when an operation is attempted which is among the kinds of operation which the system is in general capable of performing, and among the kinds which you are in general allowed to request, but this particular time it isn't permitted. Usually, the reason for a permission error is that the "owner" of one of the objects has requested that the object be protected. An example of this inside Prolog is attempting to change a predicate which has not been declared ":-dynamic". File system protection is the main cause of such errors. The exception code associated with a permission error is

permission_error(Goal, Operation, ObjectType, Culprit, Message)

Operation
operation attempted; Operation exists but is not permitted with Culprit. Some Operations recognized by the system include:
0
no operation specified
'find absolute path of'
'get the time stamp of'
'set prompt on'
'use close(filename) on'
abolish
change
check_advice
clauses
access clauses of
close
create
export
flush
load
nospy
nocheck_advice
open
position
read
redefine
save
spy
write
ObjectType
Culprit's type.
Culprit
name of protected object.
Message
provides such operating-system-specific additional information as may be available. A message of 0 or '' provides no further information.

g-19-4-8: Context Errors

A context error occurs when a goal or declaration appears in the wrong place. There may or may not be anything wrong with the goal or declaration as such; the point is that it is out of place. Calling :-multifile/1 as a goal is a context error, as is having :-module/2 anywhere but as the first term in a source file. The exception code associated with a context error is

context_error(Goal, ContextType, CommandType)

ContextType
the context in which the command was attempted. Some ContextTypes recognized by the system include:
'pseudo-file ''user'''
for pseudo-file 'user'
if
inside an if
bof
at beginning of file
bom
at beginning of module
query
in query
before
before
'after clauses'
after clauses
'not multifile and defined'
for defined, non-multifile procedure
'static multifile'
for static multifile procedure.
language(L)
for language L.
file_load
during load of file(s).
started
started up
notoplevel
when no top level
CommandType
the type of command that was attempted. Some CommandTypes recognized by the system include:
0
no commandtype specified
cut
clause
declaration
'meta_predicate declaration'
use_module
'multifile assert'
'module declaration'
'dynamic declaration'
meta_predicate(M)
meta_predicate declaration for M
argspec(A)
Invalid argument specification A
foreign_file(File)
foreign_file/2 declaration for File
foreign(F)
foreign/3 declaration for F
(initialization)
initialization hook
abort
call to abort/0

g-19-4-9: Consistency Errors

A consistency error occurs when two otherwise valid values or operations have been specified which are inconsistent with each other. For example, if two modules each import the same predicate from the other, that is a consistency error. The exception code associated with a consistency error is

consistency_error(Goal, Culprit1, Culprit2, Message)

Culprit1
One of the conflicting values/operations.
Culprit2
The other conflicting value/operation..
Message
Additional information, or 0, or ''.

g-19-4-10: Syntax Errors

A syntax error occurs when data are read from some external source but have an improper format or cannot be processed for some other reason. This category mainly applies to read/1 and its variants. The exception code associated with a syntax error is

syntax_error(Goal, Position, Message, Left, Right)

 

where Goal is the goal in question, Position identifies the position in the stream where reading started, and Message describes the error. Left and right are lists of tokens before and after the error, respectively. Note that the Position is where reading started, not where the error "is". read/1 does two things. First, it reads a sequence of characters from the current input stream up to and including a clause terminator, or the end of file marker, whichever comes first. Then it attempts to parse the sequence of characters as a Prolog term. If the parse is unsuccessful, a syntax error occurs. Thus, in the case of syntax errors, read/1 disobeys the normal rule that predicates should detect and report errors before they perform any side-effects, because the side-effect of reading the characters has been done. A syntax error does not necessarily cause an exception to be raised. The behavior can be controlled via a prolog_flag as follows:

  1. prolog_flag(syntax_errors, quiet)
    When a syntax error is detected, nothing is printed, and read/1 just quietly fails.
  2. prolog_flag(syntax_errors, dec10)
    This provides compatibility with DEC-10 Prolog and earlier versions of Quintus Prolog: when a syntax error is detected, a syntax error message is printed on user_error, and the read is repeated. This is the default for the sake of compatibility with earlier releases.
  3. prolog_flag(syntax_errors, fail)
    This provides compatibility with C Prolog. When a syntax error is detected, a syntax error message is printed on user_error, and the read then fails.
  4. prolog_flag(syntax_errors, error)
    When a syntax error is detected, an exception is raised.

g-19-4-11: Resource Errors

A resource error occurs when some resource runs out. For example, you can run out of virtual memory, or you can exceed the operating system limit on the number of simultaneously open files. Often a resource error arises because of a programming mistake: for example, you may exceed the maximum number of open files because your program doesn't close files when it has finished with them. Or, you may run out of virtual memory because you have a non-terminating recursion in your program. The exception code for a resource error is

resource_error(Goal, Resource, Message)

Goal
A copy of the goal, or 0 if no goal was responsible; for example there is no particular goal to blame if you run out of virtual memory.
Resource
identifies the resource which was exhausted. Some Resources recognized by the system include:
0
No resource specified
memory
out of memory
'too many open files'
Message
an operating-system-specific message. Usually it will be errno(ErrNo).

g-19-4-12: System Errors

System errors are problems that the operating system notices (or causes). Note that many of the exception indications returned by the operating system (such as "file does not exist") are mapped to Prolog exceptions; it is only really unexpected things which show up as system errors. The exception code for a system error is

system_error(Message)

 

where Message is not further specified.

g-19-5: An Example

Suppose you want a routine which is given a file name and a prompt string. This routine is to open the file if it can; otherwise it is to prompt the user for a replacement name. If the user enters an empty name, it is to fail. Otherwise, it is to keep asking the user for a name until something works, and then it is to return the stream which was opened. (There is no need to return the file name which was finally used. We can get it from the stream.)

:- use_module(library(prompt), [
        prompted_line/2
   ]).

open_output(FileName, Prompt, Stream) :-
        on_exception(Error,
            open(FileName, write, Stream),
            (   file_error(Error) ->
                print_message(the atom warning, Error),
                retry_open_output(Prompt, Stream)
            ;   raise_exception(Error)
            )).

file_error(domain_error(open(_,_,_), 1, _, _, _)).
file_error(existence_error(open(_,_,_), 1, _, _, _)).
file_error(permission_error(open(_,_,_), _, _, _, _)).

retry_open_output(Prompt, Stream) :-
        prompted_line(Prompt, Chars),
        atom_chars(FileName, Chars),
        FileName \== '',
        open_output(FileName, Prompt, Stream).

What this example does not catch is as interesting as what it does. All instantiation errors, type errors, context errors, and range errors are re-raised, as they represent errors in the program. As the previous example shows, you generally do not want to catch all exceptions that a particular goal might raise.

g-19-6: Exceptions and Critical Regions

The point of critical regions in your code is that sometimes you have data-base updates or other operations which, once started, must be completed in order to avoid an inconsistent state. In particular, such operations should not be interrupted by a ^C from the keyboard. In releases of Quintus Prolog prior to release 3.0, library(critical) was provided to allow critical regions to be specified using the predicates begin_critical/0 and end_critical/0. These predicates are still provided, but they should be regarded as obsolete since they do not interact well with exception handling. An exception occurring in between the begin_critical and the end_critical could cause two problems:

  1. the Prolog database could be left in an inconsistent state, and
  2. the critical region would never be exited, so interrupts would be left disabled indefinitely.

To avoid these problems, you should use the new predicates

critical(Goal)
critical_on_exception(ErrorCode, Goal, Handler)

 

which library(critical) now provides. critical/1 runs the specified goal inside a critical region. It solves (2) by catching any exceptions that are raised and taking care to close the critical region before re-raising the exception. critical_on_exception/3 allows you to solve (1) by specifying appropriate clean-up actions in Handler. If an exception occurs during Goal, and the exception code unifies with ErrorCode, critical_on_exception/3 acts as if you had written

critical(Handler)

 

instead. That is, the Handler will still be inside the critical region, and only the first solution it returns will be taken. These forms also have the effect of committing to the first solution of Goal. Since the point of a critical region is to ensure that some operation with side-effects is completed, Goal should be determinate anyway, so this should be no problem.

g-19-7: Summary of Predicates and Functions

g-19-8: Summary of Relevant Libraries


Copyright (C) 1997 AI International Ltd
contact: product support sales information