The pymdl book
19. Errors, frames, etc.
16.1. LISTEN
This SUBR takes any number of arguments. It first checks the LVALs of
INCHAN, OUTCHAN, and OBLIST for reasonability and terminal usability. In
each case, if the value is unreasonable, the ATOM is rebound to the
corresponding GVAL, if reasonable, or to an invented reasonable value.
LISTEN then does <TTYECHO .INCHAN T> and <ECHOPAIR .INCHAN .OUTCHAN>.
Next, it PRINTs its arguments, then PRINTs
LISTENING-AT-LEVEL i PROCESS p
where i is an integer (FIX) which is incremented each time LISTEN is called
recursively, and p is an integer identifying the PROCESS (chapter 23) in which
the LISTEN was EVALed. LISTEN then does <APPLY <VALUE REP>>, if there is
one, and if it is APPLICABLE. If not, it applies the SUBR REP (without
making a new FRAME -- see below). This SUBR drops into an infinite
READ-EVAL-PRINT loop, which can be left via ERRET (section 16.4).
The standard LISTEN loop has two features for getting a handle on objects that
you have typed in and MDL has typed out. If the ATOM L-INS has a local value
that is a LIST, LISTEN will keep recent inputs (what READ returns) in it,
most recent first. Similarly, if the ATOM L-OUTS has a local value that is a
LIST, LISTEN will keep recent outputs (what EVAL returns) in it, most
recent first. The keeping is done before the PRINTing, so that ^S does not
defeat its purpose. The user can decide how much to keep around by setting the
length of each LIST. Even if L-OUTS is not used, the atom LAST-OUT is
always SET to the last object returned by EVAL in the standard LISTEN loop.
Example:
<SET L-INS (NEWEST NEWER NEW)>$
(NEWEST NEWER NEW)
.L-INS$
(.L-INS NEWEST NEWER)
<SET FOO 69>$
69
<SET FIXIT <2 .L-INS>> ;"grab the last input"$
<SET FOO 69>
.L-INS$
(.L-INS <SET FIXIT <2 .L-INS>> <SET FOO 69>)
<PUT .FIXIT 3 105>$
<SET FOO 105>
<EVAL .FIXIT>$
105
.L-INS$
(.L-INS <EVAL .FIXIT> <PUT .FIXIT 3 105>)
.FOO$
105
That transcript is shown rather than run, because it is a conversation with a
listener rather than a sequence of evaluations -- .L-INS only fills up because
a listener is reading the lines. It is worth reading closely all the same:
the LIST never grows, each new input pushes the oldest off the end, and FIXIT
ends up holding the actual FORM that was typed, so PUTting into it edits a
command you already gave and EVAL runs the edited version. That is a command
history you can PUT into, in 1979, in eight SUBRs.
pymdl's own. The listener here is the same loop, including the level counter and the
PROCESSnumber in the banner, because chapter 37 needs pymdl's banners to be comparable with MDL 55's word for word. What differs is the terminal:^Sand^Gbelow are real keys on a real terminal, and a pymdl session is as often a pipe. Chapter 2 has what the driver does instead, and chapter 29's debugger is a program that drives this loop deliberately.
16.2. ERROR
This SUBR is the same as LISTEN, except that (1) it generates an interrupt
(chapter 24), if enabled, and (2) it PRINTs *ERROR* before PRINTing its
arguments.
When any SUBR or FSUBR detects an anomalous condition (for example, its
arguments are of the wrong TYPE), it calls ERROR with at least two arguments,
including:
- an
ATOMwhosePNAMEdescribes the problem, normally from theOBLISTERRORS!-(appendix C), - the
ATOMthat names theSUBRorFSUBR, and - any other information of interest,
and then returns whatever the call to ERROR returns. Exception: a few (for
example DEFINE) will take further action that depends on the value returned.
This nonstandard action is specified in the error message (first ERROR
argument).
Point 1 is literally true, and it is the reason chapter 18 listed ERRORS!-
among the initial OBLISTs:
<GET <ERRORS> OBLIST> ⇒ ERRORS
<LOOKUP "OUT-OF-BOUNDS" <ERRORS>> ⇒ OUT-OF-BOUNDS!-ERRORS
<LOOKUP "NOT-AN-ERROR-NAME" <ERRORS>> ⇒ #FALSE ()
An error message is not a string; it is an ATOM on a particular OBLIST, which
is why a program can compare against it and why the trailer prints.
Measured, MDL 55. Which
ATOM, and which extra arguments, is the single most heavily measured thing in this project. The banner aSUBRprints is the most visible way an interpreter can be subtly wrong, so pymdl's table of them was not derived from this chapter or from appendix C -- every entry was obtained by running the offending form through MDL 55 under the emulator and through pymdl and diffing the output, over 720 generated forms. Chapter 6's box has what that turned up about how irregular the era's own choices are, chapter 37 has how the measuring is done, and appendix E has the differences that remain.
16.3. FRAME (the TYPE)
A FRAME is the object placed on a PROCESS's control stack (chapter 23)
whenever a SUBR, FSUBR, RSUBR, or RSUBR-ENTRY (chapter 33) is applied.
(These objects are herein collectively called "Subroutines".) It contains
information describing what was applied, plus a TUPLE whose elements are the
arguments to the Subroutine applied. If any of the Subroutine's arguments are to
be evaluated, they will have been by the time the FRAME is generated.
A FRAME is an anomalous TYPE in the following ways:
- It cannot be typed in. It can be generated only by applying a Subroutine.
- It does not type out in any standard format, but rather as
#FRAMEfollowed by thePNAMEof the Subroutine applied.
16.3.1. ARGS
<ARGS frame>
("arguments") returns the argument TUPLE of frame.
16.3.2. FUNCT
<FUNCT frame>
("function") returns the ATOM whose G/LVAL is being applied in frame.
16.3.3. FRAME (the SUBR)
<FRAME frame>
returns the FRAME stacked before frame or, if there is none, it will
generate an error. The oldest (lowest) FRAME that can be returned without
error has a FUNCT of TOPLEVEL. If called with no arguments, FRAME returns
the topmost FRAME used in an application of ERROR or LISTEN, which was
bound by the interpreter to the ATOM LERR\ !-INTERRUPTS ("last error").
<TYPE <FRAME>> ⇒ FRAME
<FUNCT <FRAME>> ⇒ LISTEN
<TYPE <ARGS <FRAME>>> ⇒ TUPLE
LERR\ !-INTERRUPTS is the third of the interpreter's own backslashed ATOMs,
after LPROG\ and LMAP\ in chapter 13, and it works the same way: an ordinary
local value on the INTERRUPTS oblist, rebound by each ERROR or LISTEN.
16.3.4. Examples
Say you have gotten an error. You can now type at ERROR's LISTEN loop and
get things EVALed. For example,
<FUNCT <FRAME>>$
ERROR
<FUNCT <FRAME <FRAME>>>$
the-name-of-the-Subroutine-which-called-ERROR:atom
<ARGS <FRAME <FRAME>>>$
the-arguments-to-the-Subroutine-which-called-ERROR:tuple
16.4. ERRET
<ERRET any frame>
This SUBR ("error return") (1) causes the control stack to be stripped down to
the level of frame, and (2) then returns any. The net result is that the
application which generated frame is forced to return any. Additional side
effects that would have happened in the absence of an error may not have
happened.
The second argument to ERRET is optional, by default the FRAME of the last
invocation of ERROR or LISTEN.
If ERRET is called with no arguments, it drops you all the way down to
the bottom of the control stack -- before the level-1 LISTEN loop --
and then calls LISTEN. As always, LISTEN first ensures that MDL is
receptive.
Examples:
<* 3 <+ a 1>>$
*ERROR*
ARG-WRONG-TYPE
+
LISTENING-AT-LEVEL 2 PROCESS 1
<ARGS <FRAME <FRAME>>>$
[a 1]
<ERRET 5>$ ;"This causes the + to return 5."
15 ;"finally returned by the *"
Note that when you are in a call to ERROR, the most recent set of bindings is
still in effect. This means that you can examine values of dummy variables while
still in the error state. For example,
<DEFINE F (A "AUX" (B "a string"))
#DECL ((VALUE) LIST (A) STRUCTURED (B) STRING)
(.B <REST .A 2>) ;"Return this LIST.">$
F
<F '(1)>$
*ERROR*
OUT-OF-BOUNDS
REST
LISTENING-AT-LEVEL 2 PROCESS 1
.A$
(1)
.B$
"a string"
<ERRET '(5)> ;"Make the REST return (5)."$
("a string" (5))
Both transcripts are shown rather than run, since each is a dialogue with an
error listener. The second is the more important one to understand: after the
error, .A and .B still answer, because the error did not unwind anything --
ERROR is an ordinary Subroutine call stacked on top of the failing one. That
is what makes MDL's error handling a debugger rather than a report.
pymdl's own. The whole of section 16.4 works here, and chapter 29 is where it is put to work: the debugger of the environment manual is built out of
FRAME,ARGS,FUNCT,ERRETandRETRYand nothing else. Driving an error listener from a script rather than a terminal is whattools/mdl55_errhandler.pyexists for on the measuring side, since a stateful error handler is the only way to ask MDL 55 seven hundred questions without restarting it seven hundred times (chapter 37).
16.5. RETRY
<RETRY frame>
causes the control stack to be stripped down just beyond frame, and then causes
the Subroutine call that generated frame to be done again. frame is optional, by
default the FRAME of the last invocation of ERROR or LISTEN. RETRY
differs from AGAIN in that (1) it is not intended to be used in programs; (2) it
can retry any old frame (any Subroutine call), whereas AGAIN requires an
ACTIVATION (PROG or REPEAT or "ACT"); and (3) if it retries the EVAL of
a FORM that makes an ACTIVATION, it will cause rebinding in the argument
LIST, thus duplicating side effects.
16.6. UNWIND
UNWIND is an FSUBR that takes two arguments, usually FORMs. It EVALs the
first one, and, if the EVAL returns normally, the value of the EVAL call is
the value of UNWIND. If, however, during the EVAL a non-local return
attempts to return below the UNWIND FRAME in the control stack, the second
argument is EVALed, its value is ignored, and the non-local return is
completed. The second argument is evaluated in the environment that was present
when the call to UNWIND was made. This facility is useful for cleaning up data
bases that are in inconsistent states and for closing temporary CHANNELs that
may be left around. FLOAD sets up an UNWIND to close its CHANNEL if the
user attempts to ERRET without finishing the FLOAD. Example:
<DEFINE CLEAN ACT ("AUX" (C <OPEN "READ" "A FILE">))
#DECL ((C) <OR CHANNEL FALSE> ...)
<COND (.C
<UNWIND <PROG () ... <CLOSE .C>>
<CLOSE .C>>)>>
Both halves are testable, and both hold:
<SETG CLEANED <>> ⇒ #FALSE ()
<UNWIND <+ 1 2> <SETG CLEANED T>> ⇒ 3
,CLEANED ⇒ #FALSE ()
-- a normal return gives the first argument's value and the cleanup does not run. And:
<SETG CLEANED <>> ⇒ #FALSE ()
<PROG OUT () <UNWIND <RETURN 9 .OUT> <SETG CLEANED T>>> ⇒ 9
,CLEANED ⇒ T
-- a RETURN past the UNWIND runs the cleanup and then completes the return.
It reaches across a function call too, which is the case that matters for the
CLEAN above:
<SETG CLEANED <>> ⇒ #FALSE ()
<DEFINE INNER (A) <RETURN 7 .A>> ⇒ INNER
<DEFINE OUTER ACT () <UNWIND <INNER .ACT> <SETG CLEANED T>>> ⇒ OUTER
<OUTER> ⇒ 7
,CLEANED ⇒ T
16.7. Control-G (^G)
Typing control-G (^G, <ASCII 7>) at MDL causes it to act just as if an error
had occurred in whatever was currently being done. You can then examine the
values of variables as above, continue by applying ERRET to one argument (which
is ignored), RETRY a FRAME lower on the control stack, or flush everything by
applying ERRET to no arguments.
16.8. Control-S (^S)
Typing control-S (^S, <ASCII 19>) at MDL causes it to stop what is happening
and return to the FRAME .LERR\ !-INTERRUPTS, returning the ATOM T. (In
the Tenex and Tops-20 versions, ^O also has the same effect.)
These are the two keys chapter 10 told you to know before trying its
self-referencing structures, and now they can be stated exactly: ^G stops and
keeps the stack so you can look around, ^S stops and abandons the
computation back to the last listener.
16.9. OVERFLOW
<OVERFLOW false-or-any>
There is one error that can be disabled: numeric overflow and underflow caused by
the arithmetic SUBRs (+, -, *, /). The SUBR OVERFLOW takes one
argument: if it is of TYPE FALSE, under/overflow errors are disabled;
otherwise they are enabled. The initial state is enabled. OVERFLOW returns T
or #FALSE (), reflecting the previous state. Calling it with no argument
returns the current state.
<OVERFLOW> ⇒ T
<* 34359738367 2> ⇒ *ERROR* ARITHMETIC-OVERFLOW
<OVERFLOW <>> ⇒ T
<OVERFLOW> ⇒ #FALSE ()
<* 34359738367 2> ⇒ -2
<OVERFLOW T> ⇒ #FALSE ()
pymdl's own. With overflow disabled the answer is
-2, which is the PDP-10 word wrapping, not Python's unbounded integers. That is deliberate and it is the whole reason this section is interesting here: chapter 9 established that aFIXis 36 bits, and this section is the only documented way to see the wrap-around a 36-bit machine actually did. pymdl therefore carries the era's word width through+,-,*and/rather than letting a big integer escape. Chapter 21'sWORDarithmetic is the same width made explicit, and chapter 36 is where the words are real."The initial state is enabled" is also why the arithmetic of chapter 6 signals rather than silently growing: an era program that relies on overflow being an error gets an error.