Error System Proposal #5 by Kent M. Pitman (KMP@Symbolics), 15-Mar-86. This draft incorporates numerous suggestions made by David A. Moon (Moon@Symbolics) and Richard Mlynarik (MLY@MIT-MC). Not all of their suggestions made it into this proposal however, either because of some technical disagreement or limitations of time, so it shouldn't be seen as contradictory if they criticize aspects of the proposal. Terminology A condition is a kind of object which is created when an exceptional situation arises in order to represent the relevant features of that situation. Once a condition is created, it is common to signal it. When a condition is signalled, a set of handlers are tried in some pre-defined order until one decides to handle the condition or until no more handlers are found. A condition is said to have been handled if a handler performs a non-local transfer of control to exit the signalling process. Although such transfers of control may be done directly using traditional Lisp mechanisms such as catch and throw, block and return, or tagbody and go, the condition system also provides a more structured way to proceed from a condition. Among other things, the use of these structured primitives for proceeding allow better a more integrated relationship between the user program and the interactive debugger. It is not necessary that all conditions be handled. Some conditions are trivial enough that a failure to handle them may be disregarded. Others, which we will call serious conditions must be handled in order to assure correct program behavior. If a serious condition is signalled but no handler is found, the debugger will be entered so that the user may interactively specify how to proceed. Serious conditions which result from incorrect programs or data are called errors. Not all serious conditions are errors, however. Storage conditions are examples of serious conditions that are not errors. For example, the control stack may legitimately overflow without a program being in error. Even though a stack overflow is not necessarily a program error, it is serious enough to warrant entry to the debugger if the condition goes unhandled. 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. Systems with non-hierarchical type systems are allowed to use such non-hierarchical types in a CL-visible way as long as (typep c 'condition) is true for such conditions. No mechanism is currently provided at this time for creating condition types that directly exploit non-hierarchical inheritance. The only CL-provided mechanism for defining new condition types is define-condition. The only CL-provided mechanism for instantiating a condition is make-condition. When a condition object is created, the most common operation to be performed upon it is to signal it (although there may be applications in which this does not happen, or does not happen immediately). When a condition is signalled, the system tries to locate the most appropriate handler for the condition and invoke that handler. Handlers are located according to the following rules: ÿÿïf ÿCheck for locally defined (ie, bound) handlers. ÿÿïf ÿIf no appropriate bound handler is found, check for the default handler first of the signalled type and then of each of its superiors. If an appropriate handler is found, it is called. In some circumstances (to be described later), 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. [An important invariant that KMP doesn't mention is that when a handler is running the "handler binding stack" is popped back to just below the binding that caused that handler to be invoked. This is to avoid infinite recursion in the case that a handler also signals a condition. -AMD] If no bound handler or default handler is found, or if all handlers which were found decline, signal returns the condition which was signalled. When a condition is signalled, handlers are searched for in the dynamic environment of the signaller. Handlers can be established within a dynamic context by use of condition-bind. A handler is a function of one argument, the condition to be handled. The handler may inspect the object (using primitives described in another section) to be sure it is interested in handling the condition. After inspecting the condition, the handler must take one of the following actions: ÿÿïf ÿIt may decline to handle the condition, by simply returning. When this happened, 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, the default action for the given condition will be taken. A default handler may also decline, in which case the condition will go unhandled. ÿÿïf ÿIt may perform some non-local transfer of control using go, return, throw, abort, or invoke-proceed-case. ÿÿïf ÿIt may signal another condition. ÿÿïf ÿIt may invoke the interactive debugger. When a condition is signalled, a facility is available for use by handlers to non-locally transfer control to an outer dynamic contour of the program. The form which creates contours which may be returned to is called proceed-case. The function which transfers control to a proceed-case clause is called invoke-proceed-case. In some cases, it may be useful to report a condition or a proceed case to a user or a log file of some sort. When the printer is invoked on a condition or proceed case while *print-escape* is nil, the report-function for that object is invoked. In particular, this means that an expression like (format t "~A" condition) will invoke condition's report function. Because of this, no special function is provided for invoking the report function of a condition or a proceed case. Program Interface to the Condition System Defining and Creating Conditions define-condition name parent-type [keyword value]* &rest slots [Special Form] Defines a new condition type with the given name, making it a subtype of the given parent-type. Except as otherwise noted, the arguments are not evaluated. The valid keyword/value pairs are: :conc-name symbol-or-string As in 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 followed by a hyphen. :report-function expression expression should be a suitable argument to the function special form. It designates a function of two arguments, a condition and a stream, which prints the condition to the stream when *print-escape* is nil. [It is intended that the :report-function describe the condition in a human-sensible form. This item is somewhat different than a structure's :print-function in that it is only used if *print-escape* is nil. -AMD] :report form A short form of :report-function to cover two common cases. If form is a constant string, this is the same as :report-function (lambda (ignore stream) (write-string form stream)). Otherwise, this is the same as :report-function (lambda (condition *standard-output*) form). In this latter case, the form describes how to print objects of the type being defined. The form should do output to standard output. The condition being printed will be the value of the variable condition. [The second flavor of :report is a form that (possibly) uses the variable condition freely and does output to *standard-output*. For example, :report (format t "~A." (frob-around-with condition)) is the same as :report-function (lambda (condition stream) (format stream "~A." (frob-around-with condition))). -AMD] :handle form An expression to be used as the body of a default handler for this condition type. While executing form, the variable condition will be bound to the condition being handled. [That is, :handle form defines a function (lambda (condition) form) as the default handler for that type. -AMD] It is an error to specify both :report-function and :report in the same define-condition. If neither :report-function nor :report is specified, information about how to print this type of condition will be inherited from the parent-type. [These key/value pairs do not define slots of the condition. You should think of them as defining class variables. -AMD] slots is a list of slot-description, and specifies slots to be used by the given type. In addition to those specified, the slots of the parent-type are also available. A slot-description is exactly the same as for defstruct except that no slot-options are allowed. [Condition objects are immutable, i.e., all of their slots are declared to be :read-only. -AMD] make-condition will accept keywords 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. For example: (define-condition bad-food-color food-lossage :report (format t "The food ~A was ~A." (bad-food-color-food condition) (bad-food-color-color condition)) food color) defines an error of type bad-food-color which inherits from the food-lossage condition type. The new type has slots food and color so that make-condition will accept :food and :color keywords and accessors bad-food-color-food and bad-food-color-color will apply to objects of this type. No functions are provided for directly accessing the report function or the default handler for a condition since it is not believed that this will be very useful in portable code (at least until an object system is better defined). The report function for a condition will be implicitly called any time a condition is printed with *print-escape* being nil. Hence, (princ condition) or (format t "~A" condition) are possible ways of invoking the condition's printer. The default handler will be implicitly called during the signalling process and never has any reason to be manually invoked. Here are some examples of creating conditions. These forms define a condition called machine-error which inherits from error: (define-condition machine-error error :report (format t "There is a problem with ~A." (machine-error-machine-name condition)) machine-name) This defines a new error condition (a subtype of machine-error) for use when machines are not available: (define-condition machine-not-available-error machine-error :report (format t "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: (define-condition my-favorite-machine-not-available-error machine-not-available-error (machine-name "MIT-MC.ARPA")) This gives the machine-name slot a default initialization. Since no :report clause was given, the information supplied in the definition of machine-not-available-error will be used if a condition of this type is printed while *print-escape* is nil. make-condition type &rest slot-initializations [Function] Calls the appropriate constructor function for the given type, passing along the given slot initializations to the constructor, and returning an instantiated condition. The slot-initializations are given in alternating keyword/value pairs. eg, (make-condition 'bad-food-color :food my-food :color my-color) [This function is provided mainly for writing subroutines that manufacture a condition to be signalled. Since all the condition signalling functions can take a type and slot-initializations it is usually easier to call them directly. -AMD] Design Note: If either defstruct or some other type system adopted later by CL ever provides a make-instance primitive, make-condition would no longer be necessary. Signalling Conditions signal datum &rest arguments [Function] Invokes the signal facility on a condition. If the condition is not handled, signal returns the condition object that was signalled. 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). [If the condition is of type serious-condition then signal will behave exactly like error, i.e., it will call debug if the condition isn't handled and never return to its caller. -AMD] error datum &rest arguments [Function] Invokes the signal facility on a condition. If the condition is not handled, (debug condition) is done. [See below. Since debug does not return to its caller, neither will error. The only way out is either by a non-local jump or by invoking a proceed case. -AMD] 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 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). cerror proceed-format-string datum &rest arguments [Function] Invokes the signal facility on a condition. If the condition is not handled, (debug condition) is done. [cerror enables the proceed type proceed, which will simply return the condition being signalled from cerror. -AMD] If datum is a condition, then that condition is used directly. In this case, arguments will be used only with the proceed-format-string and will not be used to initialize datum. If datum 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 proceed-format-string must be a string. Note that if datum is not a string, then the format arguments used by the proceed-format-string will still be the arguments (in the keyword format as specified). In this case, some care may be necessary to set up the proceed-format-string correctly. The format op ~* may be particularly useful in this situation. The value returned by cerror is the condition which was signalled. [This is a change from CLtL: cerror returns nil there. -AMD] warn datum &rest arguments [Function] Invokes the signal facility on a condition. If the condition is not handled, then the text of the warning is output to *error-output* (with possible implementation-specific extras such as moving to a fresh line before and after the display of the warning, or supplying some introductory text that might mention the name of the function which called warn). If *break-on-warnings* is true, then in addition to printing the warning, the debugger is entered. In this case, warn returns only if proceed is done from the debugger. The value returned by warn is the condition which was signalled. 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-warning :format-string datum :format-arguments arguments). [I'm not too clear on what you do if a condition is supplied. Perhaps Vol. 4 of the Symbolics manual will clear things up. -AMD] *break-on-warnings* [Variable] {As described in CLtL} Handling Conditions condition-bind bindings &rest forms [Special Form] Executes body in a dynamic context where the giving local handler bindings are in effect. The bindings must take the form (type handler). type may be the name of a condition type or a list of condition types. handler should evaluate to a function to be used to handle conditions of the given type(s) during execution of the forms. condition-case form &rest cases [Special Form] Executes the given form. Each case has the form (type ([var]) . body). If a condition is signalled (and not handled by an intervening handler) during the execution of the form for which there is an appropriate clauseÿÿï%ÿie, one for which (typep condition 'type) is trueÿÿï%ÿthen control is transferred to the body of the relevant clause, binding var, if present, to the condition which was signalled. If no condition is signalled, then the values resulting from the form are returned by the condition-case. If the condition is not needed, var may be omitted. type may also be a list of types, in which case it will catch conditions of any of the specified types. Examples: (condition-case (/ x y) (division-by-zero () nil)) (condition-case (open *the-file* :direction :input) (file-error (condition) (format t "~&Open failed: ~A~%" condition))) (condition-case (some-user-function) (file-error (condition) condition) (division-by-zero () 0) ((unbound-variable undefined-function) () 'unbound)) ignore-errors &body forms [Macro] Executes its body in a context which handles errors of type error by returning control to this form. If no error is signalled, any return values returned by the last form are returned by ignore-errors. Otherwise, nil is returned. Synonym for (condition-case (progn . forms) (error () nil)). [Previous incarnations of this proposal also defined catch-condition, which was like ignore-errors but allowed you to specify an arbitrary condition type. This may be useful to have around. -AMD] Although they are not described in detail here, it is expected that the macros check-type, assert, etypecase, ctypecase, ecase, ccase, &c. should continue to be supported essentially as described in CLtL. debug condition [Function] Enters the debugger with a given condition. [When the debugger is entered, it will announce the condition by invoking the condition's report function. -AMD] This function will never directly return to its caller. Return can occur only by a special transfer of control, such as to a proceed-case or catch-abort. break datum &rest arguments [Function] Directly enters the debugger without trying to invoke the signal facility. [break enables the proceed type proceed. -AMD] Executing the function proceed while in the debugger will cause a return from the break. 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-break :format-string datum :format-arguments arguments). [This is probably the only time that simple-break is ever used.-AMD] If the break is proceeded, the value returned is the condition that was used. Implementation Note: break could be defined by: (defun break (datum &rest arguments) (proceed-case (debug (cond ((typep datum 'condition) datum) ((symbolp datum) ;roughly, (subtypep datum 'condition) (apply #'make-condition datum arguments)) ((stringp datum) (make-condition 'simple-break :format-string datum :format-arguments arguments)) (t (error "Bad argument to BREAK: ~S" datum)))) (proceed (condition) :test (lambda (ignore) t) :report "Return from BREAK." condition))) See Footnote: {true} Proceeding from Conditions proceed-case form &rest clauses [Special Form] The form is evaluated in a dynamic context where the clauses have special meanings as points to which control may be transferred in the event that a condition is signalled. If form runs to completion and returns any values, all values returned by the form are simply returned by the proceed-case form. If a condition is signalled while form is running, a handler may tranfer control to one of the clauses. [The stack is unwound to that point: cf. condition-case -AMD] If a transfer to a clause 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 proceed-case form. [proceed-case defines a point from which it is possible to proceed and enables ways to proceed. -AMD] A proceed-case clause has the form: (proceed-function-name arglist [keyword value]* [body-form]*) The proceed-function-name may be nil or the name of a defined proceed function. The arglist is a list of variables to be bound during the execution of the body-forms. The first variable will be the condition, and the remaining will be additional data provided by the proceed function. By special exemption, the arglist may be () if you don't care about any of the arguments; otherwise, the argument list must be compatible with the arguments as passed by invoke-proceed-case. If a proceed-function-name was supplied, then the arguments in the arglist need not be optional, since the proceed-function will have taken care of filling in all optional arguments and a fixed number of arguments will always be passed to invoke-proceed-case. [It bothers me to have to separately define a proceed function to be able to use a named proceed case. Seems to me that you should be able to put a name in anyway, and if there happens to be a proceed function around that will collect arguments for you, so much the better. This is especially true since named proceed cases are the only ones that may be invoked non-interactively. -AMD] The valid keyword/value pairs are: :test function A function of one argument, the condition, which must return true for this case to be visible to handlers. The function should be in a form which is acceptable as an argument to the function special form. :condition type Shorthand for the common special case of :test in which the user is doing a test of the given type of condition being signalled to determine its visibility. The following two key/value pairs are equivalent: :condition foo :test (lambda (c) (typep c 'foo)) :report-function exp The exp must be an appropriate argument to the function special form, and should designate a function of two arguments, a proceed case and a stream, which summarizes the action that this proceed case will take. :report form This is a shorthand for two important special cases of :report-function. If form is a constant string, then this is the same as: :report-function (lambda (ignore stream) (write-string form stream)). Otherwise, this is the same as :report-function (lambda (condition *standard-output*) form). In the latter case, form must do output to *standard-output*, summarizing the action that this proceed case will take. [See the explanation under define-condition. -AMD] Only one of :test or :condition may be specified. Only one of :report or :report-function may be specified. If a named proceed function has a default for any of :test or :condition and the proceed case specifies any of :test or :condition, then the information supplied in the proceed case takes precedence. Similarly, if :report or :report-function is specified in the proceed case, then only that information is considered, and any :report or :report-function specified as a default for the named proceed function is not used. If a named proceed function is used but no report information is supplied, the name of the proceed function is used to generate the default help information. It is an error if no named proceed case is used and no report information is provided; implementations are encouraged to flag this error at the earliest convenient time (eg, compilation time). [In plain English, this means that you must always have a way of describing to the user how to proceed. If you don't specify report methods, make sure that the name of the proceed type is something sensible. -AMD] When *print-escape* is nil, the printer will use the report information for a proceed case. E.g., a debugger might announce the action of typing Control-Z by doing: (format t "~&Control-Z: ~A~%" some-proceed-case) which would then display as something like: Control-Z: Return to command level. Examples: (proceed-case (a-random-computation) (new-function (ignore new-function) (setq function new-function))) (proceed-case (a-random-computation) (nil (ignore &optional (new-function (read-typed-object 'function "Function: "))) :report "Use a different function." :condition undefined-function (setq function new-function))) (proceed-case (a-command-loop) (return-from-command-level () :report (format t "Return from command level ~D." level) nil)) (loop (proceed-case (another-random-computation) (proceed ()))) Assuming that new-function is defined as a proceed function with defaults: :report "Use a different function." :condition undefined-function then the first and second examples 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" proceed function names, as in: (when (find-proceed-case 'new-function condition) (new-function condition the-replacement)) then only the first example, and not the second, will have control transferred to its correction clause. Here's a more complete [though contrived -AMD] example: (let ((my-food 'milk) (my-color 'greenish-blue)) (do () ((not (bad-food-color-p food color))) (proceed-case (error 'bad-food-color :food my-food :color my-color) (use-food (ignore new-food) (setf my-food new-food)) (use-color (ignore new-color) (setf my-color new-color)))) ;; We won't get to here until my-food and my-color are compatible. (list my-food my-color)) A handler can then proceed the error in either of two ways. It may correct the color or correct the food. For example: #'(lambda (condition) ... (use-color condition 'white) ...) ;Corrects color or #'(lambda (condition) ... (use-food condition 'cheese) ...) ;Corrects food Here is an example using condition-bind and proceed-case... (condition-bind ((foo-error #'(lambda (condition) (use-value condition 7)))) (proceed-case (error 'foo-error) (use-value (ignore x) (* x x)))) => 49 define-proceed-function name [keyword value]* &rest variables [Special Form] Valid keyword/value pairs are the same as those which are defined for the proceed-case special form. That is, :test, :condition, :report-function, and :report. This form defines a function called name which will proceed an error in a typed way. The proceed function takes a required argument of a condition and optional arguments which are given by the variables specification. The variable condition is bound to the condition object itself so that it will be accessible during the initialization of the optional arguments. [I.e., the parameter list for the proceed function will look like (&optional condition . variables). The only thing that a proceed function really does is collect values to be passed on to a proceed-case clause. -AMD] Each element of variables has the form variable-name or (variable-name initial-value). If initial-value is not supplied, it defaults to nil. [Unlike standard &optional parameters, this does not provide for a supplied-p parameter. Adding this is not particularly useful since there's no way to access it unless it has been proclaimed special. -AMD] For example, here are some possible proceed functions which might be useful in conjunction with the bad-food-color error we used as an example earlier: (define-proceed-function use-food :report "Use another food." (food (read-typed-object 'food "Food to use instead: "))) (define-proceed-function use-color :report "Change the food's color." (color (read-typed-object 'food "Color to make the food: "))) (defun maybe-use-water (condition) ;A sample handler (when (eq (bad-food-color-food condition) 'milk) (use-food condition 'water))) (condition-bind ((bad-food-color #'maybe-use-water)) ...) The condition argument to a proceed function is optional. If not provided, it defaults to nil. This may be useful in the implementation of proceed functions such as abort, which are not normally called with a condition argument. If a named proceed function is invoked in a context in which there is no active proceed case by that name, the proceed function simply returns nil. So, for example, in each of the following pairs of handlers, the first is equivalent to the second and less efficient: #'(lambda (condition) ;OK, but slow (when (find-proceed-case 'use-food condition) (use-food condition 'milk))) #'(lambda (condition) (use-food condition 'milk)) ;Preferred #'(lambda (condition) (cond ((find-proceed-case 'use-food condition) (use-food condition 'chocolate)) ((find-proceed-case 'use-color condition) (use-color condition 'orange)))) #'(lambda (condition) (use-food condition 'chocolate) (use-color condition 'orange)) compute-proceed-cases condition [Function] Uses the dynamic state of the program to compute a list of proceed cases which may be used with the given condition. Each proceed case represents a point in the current dynamic state of the program to which control may be transferred. Implementations are free to implement these objects in whatever manner is most convenient; the objects need have only dynamic extent. The only operations which Common Lisp defines for such objects are proceed-case-name, find-proceed-case, invoke-proceed-case, princ and print, the identification of an object as a proceed case using (typep x 'proceed-case), and standard lisp operations which work for all objects such as eq, eql, describe, etc. The list which results from a call to compute-proceed-cases is ordered so that the innermost (ie, more-recently established) proceed cases are nearer the head of the list. Note, too, that compute-proceed-cases returns all valid proceed cases for condition, even if some of them have the same name as others and therefore would not be found by find-proceed-case. Implementations are permitted, but not required, to return different (ie, non-eq) lists from repeated calls to compute-proceed-cases while in the same lexical/dynamic environment. It is an error to modify the list which is returned by compute-proceed-cases. proceed-case-name proceed-case [Function] Returns the name of the given proceed-case, or nil if it is not named. find-proceed-case name condition [Function] Searches for a proceed case by the given name which is applicable to the given condition in the current dynamic contour. If name is a proceed function name, then the innermost (ie, most recently established) proceed case with that function name that matches the given condition is returned. nil is returned if no such proceed case is found. If name is a proceed case object, then it is simply returned unless it is not currently valid for use. In that case, nil is returned. invoke-proceed-case proceed-case condition &rest values [Function] Transfers control to the given proceed-case, passing the given values. The proceed-case must a proceed case or the name of a proceed function which is valid in the current dynamic context. If the argument is not valid, an error will be signalled. [What type? -AMD] This operation is used primarily as a sub-primitive for implementing named proceed functions, but may be necessary when writing certain kinds of portable, interactive debuggers. See Footnote: {invoke-proceed-case vs Named Proceed Functions} Note: In contrast to the way that Zetalisp has defined abort as a kind of condition to be handled, we define abort as a manner of proceeding a condition rather than as a condition type to be handled. [This implies that the top-level form of a process should have (catch-abort ... ) wrapped around it. -AMD] catch-abort print-form &body forms [Macro] Sets up a proceed-case context for the proceed function abort. If no abort is done while executing forms and they return normally, all values returned by the last form in forms are returned. If an abort transfers control to this catch-abort, two values are returned: nil and the condition which was given to abort (or nil if none was given). catch-abort could be defined by: (defmacro catch-abort (print-form &body forms) `(proceed-case (progn ,@forms) (abort (condition) :report ,print-form :test (lambda (ignore) t) (values nil condition)))) Example: (defun read-eval-print-loop (level) (catch-abort (format t "Exit command level ~D." level) (loop (catch-abort (format t "Return to command level ~D." level) (print (eval (read))))))) If while executing (read-eval-print-loop 1) the user typed (+ 'a 3) and landed in the debugger, he might expect to see something like: [Well, only if you're in the Symbolics debugger. -AMD] The argument, A, to the function + was of the wrong type. The function expected a number. [0] Lisp Toplevel [1] Exit command level 1 [2] Return to command level 1 [3] Supply a new value for this argument Selection: abort &optional condition [Function] Transfers control to the innermost (dynamic) catch-abort form, causing it to return nil immediately. [If there is no proceed case for abort currently enabled, will it simply return? Again, there is an implication that the top-level loop has (catch-abort ...) wrapped around it. -AMD] It is not usually useful to specify a condition. This is because the default test for abort unconditionally returns true and all catch-abort forms are therefore likely to be visible. The only such forms which might not be visible are those which override the default test. In that rare case, specifying a condition may make a difference. abort could be defined by: (define-proceed-function abort :report "Abort." :test (lambda (ignore) t)) Predefined Types Implementation Note: The types proceed-case, condition, etc. are new ``first class'' types in the sense that they must be distinguishable from any other CL types of which they are subtypes. This is necessary because princ is specified to treat them specially. defstruct is one way to implement these types. proceed-case [Type] This is the data type used to represent a proceed case. The condition type hierarchy looks like this: condition | +-------------+----------------+ | | | simple-condition warning serious-condition | | simple-warning | | +----------------+---------------+-------+ | | | simple-break storage-condition error | | +--------+--------+ | | | | stack-overflow storage-exhausted | ! +---------------+---------+-------+ | | | simple-error arithmetic-error control-error The types which are non-terminals in the above tree (ie, condition, warning, serious-condition, 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 above, below, or between the types shown in this tree as long as the indicated subtype relationships are not violated. See Footnote: {typep vs condition-typep} 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] Any condition, whether error or non-error, which should enter the debugger when signalled but not handled should inherit from this type. This is a subtype of condition. Note: ignore-errors will ignore conditions of type error, not of type serious-condition. Conditions which are serious conditions but not errors are typically those that may require more sophisticated handling than simply being ignored. For example, ignore-errors will not ignore a storage-condition, which is a serious condition but is not generally a program error. Compatibility Note: serious-condition is similar to Zetalisp's dbg:debugger-condition. error [Type] All types of error conditions inherit from this condition. This is a subtype of condition. The default condition type for signal is simple-condition, for break is simple-break, and 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. 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. simple-break [Type] Conditions used by break when given a format string as a first argument are of this type. This is a subtype of serious-condition. The init keywords :format-string and :format-arguments are supported. Note: This type of condition is not generally signalled, but objects of this type are generally used as a reference while in a break to compute the available proceed types. See the description of break for more details. 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. storage-condition [Type] Conditions which relate to memory overflow conditions should inherit from this type. This is a subtype of serious-condition. stack-overflow [Type] Conditions which relate to stack overflow should inherit from this type. This is a subtype of storage-condition. storage-exhausted [Type] Conditions which relate to any kind of GC overflow should inherit from this type. This is a subtype of storage-condition. control-error [Type] Errors in the transfer of control in a program should inherit from this type. This is a subtype of error. illegal-throw [Type] The error which results when throw is given a tag which is not active should inherit from this. This is a subtype of control-error. The function illegal-throw-tag will access the offending tag. illegal-go [Type] The error which results when go is given a tag which is no longer available should inherit from this. This is a subtype of control-error. The function illegal-go-tag will access the offending tag. illegal-return [Type] The error which results when return-from is given a block name which is no longer accessible should inherit from this. This is a subtype of control-error. The function illegal-return-tag will access the offending block name. stream-error [Type] Errors which occur during input from or output to a stream should inherit from this type. This is a subtype of error. The function stream-error-stream will access the offending stream. read-error [Type] Errors which occur during an input operation on a stream should inherit from this type. This is a subtype of 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 read-error. cell-error [Type] Errors which occur while accessing a location should inherit from this type. This is a subtype of error. The function cell-error-name will access the name of the offending cell. 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. arithmetic-error [Type] Errors which occur while doing arithmetic type operations should inherit from this type. This is a subtype of error. The functions arithmetic-error-operation and arithmetic-error-operands will access the offending operation and arguments, respectively. [This list is incomplete. I believe that there should be a well-defined condition that is signalled for all of the places where CLtL says "signals an error." For instance, parse-namestring specifies that "an error is signalled if the hosts don't match." The only condition defined so far that seems appropriate is error (maybe simple-error), but this is far too vague for this particular problem. It's certainly not an instance of stream-error, for example. A full specification of the error system should include this mapping between condition types and things that signal errors. You could probably get away with having all of them call error and cerror with format strings, but then why bother defing this elaborate type hierarchy? -AMD] Footnotes {invoke-proceed-case vs Named Proceed Functions} Some readers may wonder why there are both proceed functions and an invoke-proceed-case primitive. The reason for named proceed functions is to formalize interfaces, while the reason for invoke-proceed-case is to provide flexible and portable access to the mechanism. invoke-proceed-case has the following two purposes: ÿÿïf ÿAs a sub-primitive, to implement named proceed functions. ÿÿïf ÿTo invoke anonymous proceed cases. (eg, in an interactive debugger). Writers of portable code are encouraged to prefer the use of named proceed functions over the use of invoke-proceed-case in any situation where it is feasible. Nevertheless, it should be recognized that some useful, portable programs (particularly, interactive debuggers) can only be written using invoke-proceed-case. [It appears then that one can have a named proceed case without there being a corresponding proceed functionÿÿï%ÿyou would use invoke-proceed-case to get to it. Wouldn't it be nice if CL had dynamically bound functions so that you could call proceed cases directly? I'm all for formalizing interfaces, but the current scheme for defining proceed functions defines them globally without reference to a condition type. This wastes names and is a possible source of name collisionsÿÿï%I can easily imagine popular names for proceed functions that would want to collect different parameters when proceedÿing from different conditions; &optional is not powerful enough for this. -AMD] {true} The function #'(lambda (ignore) t) is going to be a common one to use in conjunction with this system. Hopefully we can agree that a function #'true could be established to mean #'(lambda (&rest ignore) t) to help reduce visual clutter. {typep vs condition-typep} An earlier version of this proposal used a function called condition-typep rather than using real typep. Over MLY's objections, I finally chose to use typep for the following reasons: ÿÿïf ÿWe need a type system which can deal with these issues. Reducing the need for typep to address these issues might reduce the perceived need for us to converge on a better type system. ÿÿïf ÿIf we later adopt a better type system, it is possible that any condition-typep introduced now would have different inheritance rules. It would be undue burden on the user to have to remember the rules for more than one kind of type system. ÿÿïf ÿIntroducing a condition-typep sets up a precedent for the introduction of other xxx-typep operations. It would be too easy for such type systems to vary in subtle ways. It is better to simply insist that typep be good enough as is, or be improved until it is good enough. {:report vs :report-function} Originally, I had :report-function, :report-string, and :report. Moon objected to a bunch of keywords which did similar things. He wanted just one keyword. He also objected to the new term "report", feeling that "print" was adequate. I circulated a draft proposal with just a :print option that did what the :report option does in this proposal, but with no analog for the :report-function. I wasn't completely happy with the absence of :report-function but figured I'd live with it if it saved a lot of fuss. MLY objected to the absence of :report-function. So I backed up a bit and decided that there were probably really two camps and that we should just serve both. MLY also wondered why this option controlled typeout only in the case where *print-escape* was nil. So I backed up and introduced the term "report" instead of print so that people wouldn't wonder why the :print option didn't control the entire behavior of prin1 and print. The final result of this was to revert back to :report and :report-function, but to omit :report-string as being unnecessary. I hope that no one will argue a lot about this ÿÿï%ÿ it will serve most people's needs and I think is the sort of thing we could go around and around about to no good end. References Some background information about motivation for decisions in this proposal may be found in my paper ``Exceptional Situations in Lisp,'' which is available as A.I. Working Paper 268 from the MIT AI Lab publications office (545 Technology Square, Cambridge, MA 02139). 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. (LIST ((PAGE NIL (PAPERSIZE Letter FOLIOINFO (ARABIC)) (0 0 612 792) ((TEXT NIL NIL (48 72 516 648) NIL))) (PAGE NIL (PAPERSIZE Letter FOLIOINFO (ARABIC) STARTINGPAGE# NIL) (0 0 612 792) ((TEXT NIL NIL (48 72 516 648) NIL))) (PAGE NIL (PAPERSIZE Letter FOLIOINFO (ARABIC) STARTINGPAGE# NIL) (0 0 612 792) ((TEXT NIL NIL (48 72 516 648) NIL))))) .°.ø1$$ø.ø.ø.ø.00@ø.<<ø+00@.ø.00@ø.ø(  TIMESROMAN TIMESROMAN  TIMESROMAN TERMINAL TIMESROMANTERMINAL TERMINAL  TIMESROMAN  TIMESROMAN ?1(DEFAULTFONT 1 (GACHA 10) (GACHA 8) (TERMINAL 8))  G  R     Š  - - : ³  b      > À  ¤ Ó  K p  _     y    C B   \ h  ¹  !   ‰  q Ï   ^ ,  ¥      «  :       #  *  Ú ,    # †  e     ˆ ) !.   , # >       N P     & ‚   e      +  3! <Æ  - 5_   c  /  ,       `  x   e   * N     =   . * O    (       %  é  c    8  }  U    &24 1 +  <75 9  +  V   ) A ;       ©   3 > ï   ?       M 2   W     E (   = S  E    M  -U   X     B (   = O ,   M  >  E  $    B (   = O    8  ^  /   '       w Ø  [  2 '   X     E (   = Q  €         ^    C  l        “     J u  "   d   3 4G %%7     < z   / 5 bO      H     , p  ~      K  4    X    E (   = O %    B   %  2 H < " 0 G A 3 /       ¨ g ) B )    X    =    ,   Ü    Õ  ‚       ¶    ) 0 m "   ( œ   7  1 D! =, ;        5  '  T  U  D  _ Õ    ‹ 1 , $  %E %T("#  = @  1  (" ï [j     1/H<AE w  M N     MJ&     7       $ ™  | B!\       !   )   d &  >< F@ 5Q :  M H ;  y  $O= TUU     ; "   ?     : B     & q   % X   N  g            ) L   £ /   n  $          Á  3 1 V ?     "     C    &      / E   $9 B"   D 6  : )    -   !f  & ' & ¥     K   ¢ ' &     8   !  "//7///77977777??E 9       o  ú       !      J     ž      ¢  E        P               W        W         W     Ä        W       j     ^     g      c       S        \       d  '     o  #     m           b  -    ƒ     …     n   B  ä       D   X >  !  <  G  e ²  OoïQ     l        ;  0   P e  B ¢   3 s ?           ©  *  : 0 9   ½  = ( .    /   F  z      û  ´¡=zº