The first section of this chapter explains how programs can handle errors, by means of condition handlers. It also explains how a program can signal an error if it detects something it doesn't like. The second explains how users can handle errors, by means of an interactive debugger; that is, it explains how to recover if you do something wrong. For a new user of the Lisp Machine, the second section is probably much more useful; you may want to skip the first. The remaining sections describe some other debugging facilities. Anyone who is going to be writing programs for the Lisp machine should familiarize himself with these. The trace facility provides the ability to perform certain actions at the time a function is called or at the time it returns. The actions may be simple typeout, or more sophisticated debugging functions. The step facility allows the evaluation of a form to be intercepted at every step so that the user may examine just what is happening throughout the execution of the form. The MAR facility provides the ability to cause a trap on any memory reference to a word (or a set of words) in memory. If something is getting clobbered by agents unknown, this can help track down the source of the clobberage.
Programmers often want to control what action is taken by their programs when errors or other exceptional situations occur. Usually different situations are handled in different ways, and in order to express what kind of handling each situation should have, each situation must have an associated name. In Lisp Machine Lisp there is the concept of a condition . Every condition has a name, which is a symbol. When an unusual situation occurs, some condition is signalled , and a handler for that condition is invoked. When a condition is signalled, the system (essentially) searches up the stack of nested function invocations looking for a handler established to handle that condition. The handler is a function which gets called to deal with the condition. The condition mechanism itself is just a convenient way for finding an appropriate handler function given the name of an exceptional situation. On top of this is built the error-condition system, which defines what arguments are passed to a handler function and what is done with the values returned by a handler function. Almost all current use of the condition mechanism is for errors, but the user may find other uses for the underlying mechanism. .page
The search for an appropriate handler is done by the function signal :
Condition handlers are established through the condition-bind special form:
(condition-bind ((cond-1 hand-1 ) (cond-2 hand-2 ) ...) body )Each cond-n is either the name of a condition, or a list of names of conditions, or nil . If it is nil , a handler is set up for all conditions (this does not mean that the handler really has to handle all conditions, but it will be offered the chance to do so, and can return nil for conditions which it is not interested in). Each hand-n is a form which is evaluated to produce a handler function. The handlers are established sequentially such that the cond-1 handler would be looked at first.
Example: (condition-bind ((:wrong-type-argument 'my-wta-handler) ((lossage-1 lossage-2) lossage-handler)) (princ "Hello there.") (= t 69))This first sets up the function my-wta-handler to handle the :wrong-type-argument condition. Then, it sets up the binding of the symbol lossage-handler to handle both the lossage-1 and lossage-2 conditions. With these handlers set up, it prints out a message and then runs headlong into a wrong-type-argument error by calling the function = with an argument which is not a number. The condition handler my-wta-handler will be given a chance to handle the error. condition-bind makes use of ordinary variable binding, so that if the condition-bind form is thrown through, the handlers will be disestablished. This also means that condition handlers are established only within the current stack.
;;; This function handles the :wrong-type-argument condition, ;;; which takes two defined parameters: a symbol indicating ;;; the correct type, and the bad value. (defun sample-wta-handler (condition control-string proceedable-flag restartable-flag function correct-type bad-value &rest rest) (prog () (format error-output "~%There was an error in ~S~%" function) (lexpr-funcall (function format) control-string correct-type bad-value rest) (cond ((and proceedable-flag (yes-or-no-p query-io "Do you want use nil instead?")) (return 'return nil)) (t (return nil))))) ;don't handleIf an error condition reaches the error handler, the control-C command may be used to continue from it. If the condition name has a si:eh-proceed property, that property is called as a function with two arguments, the stack and the "ete" (an internal error-handler data structure). Usually it will ignore these arguments. If this function returns, its value will be returned from the ferror or cerror that signalled the condition. If no such property exists, the error-handler asks the user for a form, evaluates it, and causes ferror or cerror to return that value. Putting such a property on can be used to change the prompt for this form, avoid asking the user, or change things in more far-reaching ways.
Some error conditions are signalled by the Lisp system when it detects that something has gone wrong. Lisp programs can also signal errors, by using any of the functions ferror , cerror , or error . ferror is the most commonly used of these. cerror is used if the signaller of the error wishes to make the error be proceedable or restartable , or both. error is provided for Maclisp compatibility. A ferror or cerror that doesn't have any particular condition to signal should use nil as the condition name. The only kind of handler that will be invoked by the signaller in this case is the kind that handles all conditions, such as is set up by
(condition-bind ((nil something ) ...) ...)In practice, the nil condition is used a great deal.
Examples: (cond ((> sz 60) (ferror nil "The size, ~S, was greater then the maximum" sz)) (t (foo sz))) (defun func (a b) (cond ((and (> a 3) (not (symbolp b))) (ferror ':wrong-type-argument "The name, ~1G~S, must be a symbol" 'symbolp b)) (t (func-internal a b))))
Example: (do () ((symbolp a)) ; Do this stuff until a becomes a symbol. (setq a (cerror t nil ':wrong-type-argument "The argument ~2G~A was ~1G~S, which is not ~3G~A" 'symbolp a 'a "a symbol")))Note: the form in this example is so useful that there is a standard special form to do it, called check-arg (see LINK:(check-arg-fun)).
(cerror (not (null interrupt )) nil (or (get interrupt 'si:condition-name) interrupt ) (cond ((missing object ) ;If no object given "~*~a") (t "~s ~a")) object message )Here is what that means in English: first of all, the condition to be signalled is nil if interrupt is nil . If there is some condition whose meaning is close to that of one of the Maclisp user interrupt channels, the name of that channel has an si:condition-name property, and the value of that property is the name of the condition to signal. Otherwise, interrupt is the name of the condition to signal; probably there will be no handler and the debugger will be entered. If interrupt is specified, the error will be proceedable. The error will not be restartable. The format control string and the arguments are chosen so that the right error message gets printed, and the handler is passed everything there is to pass.
(error-restart form-1 form-2 ... )The forms of the body are evaluated sequentially. If an error occurs within the evaluation of the body and is restarted (by a condition handler or the debugger), the evaluation resumes at the beginning of the error-restart 's body.
Example: (error-restart (setq a (* b d)) (cond ((> a maxtemp) (cerror nil t 'overheat "The frammistat will overheat by ~D. degrees!" (- a maxtemp)))) (setq q (cons a a)))If the cerror happens, and the handler invoked (or the debugger) restarts the error, then evaluation will continue with the (setq a (* b d)) , and the condition (> a maxtemp) will get checked again.
error-restart is implemented as a macro that expands into: (prog () loop (*catch 'error-restart (return (progn form-1 form-2 ... ))) (go loop))
(check-arg foo stringp "a string")foo is the name of an argument whose value should be a string. stringp is a predicate of one argument, which returns t if the argument is a string. "a string" is an English description of the correct type for the variable. The general form of check-arg is
(check-arg var-name predicate description type-symbol )var-name is the name of the variable whose value is of the wrong type. If the error is proceeded this variable will be setq 'ed to a replacement value. predicate is a test for whether the variable is of the correct type. It can be either a symbol whose function definition takes one argument and returns non-nil if the type is correct, or it can be a non-atomic form which is evaluated to check the type, and presumably contains a reference to the variable var-name . description is a string which expresses predicate in English, to be used in error messages. type-symbol is a symbol which is used by condition handlers to determine what type of argument was expected. It may be omitted if it is to be the same as predicate , which must be a symbol in that case. The use of the type-symbol is not really well-defined yet, but the intention is that if it is numberp (for example), the condition handlers can tell that a number was needed, and might try to convert the actual supplied value to a number and proceed. [We need to establish a conventional way of "registering" the type-symbols to be used for various expected types. It might as well be in the form of a table right here.] The predicate is usually a symbol such as fixp , stringp , listp , or closurep , but when there isn't any convenient predefined predicate, or when the condition is complex, it can be a form. In this case you should supply a type-symbol which encodes the type. For example:
(check-arg a (and (numberp a) (≤ a 10.) (> a 0.)) "a number from one to ten" one-to-ten)If this error got to the debugger, the message
The argument a was 17, which is not a number from one to ten.would be printed. In general, what constitutes a valid argument is specified in three ways in a check-arg . description is human-understandable, type-symbol is program-understandable, and predicate is executable. It is up to the user to ensure that these three specifications agree. check-arg uses predicate to determine whether the value of the variable is of the correct type. If it is not, check-arg signals the :wrong-type-argument condition, with four parameters. First, type-symbol if it was supplied, or else predicate if it was atomic, or else nil . Second, the bad value. Third, the name of the argument (var-name ). Fourth, a string describing the proper type (description ). If the error is proceeded, the variable is set to the value returned, and check-arg starts over, checking the type again. Note that only the first two of these parameters are defined for the :wrong-type-argument condition, and so :wrong-type-argument handlers should only depend on the meaning of these two.
:wrong-type-argument type-name value | ||
value is the offending argument, and type-name is a symbol for what type is required. Often, type-name is a predicate which returns non-nil if applied to an acceptable value. If the error is proceeded, the value returned by the handler should be a new value for the argument to be used instead of the one which was of the wrong type. | ||
:inconsistent-arguments list-of-inconsistent-argument-values | ||
These arguments were inconsistent with each other, but the fault does not belong to any particular one of them. This is a catch-all, and it would be good to identify subcases in which a more specific categorization can be made. If the error is proceeded, the value returned by the handler will be returned by the function whose arguments were inconsistent. | ||
:wrong-number-of-arguments function number-of-args-supplied list-of-args-supplied | ||
function was invoked with the wrong number of arguments. The elements of list-of-args-supplied have already been evaluated. If the error is proceeded, the value returned should be a value to be returned by function . | ||
:invalid-function function-name | ||
The name had a function definition but it was no good for calling. You can proceed, supplying a value to return as the value of the call to the function. | ||
:invalid-form form | ||
The so-called form was not a meaningful form for eval . Probably it was of a bad data type. If the error is proceeded, the value returned should be a new form; eval will use it instead. | ||
:undefined-function function-name | ||
The symbol function-name was not defined as a function. If the error is proceeded, then the symbol will be defined to the function returned, and that function will be used to continue execution. | ||
:unbound-variable variable-name | ||
The symbol variable-name had no value. If the error is proceeded, then the symbol will be set to the value returned by the handler, and that value will be used to continue execution. |
When an error condition is signalled and no handlers decide to handle the error, an interactive debugger is entered to allow the user to look around and see what went wrong, and to help him continue the program or abort it. This section describes how to use the debugger. The user interface described herein is not thought too well of, and we hope to redesign it sometime soon.
>>TRAP 5543 (TRANS-TRAP) The symbol FOOBAR is unbound. While in the function *EVAL ← SI:LISP-TOP-LEVEL1The first line of this error message indicates entry to the debugger and contains some mysterious internal microcode information: the micro program address, the microcode trap name and parameters, and a microcode backtrace. Users can ignore this line in most cases. The second line contains a description of the error in English. The third line indicates where the error happened by printing a very abbreviated "backtrace" of the stack (see below); in the example, it is saying that the error was signalled inside the function *eval , which was called by si:lisp-top-level1 . Here is an example of an error from Lisp code:
>>ERROR: The argument X was 1, which is not a symbol, While in the function { FERROR ← } FOO ← *EVALHere the first line contains the English description of the error message, and the second line contains the abbreviated backtrace. The backtrace indicates that the function which actually entered the error handler was ferror , but that function is enclosed in braces because it is not very important; the useful information here is that the function foo is what called ferror and thus signalled the error. There is not any good way to manually get into the debugger; the interface will someday be fixed so that you can enter it at any time if you want to use its facilities to examine the state of the Lisp environment and so on. In the meantime, just type an unbound symbol at Lisp top level.
→At this point, you may either type in a Lisp expression, or type a command (a Control or Meta character is interpreted as a command, whereas a normal character is interpreted as the first character of an expression). If you type a Lisp expression, it will be interpreted as a Lisp form, and will be evaluated in the context of the function which got the error. (That is, all bindings which were in effect at the time of the error will be in effect when your form is evaluated.) The result of the evaluation will be printed, and the debugger will prompt again with an arrow. If, during the typing of the form, you change your mind and want to get back to the debugger's command level, type a Control-Z; the debugger will respond with an arrow prompt. In fact, at any time that typein is expected from you, you may type a Control-Z to flush what you are doing and get back to command level. This read-eval-print loop maintains the values of + , * , and - SAIL just as the top-level one does. Various debugger commands ask for Lisp objects, such as an object to return, or the name of a catch-tag. Whenever it tries to get a Lisp object from you, it expects you to type in a form ; it will evaluate what you type in. This provides greater generality, since there are objects to which you might want to refer that cannot be typed in (such as arrays). If the form you type is non-trivial (not just a constant form), the debugger will show you the result of the evaluation, and ask you if it is what you intended. It expects a Y or N answer (see the function y-or-n-p , LINK:(y-or-n-p-fun)), and if you answer negatively it will ask you for another form. To quit out of the command, just type Control-Z.
BAZ ← BAR ← FOO ← *SI:EVAL ← SI:LISP-TOP-LEVEL1 ← SI:LISP-TOP-LEVELThe arrows indicate the direction of calling. The Meta-B command prints a more extensive backtrace, indicating the names of the arguments to the functions and their current values, and also the saved address at which the function was executing (in case you want to look at the code generated by the compiler); for the example above it might look like:
FOO: (P.C. = 23) Arg 0 (X): 13 Arg 1 (Y): 1 BAR: (P.C. = 120) Arg 0 (ADDEND): 13and so on. This means that foo was executing at instruction 23, and was called with two arguments, whose names (in the Lisp source code) are x and y . The current values of x and y are 13 and 1 respectively. The debugger knows about a "current stack frame", and there are several commands which use it. The initially "current" stack frame is the one which signalled the error; either the one which got the microcode error, or the one which called ferror or error . The command Control-L (or Form) clears the screen, retypes the error message that was initially printed when the debugger was entered, and then prints out a description of the current frame, in the format used by Meta-B. The Control-N command moves "down" to the "next" frame (that is, it changes the current frame to be the frame which called it), and prints out the frame in this same format. Control-P moves "up" to the "previous" frame (the one which this one called), and prints out the frame in the same format. Meta-< moves to the top of the stack, and Meta-> to the bottom; both print out the new current frame. Control-S asks you for a string, and searches the stack for a frame whose executing function's name contains that string. That frame becomes current and is printed out. These commands are easy to remember since they are analogous to editor commands. Meta-L prints out the current frame in "full screen" format, which shows the arguments and their values, the local variables and their values, and the machine code with an arrow pointing to the next instruction to be executed. Meta-N moves to the next frame and prints it out in full-screen format, and Meta-P moves to the previous frame and prints it out in full-screen format. Meta-S is like Control-S but does a full-screen display. Control-A prints out the argument list for the function of the current frame, as would be returned by the function arglist (see LINK:(arglist-fun)). Control-R is used to return a value from the current frame; the frame that called that frame continues running as if the function of the current frame had returned. This command prompts you for a form, which it will evaluate; it returns the resulting value, possibly after confirming it with you. Meta-R is used to return multiple values from the current frame, but it is not currently implemented. The Control-T command does a throw to a given tag with a given value; you are prompted for the tag and the value. Commands such as Control-N and meta-N, which are meaningful to repeat, take a prefix numeric argument and repeat that many types. The numeric argument is typed by using Control- or Meta- and the number keys, as in the editor. Control-Meta-A takes a numeric argument n , and prints out the value of the n th argument of the current frame. It leaves * set to the value of the argument, so that you can use the Lisp read-eval-print loop to examine it. It also leaves + set to a locative pointing to the argument on the stack, so that you can change that argument (by calling rplaca or rplacd on the locative). Control-Meta-L is similar, but refers to the n th local variable of the frame.
Control-A | Print argument list of function in current frame. |
Control-Meta-A | Examine or change the n th argument of the current frame. |
Control-B | Print brief backtrace. |
Meta-B | Print longer backtrace. |
Control-C | Attempt to continue. |
Meta-C | Attempt to restart. |
Control-G | Quit to command level. |
Control-L | Redisplay error message and current frame. |
Meta-L | Full-screen typeout of current frame. |
Control-N | Move to next frame. With argument, move down n frames. |
Meta-N | Move to next frame with full-screen typeout. With argument, move down n frames. |
Control-P | Move to previous frame. With argument, move up n frames. |
Meta-P | Move to previous frame with full-screen typeout. With argument, move up n frames. |
Control-R | Return a value from the current frame. |
Meta-R | Return several values from the current frame. (doesn't work) |
Control-S | Search for a frame containing a specified function. |
Meta-S | Same as control-S but does a full display. |
Control-T | Throw a value to a tag. |
Control-Z | Throw back to Lisp top level. |
? or Help | Print a help message. |
Meta-< | Go to top of stack. |
Meta-> | Go to bottom of stack. |
Form | Same as Control-L. |
Line | Move to next frame. With argument, move down n frames. Same as Control-N. |
Return | Move to previous frame. With argument, move up n frames. Same as control-P. |