{Begin SubSec Creating Breaks with BREAK1} {Title Creating Breaks with BREAK1} {Text The basic function of the break package is {fn BREAK1}, which creates a break. A break appears to be a regular executive, with the prompt "{lisp :}", but {fn BREAK1} also detects and interpretes break commands ({PageRef Tag BreakCommands}). {FnDef {FnName BREAK1} {FnArgs BRKEXP BRKWHEN BRKFN BRKCOMS BRKTYPE ERRORN} {Type NLAMBDA} {Text If {index *PRIMARY* BRKWHEN Var}{arg BRKWHEN} (evaluated) is non-{lisp NIL}, a break occurs and commands are then taken from {index BRKCOMS Var}{arg BRKCOMS} or the terminal and interpreted. All inputs not recognized by {fn BREAK1} are simply passed on to the programmer's assistant. If {arg BRKWHEN} is {lisp NIL}, {index BRKEXP Var}{arg BRKEXP} is evaluated and returned as the value of {fn BREAK1}, without causing a break. When a break occurs, if {arg ERRORN} is a list whose {lisp CAR} is a number, {index ERRORMESS Fn}{fn ERRORMESS} ({PageRef Fn ERRORMESS}) is called to print an identifying message. If {arg ERRORN} is a list whose {lisp CAR} is not a number, {index ERRORMESS1 Fn}{fn ERRORMESS1} ({PageRef Fn ERRORMESS1}) is called. Otherwise, no preliminary message is printed. Following this, the message {lisp ({arg BRKFN} broken)}{index *PRIMARY* BRKFN Var} is printed. {note above is unclear} Since {fn BREAK1} itself calls functions, when one of these is broken, an infinite loop would occur. {fn BREAK1} detects this situation, and prints {lisp Break within a break on {arg FN}}, and then simply calls the function without going into a break. {indexX {Name Break within a break on} {Type Printed by system} {Info *PRIMARY*} {Text {lisp Break within a break on {arg FN}}} } The commands {breakcom GO}, {breakcom !GO}, {breakcom OK}, {breakcom !OK}, {breakcom RETURN} and {breakcom ^} are the only ways to leave {fn BREAK1}. The command {breakcom EVAL} causes {index BRKEXP Var}{arg BRKEXP} to be evaluated, and saves the value on the variable {var !VALUE}.{index !VALUE Var} Other commands can be defined for {fn BREAK1} via {index BREAKMACROS Var}{var BREAKMACROS} (below). {arg BRKTYPE}{index *PRIMARY* BRKTYPE Var} is {lisp NIL} for user breaks, {atom INTERRUPT}{index INTERRUPT Litatom} for control-H breaks, and {atom ERRORX}{index ERRORX Litatom} for error breaks. For breaks when {arg BRKTYPE} is not {lisp NIL}, {fn BREAK1} will clear and save the input buffer.{index input buffer} If the break returns a value (i.e., is not aborted via {breakcom ^}{index ^ BreakCom} or {index control-D (Interrupt Character)}control-D) the input buffer will be restored. }} The fourth argument to {fn BREAK1} is {index *PRIMARY* BRKCOMS Var}{arg BRKCOMS}, a list of break commands{index break commands} that {fn BREAK1} interprets and executes as though they were keyboard input. One can think of {arg BRKCOMS} as another input file which always has priority over the keyboard. Whenever {arg BRKCOMS}={lisp NIL}, {fn BREAK1} reads its next command from the keyboard. Whenever {arg BRKCOMS} is not {lisp NIL}, {fn BREAK1} takes {lisp (CAR BRKCOMS)} as its next command and sets {arg BRKCOMS} to {lisp (CDR BRKCOMS)}. For example, suppose the user wished to see the value of the variable {lisp X} {it after} a function was evaluated. He could set up a break with {arg BRKCOMS}={lisp (EVAL (PRINT X) OK)}, which would have the desired effect. Note that if {arg BRKCOMS} is not {lisp 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. The function {index TRACE FN}{fn TRACE} ({PageRef Fn TRACE}) uses {arg 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 {breakcom GO}, which causes the function to be evaluated and its value printed. Note: If an error occurs while interpreting the {arg BRKCOMS} commands, {arg BRKCOMS} is set to {lisp NIL}, and a full interactive break occurs. The break package has a facility for redirecting ouput to a file. All output resulting from {index BRKCOMS Var}{arg BRKCOMS} will be output to the value of the variable {var BRKFILE},{index *PRIMARY* BRKFILE Var} which should be the name of an open file. Output due to user typein is not affected, and will always go to the terminal. {var BRKFILE} is initially {lisp T}. {VarDef {Name BREAKMACROS} {Text {var BREAKMACROS} is a list of the form {lisp ( ({arg NAME{sub 1}} {arg COM{sub 11}} {ellipsis} {arg COM{sub 1n}}) ({arg NAME{sub 2}} {arg COM{sub 21}} {ellipsis} {arg COM{sub 2n}}) {ellipsis})}. Whenever an atomic command is given to {fn BREAK1}, it first searches the list {var BREAKMACROS} for the command. If the command is equal to {arg NAME{sub i}}, {fn BREAK1} simply appends the corresponding commands to the front of {arg BRKCOMS}, and goes on. If the command is not found on {var BREAKMACROS}, {fn BREAK1} then checks to see if it is one of the built in commands, and finally, treats it as a function or variable as before. If the command is not the name of a defined function, bound variable, or {fn LISPX} command, {fn BREAK1} will attempt spelling correction using {var BREAKCOMSLST}{index BREAKCOMSLST Var} as a spelling list.{index spelling lists}{index spelling correction} If spelling correction is unsuccessful, {fn BREAK1} will go ahead and call {fn LISPX} anyway, since the atom may also be a misspelled history command. For example, the command {breakcom ARGS} could be defined by including on {var BREAKMACROS} the form: {lispcode (ARGS (PRINT (VARIABLES LASTPOS T)))} }} {FnDef {FnName BREAKREAD} {FnArgs TYPE} {Text Useful within {var BREAKMACROS} for reading arguments. If {var BRKCOMS} is non-{lisp NIL} (the command in which the call to {fn BREAKREAD} appears was not typed in), returns the next break command from {var BRKCOMS}, and sets {var BRKCOMS} to {lisp (CDR BRKCOMS)}. If {var BRKCOMS} is {lisp NIL} (the command was typed in), then {fn BREAKREAD} returns either the rest of the commands on the line as a list (if {arg TYPE}={lisp LINE}) or just the next command on the line (if {arg TYPE} is not {lisp LINE}). For example, the {breakcom BT} command is defined as {lisp (BAKTRACE LASTPOS NIL (BREAKREAD 'LINE) 0 T)}. Thus, if the user types {breakcom BT}, the third argument to {fn BAKTRACE} will be {lisp NIL}. If the user types {lisp BT SUBRP}, the third argument will be {lisp (SUBRP)}. }} {VarDef {Name BREAKRESETFORMS} {Text 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. {var BREAKRESETFORMS} is designed to solve this problem. The user puts on {var BREAKRESETFORMS} expressions suitable for use in conjunction with {fn RESETFORM} or {fn RESETSAVE} ({PageRef Fn RESETSAVE}). When a break occurs, {fn BREAK1} evaluates each expression on {var BREAKRESETFORMS} {it before} any interaction with the terminal, and saves the values. When the break expression is evaluated via an {breakcom EVAL}, {breakcom OK}, or {breakcom GO}, {fn BREAK1} first restores the state of the system with respect to the various expressions on {var BREAKRESETFORMS}. When control returns to {fn BREAK1}, the expressions on {var BREAKRESETFORMS} are {it again} evaluated, and their values saved. When the break is exited with an {breakcom OK}, {breakcom GO}, {breakcom RETURN}, or {breakcom ^} command, by typing control-D, or by a {fn RETFROM} or {fn RETEVAL} typed in by the user, {fn 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. Note: All user type-in is scanned in order to make the operations undoable as described on {PageRef Tag SlashifyingTypedInFns}. At this point, {fn RETFROM}s and {fn RETEVAL}s are also noticed. However, if the user types in an expression which calls a function that then does a {fn RETFROM}, this {fn RETFROM} will not be noticed, and the effects of {var BREAKRESETFORMS} will {it not} be reversed. As mentioned earlier, {fn BREAK1} detects "Break within a break" situations, and avoids infinite loops. If the loop occurs because of an error, {fn BREAK1} simply rebinds {var BREAKRESETFORMS} to {lisp NIL}, and calls {fn HELP}. This situation most frequently occurs when there is a bug in a function called by {var BREAKRESETFORMS}. Note: {fn SETQ} expressions can also be included on {var BREAKRESETFORMS} for saving and restoring system parameters, e.g. {lisp (SETQ LISPXHISTORY NIL)}, {lisp (SETQ DWIMFLG NIL)}, etc. These are handled specially by {fn BREAK1} in that the current value of the variable is saved before the {fn SETQ} is executed, and upon restoration, the variable is set back to this value. }} }{End SubSec Creating Breaks with BREAK1} {Begin SubSec Signalling Errors} {Title Signalling Errors} {Text {FnDef {FnName ERRORX} {FnArgs ERXM} {Text The entry to the error routines. If {arg ERXM}={lisp NIL}, {lisp (ERRORN)} is used to determine the error-message. Otherwise, {lisp (SETERRORN (CAR {arg ERXM}) (CADR {arg ERXM}))} is performed, "setting" the error number and argument. Thus following either {lisp (ERRORX '(10 T))} or {lisp (PLUS T)}, {lisp (ERRORN)} is {lisp (10 T)}. {fn ERRORX} calls {index BREAKCHECK FN}{fn BREAKCHECK}, and either induces a break or prints the message and unwinds to the last {index ERRORSET FN}{fn ERRORSET} ({PageRef Fn BREAKCHECK}). Note that {fn ERRORX} can be called by any program to intentionally induce an error of any type. However, for most applications, the function {fn ERROR} will be more useful. }} {FnDef {FnName ERROR} {FnArgs MESS1 MESS2 NOBREAK} {Text Prints {arg MESS1} (using {fn PRIN1}), followed by a space if {arg MESS1} is an atom, otherwise a carriage return. Then {arg MESS2} is printed (using {fn PRIN1} if {arg MESS2} is a string, otherwise {fn PRINT}). For example, {lisp (ERROR "NON-NUMERIC ARG" T)} prints {lispcode NON-NUMERIC ARG T} and {lisp (ERROR 'FOO "NOT A FUNCTION")} prints {lisp FOO NOT A FUNCTION}. If both {arg MESS1} and {arg MESS2} are {lisp NIL}, the message printed is simply {lisp ERROR}.{index ERROR Error} If {arg NOBREAK}={lisp T}, {fn ERROR} prints its message and then calls {fn ERROR!} (below). Otherwise it calls {lisp (ERRORX '(17 ({arg MESS1} . {arg MESS2})))}, i.e., generates error number 17, in which case the decision as to whether or not to break, and whether or not to print a message, is handled as per any other error. If the value of {var HELPFLAG} ({PageRef Var HELPFLAG}) is {lisp BREAK!}, a break will always occur, irregardless of the value of {arg NOBREAK}. Note: If {fn ERROR} causes a break, the "break expression" ({PageRef Term Break Expression}) will be {lisp (ERROR MESS1 MESS2 NOBREAK)}. Using the {lisp GO}, {lisp OK}, or {lisp EVAL} break commands ({PageRef (Break Command) OK}) will simply call {fn ERROR} again. It is sometimes helpful to design programs that call {fn ERROR} such that if the call to {fn ERROR} returns (as the result of using the {lisp RETURN} break command), the operation is tried again. This allows the use to fix any problems within the break environment, and try to continue the operation. }} {FnDef {FnName HELP} {FnArgs MESS1 MESS2 BRKTYPE} {Text Prints {arg MESS1} and {arg MESS2} similar to {fn ERROR}, and then calls {index BREAK1 FN}{fn BREAK1} passing {arg BRKTYPE} as the {lisp BRKTYPE} argument. If both {arg MESS1} and {arg MESS2} are {lisp NIL}, {lisp Help!}{index Help! Error} is used for the message. {fn HELP} is a convenient way to program a default condition, or to terminate some portion of a program which the computation is theoretically never supposed to reach. }} {FnDef {FnName SHOULDNT} {FnArgs MESS} {Text Useful in those situations when a program detects a condition that should never occur. Calls {fn HELP} with the message arguments {arg MESS} and {lisp "Shouldn't happen!"} and a {lisp BRKTYPE} argument of {lisp 'ERRORX}.{index Shouldn't happen! Error} }} {FnDef {FnName ERROR!} {FnArgs} {Text Programmable control-E;{index control-E (Interrupt Character)} immediately returns from last {index ERRORSET FN}{fn ERRORSET} or resets. {note FROM the last ERRORSET, or TO ???} }} {FnDef {FnName RESET} {FnArgs} {Text Programmable control-D;{index control-D (Interrupt Character)} immediately returns to the top level. }} {FnDef {FnName ERRORN} {FnArgs} {Text Returns information about the last error in the form {lisp ({arg NUM} {arg EXP})} where {arg NUM} is the error number {index Error numbers}({PageRef Term Error Numbers}) and {arg EXP} is the expression which was printed out after the error message. For example, following {lisp (PLUS T)}, {lisp (ERRORN)} would return {lisp (10 T)}. }} {FnDef {FnName SETERRORN} {FnArgs NUM MESS} {Text Sets the value returned by {lisp ERRORN} to {lisp ({arg NUM} {arg MESS})}. }} {FnDef {FnName ERRORMESS} {FnArgs U} {Text Prints message corresponding to an {fn ERRORN} that yielded {arg U}. For example, {lisp (ERRORMESS '(10 T))} would print {lispcode NON-NUMERIC ARG T} }} {FnDef {FnName ERRORMESS1} {FnArgs MESS1 MESS2 MESS3} {Text Prints the message corresponding to a {fn HELP} or {fn ERROR} break. }} {FnDef {FnName ERRORSTRING} {FnArgs X} {Text Returns as a new string the message corresponding to error number {arg X}, e.g., {lisp (ERRORSTRING 10)}={lisp "NON-NUMERIC ARG"}. }} }{End SubSec Signalling Errors} {Begin SubSec Catching Errors} {Title Catching Errors} {Text All error conditions are not caused by program bugs. For some programs, it is reasonable for some errors to occur (such as file not found errors) and it is possible for the program to handle the error itself. There are a number of functions that allow a program to "catch" errors, rather than abort the computation or cause a break. {FnDef {FnName ERRORSET} {FnArgs FORM FLAG {anonarg}} {Text Performs {lisp (EVAL {arg FORM})}. If no error occurs in the evaluation of {arg FORM}, the value of {fn ERRORSET} is a list containing one element, the value of {lisp (EVAL {arg FORM})}. If an error did occur, the value of {fn ERRORSET} is {lisp NIL}. {fn ERRORSET} is a lambda function, so its arguments are evaluated {it before} it is entered, i.e., {lisp (ERRORSET X)} means {fn EVAL} is called with the {it value} of {lisp X}. In most cases, {fn ERSETQ} and {fn NLSETQ} (below) are more useful. Performance Note: When a call to {fn ERSETQ} or {fn NLSETQ} is compiled, the form to be evaluated is compiled as a separate function. However, compiling a call to {fn ERRORSET} does not compile {arg FORM}. Therefore, if {arg FORM} performs a lengthy computation, using {fn ERSETQ} or {fn NLSETQ} can be much more efficient than using {fn ERRORSET}. The argument {arg FLAG} controls the printing of error messages if an error occurs. Note that if a {it break} occurs below an {fn ERRORSET}, the message is printed regardless of the value of {arg FLAG}. If {arg FLAG}={lisp T}, the error message is printed; if {arg FLAG}={lisp NIL}, the error message is not printed (unless {var NLSETQGAG} is {lisp NIL}, see below). If {arg FLAG}={lisp INTERNAL}, this {fn ERRORSET} is ignored for the purpose of deciding whether or not to break or print a message (see {PageRef Tag WhenToBreak}). However, the {fn ERRORSET} is in effect for the purpose of flow of control, i.e., if an error occurs, this {fn ERRORSET} returns {lisp NIL}. If {arg FLAG}={lisp NOBREAK}, no break will occur, even if the time criterion for breaking is met (see {PageRef Tag WhenToBreak}). Note that {arg FLAG}={lisp NOBREAK} will {it not} prevent a break from occurring if the error occurs more than {var HELPDEPTH} function calls below the errorset, since {fn BREAKCHECK} will stop searching before it reaches the {fn ERRORSET}. To guarantee that no break occurs, the user would also either have to reset {var HELPDEPTH} or {var HELPFLAG} ({PageRef Var HELPDEPTH}). }} {FnDef {FnName ERSETQ} {FnArgs FORM} {Type NLAMBDA} {Text Performs {lisp (ERRORSET '{arg FORM} T)}, evaluating {arg FORM} and printing error messages. }} {FnDef {FnName NLSETQ} {FnArgs FORM} {Type NLAMBDA} {Text Performs {lisp (ERRORSET '{arg FORM} NIL)}, evaluating {arg FORM} without printing error messages. }} {VarDef {Name NLSETQGAG} {Text If {var NLSETQGAG} is {lisp NIL}, error messages will print, regardless of the {arg FLAG} argument of {fn ERRORSET}. {var NLSETQGAG} effectively changes all {fn NLSETQ}s to {fn ERSETQ}s. {var NLSETQGAG} is initially {lisp T}. }} {Begin Note} Date: 9 NOV 1981 1858-PST From: KAPLAN.PA Subject: ERROR, NLSETQ, and FNCHECK Tracking down a user-bug led me into a thicket of non-features in the error machinery. NLSETQ First, as is well known, NLSETQ does not guarantee that an error message won't be printed and a break not happen. The reason is that it doesn't consult the flag that affects the behavior if it doesn't find it after scanning HELPDEPTH frames. This is obviously a bug, which I propose to fix by not bounding FINDERSET inside BREAKCHECK by HELPDEPTH, letting the search go all the way to the top if necessary, then using the number of frames scanned and HELPDEPTH only to determine what should then happen. A much more efficient implementation would be possible if the compiler for ERRORSETs bound a SPECVAR ERRORSET.FLG in that frame, so a simple free-variable lookup instead of a stack scan can be used as an initial filter. Can this be done uniformly in the various implementations?? Second, the HELPDEPTH criterion currently seems to count all frames on the stack. Since the value of HELPDEPTH is a heuristic set up for the 10 implementation, it is not really appropriate for other implementations where the number of internal frames on the stack differs wildly, particularly when there isn't a block compiler. I propose changing this to count only REALFRAMEP's. ERROR ERROR has a third argument, NOBREAK, which if T currently means print out the error message and call ERROR!. The bug in this is that BREAKCHECK is not called to determine, e.g., whether this is an error under an NLSETQ or an ERSETQ. Thus, a client of a function that has this kind of call to ERROR cannot suppress the error message or printing, even if he is prepared to handle the error situation himself. I think this is also a bug, and propose changing ERROR to first call BREAKCHECK to determine whether there is an NLSETQ above (or alternatively, to look at ERRORSET.FLG), and if so, let the behavior appropriate to NLSETQ prevail. IN other words, the NOBREAK argument to ERROR would operate only if under an ERSETQ. FNCHECK The reason I got into this is that SMARTARGLIST calls FNCHECK with a flag indicating that the FNCHECK error should not be suppressed. FNCHECK then calls ERROR, with a 3rd argument of T (NOBREAK) just in case neither LOAD nor LOADFROM frames exist on the stack. This is obviously a crock. Does anyone know what's so special about LOAD and LOADFROM?? In my particular case, with LOAD not on the stack, embedding the SMARTARGLIST in an NLSETQ did not suppress the error, but did suppress the user. ----- Are there any reasonable objections to my proposal to change FINDERSET and ERROR as above? Can the compiler guys install the specvar? Any hypotheses about LOAD/LOADFROM in FNCHECK? {End Note} Occasionally the user may want to treat certain types of errors differently from others, e.g., always break, never break, or perhaps take some corrective action. This can be accomplished via {var ERRORTYPELST}: {VarDef {Name ERRORTYPELST} {Text {var ERRORTYPELST} is a list of elements, where each element is of the form {lisp ({arg NUM} {arg FORM{sub 1}} {ellipsis} {arg FORM{sub N}})}. {arg NUM} is one of the error numbers {index Error numbers}({PageRef Term Error Numbers}). During an error, after {index BREAKCHECK FN}{fn BREAKCHECK} has been completed, but before any other action is taken, {var ERRORTYPELST} is searched for an element with the same error number as that causing the error. If one is found, the corresponding forms are evaluated, and if the last one produces a non-{lisp NIL} value, this value is substituted for the offender, and the function causing the error is reentered. Note: {var ERRORTYPELST} is accessed as a special variable (see {PageRef Term Special Variables}), so it can be rebound in a function argument list of {fn PROG} form to catch errors in a dynamic context. }} {note offender?? how substituted? how reentered? (by EVALing BRKEXP??)} Within {var ERRORTYPELST} entries, the following variables may be useful: {VarDef {Name ERRORMESS} {Text {fn CAR} is the error number, {fn CADR} the "offender", e.g., {lisp (10 NIL)} corresponds to a {lisp NON-NUMERIC ARG NIL} error. }} {VarDef {Name ERRORPOS} {Text Stack pointer to the function in which the error occurred, e.g., {lisp (STKNAME ERRORPOS)} might be {fn IPLUS}, {fn RPLACA}, {fn INFILE}, etc. Note: If the error is going to be handled by a {fn RETFROM}, {fn RETTO}, or a {fn RETEVAL} in the {var ERRORTYPELST} entry, it probably is a good idea to first release the stack pointer {var ERRORPOS}, e.g. by performing {lisp (RELSTK ERRORPOS)}. }} {VarDef {Name BREAKCHK} {Text Value of {fn BREAKCHECK}, i.e., {lisp T} means a break will occur, {lisp NIL} means one will not. This may be reset within the {var ERRORTYPELST} entry. }} {VarDef {Name PRINTMSG} {Text If {lisp T}, means print error message, if {lisp NIL}, don't print error message, i.e., corresponds to second argument to {fn ERRORSET}. The user can force or suppress the printing of error messages for various error types by including on {var ERRORTYPELST} an expression which explicitly sets {var PRINTMSG}. }} For example, putting {lispcode [10 (AND (NULL (CADR ERRORMESS)) (SELECTQ (STKNAME ERRORPOS) ((IPLUS ADD1 SUB1) 0) (ITIMES 1) (PROGN (SETQ BREAKCHK T) NIL]} on {var ERRORTYPELST} would specify that whenever a {lisp NON-NUMERIC ARG - NIL} error occurred, and the function in question was {fn IPLUS}, {fn ADD1}, or {fn SUB1}, {lisp 0} should be used for the {lisp NIL}. If the function was {fn ITIMES}, {lisp 1} should be used. Otherwise, always break. Note that the latter case is achieved not by the value returned, but by the {it effect} of the evaluation, i.e., setting {var BREAKCHK} to {lisp T}. Similarly, {lisp (16 (SETQ BREAKCHK NIL))} would prevent {lisp END OF FILE} errors from ever breaking. {var ERRORTYPELST} is initially {lisp ((23 (SPELLFILE (CADR ERRORMESS) NIL NOFILESPELLFLG)))}, which causes {index SPELLFILE FN}{fn SPELLFILE} to be called in case of a {lisp FILE NOT FOUND} error (see {PageRef Fn SPELLFILE}). If {fn SPELLFILE} is successful, the operation will be reexecuted with the new (corrected) file name. }{End SubSec Catching Errors} ?1(DEFAULTFONT 1 (GACHA 10) (GACHA 8) (TERMINAL 8)) ?1(DEFAULTFONT 1 (GACHA 10) (GACHA 8) (TERMINAL 8)) ZÂZÂzº