Artifact fca612a5b6fad0d582e9d6fe27e1e4c1c4af15642e393e619fff7011383f1b5d:
- File
psl-1983/doc/debug.doc
— part of check-in
[eb17ceb7f6]
at
2020-04-21 19:40:01
on branch master
— Add Reduce 3.0 to the historical section of the archive, and some more
files relating to version sof PSL from the early 1980s. Thanks are due to
Paul McJones and Nelson Beebe for these, as well as to all the original
authors.git-svn-id: https://svn.code.sf.net/p/reduce-algebra/code/historical@5328 2bfe0521-f11c-4a00-b80e-6202646ff360 (user: arthurcnorman@users.sourceforge.net, size: 31967) [annotate] [blame] [check-ins using] [more...]
THE REDUCE DEBUGGING PACKAGE A. C. Norman D. F. Morrison Last updated 19 February 1981. ABSTRACT A library of routines useful for program development and debugging in Reduce/Rlisp is described. Table of Contents 1. Introduction 1 1.1. Use 1 1.2. Functions which depend on redefining user functions 1 1.3. Special considerations for compiled functions 1 1.4. A few known deficiencies 1 2. Tracing function execution 1 2.1. Saving trace output 1 2.2. Making tracing more selective 2 2.3. Turning off tracing 2 2.4. Automatic tracing of newly defined functions 2 3. A heavy handed backtrace facility 2 4. Embeded Functions 2 5. Counting function invocations 2 6. Stubs 3 7. Functions for printing useful information 3 8. Printing circular and shared structures 3 9. Safe List Access Functions 3 10. Library of Useful Functions 3 11. Internals and cusomization 3 11.1. User Hooks 3 11.2. Functions used for printing/reading 3 11.3. Flags 3 APPENDIX A: Example 4 1. Introduction The REDUCE debugging package contains a selection of functions that can be used to aid program development and to investigate faulty programs. It contains the following facilities. - A trace package. This allows the user to see the arguments passed to and the values returned by selected functions. It is also possible to have traced interpreted functions print all the assignments they make with SETQ (see section 2). - A backtrace facility. This allows one to see which of a set of selected functions were active when an error occurred (section 3). - Embedded functions make it possible to do everything that the trace package can do, and much more besides (section 4). - Some primitive statistics gathering (section 5). - Generation of simple stubs. When invoked, procedures defined as stubs simply print their argument and read a value to return (section 6). - Some functions for printing useful information, such as property lists, in an intelligible format (section 7). - PRINTX is a function that can print circular and re-entrant lists, and so can sometimes allow debugging to proceed even in the face of severe damage caused by the wild use of RPLACA and RPLACD (section 8). - A set of functions !:CAR,...,!:CDDDDR, !:RPLACA, !:RPLACD and !:RPLACW that behave exactly as the corresponding functions with the !: removed, except that they explicitly check that they are not used improperly on atomic arguments (section 9). - A collection of utility functions, not specifically intended for examining or debugging code, but often useful (section 10). 1.1. Use To use load <REDUCE.UTAH>DEBUG.FAP FLOAD <REDUCE.UTAH>DEBUG.FAP; 1.2. Functions which depend on redefining user functions A number of facilities in Debug depend on redefining user functions, so that they may log or print behavior when called. The Debug package tries to redefine user functions once and for all, and then keep specific information about what is required at run time in a table. This allows considerable flexibility, and is used for a number different facilities, including trace/traceset (section 2), a backtrace facility (section 3), some statistics gathering (section 5)and EMB functions (section 4). Some, like trace and EMB, only take effect if further action is requested on specific user functions. Others, like backtrace and statistics are of a more global nature. Once one of these global facilities is enabled it applies to all functions which have been made "known" to Debug. To undo this, use RESTR (section 2.3). 1.3. Special considerations for compiled functions All functions in Debug which depend on redefining user functions must make some assumptions about the number of arguments. The Debug package is able to find the correct names for the arguments of interpreted functions, and also for functions loaded from FAP files and generated with an argument naming option. This option is enabled by setting the switch ON ARGNAMES; % for full names of all arguments or ON ARGCOUNT; % args will be printed with names A1,A2,... before compiling the relevant functions. If Debug can not find out for itself how many arguments a function has, it will interactively ask for assistance. In reply to the question HOW MANY ARGUMENTS DOES xxxx HAVE? it is possible to reply one of: ? ask for assistance UNKNOWN give up <number> specify the number of arguments (name ...) give the names of arguments. If you give an incorrect answer to the question, the system may misbehave in an arbitrary manner. There can be problems if the answer UNKNOWN is given and subsequently functions get redefined or recompiled - if at all possible find out how many arguments are taken by the function that you wish to trace. It is possible to suppress the argument number query with ON TRUNKNOWN This is equivalent to always answering "UNKNOWN". 1.4. A few known deficiencies - An attempt to trace certain system functions (e.g.CONS) will cause the trace package to overwrite itself. Given the names of functions that cause this sort of trouble it is fairly easy to change the trace package to deal gracefully with them - so report trouble to a system expert. - Once fast links are established trace can not work. Fast links are turned off when Debug is loaded, and even if they are restored they are turned off each time TR or a related function is called. In Standard Lisp 1.6 on the PDP10/20 the statement ON NOUUO; will also suppress fast links. Thus either load Debug or do ON NOUUO prior to any attempt to execute code that will need to be traced. - The portable Lisp compiler uses information about which registers certain system functions destroy. Tracing these functions may make the optimizations based thereon invalid. The correct way of handling this problem is currently under consideration. In the mean time you should avoid tracing any functions with the ONEREG or TWOREG flags. On the PDP10/20 these currently include UPBV FLOATP FLOAT NUMVAL LPOSN NCONS POSN FIXP GET EXAMINE SCANSET SETPCHAR EJECT TYO BINI BIGP PRINC ABS CODEP LINELENGTH STRINGP MINUS PAIRP RECLAIM TERPRI XCONS UNTYI *BOX CONS MKVECT GETD ATSOC CLOSE GCTIME MKCODE REVERSE ASCII BINO LENGTH FILEP PUTV SPEAK DELIMITER PAGELENGTH RDSLSH TIME REMD FIX CONSTANTP INUMP ATOM VECTORP GETV IDP REMPROP EXCISE NUMBERP PUT LETTER - The current implementation does not handle MACROs correctly. It is not possible to expand a MACRO and not evaluate the resulting expansion. This deficiency will be remedied shortly. In the mean time do not use any traced MACROs under the influence of ON DEFN. 2. Tracing function execution To see when a function gets called, what arguments it is given and what value it returns, do TR functionname; or if several functions are of interest, TR name1,name2,...; If the specified functions are defined (as EXPR, FEXPR or MACRO), and fast links to them have not yet been established (section 1.4), this REDUCE statement modifies the function definition to include print statements. The following example shows the style of output produced by this sort of tracing: The input... SYMBOLIC PROCEDURE XCDR A; CDR A; % A very simple function; TR XCDR; XCDR '(P Q R); gives output... XCDR entered A: (P Q R) XCDR = (Q R) Interpreted functions can also be traced at a deeper level. TRST name1,name2...; causes the body of an interpreted function to be redefined so that all assignments (made with SETQ) in its body are printed. Calling TRST on a function automatically has the effect of doing a TR on it too, and the use of UNTR automatically does an UNTRST if necessary (section 2.3), so that it is not possible to have a function subject to TRST but not TR. Trace output will often appear mixed up with output from the program being studied, and to avoid too much confusion TR arranges to preserve the column in which printing was taking place across any output that it generates. If trace output is produced when part of a line has been printed, the trace data will be enclosed in markers '<' and '>', and these symbols will be placed on the line so as to mark out the amount of printing that had occurred before trace was entered. 2.1. Saving trace output The trace facility makes it possible to discover in some detail how a function is used, but in certain cases its direct use will result in the generation of vast amounts of (mostly useless) print-out. There are several options. One is to make tracing more selective (section 2.2). The other, discussed here, is to either print only the most recent information, or dump it all to a file to be perused at leisure. Debug has a ring buffer in which it saves information to reproduce the most recent information printed by the trace facility (both TR and TRST). To see the contents of this buffer use TR without any arguments TR; To set the number of entries retained to n use NEWTRBUFF(n); It is initially set to 5. Turning off the TRACE flag OFF TRACE; will suppress the printing of any trace information at run time; it will still be saved in the ring buffer. Thus a useful technique for isolating the function in which an error occurs is to trace a large number of candidate functions, do OFF TRACE and after the failure look at the latest trace information by calling TR with no arguments. Normally trace information is directed to the standard output, rather than the currently selected output. To send it elsewhere use the statement TROUT filename; The statement STDTRACE; Will close that file and cause future trace output to be sent to the standard output. Note that output saved in the ring buffer is sent to the currently selected output, not that selected by TROUT. 2.2. Making tracing more selective The function TRACECOUNT(n) can be used to switch off trace output. If n is a positive number, after a call to TRACECOUNT(n) the next n items of trace output that are generated will not be printed. TRACECOUNT(n) with n negative or zero switches all trace output back on. TRACECOUNT(NIL) returns the residual count, i.e. the number of additional trace entries that will be suppressed. Thus to get detailed tracing in the stages of a calculation that lead up to an error, try TRACECOUNT 1000000; % or some other suitable large number TR ....; % as required % run the failing problem TRACECOUNT NIL; It is now possible to calculate how many trace entries occurred before the error, and so the problem can now be re-run with TRACECOUNT set to some number slightly less than that. An alternative to the direct of TRACECOUNT is TRIN. To use TRIN, establish tracing for a collection of functions, using TR in the normal way. Then do TRIN on some small collection of other functions. The effect is just as for TR, except that trace output will be inhibited except when control is dynamically within the TRIN functions. This makes it possible to use TR on a number of heavily used general purpose functions, and then only see the calls to them that occur within some specific sub-part of your entire program. UNTR undoes the effect of TRIN (section 2.3). The global variables TRACEMINLEVEL!* and TRACEMAXLEVEL!* (which should be non-negative integers) are the minimum and maximum depths of recursion at which to print trace information. Thus if you only want to see top level calls of a highly recursive function (like a simple-minded version of LENGTH) simply do TRACEMAXLEVEL!* := 1; 2.3. Turning off tracing When a particular function no longer needs tracing, do UNTR functionname; or UNTR name1,name2...; This merely suppresses generation of trace output. Other information, such as invocation counts, bactrace information, and the number of arguments is retained. Thus UNTR followed later by TR will not have to enquire about the number of arguments. To completely destroy information about a function use RESTR name1,name2...; This returns the function to it's original state. To suppress traceset output without suppressing normal trace output use UNTRST name1,name2...; UNTRing a TRSTed function also UNTRST's it. TRIN (section 2.2) is undone by UNTR (but not by UNTRST). 2.4. Automatic tracing of newly defined functions Under the influence of ON TRACEALL; any functions successfully defined by PUTD will be traced. Note that if PUTD fails (as might happen under the influence of the LOSE flag) no attempt will be made to trace the function. To enable those facilities (such as BTR (section 3) and TRCOUNT (section 5)) which require redefinition, but without tracing, use ON INSTALL; Thus, a common scenario might look like ON INSTALL; IN MYFNS.RED$ OFF INSTALL; which would enable the backtrace and statistics routines to work with all the functions defined in MYFNS.RED. Warning: if you intend to use ON TRACEALL or ON INSTALL, make sure that fast links are suppressed before you define ANY functions, even those you will never trace (section 1.4). 3. A heavy handed backtrace facility BTR f1,f2,...; arranges that a stack of functions entered but not left is kept - this stack records the names of functions and the arguments that they were called with. If a function returns normally the stack is unwound. If however the function fails, the stack is left alone by the normal LISP error recovery processes. To print this information call BTR without any arguments BTR; Calling BTR on new functions resets the stack. This may also be done by explicitly calling RESBTR RESBTR; The disposition of information about functions which failed within an ERRORSET is controlled by the BTRSAVE. ON BTRSAVE will cause them to be save separately, and printed when the stack is printed; OFF BTRSAVE will cause them to be thrown away. OFF BTR will suppress saving of any BTR information. Note that any traced function will have its invocations pushed and popped by the BTR maechanism. 4. Embeded Functions EMBEDDING means redefining a function in terms of its old definition, usually with the intent that the new version will do some tests or printing, use the old one, do some more printing and then return. If ff is a function of two arguments, it can be embedded using a statement of the form: SYMBOLIC EMB PROCEDURE ff(A1,A2); << PRINT A1; PRINT A2; PRINT ff(A1,A2) >>; The effect of this particular use of embed is broadly similar to a call TR ff, and arranges that whenever ff is called it prints both its arguments and its result. After a function has been embedded, the embedding can be temporarily removed by the use of UNEMBED ff; and it can be reinstated by EMBED ff; 5. Counting function invocations Whenever the flag TRCOUNT is ON the number of times user functions known to Debug are entered is counted. The statement ON TRCOUNT; also resets that count to zero. The statement OFF TRCOUNT; causes a simple histogram of function invocations to be printed. To make Debug aware of a function use TRCNT name1,name2,...; See also section 2.4. 6. Stubs The statement STUB FOO(U,V); defines an EXPR, FOO, of two arguments. When executed such a stub will print its arguments and read a value to return. FSTUB is used to define FEXPR's. This is often useful when developing programs in a top down fashion. At present the currently (i.e. when the stub is executed) selected input and output are used. This may be changed in the future. Algebraic and possibly MACRO stubs may be implemented in the future. 7. Functions for printing useful information PLIST id1,id2,...; prints the property lists of the specified id's. PPF fn1,fn2,...; prints the definitions and other useful information about the specified functions. 8. Printing circular and shared structures Some LISP programs rely on parts of their datastructures being shared, so that an EQ test can be used rather than the more expensive EQUAL one. Other programs (either deliberately or by accident) construct circular lists through the use of RPLACA or RPLACD. Such lists can be displayed by use of the function PRINTX. If given a normal list the behaviour of this function is similar to that of PRINT - if it is given a looped or re-entrant datastructure it prints it in a special format. The representation used by PRINTX for re-entrant structures is based on the idea of labels for those nodes in the structure that are referenced more than once. Consider the list created by the operations: A:=NIL . NIL; % make a node RPLACA(A,A); RPLACD(A,A); % point it at itself If PRINTX is called on the list A it will discover that the node is referenced repeatedly, and will invent the label %L1 for it. The structure will then be printed as %L1: (%L1 . %L1) where %L1: sets the label, and the other instances of %L1 refer back to it. Labelled sublists can appear anywhere within the list being printed. Thus the list B := 'X . A; could be printed as (X . %L1: (%L1 . %L1)) This use of dotted pair representation is often clumsy, and so it gets contracted to (X %L1, %L1 . %L1) where a label set with a comma (rather than a colon) is a label for part of a list, not for the sublist. 9. Safe List Access Functions The functions !:CAR, ... !:CDDDDR, !:RPLACA, !:RPLACD and !:RPLACW all contain explicit checks to ensure that they are not used improperly on atomic arguments. The user can either edit source files systematically changing CAR into !:CAR etc and recompile everything to use these, or use !:REDEFINE. The function !:REDEFINE (of no arguments) redefines CAR, CDR, etc. to be !:CAR, etc. It leaves the original, "dangerous" definitions under !%CAR, etc. A second call on !:REDEFINE undoes the process. Warning: the second technique will not normally work with compiled functions, as CAR, CDR, etc are often compiled inline. 10. Library of Useful Functions Debug contains a library of utility functions which may be useful to those debugging code. The collection is as yet very small. Suggestions for further functions to be in corporated are definitely solicited. Those currently available: REDEFINE(nam,old,new) redefines the function named <nam> to be the same as that named <new>. If <old> is non-nil, the former definition is stored under the name <old>. For example, REDEFINE('EVAL,'!%EVAL,'MYEVAL) saves the definition of EVAL as %EVAL, and redfines it to be MYEVAL. COPY U returns a freshly cons'd together copy of U, often usefull in debugging functions which use RPLACA/RPLACD. VCOPY U Like COPY, but copies vectors, non-unique numbers, and strings, too. 11. Internals and cusomization This section describes some internal details of the Debug package which may be useful in customizing it for specific applications. The reader is urged to consult the source (section <REDUCE.UTAH>DEBUG.RED) for further details. 11.1. User Hooks These are all global variables whose value is normally NIL. If non-nil they should be exprs taking the number of variables specified, and will be called as specified. PUTDHOOK!* takes one argument, the function name. It is called after the function has been defined, and any tracing under the influence of TRACEALL or INSTALL has taken place. It is not called if the function cannot be defined (as might happen under the influence of the LOSE flag). TRACENTRYHOOK!* takes two arguments, the function name and a list of the actual arguments. It is called by the trace package whenever a traced function is entered, but before it is executed. The execution of a surrounding EMB function takes place after TRACENTRYHOOK!* is called. This is useful when you need to call special user- provided print routines to display critical data structures, as are TRACEXITHOOK!* and TRACEXPANDHOOK!*. TRACEXITHOOK!* takes two arguments, the function name and the value. It is called after the function has been evaluated. TRACEXPANDHOOK!* takes two arguments, the function name and the macro expansion. It is only called for macros, and is called after the macro is expanded, but before the expansion has been evaluated. TRINSTALLHOOK!* takes one argument, a function name. It is called whenever a function is redefined by the Debug package, as for example when it is first traced. It is called before the redefinition takes place. 11.2. Functions used for printing/reading These should all contain EXPRS taking the specified number of arguments. The initial values are given in square brackets. PPFPRINTER!* [RPRINT] takes one argument. It is used by PPF to print the body of an interpreted function. PROPERTYPRINTER!* [PRETTYPRINT] takes one argument. It is used by PLIST to print the values of properties. STUBPRINTER!* [PRINTX] takes one argument. Stubs defined with STUB/FSTUB use it to print their arguments. STUBREADER!* [XREAD(NIL)] takes no arguments. Stubs defined with STUB/FSTUB use it to read their return value. TREXPRINTER!* [RPRINT] takes one argument. It is used to print the expansions of traced macros. TRPRINTER!* [PRINTX] takes one argument. It is used to print the arguments and values of traced functions. 11.3. Flags These are all flags which can be set with the Reduce/Rlisp ON/OFF statements. Their initial setting is given in square brackets. Many have been described above, but are collected here for reference. BTR [on] enables backtracing of functions which the Debug package has been told about. BTRSAVE [on] causes backtrace information leading up to an error within an errorset to be saved. INSTALL [off] causes all Debug to know about all functions defined with PUTD. SAVENAMES [off] causes names assigned to substructures by PRINTX to be retained from one use to the next. Thus substurctures common to different items will be show as the same. TRACE [on] enables runtime printing of trace information for functions which have been traced. TRACEALL [off] causes all functions defined with PUTD to be traced. TRUNKNOWN [off] instead of querying the user for the number of arguments to a compiled EXPR, just assumes the user will say "UNKNOWN". TRCOUNT [on] enables counting invocations of functions known to Debug. Note that ON TRCOUNT resets the count, and OFF TRCOUNT prints a simple histogram of the available counts. APPENDIX A: Example This contrived example demonstrates many of the available features. It is a transcript of an actual Reduce session. REDUCE 2 (Dec-1-80) ... FOR HELP, TYPE HELP<ESCAPE> 1: CORE 80; 2: FLOAD <MORRISON>NUDBUG.FAP; 3: SYMBOLIC PROCEDURE FOO N; 3: BEGIN SCALAR A; 3: IF REMAINDER(N,2) NEQ 0 AND N < 0 THEN 3: A := !:CAR N; % Should err out if N is a number 3: IF N = 0 THEN 3: RETURN 'BOTTOM; 3: N := N-2; 3: A := BAR N; 3: N := N-2; 3: RETURN LIST(A,BAR N,A) 3: END FOO; FOO 4: SYMBOLIC PROCEDURE FOOBAR N; 4: << FOO N; NIL>>; FOOBAR 5: SYMBOLIC OPERATOR FOOBAR; NIL 6: TR FOO,FOOBAR; (FOO FOOBAR) 7: PPF FOOBAR,FOO; EXPR procedure FOOBAR(N) [Traced;Invoked 0 times;Flagged: OPFN]: <<FOO N; NIL>>; EXPR procedure FOO(N) [Traced;Invoked 0 times]: BEGIN SCALAR A; IF NOT REMAINDER(N,2)=0 AND N<0 THEN A := !:CAR N; IF N=0 THEN RETURN 'BOTTOM; N := N - 2; A := BAR N; N := N - 2; RETURN LIST(A,BAR N,A) END; FOOBAR(FOO) 8: ON COMP; 9: SYMBOLIC PROCEDURE BAR N; 9: IF REMAINDER(N,2)=0 THEN FOO(2*(N/4)) ELSE FOO(2*(N/4)-1); *** BAR 164896 BASE 20 WORDS 63946 LEFT BAR 10: OFF COMP; 11: FOOBAR 8; FOOBAR being entered N: 8 FOO being entered N: 8 FOO (level 2) being entered N: 2 FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM FOO (level 2) = (BOTTOM BOTTOM BOTTOM) FOO (level 2) being entered N: 2 FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM FOO (level 2) = (BOTTOM BOTTOM BOTTOM) FOO = (%L1: (BOTTOM BOTTOM BOTTOM) (BOTTOM BOTTOM BOTTOM) %L1) FOOBAR = NIL 0 12: % Notice how in the above PRINTX printed the return values 12: % to show shared structure 12: TRST FOO; (FOO) 13: FOOBAR 8; FOOBAR being entered N: 8 FOO being entered N: 8 N := 6 FOO (level 2) being entered N: 2 N := 0 FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM A := BOTTOM N := -2 FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM FOO (level 2) = (BOTTOM BOTTOM BOTTOM) A := (BOTTOM BOTTOM BOTTOM) N := 4 FOO (level 2) being entered N: 2 N := 0 FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM A := BOTTOM N := -2 FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM FOO (level 2) = (BOTTOM BOTTOM BOTTOM) FOO = (%L1: (BOTTOM BOTTOM BOTTOM) (BOTTOM BOTTOM BOTTOM) %L1) FOOBAR = NIL 0 14: TR BAR; *** How many arguments does BAR take ? 1 (BAR) 15: FOOBAR 8; FOOBAR being entered N: 8 FOO being entered N: 8 N := 6 BAR being entered A1: 6 FOO (level 2) being entered N: 2 N := 0 BAR (level 2) being entered A1: 0 FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM BAR (level 2) = BOTTOM A := BOTTOM N := -2 BAR (level 2) being entered A1: -2 FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM BAR (level 2) = BOTTOM FOO (level 2) = (BOTTOM BOTTOM BOTTOM) BAR = (BOTTOM BOTTOM BOTTOM) A := (BOTTOM BOTTOM BOTTOM) N := 4 BAR being entered A1: 4 FOO (level 2) being entered N: 2 N := 0 BAR (level 2) being entered A1: 0 FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM BAR (level 2) = BOTTOM A := BOTTOM N := -2 BAR (level 2) being entered A1: -2 FOO (level 3) being entered N: 0 FOO (level 3) = BOTTOM BAR (level 2) = BOTTOM FOO (level 2) = (BOTTOM BOTTOM BOTTOM) BAR = (BOTTOM BOTTOM BOTTOM) FOO = (%L1: (BOTTOM BOTTOM BOTTOM) (BOTTOM BOTTOM BOTTOM) %L1) FOOBAR = NIL 0 16: OFF TRACE; 17: FOOBAR 8; 0 18: TR; *** Start of saved trace information *** BAR (level 2) = BOTTOM FOO (level 2) = (BOTTOM BOTTOM BOTTOM) BAR = (BOTTOM BOTTOM BOTTOM) FOO = (%L1: (BOTTOM BOTTOM BOTTOM) (BOTTOM BOTTOM BOTTOM) %L1) FOOBAR = NIL *** End of saved trace information *** 19: FOOBAR 13; ***** -1 illegal CAR 20: TR; *** Start of saved trace information *** BAR being entered A1: 11 FOO (level 2) being entered N: 3 N := 1 BAR (level 2) being entered A1: 1 FOO (level 3) being entered N: -1 *** End of saved trace information *** 21: BTR; *** Backtrace: *** These functions were left abnormally: FOO N: -1 BAR A1: 1 FOO N: 3 BAR A1: 11 FOO N: 13 FOOBAR N: 13 *** End of backtrace *** 22: SYMBOLIC EMB PROCEDURE FOO N; 22: IF N < 0 THEN << 22: LPRIM "FOO would have failed"; 22: NIL >> 22: ELSE 22: FOO N; FOO 23: RESBTR; 24: FOOBAR 13; *** FOO WOULD HAVE FAILED *** FOO WOULD HAVE FAILED *** FOO WOULD HAVE FAILED *** FOO WOULD HAVE FAILED 0 25: TR; *** Start of saved trace information *** BAR (level 2) = NIL FOO (level 2) = (NIL NIL NIL) BAR = (NIL NIL NIL) FOO = (%L1: (NIL NIL NIL) (NIL NIL NIL) %L1) FOOBAR = NIL *** End of saved trace information *** 26: BTR; *** No traced functions were left abnormally *** 27: UNEMBED FOO; (FOO) 28: FOOBAR 13; ***** -1 illegal CAR 29: STUB FOO N; *** FOO REDEFINED 30: FOOBAR 13; Stub FOO called N: 13 Return? : 30: BAR(N-2); Stub FOO called N: 3 Return? : 30: BAR(N-2); Stub FOO called N: -1 Return? : 30: 'ERROR; 0 31: TR; *** Start of saved trace information *** BAR being entered A1: 11 BAR (level 2) being entered A1: 1 BAR (level 2) = ERROR BAR = ERROR FOOBAR = NIL *** End of saved trace information *** 32: OFF TRCOUNT; FOOBAR(8) **************** BAR(24) ************************************************ 33: QUIT;