SECTION 15 1 DEBUGGING - THE BREAK PACKAGE 15.1 Debugging Facilities Debugging a collection of LISP functions involves isolating problems within particular functions and/or determining when and where incorrect data are being generated and transmitted. In the INTERLISP system, there are three facilities which allow the user to (temporarily) modify selected function definitions so that he can follow the flow of control in his programs, and obtain this debugging information. These three facilities together are called the break package. All three redefine functions in terms of a system function, break1 described below. Break modifies the definition of its argument, a function fn, so that if a break condition (defined by the user) is satisfied, the process is halted temporarily on a call to fn. The user can then interrogate the state of the machine, perform any computation, and continue or return from the call. Trace modifies a definition of a function fn so that whenever fn is called, its arguments (or some other values specified by the user) are printed. When the value of fn is computed it is printed also. (trace is a special case of break). Breakin allows the user to insert a breakpoint inside an expression defining a function. When the breakpoint is reached and if a break condition (defined by the user) is satisfied, a temporary halt occurs and the user can again investigate the state of the computation. The following two examples illustrate these facilities. In the first example, the user traces the function factorial. trace redefines factorial so that it calls break1 in such a way that it prints some information, in this case the arguments and value of factorial, and then goes on with the computation. When an error occurs on the fifth recursion, break1 reverts to interactive mode, and a full break occurs. The situation is then the same as though the user had originally performed BREAK(FACTORIAL) instead of TRACE(FACTORIAL), and the user can evaluate various INTERLISP forms and direct the course of the computation. In this case, the user examines the variable n, and ------------------------------------------------------------------------ 1 The break package was written by W. Teitelman. 15.1 instructs break1 to return 1 as the value of this cell to factorial. The rest of the tracing proceeds without incident. The user would then presumably edit factorial to change L to 1. In the second example, the user has constructed a non-recursive definition of factorial. He uses breakin to insert a call to break1 just after the PROG label LOOP. This break is to occur only on the last two iterations, i.e., when n is less than 2. When the break occurs, the user looks at the value of n. mistakenly typing NN. However, the break is maintained and no damage is done. After examining n and m the user allows the computation to continue by typing OK. A second break occurs after the next iteration, this time with N=0. When this break is released, the function factorial returns its value of 120. _PP FACTORIAL (FACTORIAL [LAMBDA (N) (COND ((ZEROP N L) (T (ITIMES N (FACTORIAL (SUB1 N]) FACTORIAL _TRACE(FACTORIAL) (FACTORIAL) _FACTORIAL(4) FACTORIAL: N = 4 FACTORIAL: N = 3 FACTORIAL: N = 2 FACTORIAL: N = 1 FACTORIAL: N = 0 U.B.A. L (FACTORIAL BROKEN) :N 0 :RETURN 1 FACTORIAL = 1 FACTORIAL = 1 FACTORIAL = 2 FACTORIAL = 6 FACTORIAL = 24 24 _ 15.2 _PP FACTORIAL (FACTORIAL [LAMBDA (N) (PROG ((M 1)) LOOP(COND ((ZEROP N) (RETURN M))) (SETQ M (ITIMES M N)) (SETQ N (SUB1 N)) (GO LOOP]) FACTORIAL _BREAKIN(FACTORIAL (AFTER LOOP) (ILESSP N 2] SEARCHING... FACTORIAL _FACTORIAL(5) ((FACTORIAL) BROKEN) :NN U.B.A. NN (FACTORIAL BROKEN AFTER LOOP) :N 1 :M 120 :OK (FACTORIAL) ((FACTORIAL) BROKEN) :N 0 :OK (FACTORIAL) 120 _ 15.2 Break1 The basic function of the break package is break1. Whenever INTERLISP types a message of the form (- BROKEN) followed by ":" the user is then "talking to" break1, and we say he is "in a break." break1 allows the user to interrogate the state of the world and affect the course of the computation. It uses the prompt character ":" to indicate it is ready to accept input(s) for evaluation, in the same way as evalqt uses "_". The user may type in an expression for evaluation as with evalqt, and the value will be printed out, followed by another :. Or the user can type in one of the commands specifically recognized by break1 described below. Since break1 puts all of the power of INTERLISP at the user's command, he can do anything he can do at evalqt. For example, he can insert new breaks on subordinate functions simply by typing: (BREAK fn1 fn2 ...) or he can remove old breaks and traces if too much information is being supplied: 15.3 (UNBREAK fn3 fn4 ...) He can edit functions, including the one currently broken: EDITF(fn) For example, the user might evaluate an expression, see that the value was incorrect, call the editor, change the function, and evaluate the expression again, all without leaving the break. Similarly, the user can prettyprint functions, define new functions or redefine old ones, load a file, compile functions, time a computation, etc. In short, anything that he can do at the top level can be done while inside of the break. In addition the user can examine the pushdown list, via the functions described in Section 12, and even force a return back to some higher function via the function retfrom or reteval. It is important to emphasize that once a break occurs, the user is in complete control of the flow of the computation, and the computation will not proceed without specific instruction from him. If the user types in an expression whose evaluation causes an error, the break is 2 maintained. Similarly if the user aborts a computation initiated from within the break, the break is maintained. Only if the user gives one of the commands that exits from the break, or evaluates a form which does a retfrom or reteval back out of break1, will the computation 3 continue. Note that break1 is just another INTERLISP function, not a special system feature like the interpreter or the garbage collector. It has arguments which are explained later, and returns a value, the same as cons or cond or prog or any other function. The value returned by break1 is called "the value of the break." The user can specify this value explicitly by using the RETURN command described below. But in most cases, the value of a is given implicitly, via a GO or OK command, and is the result of evaluating "the break expression," brkexp, which is one of the arguments to break1. The break expression is an expression equivalent to the computation that would have taken place had no break occurred. For example, if the user breaks on the function FOO, the break expression is the body of the definition of FOO. When the user types OK or GO, the body of FOO is evaluated, and its value returned as the value of the break, i.e., to whatever function called FOO. The effect is the same as though no break had occurred. In other words, one can think of break1 as a fancy eval, ------------------------------------------------------------------------ 2 By typing control-E, see Section 16. 3 Except that break1 does not "turn off" control-D, i.e., a control-D will force an immediate return back to the top level. 15.4 which permits interaction before and after evaluation. The break expression then corresponds to the argument to eval. Break Commands GO Releases the break and allows the computation to proceed. break1 evaluates brkexp, its first argument, prints the value of the break. brkexp is set up by the function that created the call to break1. For break or trace, brkexp is equivalent to the body of the definition of the broken function. For breakin, using BEFORE or AFTER, brkexp is NIL. For breakin AROUND, brkexp is the indicated expression. See breakin, page 15.17. OK Same as GO except the value of brkexp is not printed. EVAL Same as GO or OK except that the break is maintained after the evaluation. The user can then interrogate the value of the break which is bound on the variable !value, and continue with the break. Typing GO or OK following EVAL will not cause reevaluation but another EVAL will. EVAL is a useful command when the user is not sure whether or not the break will produce the correct value and wishes to be able to do something about it if it is wrong. RETURN form The value of the indicated computation is returned or as the value of the break. RETURN fn[args] For example, one might use the EVAL command and follow this with RETURN (REVERSE !VALUE). ^ Calls error! and aborts the break. i.e., makes it "go away" without returning a value. This is a useful way to unwind to a higher level break. All other errors, including those encountered while executing the GO, OK, EVAL, and RETURN commands, maintain the break. !EVAL function is first unbroken, then the break expression is evaluated, and then the function is rebroken. Very useful for dealing with recursive functions. !OK Function is first unbroken, evaluated, rebroken, and then exited, i.e., !OK is equivalent to !EVAL followed by OK. 15.5 !GO Function is first unbroken, evaluated, rebroken, and exited with value typed, i.e., !EVAL followed by GO. UB unbreaks brkfn, e.g., (FOO BROKEN) :UB FOO : and FOO is now unbroken @ resets the variable lastpos, which establishes a context for the commands ?=, ARGS, BT, BTV, BTV*, and EDIT, and IN? described below. lastpos is the position of a function call on the push-down stack. It is initialized to the function just before the call to break1, i.e., stknth[-1;BREAK1] @ treats the rest of the teletype line as its argument(s). It first resets lastpos to stknth[-1;BREAK1] and then for each atom on the line, @ searches backward, for a call to that atom. The following atoms are treated specially: @ do not reset lastpos to stknth[-1;BREAK1] but leave it as it was, and continue searching from that point. numbers if negative, move lastpos back that number of calls, if positive, forward, i.e., reset lastpos to stknth[n;lastpos] / the next atom is a number and can be used to specify more than one call e.g., @ FOO / 3 is equivalent to @ FOO FOO FOO = resets lastpos to the value of the next expression, e.g., if the value of FOO is a stack pointer, @ = FOO FIE will search for FIE in the environment specified by FOO. 15.6 Example: If the push-down stack looks like BREAK1 (13) FOO (12) SETQ (11) COND (10) PROG (9) FIE (8) COND (7) FIE (6) COND (5) FIE (4) COND (3) PROG (2) FUM (1) then @ FIE COND will set lastpos to the position corresponding to (7); @ @ COND will then set lastpos to (5); and @ FIE / 3 -1 to (3). If @ cannot successfully complete a search, it types (fn NOT FOUND), where fn is the name of the function for which it was searching. When @ finishes, it types the name of the function at lastpos, i.e., stkname[lastpos] @ can be used on brkcoms. In this case, the next command on brkcoms is treated the same as the rest of the teletype line. 4 ?= This is a multi-purpose command. Its most common use is to interrogate the value(s) of the arguments of the broken function, e.g., if FOO has three arguments (X Y Z), then typing ?= to a break on FOO, will produce: :?= X = value of X Y = value of Y Z = value of Z : ------------------------------------------------------------------------ 4 In fact, ?= is a universal mnemonic for displaying argument names and their corresponding values. In addition to being a break command, ?= is an edit macro which prints the argument names and values for the current expression (see Section 9), and a read-macro (actually ? is the read-macro character) which does the same for the current level list being read (see Sections 2 and 22). 15.7 ?= operates on the rest of the teletype line as its arguments. If the line is empty, as in the above case, it prints all of the arguments. If the user types ?= X (CAR Y), he will see the value of X, and the value of (CAR Y). The difference between using ?= and typing X and (CAR Y) directly to break1 is that ?= evaluates its inputs as of lastpos, i.e., it uses stkeval. This provides a way of examing variables or performing computations as of a particular point on the stack. For example, @ FOO / 2 followed by ?= X will allow the user to examine the value of X in the previous call to FOO, etc. ?= also recognizes numbers as refering to the correspondingly numbered argument, i.e., it uses stkarg in this case. Thus :@ FIE FIE :?= 2 will print the name and value of the second argument of FIE. ?= can also be used on brkcoms, in which case the next command on brkcoms is treated as the rest of the teletype line. For example, if brkcoms is (EVAL ?= (X Y) GO), brkexp will be evaluated, the values of X and Y printed, and then the function exited with its value being printed. BT Prints a backtrace of function names only starting at lastpos. (See discussion of @ above) The several nested calls in system packages such as break, edit, and the top level executive appear as the single entries **BREAK**, **EDITOR**, and **TOP** respectively. BTV Prints a backtrace of function names with variables beginning at lastpos. BTV* Same as BTV except also prints arguments of internal calls to eval (see Section 12). BTV! Same as BTV except prints everything on stack. (See Section 12). BT, BTV, BTV*, and BTV! all permit an optional functional argument which is a predicate that chooses functions to be skipped on the backtrace, e.g., BT SUBRP will skip all SUBRs, BTV (LAMBDA (X) (NOT (MEMB X FOOFNS))) will skip all but those functions 15.8 on FOOFNS. If used as a brkcom the functional argument is no longer optional, i.e., the next brkcom must either be the functional argument, or NIL if no functional argument is to be applied. For BT, BTV, BTV*, and BTV!, if control-P is used to change a printlevel during the backtrace, the printlevel will be restored after the backtrace is completed. ARGS Prints the names of the variables bound at lastpos, i.e., variables[lastpos] (Section 12). For most cases, these are the arguments to the function entered at that position, i.e., arglist[stkname[lastpos]]. The following two commands are for use only with unbound atoms or undefined function breaks (see Section 16). = form, = fn[args] only for the break following an unbound atom error. Sets the atom to the value of the form, or function and arguments, exits from the break returning that value, and continues the computation, e.g., U.B.A. (FOO BROKEN) := (COPY FIE) sets FOO and goes on. -> expr for use either with unbound atom error, or undefined function error. Replaces the 5 expression containing the error with expr (not the value of expr) e.g., U.D.F. (FOO1 BROKEN) :-> FOO changes the FOO1 to FOO and continues the computation. ------------------------------------------------------------------------ 5 -> does not change just brkexp; it changes the function or expression containing the erroneous form. In other words, the user does not have to perform any additional editing. 15.9 expr need not be atomic, e.g., U.B.A. (FOO BROKEN) :-> (QUOTE FOO) For U.D.F. breaks, the user can specify a function and initial arguments, e.g., U.D.F. (MEMBERX BROKEN) :-> MEMBER X Note that in the case of a U.D.F. error occurring immediately following a call to apply, e.g., (APPLY X Y) where the value of x is FOO and FOO is undefined, or a U.B.A. error immediately following a call to eval, e.g., (EVAL X), where the value of x is FOO and FOO is unbound, there is no expression containing the offending atom. In this case, -> cannot operate, so ? is printed and no action taken. EDIT designed for use in conjunction with breaks caused by errors. Facilitates editing the expression causing the break: NON-NUMERIC ARG NIL (IPLUS BROKEN) :EDIT IN FOO... (IPLUS X Z) EDIT *(3 Y) *OK FOO : and user can continue by typing OK, EVAL, etc. This command is very simple conceptually, but complicated in its implementation by all of the exceptional cases involving interactions with compiled functions, breaks on user functions, error breaks, breaks within breaks, et al. Therefore, we shall give the following simplified explanation which will account for 90% of the situations arising in actual usage. For those others, EDIT will print an appropriate failure message and return to the break. EDIT begins by searching up the stack beginning at lastpos (set by @ command, initially position of the break) looking for a form, i.e., an internal call to eval. Then EDIT continues from that point looking for a call to an interpreted function, or to eval. It then calls the editor on either the EXPR or the argument to eval in such a way as to look for an expression eq to the form that it first found. It then prints the form, and permits interactive editing to begin. Note that the user can then type successive 0's to the editor to see the chain of superforms for this computation. 15.10 If the user exits from the edit with an OK, the break expression is reset, if possible, so that the user can continue with the computation 6 by simply typing OK. However, in some situations, the break expression cannot be reset. For example, if a compiled function FOO incorrectly called putd and caused the error ARG NOT ATOM followed by a break on putd, EDIT might be able to find the form headed by FOO, and also find that form in some higher interpreted function. But after the user corrected the problem in the FOO-form, if any, he would still not have in any way informed EDIT what to do about the immediate problem, i.e., the incorrect call to putd. However, if FOO were interpreted EDIT would find the putd form itself, so that when the user corrected that form, EDIT could use the new corrected form to reset the break expression. The two cases are shown below: ARG NOT ATOM ARG NOT ATOM (FUM) (PUTD BROKEN) (PUTD BROKEN) :EDIT :EDIT IN FOO... IN FIE... (PUTD X) (FOO X) EDIT EDIT *(2 (CAR X)) *(2 (CAR X)) *OK *OK FOO NOTE: BRKEXP NOT CHANGED :OK FIE PUTD :?= U = (FUM) :(SETQ U (CAR U)) FUM :OK PUTD IN? similar to EDIT, but just prints parent form, and superform, but does not call editor, e.g., ATTEMPT TO RPLAC NIL T (RPLACD BROKEN) :IN? FOO: (RPLACD X Z) Although EDIT and IN? were designed for error breaks, they can also be useful for user breaks. For example, if upon reaching a break on his function FOO, the user determines that there is a problem in the call to FOO, he can edit the calling form and reset the break expression with one operation by using EDIT. The following two protocol's with and without the use of EDIT, illustrate this: ------------------------------------------------------------------------ 6 Evaluating the new brkexp will involve reevaluating the form that causes the break, e.g., if (PUTD (QUOTE (FOO)) big-computation) were handled by EDIT, big-computation would be reevaluated. 15.11 (FOO BROKEN) (FOO BROKEN) :?= :?= X = (A B C) X = (A B C) Y = D Y = D :B :EDIT IN FIE... FOO (FOO V U) SETQ EDIT COND find which function *(SW 2 3) PROG FOO is called from *OK 7 FIE FIE (aborted with ^E) :OK :EDITF(FIE) FOO EDIT *F FOO P (FOO V U) edit it *(SW 2 3) *OK FIE :(SETQ Y X) reset X and Y (A B C) :(SETQQ X D) D :?= X = D Y = (A B C) check them :OK FOO REVERT goes back to position lastpos on stack and reenters the function called at that point with 8 the arguments found on the stack. If the function is not already broken, REVERT first breaks it, and then unbreaks it after it is reentered. REVERT is useful for restarting a computation in the situation where a bug is discovered at some point below where the problem actually occurred. REVERT essentially says "go back there and start over in a ------------------------------------------------------------------------ 7 x and y have not been changed, but brkexp has. See previous footnote. 8 REVERT can also be given the position using the conventions described for @ on page 15.6, e.g., REVERT FOO -1 is equivalent to @ FOO -1 followed by REVERT. 15.12 9 break." ? prints the names of the break commands. Brkcoms The fourth argument to break1 is brkcoms, a list of break commands that break1 interprets and executes as though they were teletype input. One can think of brkcoms as another input file which always has priority over the teletype. Whenever brkcoms=NIL, break1 reads its next command from the teletype. Whenever brkcoms is not NIL, break1 takes as its 10 next command car[brkcoms] and sets brkcoms to cdr[brkcoms]. For example, suppose the user wished to see the value of the variable x after a function was evaluated. He would set up a break with brkcoms=(EVAL (PRINT X) OK), which would have the desired effect. The function trace uses brkcoms: it sets up a break with two commands; the first one prints the arguments of the function, or whatever the user specifies, and the second is the command GO, which causes the function to be evaluated and its value printed. If brkcoms is not NIL, the value of a break command is not printed. If you desire to see a value, you must print it yourself, as in the above example with the command (PRINT X). Note: whenever an error occurs, brkcoms is set to NIL, and a full interactive break occurs. Brkfile The break package has a facility for redirecting ouput to a file. The variable brkfile should be set to the name of the file, and the file must be opened. All output resulting from brkcoms will be output to brkfile, e.g., output due to TRACE. Output due to user typein is not affected, and will always go to the terminal. brkfile is initially T. ------------------------------------------------------------------------ 9 REVERT will work correctly if the names or arguments to the function, or even its function type, have been changed. 10 Normally, when a user breaks or traces a function, the value of brkcoms for the corresponding call to break1 will be defaulted to NIL. However, it is possible to specify a list of break commands, as described in the discussion of break and break1 below. 15.13 Breakmacros Whenever an atomic command is given break1 that it does not recognize, either via brkcoms or the teletype, it searches the list breakmacros for the command. The form of breakmacros is ( ... (macro command1 command2 ... commandn) ...). If the command is defined as a macro, break1 simply appends its definition, which is a sequence of commands, to the front of brkcoms, and goes on. If the command is not contained in breakmacros, it is treated as a function or 11 variable as before. Example: the command ARGS could be defined by including on breakmacros: (ARGS (PRINT (VARIABLES LASTPOS T))) Breakresetforms If the user is developing programs that change the way a user and INTERLISP normally interact, e.g., change or disable the interrupt or line-editing characters, turn off echoing, etc., debugging them by breaking or tracing may be difficult, because INTERLISP might be in a "funny" state at the time of the break. breakresetforms is designed to solve this problem. The user puts on breakresetforms expressions 12 suitable for use in conjunction with resetform (section 5). When a break occurs, break1 evaluates each expression on breakresetforms before any interaction with the terminal, and saves the values. When the break expression is evaluated via an EVAL, OK, or GO, break1 first restores the state of the system with respect to the various expressions on breakresetforms. When (if) control returns to break1, the expressions 13 on breakresetforms are again evaluated, and their values saved. When the break is exited via an OK, GO, RETURN, or command, break1 again restores state. Thus the net effect is to make the break invisible with respect to the user's programs, but nevertheless allow the user to interact in the break in the normal fashion. ------------------------------------------------------------------------ 11 If the command is not the name of a defined function, bound variable, or lispx command, break1 will attempt spelling correction using breakcomslst as a spelling list. If spelling correction is unsuccessful, break1 will go ahead and call lispx anyway, since the atom may also be a misspelled history command. 12 i.e., the value of each form is its "previous state," so that the effect of evaluating the form can be undone by applying car of the form to the value, e.g., radix, printlevel, linelength, setreadtable, interruptchar, etc., all have this property. 13 Because a lower function might have changed the state of the system with respect to one of the these expressions! 15.14 15.3 Break Functions break1[brkexp;brkwhen;brkfn;brkcoms;brktype] is an nlambda. brkwhen determines whether a break is to occur. If its value is NIL, brkexp is evaluated and returned as the value of break1. Otherwise a break occurs and an identifying message is printed using brkfn. Commands are then taken from brkcoms or the teletype and interpreted. The commands, GO, !GO, OK, !OK, RETURN and ^, are the only ways to leave break1. The command EVAL causes brkexp to be evaluated, and saves the value on the variable !value. Other commands can be defined for break1 via breakmacros. brktype is NIL for user breaks, INTERRUPT for control-H breaks, and ERRORX for error breaks. For error breaks, the input buffer is cleared and saved. (For control-H breaks, the input buffer was cleared at the time the control-H was typed, see Section 16.) In both cases, if the break returns a value, i.e., is not aborted via ^ or control-D, the input buffer will be restored (see Section 14). break0[fn;when;coms] sets up a break on the function fn by redefining fn as a call to break1 with brkexp an equivalent definition of fn, and when, fn, and coms, as brkwhen, brkfn, brkcoms. Puts property BROKEN on property list of fn with value a gensym defined with the original definition. Puts property BRKINFO on property list of fn with value (BREAK0 when coms) (For use in conjunction with rebreak). Adds fn to the front of the list brokenfns. Value is fn. If fn is non-atomic and of the form (fn1 IN fn2), break0 first calls a function which changes the name of fn1 wherever it appears inside of fn2 to that of a new function, fn1-IN-fn2, which it initially defines as fn1. Then break0 proceeds to break on fn1-IN-fn2 exactly as described above. This procedure is useful for breaking on a function that is called from many places, but where one is only interested in the call from a specific function, e.g., (RPLACA IN FOO), (PRINT IN FIE), etc. It is similar to breakin described below, but can be performed even when FN2 is compiled or blockcompiled, whereas breakin only works on interpreted functions. If fn1 is not found in fn2, break0 returns the value (fn1 NOT FOUND IN fn2). If fn1 is found in fn2, in addition to breaking fn1-IN-fn2 and adding fn1-IN-fn2 to the list brokenfns, break0 adds fn1 to the property value 15.15 for the property NAMESCHANGED on the property list of fn2 and adds the property ALIAS with the value (fn2 . fn1) to the property list of fn1-IN-fn2. This will enable unbreak to recognize what changes have been made and restore the function fn2 to its original state. If fn is nonatomic and not of the above form, break0 is called for each member of fn using the same values for when, coms, and file specified in this call to break0. This distributivity permits the user to specify complicated break conditions on several functions without excessive retyping, e.g., break0[(FOO1 ((PRINT PRIN1) IN (FOO2 FOO3))); (NEQ X T);(EVAL ?= (Y Z) OK)] will break on FOO1, PRINT-IN-FOO2, PRINT-IN-FOO3, PRIN1-IN-FOO2 and PRIN1-IN-FOO3. If fn is non-atomic, the value of break0 is a list of the individual values. break[x] is a nospread nlambda. For each atomic argument, it performs break0[atom;T]. For each list, it performs apply[BREAK0;list]. For example, break[FOO1 (FOO2 (GREATERP N 5) (EVAL))] is equivalent to break0[FOO1,T] and break0[FOO2; (GREATERP N 5); (EVAL)] trace[x] is a nospread nlambda. For each atomic argument, it performs 14 break0[atom;T;(TRACE ?= NIL GO)] For each list argument, car is the function to be traced, and cdr the forms the user wishes to see, i.e., trace performs: break0[car[list];T;list[TRACE;?=; cdr[list],GO]] For example, TRACE(FOO1 (FOO2 Y)) will cause both FOO1 and FOO2 to be traced. All the arguments of FOO1 will be printed; only the value of Y will be printed for FOO2. In the special case that the user wants to see only the value, he can perform TRACE((fn)). This sets up a break with commands (TRACE ?= (NIL) GO). ------------------------------------------------------------------------ 14 The flag TRACE is checked for in break1 and causes the message "function :" to be printed instead of (function BROKEN). 15.16 Note: the user can always call break0 himself to obtain combination of options of break1 not directly available with break and trace. These two functions merely provide convenient ways of calling break0, and will serve for most uses. Breakin Breakin enables the user to insert a break, i.e., a call to break1, at a specified location in an interpreted function. For example, if foo calls fie, inserting a break in foo before the call to fie is similar to breaking fie. However, breakin can be used to insert breaks before or after prog labels, particular SETQ expressions, or even the evaluation of a variable. This is because breakin operates by calling the editor and actually inserting a call to break1 at a specified point inside of the function. The user specifies where the break is to be inserted by a sequence of editor commands. These commands are preceded by BEFORE, AFTER, or AROUND, which breakin uses to determine what to do once the editor has found the specified point, i.e., put the call to break1 BEFORE that point, AFTER that point, or AROUND that point. For example, (BEFORE COND) will insert a break before the first occurrence of cond, (AFTER COND 2 1) will insert a break after the predicate in the first cond clause, (AFTER BF (SETQ X &)) after the last place X is set. Note that (BEFORE TTY:) or (AFTER TTY:) permit the user to type in commands to the editor, locate the correct point, and verify it for himself using 15 the P command if he desires, and exit from the editor with OK. breakin then inserts the break BEFORE, AFTER, or AROUND that point. For breakin BEFORE or AFTER, the break expression is NIL, since the value of the break is irrelevant. For breakin AROUND, the break expression will be the indicated form. In this case, the user can use the EVAL command to evaluate that form, and examine its value, before allowing the computation to proceed. For example, if the user inserted a break after a cond predicate, e.g., (AFTER (EQUAL X Y)), he would be powerless to alter the flow of computation if the predicate were not true, since the break would not be reached. However, by breaking (AROUND (EQUAL X Y)), he can evaluate the break expression, i.e., (EQUAL X Y), look at its value, and return something else if he wished. The message typed for a breakin break, is ((fn) BROKEN), where fn is the name of the function inside of which the break was inserted. Any error, or typing control-E, will cause the full identifying message to be printed, e.g., (FOO BROKEN AFTER COND 2 1). A special check is made to avoid inserting a break inside of an expression headed by any member of the list nobreaks, initialized to ------------------------------------------------------------------------ 15 A STOP command typed to TTY: produces the same effect as an unsuccessful edit command in the original specification, e.g., (BEFORE CONDD). In both cases, the editor aborts, and breakin types (NOT FOUND). 15.17 (GO QUOTE *), since this break would never be activated. For example, if (GO L) appears before the label L, breakin (AFTER L) will not insert the break inside of the GO expression, but skip this occurrence of L and go on to the next L, in this case the label L. Similarly, for BEFORE or AFTER breaks, breakin checks to make sure that the break is being inserted at a "safe" place. For example, if the user requests a break (AFTER X) in (PROG -- (SETQ X &) --), the break will actually be inserted AFTER (SETQ X &), and a message printed to this effect, e.g., BREAK INSERTED AFTER (SETQ X &). breakin[fn;where;when;coms] breakin is an nlambda. when and coms are similar to when and coms for break0, except that if when is NIL, T is used. where specifies where in the definition of fn the call to break1 is to be inserted. (See earlier discussion). If fn is a compiled function, breakin returns (fn UNBREAKABLE) as its value. If fn is interpreted, breakin types SEARCHING... while it calls the editor. If the location specified by where is not found, breakin types (NOT FOUND) and exits. If it is found, breakin adds the property BROKEN-IN with value T, and the property BRKINFO with value (where when coms) to the property list of fn, and adds fn to the front of the list brokenfns. Multiple break points, can be inserted with a single call to breakin by using a list of the form ((BEFORE ...) .. (AROUND ...)) for where. It is also possible to call break or trace on a function which has been modified by breakin, and conversely to breakin a function which has been redefined by a call to break or trace. unbreak[x] unbreak is a nospread nlambda. It takes an indefinite number of functions modified by break, trace, or breakin and restores them to their original state by calling unbreak0. Value is list of values of unbreak0. unbreak[] will unbreak all functions on brokenfns, in reverse order. It first sets brkinfolst to NIL. unbreak[T] unbreaks just the first function on brokenfns, i.e., the most recently broken function. unbreak0[fn] restores fn to its original state. If fn was not broken, value is (NOT BROKEN) and no changes are made. If fn was modified by breakin, unbreakin is called to edit it back to its 15.18 original state. If fn was created from (fn1 IN fn2), i.e., if it has a property ALIAS, the function in which fn appears is restored to its original state. All dummy functions that were created by the break are eliminated. Adds property value of BRKINFO to (front of) brkinfolst. Note: unbreak0[(fn1 IN fn2)] is allowed: unbreak0 will operate on fn1-IN-fn2 instead. unbreakin[fn] performs the appropriate editing operations to eliminate all changes made by breakin. fn may be either the name or definition of a function. Value is fn. Unbreakin is automatically called by unbreak if fn has property BROKEN-IN with value T on its property list. rebreak[x] is an nlambda, nospread function for rebreaking functions that were previously broken without having to respecify the break information. For each function on x, rebreak searches brkinfolst for break(s) and performs the corresponding operation. Value is a list of values corresponding to calls to break0 or breakin. If no information is found for a particular function, value is (fn - NO BREAK INFORMATION SAVED). rebreak[] rebreaks everything on brkinfolst, i.e., rebreak[] is the inverse of unbreak[]. rebreak[T] rebreaks just the first break on brkinfolst, i.e., the function most recently unbroken. changename[fn;from;to] changes all occurrences of from to to in fn. fn may be compiled or blockcompiled. Value is fn if from was found, otherwise NIL. Does not perform any modifications of property lists. Note that from and to do not have to be functions, e.g., they can be names of variables, or any other literals. virginfn[fn;flg] is the function that knows how to restore functions to their original state regardless of any amount of breaks, breakins, advising, compiling and saving exprs, etc. It is used by prettyprint, define, and the compiler. If flg=NIL, as for prettyprint, it does not modify the definition of fn in the process of producing a "clean" version of the definition, i.e., it works on a copy. If flg=T as for the compiler and define, it physically restores the function 15.19 to its original state, and prints the changes it is making, e.g., FOO UNBROKEN, FOO UNADVISED, FOO NAMES RESTORED, etc. Value is the virgin function definition. baktrace[ipos;epos;skipfn;flags] prints backtrace from ipos to epos. flags specifies the options of the backtrace, e.g., do/don't print arguments, do/don't print temporaries of the interpreter, etc., and is the same as for backtrace (Section 12). baktrace collapses the sequence of several function calls corresponding to a call to a system package into a single "function", e.g., any call to the editor is printed as **EDITOR**, a break is printed as **BREAK**, etc. If skipfn is not NIL and skipfn[stkname[pos]] is T, pos is skipped (including all variables). baktrace is used by the BT, BTV, BTV*, and BTV! commands, with flags=0,1,3, and 7 respectively. 15.20