-*- Mode: LISP; Syntax: Common-Lisp -*- Condition System, Revision #18 by KMP 12-Mar-88, Page 1 Common Lisp Condition System, Revision #18 Written by Kent M. Pitman ***** This draft is a snapshot of work in progress provided for ***** ***** informational purposes to X3J13 by the X3J13 subcommittee ***** ***** on Error Handling. It is not a request for voting action. ***** Members of the X3J13 subcommittee on ``Error Handling'' are: Andy Daniels ; Daniels.PA@Xerox.COM Kent Pitman ; KMP@STONY-BROOK.SCRC.Symbolics.COM The following people have made major contributions to this design: Richard Mlynarik ; MLY@MC.LCS.MIT.EDU David A. Moon ; Moon@STONY-BROOK.SCRC.Symbolics.COM The following people have provided useful comments, questions, suggestions, and criticisms: Paul Anagnostopoulos Susan Lander Alan Bawden David D. Loeffler William Chiles Ken Olum Pavel Curtis David C. Plummer Mary Fontana Alan Snyder Dick Gabriel Eric Weaver Dick King Daniel L. Weinreb In some cases, suggestions by people other than the author were not used, either because of some technical disagreement or limitations of time. Although it's the hope of the author that all of these people will ultimately be satisfied by the work described in this document, the mere presence of their names here does should not be taken to mean that these people have endorsed its contents. The design of this condition system has been made specifically to accomodate the needs of Common Lisp. It is not simply a proposal to adopt a set of primitives already supported by some other dialect. The design is, however, most directly based on the ``New Error System'' (NES) developed at Symbolics by: David L. Andre ; DLA@DIAMOND.S4CC.Symbolics.COM Bernard S. Greenberg ; BSG@STONY-BROOK.SCRC.Symbolics.COM Mike McMahon ; MMcM@STONY-BROOK.SCRC.Symbolics.COM David A. Moon ; Moon@STONY-BROOK.SCRC.Symbolics.COM Daniel L. Weinreb ; DLW@STONY-BROOK.SCRC.Symbolics.COM The NES was in turn based on experiences with the original Lisp Machine error system (developed at MIT) which was found to be inadequate for the needs of the modern Lisp Machine environments. Many aspects of the NES were inspired by the (PL/1) condition system used by the Honeywell Multics operating system. Some conceptual guidance and encouragement in the design of the NES was due to: Henry Lieberman ; Henry@AI.AI.MIT.EDU A reimplementation of the NES for non-Symbolics Lisp Machine dialects (MIT, LMI, and TI) was done at MIT. During the process of that reimplementation, some conceptual changes were made which have significantly influenced this proposal. That work was done by: Richard M. Stallman ; RMS@AI.AI.MIT.EDU Condition System, Revision #18 by KMP 12-Mar-88, Page 2 Modification History: Rev Date Public References 0 08-Feb-85 No S:>kmp>cl>eh.text 1 26-Nov-85 No S:>kmp>cl>errsys.text 2 10-Dec-85 No S:>kmp>cl>error-proposal.text 3 17-Feb-86 No S:>kmp>cl>new-error-proposal.text 4 15-Mar-86 Yes S:>kmp>cl>error-proposal-4.text (distributed) 5 17-Mar-86 Yes S:>kmp>cl>error-proposal-5.text MIT-AI: COMMON; EPROP5 TEXT 6 18-Jul-86 No S:>kmp>cl>error-proposal-6.text 7 29-Jul-86 No S:>kmp>cl>error-proposal-7.text 8 04-Aug-86 Yes S:>kmp>cl>error-proposal-8.text AI.AI.MIT.EDU: COMMON; EPROP8 TEXT C.CS.CMU.EDU: PRVA:ERROR-PROP-8.TXT SAIL.STANFORD.EDU: EPROP8.TXT[COM,LSP] X3J13/86-012 9 14-Mar-87 No S:>kmp>cl>conditions>revision-9.text 10 17-May-87 No S:>kmp>cl>conditions>revision-10.text 11 07-Jun-87 No S:>kmp>cl>conditions>revision-11.text 12 08-Jun-87 No S:>kmp>cl>conditions>revision-12.text 13 09-Jun-87 No S:>kmp>cl>conditions>revision-13.text 14 29-Jun-87 No S:>kmp>cl>conditions>revision-14.text 15 30-Jun-87 Yes S:>kmp>cl>conditions>revision-15.text MIT-AI: COMMON; COND15 TEXT X3J13/87-??? 16 01-Jul-87 No S:>kmp>cl>conditions>revision-16.text 17 19-Feb-88 No S:>kmp>cl>conditions>revision-17.text 18 12-Mar-88 Yes S:>kmp>cl>conditions>revision-18.text MIT-AI: COMMON; COND18 TEXT X3J13/88-005 Note: Revisions not described as public were special purpose drafts produced for very limited distribution or for my internal use. If you never saw them, don't be concerned. Condition System, Revision #18 by KMP 12-Mar-88, Page 3 The major features of this proposal since Revision 15 include: Renamings: None Additions: * p30 HANDLER-CASE now allows a :NO-ERROR clause. * p42 New variable *DEBUGGER-HOOK* allowing customized interactive debuggers. * p47 New condition type PACKAGE-ERROR and accessor PACKAGE-ERROR-PACKAGE. Removals: None Changes: * pp47-48 CLOSE operates on streams, not files, so should signal a STREAM-ERROR, not a FILE-ERROR if it has problems. Condition System, Revision #18 by KMP 12-Mar-88, Page 4 Clarifications and Corrections: * p30 Typo: HANDLER-CASE does not take keyword/value pairs. * p35 Typo: INVOKE-ESCAPE-INTERACTIVELY should have been INVOKE-RESTART-INTERACTIVELY. * p44 The condition type WARNING does not take a :WARNING-CONDITION init keyword. Condition System, Revision #18 by KMP 12-Mar-88, Page 5 Outstanding issues: * We need a list of places where CLtL specifies that errors must (or might) be signalled and where we might want to specify the particular condition to be signalled. [KMP has started making this list, but there's quite a lot of work involved in doing it and things are not yet in a form suitable for reporting.] Moon suggests (and this motivates some changes in this document) that Zetalisp initially went overboard in what conditions it documents. He suggests that we document only those condition types which we can reasonably expect portable code to want to use. In many cases, a more specific type will want to be signalled by particular implementations, but portable code will not be able to rely on that. Since the details of errors vary a lot from implementation to implementation, Moon thinks it's a bad idea to try to enforce a lot of detail in places where it hasn't been demonstrated to be a good idea. * Daniels still thinks there should be a way to access and modify report functions for conditions without going through DEFINE-CONDITION again. [This has been blocked waiting for CLOS to settle down.] * Moon writes: ``I'm still bothered by the fact that there is no way for a handler, handling a given condition, to know when it invokes a restart that that restart was established to recover from that particular condition. This is simply the general problem of dynamic scoping in Lisp. I think this issue only arises when a condition is signalled while executing a handler. I have been unable to think of any reasonable way out of this, but it is certainly a weakness in the proposal.'' KMP concurs heartily, and has heard noises to the same effect from others who've been reading this document closely. It is not clear whether this criticism means that this proposal is missing the mark someplace or if it means that we need to simply layer on some new thing to address this issue. Nevertheless, this problem is an important one and the answers are not obvious. [Suggestions solicited.] Condition System, Revision #18 by KMP 12-Mar-88, Page 6 INTRODUCTION ------------ Often we find it useful to describe a function in terms of its behavior in ``normal situations.'' For example, we may say informally that the function + returns the sum of its arguments or that the function READ-CHAR returns the next available character on a given input stream. Sometimes, however, an ``exceptional situation'' will arise which does not fit neatly into such descriptions. For example, + might receive an argument which is not a number, or READ-CHAR might receive a single argument which was a stream that had no more available characters. This distinction between normal and exceptional situations is in some sense arbitrary, but is often very useful in practice. For example, suppose you had a function F which you defined to allow only integer arguments, but you also guaranteed that the function F would detect and signal an error for non-integer arguments. Such a description is in fact internally inconsistent (i.e., paradoxical) because the behavior is well-defined for non-integers. Yet we would not want this annoying paradox to force us to have to describe F as a function that accepts any kind of argument (just in case F is being called only as a quick way to signal an error, for example). Using our new terminology, we can say clearly that F accepts integers in the normal situation, and signals an error in exceptional situations. Moreover, we can say that when we refer to the definition of a function informally, it is acceptable to speak only of its normal behavior. For example, we can speak informally about F as a function that accepts only integers without feeling that we are committing some awful fraud. Not all exceptional situations are errors. For example, a program which is typing out a long line of text may notice that it is at the end of the line. It is possible that no real harm will result from continuing to type past the end of the line because the operating system will simply force a carriage return on the output device and continue typing on the next line. However, it may still be interesting to establish a protocol whereby that program can inform its callers of end-of-line exceptions. The controlling program could then opt to deal with these situations in interesting ways at certain times. For example, it might choose to terminate printing, obtaining a end-of-line truncation. The important thing, however, is that the failure of the controlling program to provide advice about the situation need not prevent the printer program from operating correctly. Mechanisms for dealing with exceptional situations vary widely. When an exceptional situation is encountered, a program may attempt to handle the situation by returning a distinguished value, returning an additional value, setting a variable, calling a function, performing a special transfer of control, or by stopping the program altogether and entering the debugger. For the most part, the facilities described in this document do not introduce any fundamentally new way of dealing with exceptional situations. Rather, they encapsulate and formalize useful patterns of data and control flow which have been seen to be useful in dealing with exceptional situations. A proper conceptual approach to errors should perhaps begin from first principles with a discussion of ``conditions'' in general and eventually work its way up to the concept of an ``error'' as just one of the many kinds of conditions. However, given the widespread primitive state of error handling technology, a proper buildup may be as inappropriate as requiring that a beggar learn to cook a gourmet meal before being allowed to eat. As such, we'll first deal with the essentials -- error handling -- and then later we'll go back and fill in the missing details. Condition System, Revision #18 by KMP 12-Mar-88, Page 7 TERMINOLOGY USED IN CLTL ------------------------ About errors, CLtL (pp6-7) says the following: When this manual specifies that it ``is an error'' for some situation to occur, this means that: * No valid Common Lisp program should cause this situation to occur. * If the situation occurs, the effects are completely undefined as far as adherence to the Common Lisp specification is concerned. * No Common Lisp implementation is required to detect such an error. Of course, implementors are encouraged to provide for detection of such errors wherever reasonable. This is not to say that some particular implementation might not define the effects and results for such a situation; the point is that no program conforming to the Common Lisp specification may correctly depend on such effects or results. On the other hand, if it is specified in this manual that in some situation ``an error is signalled,'' this means that: * If this situation occurs, an error will be signalled. See ERROR and CERROR. * Valid Common Lisp programs may rely on the fact that an error will be signalled. * Every Common Lisp implementation is required to detect such an error. In all cases where it is stated that so-and-so ``must'' or ``must not'' or ``may not'' be the case, then it ``is an error'' if the stated requirement is not met. For example, if an argument ``must be a symbol,'' then it ``is an error'' if the argument is not a symbol. In all cases where an error is to be -signalled-, the word ``signalled'' is always used explicitly in this manual. Condition System, Revision #18 by KMP 12-Mar-88, Page 8 CHANGES TO TERMINOLOGY ---------------------- A ``condition'' is an interesting situation in a program which has been detected and announced. Later we will allow this term to also refer to objects which programs use to represent such situations. An ``error'' is a ``condition'' in which normal program execution may not continue without some form of intervention (either interactively by the user or under some sort of program control as described later in this document). The process by which a condition is formally announced by a program is called ``signalling.'' The function SIGNAL is the primitive mechanism by which such announcement is done. Other abstractions, such as ERROR and CERROR, are built using SIGNAL. As written, CLtL is ambiguous about the reason why a particular program action ``is an error.'' There are two principal reasons why an action may be an error without being required to signal an error: * Detecting the error might be prohibitively expensive. For example, (+ NIL 3) is an error. It is likely that the designers of Common Lisp believed that this would be an error in all implementations but that they felt that it might be excessively expensive to detect the problem in compiled code on stock hardware, so they did not require that it signal an error. * Some implementations might implement the behavior as an extension. For example, (LOOP FOR X FROM 1 TO 3 DO (PRINT X)) is an error because LOOP is not defined to take atoms in its body. In fact, however, some implementations offer an extension which makes this well-defined. In order to leave room for such extensions, CLtL again uses the ``is an error'' terminology to keep implementors from being forced to signal an error in the extended implementations. In this document, we will use the following terminology, which we also propose become standard in the next edition of the CL specification: * If the signalling of a condition or error is part of a function's contract in all situations, we will say that it ``signals'' or ``must signal'' that condition or error. * If the signalling of a condition or error is optional for some important reason (such as performance), we will say that the program ``might signal'' that condition or error. In this case, we are defining the operation to be illegal in all implementations, but allowing some implementations to not actually detect the error. * If an action is left undefined for the sake of implementation-dependent extension, we will say that it ``is undefined'' or ``has undefined effect.'' This means that it is not possible to portably depend upon the effects of that action. A program which has undefined effect may enter the debugger, transfer control, or modify data in unpredictable ways. * In the special case where only the return value of an operation is not well defined but any side-effect and transfer-of-control behavior is well defined, we will say that it has ``undefined value.'' In this case, the number and nature of the return values is not defined, but the function can reasonably be expected to return. It is worth noting that under this description, there are some (though not many) legitimate ways in which such return value(s) can be used. For example, if the function FOO has no side-effects and undefined value, the expression (LENGTH (LIST (FOO))) is completely well-defined even for portable code. The effect of (PRINT (LIST (FOO))) is not, however, well-defined. Condition System, Revision #18 by KMP 12-Mar-88, Page 9 SURVEY OF CONCEPTS ------------------ Signalling Errors ----------------- Conceptually, signalling an error in a program is an admission by that program that it does not know how to continue and requires external intervention. Once an error is signalled, any decision about how to continue must come from the ``outside.'' The simplest way to signal an error is to use the ERROR function with FORMAT-style arguments describing the error for the sake of the user interface. If ERROR is called and there are no active handlers (described later), the debugger will be entered and the error message will be typed out. For example, you might see an interaction such as: Lisp> (DEFUN FACTORIAL (X) (COND ((OR (NOT (TYPEP X 'INTEGER)) (MINUSP X)) (ERROR "~S is not a valid argument to FACTORIAL." X)) ((ZEROP X) 1) (T (* X (FACTORIAL (- X 1)))))) => FACTORIAL Lisp> (FACTORIAL 20) => 2432902008176640000 Lisp> (FACTORIAL -1) Error: -1 is not a valid argument to FACTORIAL. To continue, type :CONTINUE followed by an option number: 1: Return to Lisp Toplevel. Debug> In general, a call to ERROR cannot directly return. Unless special work has been done to override this behavior, the debugger will be entered and there will be no option to simply continue. The only exception may be that some implementations may provide debugger commands for interactively returning from individual stack frames; even then, however, such commands should never be used except by someone who has read the erring code and understands the consequences of continuing from that point. In particular, it should be possible for the programmer should feel confident about writing code like: (DEFUN WARGAMES:NO-WIN-SCENARIO () (IF (TRUE) (ERROR "Pushing the button would be stupid.")) (PUSH-THE-BUTTON)) In this scenario, there should be no chance that the function ERROR will return and the button will get pushed. Condition System, Revision #18 by KMP 12-Mar-88, Page 10 In some cases, the programmer may have a single, well-defined idea of a reasonable recovery strategy for this particular error. In that case, he can use the function CERROR, which specifies information about what would happen if the user did simply continue from the call to CERROR. For example: Lisp> (DEFUN FACTORIAL (X) (COND ((NOT (TYPEP X 'INTEGER)) (ERROR "~S is not a valid argument to FACTORIAL." X)) ((MINUSP X) (LET ((X-MAGNITUDE (- X))) (CERROR "Compute -(~D!) instead." "(-~D)! is not defined." X-MAGNITUDE) (- (FACTORIAL X-MAGNITUDE)))) ((ZEROP X) 1) (T (* X (FACTORIAL (- X 1)))))) => FACTORIAL Lisp> (FACTORIAL -3) Error: (-3)! is not defined. To continue, type :CONTINUE followed by an option number: 1: Compute -(3!) instead. 2: Return to Lisp Toplevel. Debug> :CONTINUE 1 => -6 Trapping Errors --------------- By default, a call to ERROR will force entry into the debugger. You can override that behavior in a variety of ways. The simplest (and most blunt) tool for inhibiting entry to the debugger on an error is to use IGNORE-ERRORS. In the normal situation, forms in the body of IGNORE-ERRORS are evaluated sequentially and the last value is returned. If a condition of type ERROR is signalled, IGNORE-ERRORS immediately returns two values -- NIL and the condition which was signalled; the debugger is not entered and no error message is printed. For example: Lisp> (SETQ FILENAME "NOSUCHFILE") => "NOSUCHFILE" Lisp> (IGNORE-ERRORS (OPEN FILENAME :DIRECTION :INPUT)) => NIL, # The second return value is an object which represents the kind of error. This will be explained in greater detail later. In many cases, however, IGNORE-ERRORS is not desirable because it deals with too many kinds of errors. Contrary to the belief of some, a program which doesn't enter the debugger is not necessarily better than one which does. Excessive use of IGNORE-ERRORS may keep you out of the debugger, but it may not increase your program's reliability because your program may be continuing to run after encountering errors other than those you meant to work past. In general, it is better to attempt to deal only with the particular kinds of errors that you believe could legitimately happen. That way, if an unexpected error comes along, you will still find out about it. Condition System, Revision #18 by KMP 12-Mar-88, Page 11 IGNORE-ERRORS is a useful special case built from a more general facility, called HANDLER-CASE, which allows you to deal with particular kinds of conditions (including non-error conditions) without affecting what happens when other kinds of conditions are signalled. For example, an effect equivalent to that of IGNORE-ERRORS above is achieved in the following example: Lisp> (SETQ FILENAME "NOSUCHFILE") => "NOSUCHFILE" Lisp> (HANDLER-CASE (OPEN FILENAME :DIRECTION :INPUT) (ERROR (CONDITION) (VALUES NIL CONDITION))) => NIL, # However, using HANDLER-CASE it is possible to specify a more specific condition type than just error. Condition types are explained in detail later, but the syntax looks roughly like the following: Lisp> (MAKUNBOUND 'FILENAME) => FILENAME Lisp> (HANDLER-CASE (OPEN FILENAME :DIRECTION :INPUT) (FILE-ERROR (CONDITION) (VALUES NIL CONDITION))) Error: The variable FILENAME is unbound. To continue, type :CONTINUE followed by an option number: 1: Retry getting the value of FILENAME. 2: Specify a value of FILENAME to use this time. 3: Specify a value of FILENAME to store and use. 4: Return to Lisp Toplevel. Debug> Handling Conditions ------------------- In fact, blind transfer of control to a HANDLER-CASE is only one possible kind of recovery action that can be taken when a condition is signalled. The low-level mechanism offers a great deal of flexibility in how to continue once a condition has been signalled. The basic idea behind condition handling is that a piece of code called the ``signaller'' recognizes and announces the existence of an exceptional situation using SIGNAL or some function built on SIGNAL (such as ERROR). The process of signalling involves the search for and invocation of a ``handler,'' a piece of code which will attempt to deal appropriately with the situation. If a handler is found, it may either ``handle'' the situation by performing some non-local transfer of control or ``decline,'' by failing to perform a non-local transfer of control. If it declines, other handlers are sought. Since the lexical environment of the signaller might not be available to handlers, a data structure called a ``condition'' is created to represent explicitly the relevant state of the situation. A condition is either created explicitly using MAKE-CONDITION and then passed to a function such as SIGNAL or is created implicitly by a function such as SIGNAL when given appropriate non-condition arguments. Condition System, Revision #18 by KMP 12-Mar-88, Page 12 In order to handle the error, a handler is permitted to use any non-local transfer of control such as GO to a tag in a TAGBODY, RETURN from a BLOCK, or THROW to a CATCH. In addition, structured abstractions on these primitives are provided for convenience in exception handling. A handler can be made dynamically accessible to a program by use of HANDLER-BIND. For example, to create a handler for a condition of type ARITHMETIC-ERROR, one might write: (HANDLER-BIND ((ARITHMETIC-ERROR ...handler...)) ...body...) The handler is a function of one argument, the condition. If a condition of the designated type is signalled while body is executing (and there are no intervening handlers), the handler would be invoked on the given condition, allowing it the option of transferring control. So, for example, to write a macro which executed a body returning either its value(s) or the two values NIL and the condition, one might write: (DEFMACRO WITHOUT-ARITHMETIC-ERRORS (&BODY FORMS) (LET ((TAG (GENSYM))) `(BLOCK ,TAG (HANDLER-BIND ((ARITHMETIC-ERROR #'(LAMBDA (CONDITION) (RETURN-FROM ,TAG (VALUES NIL CONDITION))))) ,@BODY)))) The handler is executed in the dynamic context of the signaller, except that the set of available condition handlers will have been rebound to the value that was active at the time the condition handler was made active. If a handler declines (i.e., it does not transfer control), other handlers are sought. If no handler is found and the condition was signalled by ERROR or CERROR (or some function such as ASSERT which behaves like these functions), the debugger is entered, still in the dynamic context of the signaller. Condition System, Revision #18 by KMP 12-Mar-88, Page 13 Object-Oriented Basis of Condition Handling ------------------------------------------- Of course, the ability of the handler to usefully handle an exceptional situation is related to the quality of the information it is provided. For example, if all errors were signalled by typing (ERROR "...some format string...") then the only piece of information that would be accessible to the handler would be an object of type SIMPLE-ERROR which had a slot containing the format string. If this were done, STRING-EQUAL would be the preferred way to tell one error from another, and it would be very hard to allow flexibility in the presentation of error messages because existing handlers would tend to be broken by even tiny variations in the wording of an error message. This phenomenon has been the major failing of most error systems previously available in Lisp. It is fundamentally important to decouple the error message string (the human interface) from the objects which formally represent the error state (the program interface). We therefore have the notion of typed conditions, and of formal operations on those conditions which make them inspectable in a structured way. This object oriented approach to condition handling has the following important advantages over a text-based approach: * Conditions are classified according to subtype relationships, making it easy to test for categories of conditions. * Conditions have named slot values through which parameters are conveyed from the program that signals the condition to the program that handles it. * Inheritance of ``methods'' and slots reduce the amount of explicit specification necessary to achieve various interesting effects. Some condition types are defined by this document, but the set of condition types is extensible using DEFINE-CONDITION. Here as an example we define a 2-argument function called DIVIDE which is patterned after the / function, but does some stylized error checking: (DEFUN DIVIDE (NUMERATOR DENOMINATOR) (COND ((OR (NOT (NUMBERP NUMERATOR)) (NOT (NUMBERP DENOMINATOR))) (ERROR "(DIVIDE '~S '~S) - Bad arguments." NUMERATOR DENOMINATOR)) ((ZEROP DENOMINATOR) (ERROR 'DIVISION-BY-ZERO :OPERATOR 'DIVIDE :OPERANDS (LIST NUMERATOR DENOMINATOR))) (T ...))) Note that in the first clause we have used ERROR with a string argument and in the second clause we have named a particular condition type, DIVISION-BY-ZERO. In the case of a string argument, the condition type which will be signalled is SIMPLE-ERROR. The particular kind of error which is signalled may be important in cases where handlers are active. For example, SIMPLE-ERROR inherits from type ERROR, which in turn inherits from type CONDITION. On the other hand, DIVISION-BY-ZERO inherits from ARITHMETIC-ERROR, which inherits from ERROR, which inherits from CONDITION. So if a handler existed for ARITHMETIC-ERROR while a DIVISION-BY-ZERO condition was signalled, that handler would be tried; however, if a SIMPLE-ERROR condition was signalled in the same context, the handler for type ARITHMETIC-ERROR would not be tried. Condition System, Revision #18 by KMP 12-Mar-88, Page 14 Restarts -------- In older Lisp dialects (such as Maclisp), an attempt to signal an error of a given type often carried with it an implicit promise to support the standard recovery strategy for that type of error. If the signaller knew the type of error but for whatever reason was unable to deal with the standard recovery strategy for that kind of error, it was necessary to signal an untyped error (for which there was no defined recovery strategy). This sometimes led to confusion when people signalled typed errors without realizing the full implications of having done so, but more often than not it meant that users simply avoided typed errors altogether. This condition system, which is modeled after the Zetalisp condition system, corrects this troublesome aspect of previous Lisp dialects by creating a clear separation between the act of signalling an error of a particular type and the act of saying that a particular way of recovery is appropriate. In the DIVIDE example above, simply signalling an error does not imply a willingness on the part of the signaller to cooperate in any corrective action. For example, the following sample interaction illustrates that the only recovery action offered for this error is ``Return to Lisp Toplevel'': Lisp> (+ (DIVIDE 3 0) 7) Error: Attempt to divide 3 by 0. To continue, type :CONTINUE followed by an option number: 1: Return to Lisp Toplevel. Debug> :CONTINUE 1 Returned to Lisp Toplevel. Lisp> When an error is detected and the function ERROR is called, execution cannot continue normally because ERROR will not directly return. Control can be transfered to other points in the program, however, by means of specially established ``restarts.'' Condition System, Revision #18 by KMP 12-Mar-88, Page 15 The simplest kind of restart involves structured transfer of control using a macro called RESTART-CASE. The RESTART-CASE form allows you to execute a piece of code in a context where zero or more restarts are active, and where if one of those restarts is ``invoked,'' control will be transfered to the corresponding clause in the RESTART-CASE form. For example, we could rewrite the DIVIDE example from the previous page as: (DEFUN DIVIDE (NUMERATOR DENOMINATOR) (LOOP (RESTART-CASE (RETURN (COND ((OR (NOT (NUMBERP NUMERATOR)) (NOT (NUMBERP DENOMINATOR))) (ERROR "(DIVIDE '~S '~S) - Bad arguments." NUMERATOR DENOMINATOR)) ((ZEROP DENOMINATOR) (ERROR 'DIVISION-BY-ZERO :OPERATOR 'DIVIDE :OPERANDS (LIST NUMERATOR DENOMINATOR))) (T ...))) (NIL (ARG1 ARG2) :REPORT "Provide new arguments for use by the DIVIDE function." :INTERACTIVE (LAMBDA () (LIST (PROMPT-FOR 'NUMBER "Numerator: ") (PROMPT-FOR 'NUMBER "Denominator: "))) (SETQ NUMERATOR ARG1 DENOMINATOR ARG2)) (NIL (RESULT) :REPORT "Provide a value to return from the DIVIDE function." :INTERACTIVE (LAMBDA () (LIST (PROMPT-FOR 'NUMBER "Result: "))) (RETURN RESULT))))) See Footnote: {PROMPT-FOR} The NIL at the head of each clause means that it is an ``anonymous'' restart. Anonymous restarts are ones which are typically only invoked from within the debugger. As we shall see later, it is possible to have ``named restarts'' which may be invoked from code without the need for user intervention. The arguments to anonymous restarts must either be optional or special information must be provided about what the debugger should use as arguments. Here we've used the :INTERACTIVE keyword to specify that information. The :REPORT keyword introduces information to be used when presenting the restart option to the user (e.g., by the debugger). Here is a sample interaction that takes advantage of the restarts provided by our revised definition of DIVIDE: Lisp> (+ (DIVIDE 3 0) 7) Error: Attempt to divide 3 by 0. To continue, type :CONTINUE followed by an option number: 1: Provide new arguments for use by the DIVIDE function. 2: Provide a value to return from the DIVIDE function. 3: Return to Lisp Toplevel. Debug> :CONTINUE 1 Numerator: 4 Denominator: 2 => 9 Condition System, Revision #18 by KMP 12-Mar-88, Page 16 Named Restarts -------------- In addition to anonymous restarts, we can also have named restarts. The interesting point here is that you can write: (RESTART-CASE (INVOKE-RESTART 'FOO 3) (FOO (X) (+ X 1))) to add 3 to 1, returning 4. This trivial case is conceptually analagous to writing: (+ (CATCH 'SOMETHING (THROW 'SOMETHING 3)) 1) For example, the code for Lisp's SYMBOL-VALUE function might signal an unbound variable error by doing: (RESTART-CASE (ERROR "The variable ~S is unbound." VARIABLE) (CONTINUE () :REPORT (LAMBDA (STREAM) (FORMAT STREAM "Retry getting the value of ~S." VARIABLE)) (SYMBOL-VALUE VARIABLE)) (USE-VALUE (VALUE) :REPORT (LAMBDA (STREAM) (FORMAT STREAM "Specify a value of ~S to use this time." VARIABLE)) VALUE) (STORE-VALUE (VALUE) :REPORT (LAMBDA (STREAM) (FORMAT STREAM "Specify a value of ~S to store and use." VARIABLE)) (SETF (SYMBOL-VALUE VARIABLE) VALUE) VALUE)) If this were the implementation of SYMBOL-VALUE, then it would be possible for users to write a variety of automatic handlers for unbound variable errors. For example, to make unbound variables evaluate to themselves, one might write: (HANDLER-BIND ((UNBOUND-VARIABLE #'(LAMBDA (CONDITION) (IF (FIND-RESTART 'USE-VALUE) (INVOKE-RESTART 'USE-VALUE (CELL-ERROR-NAME CONDITION)))))) ...body...) Restart Functions ----------------- For commonly used restarts, it is conventional to define a program interface which hides the use of INVOKE-RESTART. Such program interfaces to restarts are called ``restart functions.'' The normal convention is for the function to share the name of the restart. The pre-defined functions ABORT, CONTINUE, MUFFLE-WARNING, STORE-VALUE, and USE-VALUE are restart functions. Using USE-VALUE, the above example of HANDLER-BIND could have been written more concisely as: (HANDLER-BIND ((UNBOUND-VARIABLE #'(LAMBDA (CONDITION) (USE-VALUE (CELL-ERROR-NAME CONDITION))))) ...body...) Condition System, Revision #18 by KMP 12-Mar-88, Page 17 Comparison of Restarts and Catch/Throw -------------------------------------- One important feature that RESTART-CASE (or RESTART-BIND) offers which CATCH does not is the ability to reason about the available points to which control might be transferred without actually attempting the transfer. One could, for example, do (IGNORE-ERRORS (THROW ...)) which is a sort of poor man's variation of (IF (FIND-RESTART 'SOMETHING) (INVOKE-RESTART 'SOMETHING)) but there is no way using just IGNORE-ERRORS and THROW to simulate something like: (IF (AND (FIND-RESTART 'SOMETHING) (FIND-RESTART 'SOMETHING-ELSE)) (INVOKE-RESTART 'SOMETHING)) or even just (IF (AND (FIND-RESTART 'SOMETHING) (YES-OR-NO-P "Do something? ")) (INVOKE-RESTART 'SOMETHING)) because the degree of inspectability which comes with simply doing (IGNORE-ERRORS (THROW ...)) is too primitive -- getting the information you want also forces you to transfer control, perhaps at a time when it's not desirable. Many programmers have previously evolved strategies like the following on a case-by-case basis: (DEFUN FN-1 () (CATCH 'FOO (LET ((*FOO-TAG-IS-AVAILABLE* T)) ... (FN-2) ...))) (DEFUN FN-2 () ... (IF *FOO-TAG-IS-AVAILABLE* (THROW 'FOO T)) ...) The facility provided by RESTART-CASE and FIND-RESTART is intended to provide a standardized protocol for this sort of information to be communicated between programs which were developed independently so that individual variations from programmer to programmer or program to do not thwart the overall modularity and debuggability of programs. Another difference between the restart facility and the catch/throw facility is that a CATCH with any given tag completely shadows any outer pending CATCH which uses the same tag. Because of the presence of COMPUTE-RESTARTS, however, it is possible to see shadowed restarts which may be very useful in some situations (particularly in an interactive debugger). Condition System, Revision #18 by KMP 12-Mar-88, Page 18 Generalized Restarts -------------------- RESTART-CASE is a mechanism which allows only imperative transfer of control for its associated restarts. RESTART-CASE is built on a lower-level mechanism called RESTART-BIND, which does not force transfer of control. RESTART-BIND is to RESTART-CASE very much as HANDLER-BIND is to HANDLER-CASE. The syntax is: (RESTART-BIND ((name function . options)) . body) Body is executed in a context where FUNCTION will be called whenever (INVOKE-RESTART 'name) is done. The options are keyword-style and are used to pass information such as that which was provided with the :REPORT keyword in RESTART-CASE. A RESTART-CASE expands into a call to RESTART-BIND where the function simply does an unconditional transfer of control to a particular body of code, passing along ``argument'' information in a structured way. It is also possible to write restarts which do not transfer control. Such restarts may be useful in implementing various special commands for the debugger which are of interest only in certain situations. For example, one might imagine a situation where file space was exhausted and the following was done: (RESTART-BIND ((NIL #'(LAMBDA () (EXPUNGE-DIRECTORY THE-DIR)) :REPORT-FUNCTION #'(LAMBDA (STREAM) (FORMAT STREAM "Expunge ~A." (DIRECTORY-NAMESTRING THE-DIR))))) (CERROR "Try this file operation again." 'DIRECTORY-FULL :DIRECTORY THE-DIR)) In this case, the debugger might be entered and the user could first perform the expunge (which would not transfer control from the debugger context) and then retry the file operation, as in: Lisp> (OPEN "FOO" :DIRECTION :OUTPUT) Error: The directory PS: is full. To continue, type :CONTINUE followed by an option number: 1: Try this file operation again. 2: Expunge PS:. 3: Return to Lisp Toplevel. Debug> :CONTINUE 2 Expunging PS: ... 3 records freed. Debug> :CONTINUE 1 => #FOO.LSP" 2323473> Condition System, Revision #18 by KMP 12-Mar-88, Page 19 Interactive Condition Handling ------------------------------ When a program does not know how to continue, and no active handler is able to advise it, the ``interactive condition handler,'' or ``debugger,'' can be entered. This happens implicitly through the use of functions such as ERROR and CERROR, or explicitly through the use of the function INVOKE-DEBUGGER. The interactive condition handler never returns directly; it returns only through structured non-local transfer of control to specially defined restart points which can be set up either by the system or by user code. The mechanisms which support the establishment of such structured restart points for portable code are outlined elsewhere in this document. Actually, implementations may also provide extended debugging facilities which allow return from arbitrary stack frames. Although such commands are frequently useful in practice, their effects are implementation dependent because they violate the Common Lisp program abstraction. As such, the effect of using such commands is undefined with respect to Common Lisp. Serious Conditions ------------------ The IGNORE-ERRORS macro will trap conditions of type ERROR. There are, however, conditions which are not of type error. Some conditions are not considered errors but are still very serious, so we call them ``serious conditions'' and we use the type SERIOUS-CONDITION to represent them. Conditions such as those which might be signalled for ``stack overflow'' or ``storage exhausted'' are in this category. The type ERROR is a subtype of SERIOUS-CONDITION, and it would technically be correct to use the term ``serious condition'' to refer to all serious conditions whether errors or not. However, normally we use the term ``serious condition'' to refer to things of type SERIOUS-CONDITION but not of type ERROR. The point of the distinction between errors and other serious conditions is that some conditions are known to occur for reasons that are beyond the scope of Common Lisp to specify clearly. For example, we know that a stack will generally be used to implement function calling, and we know that stacks tend to be of finite size and are prone to overflow. Since the available stack size may vary from implementation to implementation, from session to session, or from function call to function call, it would be confusing to have expressions such as (IGNORE-ERRORS (+ A B)) return a number sometimes and NIL other times if A and B were always bound to numbers and the stack just happened to overflow on a particular call. For this reason, only conditions of type ERROR and not all conditions of type SERIOUS-CONDITION are trapped by IGNORE-ERRORS. To trap other conditions, a lower level facility must be used (such as HANDLER-BIND or HANDLER-CASE). By convention, the function ERROR preferred over SIGNAL to signal conditions of type SERIOUS-CONDITION (including those of type ERROR). It is the use of the function ERROR, and not the type of the condition being signalled, which actually causes the debugger to be entered. Condition System, Revision #18 by KMP 12-Mar-88, Page 20 Non-Serious Conditions ---------------------- Some conditions are neither errors nor serious conditions. They are signalled to give other programs a chance to intervene, but if no action is taken, computation simply continues normally. For example, an implementation might choose to signal a non-serious condition called END-OF-LINE when output reaches the last character position on a line of character output. In such an implementation, the signalling of this condition might allow a convenient way for other programs to intervene, producing output that is truncated at the end of a line. By convention, the function SIGNAL is used to signal conditions that are not serious. It would be possible to signal serious conditions using SIGNAL, and the debugger would not be entered if the condition went unhandled, but handlers will generally tend to assume that serious conditions and errors will force entry to the interactive condition handler and that they should work to avoid this. Condition Types --------------- Some types of conditions are predefined by the system. All types of conditions are subtypes of CONDITION. That is, (TYPEP C 'CONDITION) is true iff C is a condition. Implementations supporting multiple (or non-hierarchical) type inheritance are expressly permitted to exploit multiple inheritance in the tree of condition types as implementation-dependent extensions, as long as such extensions are compatible with this document. In order to avoid problems in portable code which run both in systems with multiple type inheritance and systems without it, programmers are explicitly warned that while all correct Common Lisp implementations will assure that (TYPEP c 'CONDITION) is true for all conditions (and all subtype relationships indicated in this document will likewise be true), it should NOT be assumed that the subtype relationships indicated in this document are mutually exclusive. In some cases disjoint subtypes will be identified, but such separation does not occur by default. In particular, it follows from the subtype descriptions contained in this document that in all implementations: (TYPEP c 'CONTROL-ERROR) implies (TYPEP c 'ERROR), but you should be careful to note that, for example: (TYPEP c 'CONTROL-ERROR) does -not- imply (NOT (TYPEP c 'CELL-ERROR)). Condition System, Revision #18 by KMP 12-Mar-88, Page 21 Signalling Conditions --------------------- When a condition is signalled, the system tries to locate the most appropriate handler for the condition and invoke that handler. Handlers are established dynamically using HANDLER-BIND or abstractions built on HANDLER-BIND. If an appropriate handler is found, it is called. In some circumstances, the handler may ``decline'' by simply returning without performing a non-local transfer of control. In such cases, the search for an appropriate handler is picked up where it left off, as if the called handler had never been present. If no handler is found, or if all handlers which were found decline, SIGNAL returns NIL. Although it follows from the description above, it is perhaps worth noting explicitly that the lookup procedure described here will prefer a general but more (dynamically) local handler over a specific but less (dynamically) local handler. Experience with existing condition systems suggests that this is a reasonable approach and works adequately in most situations. Some care should be taken when binding handlers for very general kinds of conditions, such as is done in IGNORE-ERRORS. Often, binding for a more specific condition type than ERROR is more appropriate. Resignalling Conditions ----------------------- Note that signalling a condition has no side-effect on that condition, and that there is no dynamic state contained in a condition object. As such, it may at times be reasonable and appropriate to consider caching condition objects for repeated use, re-signalling conditions from within handlers, or saving conditions away somewhere and re-signalling them later. For example, the system may wish to preallocate objects of type STORAGE-CONDITION so that they can be signalled when needed without attempting to allocate more storage. Condition System, Revision #18 by KMP 12-Mar-88, Page 22 Condition Handlers ------------------ A ``handler'' is a function of one argument, the condition to be handled. The handler may inspect the object (using facilities described in another section) to be sure it is `interested' in handling the condition. A handler is executed in the dynamic context of the signaller, except that the set of available condition handlers will have been rebound to the value that was active at the time the condition handler was made active. The intent of this is to prevent infinite recursion due to errors in a condition handler. After inspecting the condition, the handler should take one of the following actions: * It might ``decline'' to handle the condition (by simply returning). When this happens, the returned values are ignored and the effect is the same as if the handler had been invisible to the mechanism seeking to find a handler. The next handler in line will be tried, or if no such handler exists, the condition will go unhandled. * It might ``handle'' the condition (by performing some non-local transfer of control). This may be done either primitively using GO, RETURN, THROW or more abstractly using a function such as ABORT or INVOKE-RESTART. * It might signal another condition. * It might invoke the interactive debugger. In fact, the latter two actions (signalling another condition or entering the debugger) are really just ways of putting off the decision to either handle or decline, or trying to get someone else to make such a decision. Ultimately, all a handler can do is to handle or decline to handle. Condition System, Revision #18 by KMP 12-Mar-88, Page 23 Printing Conditions ------------------- When *PRINT-ESCAPE* is NIL (e.g., when the PRINC function or the ~A format op is used), the report method for the condition will be invoked. This will be done automatically by functions like INVOKE-DEBUGGER, BREAK, and WARN, but there may still be situations in which it is desirable to have a condition report under explicit user control. For example, (LET ((FORM '(OPEN "NOSUCHFILE"))) (HANDLER-CASE (EVAL FORM) (SERIOUS-CONDITION (CONDITION) (FORMAT T "~&Evaluation of ~S failed:~%~A" FORM CONDITION)))) might print something like: Evaluation of (OPEN "NOSUCHFILE") failed: The file "NOSUCHFILE" was not found. Some notes about the form of text typed by report methods: * The message should generally be a complete sentence, beginning with a capital letter and ending with appropriate punctuation (usually a period). * The message should -not- include any introductory text such as "Error:" or "Warning:" and should not be followed by a trailing newline. Such text will be added as appropriate to context by the routine invoking the report method. * Except where unavoidable, the Tab character should not be used in error messages. Its effect may vary between implementations and may cause problems even within an implementation because it may do different things depending on what column the error report begins in. * Single line messages are preferred, but newlines in the middle of long messages are acceptable. * If any program (e.g., the debugger) displays messages indented from the prevailing left margin (for example, indented seven spaces because they are prefixed by the seven-charactr herald "Error: "), then that program will take care of inserting the appropriate indentation into the extra lines of a multi-line error message. Similarly, a program that prefixes error messages with semicolons so that they appear to be comments should take care of inserting a semicolon at the beginning of each line in a multi-line error message. (These rules are important because even within a single implementation, there may be more than one program that presents error messages to the user, and they may use different sytles of presentation. The caller of ERROR cannot anticipate all such possible styles, and so it is incumbent upon the presenter of the message to make any necessary adjustments.) When *PRINT-ESCAPE* is not NIL, the object should print in some useful (but usually fairly abbreviated) fashion according to the style of the implementation. It is not expected that a condition will be able to print re-readably. Something like # is fine. No function is provided for directly accessing or setting the printer for a condition type, or for invoking it. The techniques described above are the sole interface to reporting. See Footnote: {Condition Methods}. Condition System, Revision #18 by KMP 12-Mar-88, Page 24 PROGRAM INTERFACE TO THE CONDITION SYSTEM ----------------------------------------- Signalling Conditions --------------------- ERROR datum &rest arguments [Function] Invokes the signal facility on a condition. If the condition is not handled, (INVOKE-DEBUGGER condition) is done. As a consequence of calling INVOKE-DEBUGGER, ERROR cannot directly return; the only exit from this function can come by non-local transfer of control in a handler or by use of an interactive debugging command. If DATUM is a condition, then that condition is used directly. In this case, it is an error for ARGUMENTS to be non-NIL. If DATUM is a condition type, then the condition used is the result of doing (APPLY #'MAKE-CONDITION datum arguments). If DATUM is a string, then the condition used is the result of doing (MAKE-CONDITION 'SIMPLE-ERROR :FORMAT-STRING datum :FORMAT-ARGUMENTS arguments). Note: If (TYPEP condition *BREAK-ON-SIGNALS*) is true, then the debugger will be entered prior to beginning the signalling process. The signalling process can be continued using the CONTINUE restart. This is true also for all other functions and macros which signal errors, such as CERROR, ASSERT, and CHECK-TYPE. CERROR continue-format-string datum &rest arguments [Function] Invokes the error facility on a condition. If the condition is not handled, (INVOKE-DEBUGGER condition) is done. While signalling is going on, and while in the debugger if it is reached, it is possible to continue program execution using the CONTINUE restart. If DATUM is a condition, then that condition is used directly. In this case, ARGUMENTS will be used only with the CONTINUE-FORMAT-STRING and will not be used to initialize DATUM. If DATUM is a condition type, then the condition used is the result of doing (APPLY #'MAKE-CONDITION datum arguments). If DATUM is a string, then the condition used is the result of doing (MAKE-CONDITION 'SIMPLE-ERROR :FORMAT-STRING datum :FORMAT-ARGUMENTS arguments). The CONTINUE-FORMAT-STRING must be a string. Note that if DATUM is not a string, then the format arguments used by the CONTINUE-FORMAT-STRING will still be the ARGUMENTS (which is in keyword format if DATUM is a condition type). In this case, some care may be necessary to set up the CONTINUE-FORMAT-STRING correctly. The format op ~* may be particularly useful in this situation. The value returned by CERROR is NIL. Condition System, Revision #18 by KMP 12-Mar-88, Page 25 SIGNAL datum &rest arguments [Function] Invokes the signal facility on a condition. If the condition is not handled, SIGNAL returns NIL. If DATUM is a condition, then that condition is used directly. In this case, it is an error for ARGUMENTS to be non-NIL. If DATUM is a condition type, then the condition used is the result of doing (APPLY #'MAKE-CONDITION datum arguments). If DATUM is a string, then the condition used is the result of doing (MAKE-CONDITION 'SIMPLE-CONDITION :FORMAT-STRING datum :FORMAT-ARGUMENTS arguments). Note: If (TYPEP condition *BREAK-ON-SIGNALS*) is true, then the debugger will be entered prior to beginning the signalling process. The CONTINUE restart may be used to continue with the signalling process. This is true also for all other functions and macros which signal conditions, such as WARN, ERROR, CERROR, ASSERT, and CHECK-TYPE. For further details about signalling and handling, see the discussion of "Condition Handlers" on page 22 of this document. *BREAK-ON-SIGNALS* [Variable] This flag is intended primarily for use when the user is debugging programs that do signalling. When (TYPEP condition *BREAK-ON-SIGNALS*) is true, then calls to SIGNAL, and to other advertised functions such as ERROR which implicitly call SIGNAL, will enter the debugger prior to signalling that condition. The CONTINUE restart may be used to continue with the normal signalling process. When setting this variable, the user is encouraged to choose the most restrictive specification which suffices. Setting this flag effectively violates the modular handling of condition signalling which this document seeks to establish. Its complete effect may be unpredictable in some cases since the user may not be aware of the variety or number of calls to SIGNAL which are used in programs that are called only incidentally. By default -- and certainly in any ``production'' use -- the value of this variable should be NIL, both for reasons of performance and for reasons of modularity and abstraction. Note: The variable *BREAK-ON-WARNINGS* continues to be supported for compatibility, but *BREAK-ON-SIGNALS* offers all the power *BREAK-ON-WARNINGS* offered and more. New code should not use *BREAK-ON-WARNINGS*. Compatibility Note: This variable is also similar to the Zetalisp variable TRACE-CONDITIONS except for the obvious difference that ZL:TRACE-CONDITIONS took a type or list of types while *BREAK-ON-SIGNALS* takes a single typespec. Condition System, Revision #18 by KMP 12-Mar-88, Page 26 Assertions ---------- CHECK-TYPE place typespec [string] [Macro] Signals an error of type TYPE-ERROR if the contents of PLACE are not of the desired type. If a condition is signalled, handlers of this condition can use the functions TYPE-ERROR-OBJECT and TYPE-ERROR-EXPECTED-TYPE to access the contents of PLACE and the TYPESPEC, respectively. This function can return only if the STORE-VALUE restart is invoked, either explicitly from a handler or implicitly as one of the options offered by the debugger. If STORE-VALUE is called, CHECK-TYPE will store the new value which is the argument to STORE-VALUE (or which is prompted for interactively by the debugger) in PLACE and start over, checking the type of the new value and signalling another error if it is still not the desired type. Subforms of PLACE may be evaluated multiple times because of the implicit loop generated. CHECK-TYPE returns NIL. The PLACE must be a generalized variable reference acceptable to SETF. The TYPESPEC must be a type specifier; it is not evaluated. The STRING should be an English description of the type, starting with an indefinite article ("a" or "an"); it is evaluated. If STRING is not supplied, it is computed automatically from TYPESPEC. (The optional STRING argument is allowed because some applications of CHECK-TYPE may require a more specific description of what is wanted than can be generated automatically from the type specifier.) The error message will mention PLACE, its contents, and the desired type. Implementation Note: An implementation may choose to generate a somewhat differently worded error message if it recognizes that place is of a particular form, such as one of the arguments to the function that called CHECK-TYPE. Examples: Lisp> (SETQ AARDVARKS '(SAM HARRY FRED)) => (SAM HARRY FRED) Lisp> (CHECK-TYPE AARDVARKS (ARRAY * (3))) Error: The value of AARDVARKS, (SAM HARRY FRED), is not a 3-long array. To continue, type :CONTINUE followed by an option number: 1: Specify a value to use instead. 2: Return to Lisp Toplevel. Debug> :CONTINUE 1 Use Value: #(SAM FRED HARRY) => NIL Lisp> AARDVARKS => # Lisp> (MAP 'LIST #'IDENTITY AARDVARKS) => (SAM FRED HARRY) Lisp> (SETQ AARDVARK-COUNT 'FOO) => FOO Lisp> (CHECK-TYPE AARDVARK-COUNT (INTEGER 0 *) "a positive integer") Error: The value of AARDVARK-COUNT, FOO, is not a positive integer. To continue, type :CONTINUE followed by an option number: 1: Specify a value to use instead. 2: Return to Lisp Toplevel. Debug> :CONTINUE 2 Lisp> Compatibility Note: In Zetalisp, the equivalent facility is called CHECK-ARG-TYPE. Condition System, Revision #18 by KMP 12-Mar-88, Page 27 ASSERT test-form [({place}*) [datum {argument}*]] [Macro] Signals an error if the value of TEST-FORM is NIL. Continuing from this error using the CONTINUE restart will allow the user to alter the values of some variables, and ASSERT will then start over, evaluating TEST-FORM again. ASSERT returns NIL. TEST-FORM is any form. Each PLACE (there may be any number of them, or none) must be a generalized-variable reference acceptable to SETF. These should be variables on which TEST-FORM depends, whose values may sensibly be changed by the user in attempting to correct the error. Subforms of each PLACE are only evaluated if an error is signalled, and may be re-evaluated if the error is re-signalled (after continuing without actually fixing the problem). The DATUM and ARGUMENTS are evaluated -only- if an error is to be signalled, and re-evaluated if the error is to be signalled again. If DATUM is a condition, then that condition is used directly. In this case, it is an error to specify any ARGUMENTS. If DATUM is a condition type, then the condition used is the result of doing (APPLY #'MAKE-CONDITION datum arguments). If DATUM is a string, then the condition used is the result of doing (MAKE-CONDITION 'SIMPLE-ERROR :FORMAT-STRING datum :FORMAT-ARGUMENTS arguments). If DATUM is omitted, then a condition of type SIMPLE-ERROR is constructed using the TEST-FORM as data. For example, the following might be used: (MAKE-CONDITION 'SIMPLE-ERROR :FORMAT-STRING "The assertion ~S failed." :FORMAT-ARGUMENTS '(test-form)) Implementation Note: The debugger need not include the TEST-FORM in the error message, and the PLACES should not be included in the message, but they should be made available for the user's perusal. If the user gives the "continue" command, he should be presented with the opportunity to alter the values of any or all of the references. The details of this depend on the implementation's style of user interface, of course. Examples: (SETQ X (MAKE-ARRAY '(3 5) :INITIAL-ELEMENT 3)) (SETQ Y (MAKE-ARRAY '(3 5) :INITIAL-ELEMENT 7)) (DEFUN MATRIX-MULTIPLY (A B) (LET ((*PRINT-ARRAY* NIL)) (ASSERT (AND (= (ARRAY-RANK A) (ARRAY-RANK B) 2) (= (ARRAY-DIMENSION A 1) (ARRAY-DIMENSION B 0))) (A B) "Cannot multiply ~S by ~S." A B) (REALLY-MATRIX-MULTIPLY A B))) (MATRIX-MULTIPLY X Y) Error: Cannot multiply # by #. To continue, type :CONTINUE followed by an option number: 1: Specify new values. 2: Return to Lisp Toplevel. Debug> :CONTINUE 1 Value for A: X Value for B: (MAKE-ARRAY '(5 3) :INITIAL-ELEMENT 6) => #2A((54 54 54 54 54) (54 54 54 54 54) (54 54 54 54 54) (54 54 54 54 54) (54 54 54 54 54)) Condition System, Revision #18 by KMP 12-Mar-88, Page 28 The syntax for ETYPECASE and CTYPECASE is the same as for TYPECASE, except that no OTHERWISE clause is permitted. Similarly, the syntax for ECASE and CCASE is the same as for CASE except for the OTHERWISE clause. ETYPECASE and ECASE are similar to TYPECASE and CASE, respectively, but signal a non-continuable error rather than returning NIL if no clause is selected. CTYPECASE and CCASE are also similar to TYPECASE and CASE, but signal a continuable error if no clause is selected. ETYPECASE keyform {(type {form}*)}* [Macro] This control construct is similar to TYPECASE, but no explicit OTHERWISE or T clause is permitted. If no clause is satisfied, ETYPECASE signals an error (of type TYPE-ERROR) with a message constructed from the clauses. It is not permissible to continue from this error. To supply his own error message, the user should use TYPECASE with an OTHERWISE clause containing a call to ERROR. The name of this function stands for ``exhaustive type case'' or ``error-checking type case.'' Example: Lisp> (SETQ X 1/3) ==> 1/3 Lisp> (ETYPECASE X (INTEGER (* X 4)) (SYMBOL (SYMBOL-VALUE X))) Error: The value of X, 1/3, is neither an integer nor a symbol. To continue, type :CONTINUE followed by an option number: 1: Return to Lisp Toplevel. Debug> CTYPECASE keyplace {(type {form}*)}* [Macro] This control construct is similar to TYPECASE, but no explicit otherwise or T clause is permitted. The KEYPLACE must be a generalized variable reference acceptable to SETF. If no clause is satisfied, CTYPECASE signals an error (of type TYPE-ERROR) with a message constructed from the clauses. This error may be continued using the STORE-VALUE restart. The argument to STORE-VALUE is stored in KEYPLACE and then CTYPECASE starts over, making the type tests again. Subforms of KEYPLACE may be evaluated multiple times. If the STORE-VALUE restart is invoked interactively, the user will be prompted for the value to be used. The name of this function is mnemonic for ``continuable (exhaustive) type case.'' Example: Lisp> (SETQ X 1/3) ==> 1/3 Lisp> (CTYPECASE X (INTEGER (* X 4)) (SYMBOL (SYMBOL-VALUE X))) Error: The value of X, 1/3, is neither an integer nor a symbol. To continue, type :CONTINUE followed by an option number: 1: Specify a value to use instead. 2: Return to Lisp Toplevel. Debug> :CONTINUE 1 Use value: 3.7 Error: The value of X, 3.7, is neither an integer nor a symbol. To continue, type :CONTINUE followed by an option number: 1: Specify a value to use instead. 2: Return to Lisp Toplevel. Debug> :CONTINUE 1 Use value: 12 ==> 48 Condition System, Revision #18 by KMP 12-Mar-88, Page 29 ECASE keyform {({({key}*) ! key} {form}*)}* [Macro] This control construct is similar to CASE, but no explicit OTHERWISE or T clause is permitted. If no clause is satisfied, ECASE signals an error (of type TYPE-ERROR) with a message constructed from the clauses. It is not permissible to continue from this error. To supply an error message, the user should use CASE with an otherwise clause containing a call to error. The name of this function stands for ``exhaustive case'' or ``error-checking case.'' Example: Lisp> (SETQ X 1/3) ==> 1/3 Lisp> (ECASE X (ALPHA (FOO)) (OMEGA (BAR)) ((ZETA PHI) (BAZ))) Error: The value of X, 1/3, is not ALPHA, OMEGA, ZETA, or PHI. To continue, type :CONTINUE followed by an option number: 1: Return to Lisp Toplevel. Debug> CCASE keyplace {({({key}*) ! key} {form}*)}* [Macro] This control construct is similar to CASE, but no explicit OTHERWISE or T clause is permitted. The KEYPLACE must be a generalized variable reference acceptable to SETF. If no clause is satisfied, CCASE signals an error (of type TYPE-ERROR) with a message constructed from the clauses. This error may be continued using the STORE-VALUE restart. The argument to STORE-VALUE is stored in KEYPLACE and then CCASE starts over, making the type tests again. Subforms of KEYPLACE may be evaluated multiple times. If the STORE-VALUE restart is invoked interactively, the user will be prompted for the value to be used. The name of this function is mnemonic for ``continuable (exhaustive) case.'' Implementation Note: The TYPE-ERROR signalled by CCASE and ECASE is free to choose any representation of the acceptable argument type that it wishes for placement in the expected-type slot. It will always work to use type (MEMBER . keys), but in some cases it may be more efficient, for example, to use a type which represents an integer subrange or a type composed using OR. Condition System, Revision #18 by KMP 12-Mar-88, Page 30 Handling Conditions ------------------- HANDLER-CASE expression {(type ([var]) {form}*)}* [Macro] Executes the given expression in a context where various handlers are active: The TYPE may be any type specifier. If during the execution of the EXPRESSION a condition is signalled for which there is an appropriate clause -- i.e., one for which (TYPEP condition 'type) is true -- and if there is no intervening handler for conditions of that type, then control is transferred to the body of the relevant clause (unwinding the dynamic state appropriately in the process) and the given VAR is bound to the condition which was signalled. If no such condition is signalled and the computation runs to completion, then the values resulting from the EXPRESSION are returned by the HANDLER-CASE. If more than one case is provided, those cases are made accessible in parallel. That is, in (HANDLER-CASE form (type1 (var1) form1) (type2 (var2) form2)) if the first clause (containing FORM1) has been selected, the handler for the second is no longer visible (or vice versa). The cases are searched sequentially from top to bottom. If there is type overlap between the cases, the earlier of the two cases will be selected. If VAR is not needed, it may be omitted. That is, a clause such as: (type (var) (DECLARE (IGNORE var)) form) may be written using the following shorthand notation: (type () form) If there are no forms in a selected case, the case returns NIL. Note that (HANDLER-CASE form (type1 (var1) . body1) (type2 (var2) . body2) ...) is approximately equivalent to: (BLOCK #:G0001 (LET ((#:G0002 NIL)) (TAGBODY (HANDLER-BIND ((type1 #'(LAMBDA (TEMP) (SETQ #:G0002 TEMP) (GO #:G0003))) (type2 #'(LAMBDA (TEMP) (SETQ #:G0002 TEMP) (GO #:G0004))) ...) (RETURN-FROM #:G0001 form)) #:G0003 (RETURN-FROM #:G0001 (LET ((var1 #:G0002)) . body)) #:G0004 (RETURN-FROM #:G0001 (LET ((var2 #:G0002)) . body)) ...))) As a special case, the TYPE can also be the symbol :NO-ERROR in the last clause. If it is, it designates a clause which will take control if the FORM returns normally. In that case, the arguments to the function are like those for MULTIPLE-VALUE-CALL on the return value of the form. For example: (HANDLER-CASE form (type1 (var1) . body1) ... (:NO-ERROR (varN-1 varN-2 ...) . bodyN)) is approximately equivalent to: (BLOCK #:ERROR-RETURN (MULTIPLE-VALUE-CALL #'(LAMBDA (varN-1 varN-2 ...) . bodyN) (BLOCK #:NORMAL-RETURN (RETURN-FROM #:ERROR-RETURN (HANDLER-CASE (RETURN-FROM #:NORMAL-RETURN form) (type1 (var1) . body1) ...))))) Condition System, Revision #18 by KMP 12-Mar-88, Page 31 Examples of HANDLER-CASE: (HANDLER-CASE (/ X Y) (DIVISION-BY-ZERO () NIL)) (HANDLER-CASE (OPEN *THE-FILE* :DIRECTION :INPUT) (FILE-ERROR (CONDITION) (FORMAT T "~&Fooey: ~A~%" CONDITION))) (HANDLER-CASE (SOME-USER-FUNCTION) (FILE-ERROR (CONDITION) CONDITION) (DIVISION-BY-ZERO () 0) ((OR UNBOUND-VARIABLE UNDEFINED-FUNCTION) () 'UNBOUND)) (HANDLER-CASE (INTERN X Y) (ERROR (CONDITION) CONDITION) (:NO-ERROR (SYMBOL STATUS) (DECLARE (IGNORE SYMBOL)) STATUS)) IGNORE-ERRORS {form}* [Macro] Executes its body in a context which handles conditions of type ERROR by returning control to this form. If no such condition is signalled, any values returned by the last form are returned by IGNORE-ERRORS. Otherwise, two values are returned -- NIL and the ERROR condition which was signalled. Synonym for (HANDLER-CASE (PROGN . forms) (ERROR (CONDITION) (VALUES NIL CONDITION))). HANDLER-BIND ({(type handler)}*) {form}* [Macro] Executes body in a dynamic context where the given handler bindings are in effect. TYPE may be any type specifier. HANDLER should evaluate to a function to be used to handle conditions of the given type(s) during execution of the FORMS. This function should take a single argument, the condition being signalled. If more than one binding is specified, the bindings are searched sequentially from top to bottom in search of a match (by visual analogy with TYPECASE). If an appropriate type is found, the associated handler is run in a context where none of the BINDINGS are visible (to avoid recursive errors). For example, in the case of: (HANDLER-BIND ((UNBOUND-VARIABLE #'(LAMBDA ...)) (ERROR #'(LAMBDA ...))) ...) if an unbound variable error is signalled in the body (and not handled by an intervening handler), the first function will be called. If any other kind of error is signalled, the second function will be called. In either case, neither handler will be active while executing the code in the associated function. Condition System, Revision #18 by KMP 12-Mar-88, Page 32 Defining Conditions ------------------- DEFINE-CONDITION name (parent-type) [({slot}*) {option}*] [Macro] Defines a new condition type called NAME, which is a subtype of the given PARENT-TYPE. Except as otherwise noted, the arguments are not evaluated. Objects of this condition type will have all of the indicated SLOTS, plus any additional slots that would be available in objects of type PARENT-TYPE. If the SLOTS list is omitted, the empty list is assumed. A SLOT must have the form: { slot-name ! (slot-name) ! (slot-name default-value) } The DEFAULT-VALUE is a form which can be evaluated by MAKE-CONDITION to produce a default value when an explicit value is not provided. If no slot default is specified, the contents of the slot will be initialized in an implementation-dependent way. It is an error to attempt to access a slot which has not been explicitly initialized and which has not been given a default value. If the type being defined and some other type from which it inherits have a slot by the same name, only one slot is allocated in the condition object, but the specified default overrides any default which might otherwise have been inherited from a parent type. If no default value is given, the inherited default (if any) is still visible. MAKE-CONDITION will accept keywords (in the keyword package) with the printname of any of the designated slots, and will initialize the corresponding slots in conditions it creates. Accessors are created according to the same rules as used by DEFSTRUCT. It is an error to attempt to assign the condition's slots by using SETF. The valid OPTIONS are: (:DOCUMENTATION doc-string) DOC-STRING should be a string which describes the purpose of the condition type or NIL. If this option is omitted, NIL is assumed. (DOCUMENTATION name 'TYPE) will retrieve this information. (:CONC-NAME symbol-or-string) As with DEFSTRUCT, this sets up automatic prefixing of the names of slot accessors. Also as in DEFSTRUCT, the default behavior is to use the name of the new type, NAME, followed by a hyphen. (interned in the package which is current at the time that the DEFINE-CONDITION is processed). (:REPORT exp) If EXP is not a literal string, it must be a suitable argument to the FUNCTION special form. The expression (FUNCTION exp) will be evaluated in the current lexical environment. It should return a function of two arguments, a condition and a stream, which prints on the stream a description of the condition. This function is called whenever the condition is printed while *PRINT-ESCAPE* is NIL. If EXP is a literal string, it is a shorthand for (LAMBDA (CONDITION STREAM) (DECLARE (IGNORE CONDITION)) (WRITE-STRING exp STREAM)) This option is processed after the new condition type has been defined, so use of the slot accessors within the report function is permitted. If this option is not specified, information about how to report this type of condition will be inherited from the PARENT-TYPE. Condition System, Revision #18 by KMP 12-Mar-88, Page 33 Examples: The following form defines a condition of type PEG/HOLE-MISMATCH which inherits from a condition type called BLOCKS-WORLD-ERROR: (DEFINE-CONDITION PEG/HOLE-MISMATCH (BLOCKS-WORLD-ERROR) (PEG-SHAPE HOLE-SHAPE) (:REPORT (LAMBDA (CONDITION STREAM) (FORMAT STREAM "A ~A peg cannot go in a ~A hole." (PEG/HOLE-MISMATCH-PEG-SHAPE CONDITION) (PEG/HOLE-MISMATCH-HOLE-SHAPE CONDITION))))) The new type has slots PEG-SHAPE and HOLE-SHAPE, so MAKE-CONDITION will accept :PEG-SHAPE and :HOLE-SHAPE keywords. The accessors PEG/HOLE-MISMATCH-PEG-SHAPE and PEG/HOLE-MISMATCH-HOLE-SHAPE will apply to objects of this type, as illustrated in the :REPORT information. Here is another example. This defines a condition called MACHINE-ERROR which inherits from ERROR: (DEFINE-CONDITION MACHINE-ERROR (ERROR) (MACHINE-NAME) (:REPORT (LAMBDA (CONDITION STREAM) (FORMAT STREAM "There is a problem with ~A." (MACHINE-ERROR-MACHINE-NAME CONDITION))))) Building on this definition, we can define a new error condition which is a subtype of MACHINE-ERROR for use when machines are not available: (DEFINE-CONDITION MACHINE-NOT-AVAILABLE-ERROR (MACHINE-ERROR) () (:REPORT (LAMBDA (CONDITION STREAM) (FORMAT STREAM "The machine ~A is not available." (MACHINE-ERROR-MACHINE-NAME CONDITION))))) This defines a still more specific condition, built upon MACHINE-NOT-AVAILABLE-ERROR, which provides a default for MACHINE-NAME but which does not provide any new slots or report information. It just gives the MACHINE-NAME slot a default initialization: (DEFINE-CONDITION MY-FAVORITE-MACHINE-NOT-AVAILABLE-ERROR (MACHINE-NOT-AVAILABLE-ERROR) ((MACHINE-NAME "MC.LCS.MIT.EDU"))) Note that since no :REPORT clause was given, the information inherited from MACHINE-NOT-AVAILABLE-ERROR will be used to report this type of condition. Creating Conditions ------------------- MAKE-CONDITION type &rest slot-initializations [Function] Constructs a condition object of the given TYPE using SLOT-INITIALIZATIONS as a specification of the initial value of the slots. The newly created condition is returned. The SLOT-INITIALIZATIONS are given in alternating keyword/value pairs. e.g., (MAKE-CONDITION 'PEG/HOLE-MISMATCH :PEG-SHAPE 'SQUARE :HOLE-SHAPE 'ROUND) Condition System, Revision #18 by KMP 12-Mar-88, Page 34 Establishing Restarts --------------------- WITH-SIMPLE-RESTART (name format-string {format-argument}*) {form}* [Macro] This is shorthand for one of the most common uses of RESTART-CASE. If the restart designated by NAME is not invoked while executing FORMS, all values returned by the last form in FORMS are returned. If that restart is invoked, control is transferred to the WITH-SIMPLE-RESTART form, which immediately returns two values NIL and T. It is permissible for NAME to be NIL. In that case, an anonymous restart is established. WITH-SIMPLE-RESTART could be defined by: (DEFMACRO WITH-SIMPLE-RESTART ((RESTART-NAME FORMAT-STRING &REST FORMAT-ARGUMENTS) &BODY FORMS) `(RESTART-CASE (PROGN ,@FORMS) (,RESTART-NAME () :REPORT (LAMBDA (STREAM) (FORMAT STREAM ,FORMAT-STRING ,@FORMAT-ARGUMENTS)) (VALUES NIL T)))) Example: Lisp> (DEFUN READ-EVAL-PRINT-LOOP (LEVEL) (WITH-SIMPLE-RESTART (ABORT "Exit command level ~D." LEVEL) (LOOP (WITH-SIMPLE-RESTART (ABORT "Return to command level ~D." LEVEL) (LET ((FORM (PROG2 (FRESH-LINE) (READ) (FRESH-LINE)))) (PRIN1 (EVAL FORM))))))) => READ-EVAL-PRINT-LOOP Lisp> (READ-EVAL-PRINT-LOOP 1) (+ 'a 3) Error: The argument, A, to the function + was of the wrong type. The function expected a number. To continue, type :CONTINUE followed by an option number: 1: Specify a value to use this time. 2: Return to command level 1. 3: Exit command level 1. 4: Return to Lisp Toplevel. Debug> Compatibility Note: In contrast to the way that Zetalisp has traditionally defined ABORT as a kind of condition to be handled, we define ABORT as a way to restart (ie, "proceed" in Zetalisp terms). See Footnote: {Keyboard Interrupts} Implementation Note: Implementors are encouraged to make sure that there is always a restart named ABORT around any user code so that user code can call ABORT at any time and expect something reasonable to happen; exactly what the reasonable thing is may vary somewhat. Typically in an interactive listener, things should be set up so that invoking ABORT returns to toplevel, though in some batch or multi-processing situations there may be situations in which having it kill the running process is more appropriate. Condition System, Revision #18 by KMP 12-Mar-88, Page 35 The lowest level form which creates restart points is called RESTART-BIND. RESTART-CASE is an abstraction which addresses many common needs for RESTART-BIND while offering a more palatable syntax. See also WITH-SIMPLE-RESTART. The function which transfers control to a restart point established by one of these macros is called INVOKE-RESTART. RESTART-CASE expression {(case-name arglist {keyword value}* {form}*)}* [Macro] The EXPRESSION is evaluated in a dynamic context where the clauses have special meanings as points to which control may be transferred. If EXPRESSION finishes executing and returns any values, all values returned are simply returned by the RESTART-CASE form. While EXPRESSION is running, any code may transfer control to one of the clauses (see INVOKE-RESTART). If a transfer occurs, the forms in the body of that clause will be evaluated and any values returned by the last such form will be returned by the RESTART-CASE form. If there are no FORMS in a selected clause, RESTART-CASE returns NIL. The CASE-NAME may be NIL or a symbol naming this restart. It is possible to have more than one clause use the same CASE-NAME. In this case, the first clause with that name will be found by FIND-RESTART. The other clauses are accessible using COMPUTE-RESTARTS. Each ARGLIST is a normal lambda list to be bound during the execution of its corresponding FORMS. These arguments are used to pass any necessary data from a call to INVOKE-RESTART to the RESTART-CASE clause. By default, INVOKE-RESTART-INTERACTIVELY will pass no arguments and all arguments must be optional in order to accomodate interactive restarting. However, the arguments need not be optional if the :INTERACTIVE keyword has been used to inform INVOKE-RESTART-INTERACTIVELY about how to compute a proper argument list. The valid KEYWORD/VALUE pairs are: :INTERACTIVE exp EXP must be a suitable argument to the FUNCTION special form. The expression (FUNCTION exp) will be evaluated in the current lexical environment. It should return a function of no arguments which returns arguments to be used by INVOKE-RESTART-INTERACTIVELY when invoking this function. This function will be called in the dynamic environment available prior to any restart attempt. It may do user interaction on the stream in *QUERY-IO*. If a restart is invoked interactively but no :INTERACTIVE option was supplied, the argument list used in the invocation is the empty list. :REPORT exp If EXP is not a literal string, it must be a suitable argument to the FUNCTION special form. The expression (FUNCTION exp) will be evaluated in the current lexical environment. It should return a function of one argument, a stream, which prints on the stream a description of the restart. This function is called whenever the restart is printed while *PRINT-ESCAPE* is NIL. If EXP is a literal string, it is a shorthand for (LAMBDA (STREAM) (WRITE-STRING exp STREAM)) If a named restart is asked to report but no report information has been supplied, the name of the restart is used in generating default report text. Condition System, Revision #18 by KMP 12-Mar-88, Page 36 It is an error if an unnamed restart is used and no report information is provided since unnamed restarts are generally only useful interactively and an interactive option which has no description is of little value. Implementations are encouraged to warn about this error at compilation time. Implementation Hint: At runtime, this error might be noticed when entering the debugger. Since signalling an error would probably cause recursive entry into the debugger (causing yet another recursive error, etc.) it is suggested that the debugger print some indication of such problems when they occur but not actually signal errors. When *PRINT-ESCAPE* is NIL, the printer will use the report information for a restart. For example, a debugger might announce the action of typing ``:CONTINUE'' by doing: (FORMAT T "~&~S -- ~A~%" ':CONTINUE SOME-RESTART) which might then display as something like: :CONTINUE -- Return to command level. Note that (RESTART-CASE expression (name1 arglist1 ...options1... . body1) (name2 arglist2 ...options2... . body2)) is essentially equivalent to (BLOCK #:G0001 (LET ((#:G0002 NIL)) (TAGBODY (RESTART-BIND ((name1 #'(LAMBDA (&REST TEMP) (SETQ #:G0002 TEMP) (GO #:G0003)) ...slightly-transformed-options1...) (name2 #'(LAMBDA (&REST TEMP) (SETQ #:G0002 TEMP) (GO #:G0004)) ...slightly-transformed-options2...)) (RETURN-FROM #:G0001 expression)) #:G0003 (RETURN-FROM #:G0001 (APPLY #'(LAMBDA arglist1 . body1) #:G0002)) #:G0004 (RETURN-FROM #:G0001 (APPLY #'(LAMBDA arglist2 . body2) #:G0002))))) Condition System, Revision #18 by KMP 12-Mar-88, Page 37 Examples: (LOOP (RESTART-CASE (RETURN (APPLY FUNCTION SOME-ARGS)) (NEW-FUNCTION (NEW-FUNCTION) :REPORT "Use a different function." :INTERACTIVE (LAMBDA () (LIST (PROMPT-FOR 'FUNCTION "Function: "))) (SETQ FUNCTION NEW-FUNCTION)))) (LOOP (RESTART-CASE (RETURN (APPLY FUNCTION SOME-ARGS)) (NIL (NEW-FUNCTION) :REPORT "Use a different function." :INTERACTIVE (LAMBDA () (LIST (PROMPT-FOR 'FUNCTION "Function: "))) (SETQ FUNCTION NEW-FUNCTION)))) (RESTART-CASE (A-COMMAND-LOOP) (RETURN-FROM-COMMAND-LEVEL () :REPORT (LAMBDA (STREAM) (FORMAT STREAM "Return from command level ~D." LEVEL)) NIL)) (LOOP (RESTART-CASE (ANOTHER-RANDOM-COMPUTATION) (CONTINUE () NIL))) The first and second example are equivalent from the point of view of someone using the interactive debugger, but differ in one important aspect for non-interactive handling. If a handler `knows about' named restarts, as in: (IF (FIND-RESTART 'NEW-FUNCTION) (INVOKE-RESTART 'NEW-FUNCTION THE-REPLACEMENT)) then only the first example, and not the second, will have control transferred to its correction clause since only the first example uses a restart named NEW-FUNCTION. Here's a more complete example: (LET ((MY-FOOD 'MILK) (MY-COLOR 'GREENISH-BLUE)) (DO () ((NOT (BAD-FOOD-COLOR-P MY-FOOD MY-COLOR))) (RESTART-CASE (ERROR 'BAD-FOOD-COLOR :FOOD MY-FOOD :COLOR MY-COLOR) (USE-FOOD (NEW-FOOD) :REPORT "Use another food." (SETQ MY-FOOD NEW-FOOD)) (USE-COLOR (NEW-COLOR) :REPORT "Use another color." (SETQ MY-COLOR NEW-COLOR)))) ;; We won't get to here until MY-FOOD and MY-COLOR are compatible. (LIST MY-FOOD MY-COLOR)) Assuming that USE-FOOD and USE-COLOR have been defined as (DEFUN USE-FOOD (NEW-FOOD) (INVOKE-RESTART 'USE-FOOD NEW-FOOD)) (DEFUN USE-COLOR (NEW-COLOR) (INVOKE-RESTART 'USE-COLOR NEW-COLOR)) then a handler can then restart from the error in either of two ways. It may correct the color or correct the food. For example: #'(LAMBDA (CONDITION) ... (USE-COLOR 'WHITE) ...) ;Corrects color or #'(LAMBDA (CONDITION) ... (USE-FOOD 'CHEESE) ...) ;Corrects food Here is an example using HANDLER-BIND and RESTART-CASE which refers to a condition type FOO-ERROR which was presumably defined elsewhere: (HANDLER-BIND ((FOO-ERROR #'(LAMBDA (IGNORE) (USE-VALUE 7)))) (RESTART-CASE (ERROR 'FOO-ERROR) (USE-VALUE (X) (* X X)))) => 49 Condition System, Revision #18 by KMP 12-Mar-88, Page 38 RESTART-BIND ({(name function {keyword value}*)}*) {form}* [Macro] Executes a body of forms in a dynamic context where the given restart bindings are in effect. NAME may be NIL to indicate an anonymous restart, or some other symbol to indicate a named restart. FUNCTION should evaluate to a function to be used to perform the restart. If invoked, this function may either perform a non-local transfer of control or it may return normally. The function may take whatever arguments the programmer feels are appropriate; it will be invoked only if INVOKE-RESTART is used from a program, or if a user interactively asks the debugger to invoke it. In the case of interactive invocation, the :INTERACTIVE-FUNCTION option comes into play; see below. The valid KEYWORD/VALUE pairs are: :INTERACTIVE-FUNCTION form The FORM will be evaluated in the current lexical environment and should return a function of no arguments which constructs a list of arguments to be used by INVOKE-RESTART-INTERACTIVELY when invoking this restart. The function may prompt interactively using *QUERY-IO* if necessary. :REPORT-FUNCTION form The FORM will be evaluated in the current lexical environment and should return a function of one argument, a stream, which prints on the stream a summary of the action that this restart will take. This function is called whenever the restart is printed while *PRINT-ESCAPE* is NIL. Finding and Manipulating Restarts --------------------------------- COMPUTE-RESTARTS [Function] Uses the dynamic state of the program to compute a list of the ``restarts'' which are currently active. See RESTART-BIND. Each ``restart'' represents a function which can be called to perform some form of recovery action, usually a transfer of control to an outer point in the running program. Implementations are free to implement these objects in whatever manner is most convenient; the objects need have only dynamic extent (relative to the scope of the binding form which instantiates them). The list which results from a call to COMPUTE-RESTARTS is ordered so that the innermost (i.e., more-recently established) restarts are nearer the head of the list. Note, too, that COMPUTE-RESTARTS returns all valid restarts, including anonymous ones, even if some of them have the same name as others and would therefore not be found by FIND-RESTART when given a symbol argument. Implementations are permitted, but not required, to return different (i.e., non-EQ) lists from repeated calls to COMPUTE-RESTARTS while in the same dynamic environment. It is an error to modify the list which is returned by COMPUTE-RESTARTS. Condition System, Revision #18 by KMP 12-Mar-88, Page 39 RESTART-NAME restart [Function] Returns the name of the given RESTART, or NIL if it is not named. FIND-RESTART identifier [Function] Searches for a particular restart in the current dynamic environment. If IDENTIFIER is a symbol, then the innermost (i.e., most recently established) restart with that name is returned. NIL is returned if no such restart is found. If IDENTIFIER is a restart object, then it is simply returned -- unless it is not currently active, in which case NIL is returned. Although anonymous restarts have a name of NIL, it is an error for the symbol NIL to be given as an IDENTIFIER. Applications which would seem to require this should be rewritten to make appropriate use of COMPUTE-RESTARTS instead. INVOKE-RESTART restart &rest arguments [Function] Calls the function associated with the given RESTART, passing any given ARGUMENTS. The RESTART must be a restart or the non-null name of a restart which is valid in the current dynamic context. If the argument is not valid, an error of type CONTROL-ERROR will be signalled. Note: Restart functions call this function, not vice versa. INVOKE-RESTART-INTERACTIVELY restart [Function] Calls the function associated with the given RESTART, prompting for any necessary ARGUMENTS. The RESTART must be a restart or the non-null name of a restart which is valid in the current dynamic context. If the argument is not valid, an error of type CONTROL-ERROR will be signalled. INVOKE-RESTART-INTERACTIVELY will prompt for arguments by executing the code provided in the :INTERACTIVE keyword to RESTART-CASE or :INTERACTIVE-FUNCTION keyword to RESTART-BIND. If no :INTERACTIVE option has been supplied in the corresponding RESTART-BIND or RESTART-CASE, then it is an error if the restart takes required arguments. If the arguments are optional, an argument list of NIL will be used in this case. Once the arguments have been determined, INVOKE-RESTART-INTERACTIVELY will simply do: (APPLY #'INVOKE-RESTART restart arguments) This operation is used internally by the debugger and may also be useful in implementing other portable, interactive debugging tools. Condition System, Revision #18 by KMP 12-Mar-88, Page 40 Warnings -------- WARN datum &rest arguments [Function] Warns about a situation, by signalling a condition of type WARNING. If DATUM is a condition, then that condition is used directly. In this case, if the condition is not of type WARNING or arguments is non-NIL, an error of type TYPE-ERROR is signalled. If DATUM is a condition type, then the condition used is the result of doing (APPLY #'MAKE-CONDITION datum arguments). This result must be of type WARNING or an error of type TYPE-ERROR is signalled. If DATUM is a string, then the condition used is the result of doing (MAKE-CONDITION 'SIMPLE-WARNING :FORMAT-STRING datum :FORMAT-ARGUMENTS arguments). The precise mechanism for warning is as follows: 1. If *BREAK-ON-WARNINGS* is true, then the debugger will be entered. This feature is primarily for compatibility with old code; use of *BREAK-ON-SIGNALS* is preferred. See *BREAK-ON-WARNINGS* below. If the break is continued using the CONTINUE restart, WARN proceeds with step 2. 2. The warning condition is then signalled. While the WARNING condition is being signalled, the MUFFLE-WARNING restart is established for use by a handler to bypass further action by WARN (i.e., to cause WARN to immediately return NIL). As a natural part of the signalling process, if (TYPEP condition *BREAK-ON-SIGNALS*) is true, then a BREAK will be done prior to beginning the signalling process. 3. If no handlers for the warning condition are found, or if all such handlers decline, then the condition will be reported to *ERROR-OUTPUT* by the WARN function (with possible implementation-specific extra output such as motion to a fresh line before and/or after the display of the warning, or supplying some introductory text that might mention the name of the function which called WARN and/or the fact that this is a warning). 4. The value returned by WARN (if it returns) is NIL. See footnote: {WARNING Motivation} *BREAK-ON-WARNINGS* [Variable] If *BREAK-ON-WARNINGS* is true, then the function WARN will enter the debugger BREAK before signalling the WARNING condition. If the CONTINUE restart is used, WARN continues normally with the warning process. It is intended primarily for use when the user is debugging programs that issue warnings; in "production" use, the value of *BREAK-ON-WARNINGS* should be NIL. Note: This flag is still supported but is considered obsolete. See *BREAK-ON-SIGNALS*. Condition System, Revision #18 by KMP 12-Mar-88, Page 41 Restart Functions ----------------- ABORT [Function] This function transfers control to the restart named ABORT. If no such restart exists, ABORT signals an error of type CONTROL-ERROR. The purpose of the ABORT restart is generally to allow return to the innermost ``command level.'' CONTINUE [Function] This function transfers control to the restart named CONTINUE. If no such restart exists, CONTINUE returns NIL. The CONTINUE restart is generally part of simple protocols where there is a single ``obvious'' way to continue, such as in BREAK and CERROR. Some user-defined protocols may also wish to incorporate it for similar reasons. In general, however, it is more reliable to design a special purpose restart with a name that more directly suits the particular application. MUFFLE-WARNING [Function] This function transfers control to the restart named MUFFLE-WARNING. If no such restart exists, MUFFLE-WARNING signals an error of type CONTROL-ERROR. WARN sets up this restart so that handlers of WARNING conditions have a way to tell WARN that a warning has already been dealt with and that no further action is warranted. STORE-VALUE value [Function] This function transfers control (and one value) to the restart named STORE-VALUE. If no such restart exists, STORE-VALUE returns NIL. The STORE-VALUE restart is generally used by handlers trying to recover from errors of types such as CELL-ERROR or TYPE-ERROR, where the handler may wish to supply a replacement datum to be stored permanently. USE-VALUE value [Function] This function transfers control (and one value) to the restart named USE-VALUE. If no such restart exists, USE-VALUE returns NIL. The USE-VALUE restart is generally used by handlers trying to recover from errors of types such as CELL-ERROR, where the handler may wish to supply a replacement datum for one-time use. Condition System, Revision #18 by KMP 12-Mar-88, Page 42 Debugging Utilities ------------------- BREAK &optional format-string &rest format-arguments [Function] Prints the message described by FORMAT-STRING and FORMAT-ARGUMENTS and then goes directly into the debugger without allowing any possibility of interception by programmed error-handling facilities. If no format-string is supplied, a suitable default will be generated. If continued, BREAK returns NIL. Note: BREAK is presumed to be used as a way of inserting temporary debugging ``breakpoints'' in a program, not as a way of signalling errors; it is expected that continuing from a BREAK will not trigger any unusual recovery action. For this reason, BREAK does not take the additional format control string that CERROR takes as its first argument. This and the lack of any possibility of interception by programmed error-handling are the only program-visible differences between BREAK and CERROR. The user interface aspects of these functions are permitted to vary more widely; for example, it is permissible for a read-eval-print loop to be entered by BREAK rather than the conventional debugger. BREAK could be defined by: (DEFUN BREAK (&OPTIONAL (FORMAT-STRING "Break") &REST FORMAT-ARGUMENTS) (WITH-SIMPLE-RESTART (CONTINUE "Return from BREAK.") (INVOKE-DEBUGGER (MAKE-CONDITION 'SIMPLE-CONDITION :FORMAT-STRING FORMAT-STRING :FORMAT-ARGUMENTS FORMAT-ARGUMENTS))) NIL) INVOKE-DEBUGGER condition [Function] Attempts interactive handling of its argument, which must be a condition. If the variable *DEBUGGER-HOOK* is not NIL, it will be funcalled on two arguments: the CONDITION being handled and the value of *DEBUGGER-HOOK*. If a hook function returns normally, the standard debugger will be tried. The standard debugger will never directly return. Return can occur only by a special transfer of control, such as the use of a restart. See footnote: {Debugger Design Notes} *DEBUGGER-HOOK* [Variable] This variable should hold either NIL or a function of a two arguments, a condition and the value of *DEBUGGER-HOOK*. This function may either handle the condition (transfer control) or return normally (allowing the standard debugger to run). Note: To minimize recursive errors while debugging, *DEBUGGER-HOOK* is bound to NIL when calling this function. When evaluating code typed in by the user interactively, the hook function may want to bind *DEBUGGER-HOOK* to the function which was its second argument so that recursive errors can be handled using the same interactive facility.] Condition System, Revision #18 by KMP 12-Mar-88, Page 43 PRE-DEFINED TYPES ----------------- Implementation Note: Objects of the types RESTART and CONDITION must be distinguishable from any other objects not of those types. This is necessary because PRINC is specified to treat them specially. DEFSTRUCT is one way to implement these types. RESTART [Type] This is the data type used to represent a restart. The CONDITION type hierarchy looks like this: CONDITION ! +---------------+-------------------+--- . . . ! ! ! ! ! ! SIMPLE-CONDITION SERIOUS-CONDITION WARNING ! ! ! ! ! SIMPLE-WARNING ! ! +-----+----------------------+--- . . . ! ! ! ! ERROR STORAGE-CONDITION ! ! +---------------+---+--------------+--- . . . ! ! ! ! ! ! SIMPLE-ERROR ARITHMETIC-ERROR CONTROL-ERROR ! ! ... ... The types which are non-terminals in the above tree (i.e., CONDITION, WARNING, STORAGE-CONDITION, ERROR, ARITHMETIC-ERROR, CONTROL-ERROR, etc.) are provided primarily for type inclusion purposes. Normally, they would not be directly instantiated. Implementations are permitted to support non-portable synonyms for these types, as well as to introduce other types which are above, below, or between the types shown in this tree as long as the indicated subtype relationships are not violated. The types SIMPLE-CONDITION, SERIOUS-CONDITION, and WARNING are pairwise disjoint. The type ERROR is also disjoint from types SIMPLE-CONDITION and WARNING. Condition System, Revision #18 by KMP 12-Mar-88, Page 44 CONDITION [Type] All types of conditions, whether error or non-error, must inherit from this type. WARNING [Type] All types of warnings should inherit from this type. This is a subtype of CONDITION. SERIOUS-CONDITION [Type] All serious conditions (conditions serious enough to require interactive intervention if not handled) should inherit from this type. This is a subtype of CONDITION. This condition type is provided primarily for terminological convenience. In fact, signalling a condition which inherits from SERIOUS-CONDITION does not force entry into the debugger. Rather, it is conventional for someone to use ERROR (or something built on ERROR) to signal conditions which are of this type, and to use SIGNAL to signal conditions which are not of this type. ERROR [Type] All types of error conditions inherit from this condition. This is a subtype of SERIOUS-CONDITION. Condition System, Revision #18 by KMP 12-Mar-88, Page 45 The default condition type for SIGNAL and WARN is SIMPLE-CONDITION. The default condition type for ERROR and CERROR is SIMPLE-ERROR. SIMPLE-CONDITION [Type] Conditions signalled by SIGNAL when given a format string as a first argument are of this type. This is a subtype of CONDITION. The init keywords :FORMAT-STRING and :FORMAT-ARGUMENTS are supported to initialize the slots, which can be accessed using SIMPLE-CONDITION-FORMAT-STRING and SIMPLE-CONDITION-FORMAT-ARGUMENTS. If :FORMAT-ARGUMENTS is not supplied to MAKE-CONDITION, the format-arguments slot defaults to NIL. SIMPLE-WARNING [Type] Conditions signalled by WARN when given a format string as a first argument are of this type. This is a subtype of WARNING. The init keywords :FORMAT-STRING and :FORMAT-ARGUMENTS are supported to initialize the slots, which can be accessed using SIMPLE-CONDITION-FORMAT-STRING and SIMPLE-CONDITION-FORMAT-ARGUMENTS. If :FORMAT-ARGUMENTS is not supplied to MAKE-CONDITION, the format-arguments slot defaults to NIL. In implementations supporting multiple inheritance, this type will also be a subtype of SIMPLE-CONDITION. SIMPLE-ERROR [Type] Conditions signalled by ERROR and CERROR when given a format string as a first argument are of this type. This is a subtype of ERROR. The init keywords :FORMAT-STRING and :FORMAT-ARGUMENTS are supported to initialize the slots, which can be accessed using SIMPLE-CONDITION-FORMAT-STRING and SIMPLE-CONDITION-FORMAT-ARGUMENTS. If :FORMAT-ARGUMENTS is not supplied to MAKE-CONDITION, the format-arguments slot defaults to NIL. In implementations supporting multiple inheritance, this type will also be a subtype of SIMPLE-CONDITION. SIMPLE-CONDITION-FORMAT-STRING condition [Function] Accesses the format-string slot of a given condition, which must be of type SIMPLE-CONDITION, SIMPLE-WARNING, SIMPLE-ERROR, or SIMPLE-TYPE-ERROR. SIMPLE-CONDITION-FORMAT-ARGUMENTS condition [Function] Accesses the format-arguments slot of a given condition, which must be of type SIMPLE-CONDITION, SIMPLE-WARNING, SIMPLE-ERROR, or SIMPLE-TYPE-ERROR. Condition System, Revision #18 by KMP 12-Mar-88, Page 46 STORAGE-CONDITION [Type] Conditions which relate to storage overflow should inherit from this type. This is a subtype of SERIOUS-CONDITION. ------------------------- TYPE-ERROR [Type] Errors in the transfer of data in a program should inherit from this type. This is a subtype of ERROR. For example, conditions to be signalled by CHECK-TYPE should inherit from this type. The init keywords :DATUM and :EXPECTED-TYPE are supported to initialize the slots, which can be accessed using TYPE-ERROR-DATUM and TYPE-ERROR-EXPECTED-TYPE. TYPE-ERROR-DATUM condition [Function] Accesses the datum slot of a given condition, which must be of type TYPE-ERROR. TYPE-ERROR-EXPECTED-TYPE condition [Function] Accesses the expected-type slot of a given condition, which must be of type TYPE-ERROR. Users of TYPE-ERROR conditions are expected to fill this slot with an object that is a valid Common Lisp type spec. SIMPLE-TYPE-ERROR [Type] Conditions signalled by facilities similar to CHECK-TYPE may want to use this type. The init keywords :FORMAT-STRING and :FORMAT-ARGUMENTS are supported to initialize the slots, which can be accessed using SIMPLE-CONDITION-FORMAT-STRING and SIMPLE-CONDITION-FORMAT-ARGUMENTS. If :FORMAT-ARGUMENTS is not supplied to MAKE-CONDITION, the format-arguments slot defaults to NIL. In implementations supporting multiple inheritance, this type will also be a subtype of SIMPLE-CONDITION. Condition System, Revision #18 by KMP 12-Mar-88, Page 47 PROGRAM-ERROR [Type] Errors which relate to incorrect program syntax which are statically detectable should inherit from this type (regardless of whether they are in fact statically detected). This is a subtype of ERROR. This is -not- a subtype of CONTROL-ERROR. CONTROL-ERROR [Type] Errors in the dynamic transfer of control in a program should inherit from this type. This is a subtype of ERROR. This is -not- a subtype of PROGRAM-ERROR. Note: The errors which result from giving THROW a tag which is not active or from giving GO or RETURN-FROM a tag which is no longer dynamically available are control errors. The errors which result from naming a GO tag or RETURN-FROM tag which is not lexically apparent are not control errors. They are program errors. See PROGRAM-ERROR. ------------------------- PACKAGE-ERROR [Type] Errors which occur during operations on packages should inherit from this type. This is a subtype of ERROR. The init keyword :PACKAGE is supported to initialize the slot, which can be accessed using PACKAGE-ERROR-PACKAGE. PACKAGE-ERROR-PACKAGE [Function] Accesses the package (or package name) which was being modified or manipulated in a condition of type PACKAGE-ERROR. ------------------------- STREAM-ERROR [Type] Errors which occur during input from, output to, or closing a stream should inherit from this type. This is a subtype of ERROR. The init keyword :STREAM is supported to initialize the slot, which can be accessed using STREAM-ERROR-STREAM. STREAM-ERROR-STREAM [Function] Accesses the offending stream of a condition of type STREAM-ERROR. END-OF-FILE [Type] The error which results when a read operation is done on a stream which has no more tokens should inherit from this type. This is a subtype of STREAM-ERROR. Condition System, Revision #18 by KMP 12-Mar-88, Page 48 FILE-ERROR [Type] Errors which occur during an attempt to open a file, or during some low-level transaction with a file system should inherit from this type. This is a subtype of ERROR. The init keyword :PATHNAME is supported to initialize the slot, which can be accessed using FILE-ERROR-PATHNAME. FILE-ERROR-PATHNAME [Function] Accesses the offending pathname of a condition of type FILE-ERROR. ------------------------- CELL-ERROR [Type] Errors which occur while accessing a location should inherit from this type. This is a subtype of ERROR. The init keyword :NAME is supported to initialize the slot, which can be accessed using CELL-ERROR-NAME. CELL-ERROR-NAME [Function] Accesses the offending cell name of a condition of type CELL-ERROR. UNBOUND-VARIABLE [Type] The error which results from trying to access the value of an unbound variable should inherit from this type. This is a subtype of CELL-ERROR. UNDEFINED-FUNCTION [Type] The error which results from trying to access the value of an undefined function should inherit from this type. This is a subtype of CELL-ERROR. See Footnote: {Undefined Function Lineage} ------------------------- ARITHMETIC-ERROR [Type] Errors which occur while doing arithmetic type operations should inherit from this type. This is a subtype of ERROR. The init keywords :OPERATION and :OPERANDS are supported to initialize the slots, which can be accessed using ARITHMETIC-ERROR-OPERATION and ARITHMETIC-ERROR-OPERANDS. ARITHMETIC-ERROR-OPERATION [Function] Accesses the offending operation of a condition of type ARITHMETIC-ERROR. ARITHMETIC-ERROR-OPERANDS [Function] Accesses a list of the offending operands in a condition of type ARITHMETIC-ERROR. DIVISION-BY-ZERO [Type] Errors which occur because of division by 0 should inherit from this type. This is a subtype of ARITHMETIC-ERROR. FLOATING-POINT-OVERFLOW [Type] Errors which occur because of floating point overflow should inherit from this type. This is a subtype of ARITHMETIC-ERROR. FLOATING-POINT-UNDERFLOW [Type] Errors which occur because of floating point underflow should inherit from this type. This is a subtype of ARITHMETIC-ERROR. Condition System, Revision #18 by KMP 12-Mar-88, Page 49 FOOTNOTES --------- {Condition Methods} Some readers may wonder why the functions for directly accessing the printer for a condition are not present in this document. The main reason is that there is not at this time an approved object system for Common Lisp. As such, this document is deliberately silent on the issue of whether the printer is represented externally (as "methods") or on a per-instance basis (as "slots"). In the latter case, if you changed the printer, you might not have a list of all the instances and some might not be updated. This issue should be carefully reconsidered if/when an object system is introduced, but the conservative thing for now is to not commit ourselves prematurely on issues like this. {Debugger Design Notes} The exact way in which the debugger interacts with users is expected to vary considerably from system to system. For example, some systems may use a keyboard interface while others may use a mouse interface. Of those systems using keyboard commands, some may use single-character commands while others use parsed line-at-a-time commands. The exact set of commands will vary as well. The important properties of a debugger are that: * It makes information about the error accessible. * It makes the set of apparent restarts easily accessible. It is desirable to have a mode where the debugger allows other features, such as the ability to inspect data, stacks, etc. However, it may sometimes be appropriate to have this kind of information hidden from users. Experience on the Lisp Machines has shown that some users who are not programmers develop a terrible phobia of debuggers. The reason for this usually traces to the fact that the debugger is very foreign to them and provides an overwhelming amount of information which is only of interest to programmers. With the advent of restarts, there is a clear mechanism for the construction of "friendly" debuggers. Programmers can be taught how to get to the information they need for debugging, but it should be possible to construct user interfaces to the debugger which are natural, convenient, intelligible, and friendly even to non-programmers. {Keyboard Interrupts} Some readers may wonder what is done by the Abort key (or whatever the implementation's interrupt key is -- Control-C or Control-G, for example). Such interrupts, whether synchronous or asynchronous in nature, are beyond the scope of this document. This may be a topic worth standardizing under separate cover. Here is some speculation about some possible things that might happen: Some implementations might simple call ABORT or BREAK directly without signalling any condition. Other implementation might signal some condition which was related to the fact that a key had been pressed rather than the action that should be taken. This is one way to allow user customization. Perhaps there would be an implementation-dependent KEYBOARD-INTERRUPT condition type with a slot containing the key that was pressed -- or perhaps would be such a condition type but rather than having slots, different subtypes of that type with names like KEYBOARD-ABORT, KEYBOARD-BREAK, etc. might be signalled. That implementation would then document the action which it would take if user programs failed to handle the condition, and perhaps ways for user programs to usefully dismiss the interrupt. Condition System, Revision #18 by KMP 12-Mar-88, Page 50 {Packaging} We need to decide where symbols defined in this proposal will go. At some later point, many or all may want to go in LISP. However, it is dangerous to change the set of symbols in the LISP package for private purposes, so for purpose of experimentation with this proposal, all symbols defined in this proposal will be assumed to live in a package named CONDITIONS except as explicitly specified otherwise. Users of this condition system are, for now, to be encouraged to USE both LISP and CONDITIONS in order avoid the need to specify any package prefixes explicitly. For example, code should avoid saying (CONDITIONS:DEFINE-CONDITION ...) and should prefer to make sure that CONDITIONS:DEFINE-CONDITION is directly available in the current package so that (DEFINE-CONDITION ...) is possible. {PROMPT-FOR} The function PROMPT-FOR used in this document in a number of places is not a part of Common-Lisp. It is presupposed by this document only to keep the presentation simple. Its description is: PROMPT-FOR typespec &optional format-string &rest format-arguments Calls READ interactively with the given format-style prompt, accepting only an object of the indicated type. A discussion of whether or not this (or something like it) would be a useful addition to Common Lisp is being taken up under separate cover by the Cleanup Committee. In spite of its use in a number of examples, nothing in this proposal depends on this function. {Undefined Function Lineage} Some readers may wonder why UNDEFINED-FUNCTION is not defined to inherit from some condition such as CONTROL-ERROR. The answer is that any such arrangement would require the presence of multiple inheritance -- a luxury we don't currently have (without resorting to DEFTYPE, which we are currently avoiding). When the Common Lisp object standard comes into being, we might want to begin to consider issues like this. Multiple inheritance makes a lot of things in a condition system much more flexible to deal with. REFERENCES ---------- Some background information about motivation for decisions in this document may be found in my paper ``Exceptional Situations in Lisp,'' which is available as AI Working Paper 268 from the MIT AI Lab publications office (545 Technology Square, Cambridge, MA 02139). This document is now also available as X3J13 document 86-011. Much of the basis of ``Exceptional Situations in Lisp'' derives from ``Signalling and Handling Conditions,'' a document published by Symbolics, Inc. which describes the Lisp Machine's condition system as it looked when originally introduced in 1983. Condition System, Revision #18 by KMP 12-Mar-88, Page 51 INDEX ----- ABORT .............................................. 41 ARITHMETIC-ERROR-OPERANDS .......................... 48 ARITHMETIC-ERROR-OPERATION ......................... 48 ARITHMETIC-ERROR ................................... 48 Assertions ......................................... 26 ASSERT ............................................. 27 BREAK .............................................. 42 *BREAK-ON-SIGNALS* ................................. 25 *BREAK-ON-WARNINGS* ................................ 40 Catch/Throw and Restarts, Comparison of ............ 17 CCASE .............................................. 29 CELL-ERROR-NAME .................................... 48 CELL-ERROR ......................................... 48 CERROR ............................................. 24 Changes since Revision 8 ........................... 3 Changes to Terminology ............................. 8 CHECK-TYPE ......................................... 26 CLtL Terminology ................................... 7 Comparison of Restarts and Catch/Throw ............. 17 COMPUTE-RESTARTS ................................... 38 Condition Methods .................................. 49 Conditions, Creating ............................... 33 Conditions, Defining ............................... 32 Conditions, Handling ............................... 11, 22, 30 Conditions, Interactive Handling ................... 19 Conditions, Methods ................................ 49 Conditions, Non-Serious ............................ 20 Conditions, Object-Oriented Basis of ............... 13 Conditions, Printing ............................... 23 Conditions, Resignalling ........................... 21 Conditions, Serious ................................ 19 Conditions, Signalling ............................. 21, 24 Conditions, Types .................................. 20 CONDITION .......................................... 44 CONTINUE ........................................... 41 CONTROL-ERROR ...................................... 47 Creating Conditions ................................ 33 CTYPECASE .......................................... 28 *DEBUGGER-HOOK* .................................... 42 Debugger Design Notes .............................. 49 Debugging Utilities ................................ 42 DEFINE-CONDITION ................................... 32 Defining Conditions ................................ 32 DIVISION-BY-ZERO ................................... 48 ECASE .............................................. 29 END-OF-FILE ........................................ 47 Errors, Signalling ................................. 9 Errors, Trapping ................................... 10 ERROR .............................................. 24 ERROR .............................................. 44 Establishing Handlers .............................. 10-11, 30-31 Establishing Restarts .............................. 15, 34-35, 38 ETYPECASE .......................................... 28 FILE-ERROR-PATHNAME ................................ 48 FILE-ERROR ......................................... 48 FIND-RESTART ....................................... 39 Finding and Manipulating Restarts .................. 38 FLOATING-POINT-OVERFLOW ............................ 48 FLOATING-POINT-UNDERFLOW ........................... 48 Generalized Restarts ............................... 18 HANDLER-BIND ....................................... 31 HANDLER-CASE ....................................... 30 Handlers, Condition ................................ 22 Handling Conditions ................................ 11, 30 Condition System, Revision #18 by KMP 12-Mar-88, Page 52 Historical Background .............................. 1 IGNORE-ERRORS ...................................... 31 Index .............................................. 51 Interactive Condition Handling ..................... 19 Introduction ....................................... 6 INVOKE-DEBUGGER .................................... 42 INVOKE-RESTART-INTERACTIVELY ....................... 39 INVOKE-RESTART ..................................... 39 Issues, Outstanding ................................ 5 Keyboard Interrupts ................................ 49 MAKE-CONDITION ..................................... 33 Manipulating and Finding Restarts .................. 38 MUFFLE-WARNING ..................................... 41 Named Restarts ..................................... 16 Non-Serious Conditions ............................. 20 Object-Oriented Basis of Condition Handling ........ 13 Outstanding Issues ................................. 5 PACKAGE-ERROR ...................................... 47 PACKAGE-ERROR-PACKAGE .............................. 47 Packaging .......................................... 50 Pre-Defined Types .................................. 43 Printing Conditions ................................ 23 PROGRAM-ERROR ...................................... 47 PROMPT-FOR ......................................... 50 References ......................................... 50 Resignalling Conditions ............................ 21 RESTART ............................................ 43 Restart ............................................ 14-18 Restart Functions .................................. 16, 41 RESTART-BIND ....................................... 38 RESTART-CASE ....................................... 35 RESTART-NAME ....................................... 39 Restarts and Catch/Throw, Comparison of ............ 17 Restarts, Establishing ............................. 34 Restarts, Finding and Manipulating ................. 38 Restarts, Generalized .............................. 18 Restarts, Named .................................... 16 Serious Conditions ................................. 19, 44 SERIOUS-CONDITION .................................. 44 Signalling Conditions .............................. 21, 24 Signalling Errors .................................. 9 SIGNAL ............................................. 25 SIMPLE-CONDITION-FORMAT-ARGUMENTS .................. 45 SIMPLE-CONDITION-FORMAT-STRING ..................... 45 SIMPLE-CONDITION ................................... 45 SIMPLE-ERROR ....................................... 45 SIMPLE-TYPE-ERROR .................................. 46 SIMPLE-WARNING ..................................... 45 STORAGE-CONDITION .................................. 46 STORE-VALUE ........................................ 41 STREAM-ERROR-STREAM ................................ 47 STREAM-ERROR ....................................... 47 Terminology, Changes to ............................ 8 Terminology, CLtL .................................. 7 Trapping Errors .................................... 10 TYPE-ERROR-DATUM ................................... 46 TYPE-ERROR-EXPECTED-TYPE ........................... 46 TYPE-ERROR ......................................... 46 Types, Condition ................................... 20 UNBOUND-VARIABLE ................................... 48 Undefined Function Lineage ......................... 50 UNDEFINED-FUNCTION ................................. 48 USE-VALUE .......................................... 41 Utilities, Debugging ............................... 42 Warnings ........................................... 40 WARNING ............................................ 44 WARN ............................................... 40 WITH-SIMPLE-RESTART ................................ 34