The pymdl book
29. Program writing and debugging aids
MDL programs are data structures that can be inspected and changed while
an interpreter is running. The tools in this chapter use that property to
print definitions, edit them, trace calls, inspect the stack, and save work.
PP and EDIT are already loaded in a fresh pymdl. Other tools can be
loaded as needed; for example, <USE "TRACE"> loads the tracing package.
Choose a starting point according to the task:
| task | start here |
|---|---|
| Read a definition or inspect a suspended call | Pretty-printing and stack inspection |
| Change part of a function interactively | The MDL editor, then its worked examples |
| Load definitions or save an edited group | Loading and dumping |
| Follow evaluation one step at a time | The one-step debugger |
| Observe calls or changes to values | Tracing and monitors |
| Check a program before running it | CRITIC |
The complete command descriptions follow those workflows. Executable
examples use the supplied fact.mud group: a global LIMIT, a function
FACT, and a function FACTS that builds a list of factorials. The source
is in the example directory.
Chapter 3. Program Writing and Debugging Aids
This chapter concentrates on editing and debugging aids for MDL programming. The basis for editing and debugging in MDL is twofold: First, MDL is an interpreter, which permits interactive testing and debugging of software. Secondly, MDL programs (even compiled MDL programs) are structures and therefore may be manipulated by other MDL programs.
Packages useful in editing and debugging range from EDIT and PPRINT, which
are preloaded, and which form the core of most editing or debugging systems,
to more sophisticated aids such as DEBUGR and TRACE, which are more
powerful, and useful for more complicated debugging.
It should be noted that, in addition to the editors discussed below, RMODE [5] and EMACS [2], TECO based text editors, understand much of the syntax and many of the conventions of MDL programs.
3.1. Pretty-Printing
The purpose of pretty printing is to clarify the structure of MDL objects by
printing them in a more human-readable format than that provided by the
SUBRs PRINT, PRIN1, etc. Objects are pretty-printed through the
judicious insertion of spaces, tabs, and new-lines between tokens.
Pretty-printed objects are readable by the MDL Reader. Pretty printing is an
aid to understanding and debugging MDL FUNCTIONs or other objects. You
will probably find pretty printing to be extremely helpful, especially if you
are working without a listing or with an old listing. In fact,
pretty-printing is one way to make a new pretty listing after editing.
PPRINT is pre-loaded in most initial MDLs. The name of the package
containing PPRINT is "PP".
<PPRINT any channel>
pretty-prints any on channel. The second argument is optional, by default
.OUTCHAN. If any is an ATOM, PPRINT will enclose it in an application
of DEFINE, DEFMAC, SETG, or SET, as seems appropriate. COMMENTs
found inside any are right-justified. PPRINT cannot output an RSUBR
without FIXUPs (that is, one that was READ in while KEEP-FIXUPS (see
section 3.4) had no LVAL or had a FALSE LVAL); it will give the ERROR
message CAN-NOT-BE-DUMPED. PPRINT returns ,NULL, which is an ATOM
whose PNAME is a single rubout, invisible on normal consoles.
<PPRINF in:string-or-atom-or-list outfile:string
width:fix eval?:boolean>
pretty-prints all the contents of in into outfile.
If in is an ATOM or a LIST of ATOMs, its VALUE(s) are the objects to
be PPRINTed. In this case, outfile is by default a file whose first name
is produced by taking the PNAME of in (or in's first element, if in is a
LIST).
If in is a STRING, it specifies a file containing objects to PPRINT. In
this case, outfile is by default "TPL:".
width is the maximum width of output lines (although output lines are
prevented from being extremely long); it is optional, by default
<13 ,OUTCHAN>.
eval? tells PPRINF whether or not to EVAL everything in the file; it is
optional, by default a FALSE (don't EVAL). eval? is meaningless if in is
not a STRING.
PPRINF returns either "DONE" or a FALSE if it couldn't open infile or
outfile. PPRINF inserts page boundaries in outfile, between objects, every
60 lines or fewer; you may want to move these afterward to more logical
places. PPRINF binds KEEP-FIXUPS and REDEFINE to T, and QUICKPRINT
(see below) to a FALSE.
<GROUP-LOAD "fact.mud" FACTG> ⇒ FACTG
<==? <PPRINT FACT> ,NULL> ⇒ T
<ASCII <1 <SPNAME ,NULL>>> ⇒ 127
which prints, between the two answers,
<DEFINE FACT (N)
#DECL ((N) FIX)
<COND (<L? .N 2> 1) (ELSE <* .N <FACT <- .N 1>>>)>>
Measured, MDL 55.
,NULLis theATOMthe manual describes -- itsPNAMEis<ASCII 127>, andPPRINTreturns it -- and it lives onROOTthere, so a program that names it finds the interpreter's; pymdl once handed it back without interning it, which<USE "PP">noticed: the package'sENTRY NULLstopped withALREADY-USED-ELSEWHERE(chapter 28).
3.1.1. PPRINT Control Switches
PPRINT's output is affected by the local values of several ATOMs. Each
value is examined only for truth.
.QUICKPRINT
If this ATOM's LVAL is a FALSE, you are in slow mode; otherwise
(including the case of no LVAL), you are in fast mode. The behavioral
difference is this: in fast mode, there may be COMMENTs in the
pretty-printed object(s) which PPRINT misses. Also, fast mode is indeed
faster than slow mode. Fast mode is the default, that is, QUICKPRINT is
initially true. The modes are really distinguished by the depth of
recursion to which PPRINT resorts. In slow mode, it recurses all the way
down to every monad in the thing pretty-printed; in fast mode, it goes down
only far enough to find something that will fit on a line.
.LOOKAHEAD
PPRINT uses full recursive lookahead to avoid packing things against the
right margin and, as a result, not being able to fit things within the right
margin. The lookahead results in very good formatting of deeply-nested
MAPFed and FUNCTIONs; all but the most bizarre cases should be very
legible. However, it can result in noticeable 'pauses' in the printing
operation and, in some cases, a net speed slightly less than with limited
lookahead. Since this can be a disadvantage when using PPRINT
interactively on a heavily-loaded system, the lookahead can be disabled: if
the LVAL of LOOKAHEAD is a FALSE, no lookahead will be performed;
otherwise it happens. LOOKAHEAD is initially true, that is, lookahead
happens by default.
.VERTICAL
If LOOKAHEAD is a FALSE, the formatting can cause too many objects to be
squeezed against the right margin. So that particular cases can be made
legible, the format when lookahead is not in use can be manually set: if the
LVAL of VERTICAL is non-FALSE, PPRINT will indent very little
whenever indenting is called for. (VERTICAL being true means a 'more
vertical' format.) VERTICAL is initially FALSE. The value of VERTICAL
is ignored when LOOKAHEAD is true; the lookahead effectively chooses
different values for VERTICAL for different parts of the object
pretty-printed.
3.1.2. Lower-level Pretty Printing
It is sometimes desirable to use some of the functions that PPRINT uses,
but in a different way. For example, a specialized pretty-printer for
Program Abstracts would want to insert indented field names into the output
and pretty-print field values with the same indentation. The names of
lower-level pretty-print functions are included in the ROOT OBLIST for
such purposes.
<EPRINT any left-margin:fix>
pretty-prints any on .OUTCHAN to the right of left-margin. The second
argument is optional, by default <VALUE LEFT-MARGIN> (see below).
<EPRIN1 any left-margin:fix>
EPRIN1 is to EPRINT as PRIN1 is to PRINT.
.LEFT-MARGIN
This is the ATOM that EPRINT binds to its second argument. You can SET
it outside calls to EPRINT in order to make a permanent left margin. Its
initial LVAL is 0.
<INDENT-TO column:fix channel>
outputs tabs and/or spaces to advance the output column (<14 channel>) to
column, if it is not already past.
<COLPP any
channel
left-margin:fix
right-margin:fix>
pretty-prints any on channel (by default .OUTCHAN) between the margins
left-margin (by default <14 channel>, the current column) and right-margin
(by default <13 channel>, the rightmost column). All arguments but the
first are optional. COLPP returns ,NULL. For example,
<COLPP any .OUTCHAN 10 70> would leave a 10-character margin at left and
right on an 80-column OUTCHAN. Also,
<PROG () <PRINT AAAAAAAAAAAAAAA> <COLPP ,FOO>>
would result in output like
AAAAAAAAAAAAAAA #FUNCTION ((X GGGGGGGGGGGGGGGGGGGGGGGG)
<+ X 1>)
EPRINT, EPRIN1, and COLPP are affected by the truth of .QUICKPRINT,
.LOOKAHEAD, and .VERTICAL.
3.1.3. Ampersand Printing
'Ampersand printing' consists of printing any object on a single line by
using the character & (ampersand) to mean "There's more stuff here." (This
technique is borrowed from the InterLisp editor.)
There are two ways in which & is used by this printer as an abbreviation:
- An
&appearing between some variety of brackets indicates that there is a big object of the indicatedTYPEthere. - The characters
..&or&..on the left or right of a structure mean that there are more objects to the left or right which have not been printed.
Examples:
#FUNCTION ((A B C D) <&>)
This is a FUNCTION with four arguments in its argument LIST, and the
FUNCTION body contains one FORM which was too big to print in the
remainder of the line.
<PROG () <KRK <+ .A 5>> <PRINC .Q> <SET BAR <ORG>> <&> &..>
This is a large FORM, namely, a PROG. In addition to the elements
printed, there are more elements to the right, and there is one FORM which
was too big to fit.
Ampersand printing is effected by two pure RSUBRs: &, analogous to
PRINT, and &1, analogous to PRIN1. A related RSUBR, &LIS, can be
applied to no arguments to put you into an endless READ-EVAL-& loop,
instead of the normal READ-EVAL-PRINT loop.
pymdl's own.
&and&1were pureRSUBRs on the era, compiled fromMUDBUG;FRAND, and here they arefrand.mudinterpreted, loaded as theFR&tool. A minimal&is built in for a bare interpreter, and the loader clears it beforeFR&loads so the package'sENTRYcan take the name (chapter 28). The 1975 compiledFR&itself,MUDBUG;FR& NBIN, also runs here on the emulator, end to end over the bridgedFRAMEinternals (tests/test_era_binaries.py), which is how one of the boundary's coherence rules was found: it reads aCHANNEL's column back out of the channel vector withMOVE AC2,33(AC1)to decide on a second tab (36.4).<GROUP-LOAD "fact.mud" FACTG> ⇒ FACTG <&1 ,FACTS> ⇒ #FUNCTION (("OPTIONAL" (TOP ,LIMIT) "AUX" (L ())) #DECL ((TOP) FIX (L) LIST) <REPEAT ((I .TOP)) <COND (<0? .I> <RETURN .L>)> <SET L (<FACT .I> !.L)> <SET I <- .I 1>>>)prints
#FUNCTION(("OPTIONAL" (TOP ,LIMIT) "AUX" (L ())) #DECL(&) <&>)and answers its argument.
3.1.4. Examining the Stack
<FRM fix>
returns the fixth FRAME down from the top application of ERROR or
LISTEN.
<FRAMES how-many:fix start:fix>
pretty-prints how-many FRAMEs (by printing the FRAME number (suitable as
an argument to FRM), FUNCT, and ARGS of the FRAME), starting with
<FRM start>. Both arguments are optional; start defaults to 0, and
how-many defaults to a large integer. A FRAME whose FUNCT is an ATOM
whose VALUE is an FSUBR is not printed, if the same information is found
in the next lower FRAME.
<FR& how-many:fix start:fix>
is like FRAMES but uses ampersand printing instead of pretty printing. It
is handy for summarizing FUNCTs and ARGS that are large or unprintable
(like RSUBRs with no fixups).
<FRATM how-many:fix start:fix>
is like FRAMES but gives an abbreviated view of the stack. It prints
FUNCTs only, and only for FRAMEs connected with named FUNCTIONs,
RSUBRs, and RSUBR-ENTRYs. It is handy when a FRAME contains a
non-LEGAL? object.
<FRLVAL atom
how-many:fix
start:fix>
prints out the stacked bindings of atom, going through how-many FRAMEs,
starting with <FRM start>. The two numeric arguments are optional;
how-many defaults to a large integer, and start defaults to 0. The format
of the printing is two columns: the first column is the number of the FRAME
in which atom has a binding; the second column is the value bound, or a
message proclaiming the lack of a value.
<FR&VAL atom
how-many:fix
start:fix>
is precisely the same as FRLVAL, except that the values are ampersand
printed instead of PRINTed.
Finally, the "FRMSP" PACKAGE contains analogues of many of the preceding
functions, but each takes as its first argument a PROCESS, by default
<ME>. These are all named by adding a 'P' to the end of the usual name.
For example,
<FR&P <MAIN>>
does a <FR&> in the PROCESS MAIN.
There is one additional function of interest in "FRMSP".
<FRTYPE how-many:fix start:fix>
is like FRAMES, but gives only the TYPEs of the arguments to each. This
is useful in those situations when the stack shows illegal FRAMEs or other
unprintable objects.
Found along the way. The era's
FRAMESworked, and it worked because it was compiled. The survivingPPRINTsource reads<TYPE? ,FUNF FSUBR>where the line after it reads<AND <ASSIGNED? .FUNF> <TYPE? ..FUNF FSUBR>>-- theLVALof theLVAL-- and nothing in the archive everSETGsFUNF; every revision from 1976 to 1983 carries the line. Interpreted,FRAMEStherefore signalsUNASSIGNED-VARIABLE FUNFon the first frame whose function is a global, which is every one of them, here and on MDL 55. Disassembling MIT'spprint.nbinwith the era's ownUNASSM(chapter 35) showed the binary is not a fix -- the compiledFRAMEScarries the very same,FUNF-- and showed what saves it:<TYPE? ,FUNF FSUBR>compiles to a test on the value cell's type word, so the value never materialises, nothing checks assignment, and the unassigned cell simply reads as "not anFSUBR". pymdl reproduces both halves. Compiled with the native compiler (<PYCOMPILE FRAMES>, chapter 34),FRAMESwalks the chain and prints what MDL 55 printed, measured at an error level (its real use: the walk starts at the lastERRORorLISTENframe, so a bare mid-computation<FRAMES>sees onlyTOPLEVELbelow); interpreted, it still signals, and a test keeps it so, because if that ever changed it would mean era source had been patched on the way in, which this project does not do.FR&,FRATM,FRTYPEandFRLVALhave no such line and work either way:<USE "FRMSP"> <FR& 2> ⇒ TOPLEVEL <SET X 3> ⇒ 3 <FRLVAL X 5> <TYPE <FRM 0>> ⇒ FRAME(
FR&prints0 LISTEN []before answering;FRLVALprints its two-column table under aFrame----Valueheading, and at the listenerX's top-level value is not a stacked binding.)
3.2. The MDL Editor
EDIT allows a MDL user to make incremental changes in MDL structured
objects, without leaving MDL and with the ability to save the results in a
file, and to set or clear conditional breakpoints of various sorts in objects
that will be evaluated, such as FUNCTIONs.
EDIT is an editor/debugger written in, written for, and running under MDL.
It comprises the package "EDIT" and several smaller packages which will be
mentioned later in this section. EDIT is preloaded in most initial MDLs.
To start editing, apply EDIT to no arguments or to the name of the object
you wish to edit: <EDIT> causes entry into EDIT and opens the last object
edited; <EDIT object> causes entry into EDIT and opens object for
editing. Permissible objects include:
ATOMs. TheGVAL(preferably) or theLVALof theATOMis opened. If it has no value,EDITreturns aFALSE.- A
PRIMTYPELIST. ThePRIMTYPELISTis opened. - A
FIX. The stack frame with that number is opened (i.e.,<ARGS <FRM fix>>).
Part of EDIT's efficiency comes from forbidding it to delve into objects
that are not of PRIMTYPE LIST, that is, not LISTs, FORMs,
FUNCTIONs, etc. Attempts to edit objects of other PRIMTYPEs will result
in error messages. These objects can, however, be treated as units when
inserting, searching, etc.; or they can be changed into LISTs, edited, and
then changed back to their original types.
pymdl's own.
EDITreads its commands from.INCHANlike any MDL program, so it runs at pymdl's listener, and it runs headless from a channel of text, which is how the tests drive it:PTthenQon aFUNCTIONprints the function with the cursor;R 1 C 99 Qon(1 2 3)leaves,Las(1 99 3), the change made in place; a macro string runs throughM. Under the ITS personalityedit.mudsetsE-OVERPRINT, and the cursor glyph is the era's overstrike --Z, backspace,N, backspace,Z, backspace,N-- which a modern terminal shows as a smudge and which the manual's scanned transcripts below print as a black block. Under the Tenex personality it is|. The cursor is written here as▮.
3.2.1. The Edit 'LISTEN Loop'
3.2.1.1. The Reader
When in EDIT, you are typing at a special, non-standard, input function:
The EDIT Reader.
The Reader allows you to type EDIT commands and have them executed, and
also to evaluate MDL expressions normally. Its characteristics are as
follows:
-
As in the normal MDL Reader, nothing is done until you type ESC. DEL,
^L,^D,^G, and^Salso work normally. -
All
EDITcommands are terminated when an ESC is encountered in the input stream. In addition, most commands will terminate whenever the maximum number of arguments required has been input or whenever an argument of the wrong type is encountered. In the former case the next object is taken as a new command; in the latter case the object of the wrong type is taken as a new command.EDITcommands may be typed in either upper or lower case. -
If you type something that
EDITdoes not recognize as a command, normal MDL evaluation and printing are performed on that something. This evaluation will have no effect on your position in the object you are editing. -
While editing a function which is part of a
PACKAGE(determined from an examination of theOBLISTcontaining theATOMwhose value is the function),EDITcauses theOBLISTpath to be set up to what it was in the environment of thatPACKAGE. This has the advantage of reducing the number of trailers printed, and causes newly enteredATOMs to fall on the correctOBLIST(the internalOBLISTof thePACKAGE). It has the slight disadvantage that it disables the dynamic loader (which depends on unbound variables falling on theINITIALOBLIST). If theGVALofE-PKGis aFALSE, this feature is disabled, and the normalOBLISTpath is in effect during editing.R 5$
Causes execution of EDIT command R, with argument 5.
<R 5>$
Causes application of the function R to 5.
3.2.1.2. The Ampersand Printer
Your current position is displayed by 'ampersand printing' (see section
3.1.3). This consists of printing any object on a single line by using the
character & (ampersand) to mean "There's more stuff here."
The ampersand printer used in EDIT is much like the standard one, with the
addition that your current position (see below) is displayed by the glyph
▮.
When you initially enter EDIT, you are in a mode called "non-verbose," in
which ampersand printing is not automatically done following execution of
EDIT commands. The V command is used to toggle you in and out of verbose
mode (see below).
#FUNCTION ( ▮ (A B C D) <&>)
Indicates that your position is just to the left of a FUNCTION's argument
list, and the FUNCTION body contains one FORM which was too big to
print.
<..& <KRK <+ .A 5>> ▮ <SET BAR <ORG>> <&> &..>
Indicates that you are in the middle of a large FORM (e.g., a REPEAT or
a PROG), positioned just to the left of the <SET BAR <ORG>>. In
addition to the objects printed, there are more objects to both the left and
the right, and there is one FORM which was too large to fit on the line.
3.2.2. Edit Commands
3.2.2.1. General
A sequence of EDIT commands is executed as soon as you type ESC. If one
command fails, subsequent commands up to the ESC are ignored, and EDIT
types out an appropriate error message. A failing EDIT command generally
has no effect whatsoever; but see individual descriptions.
Note that all arguments to EDIT functions must be legal MDL objects. In
particular, you can't search for <SET, since the <>'s aren't balanced.
Nor can you insert it. (But you can, for instance, search for and insert
<SET THING 1>.)
If a command expects an argument and doesn't get one, an error message will be printed.
Many EDIT commands take FIXes as arguments. Those that do interpret the
ATOM * as an argument to mean 'as many as possible'.
Whenever you are in EDIT, you have a well-defined 'position'. A position
is a 'place' inside a MDL structure; this 'place' is either between two
elements of the structure, or between an element and either end of the
structure, or inside an empty structure. All editing, movement and printing
commands operate relative to your current position. The term 'cursor' is
used in the following descriptions to refer to an embodiment of a position.
The format used in each of the following command descriptions is:
Command as Typed English Name
Description
3.2.2.2. General Commands
? -- duh?
Causes a short summary of all EDIT commands to be typed out. The same
summary appears later in this chapter.
?? -- huh?
Similar to the above, but the summary is even shorter, and should fit entirely on the screen of an Imlac terminal.
Q -- Quit
Leave EDIT and return to MDL. (Causes EDIT to return the ATOM T.)
QR fix -- Quit and Retry
Quit from EDIT and then retry the frame specified, or by default, the one
originally given to an open command or, if none was given, the frame beneath
the last ERROR or LISTEN frame.
^F -- Control-F
This is not really an EDIT command; rather, it is a character, obtained
from the input stream at interrupt level, which is used to return you to the
EDIT Reader from some higher level of application, e.g., an ERROR's
LISTEN. It is the EDIT equivalent of ERRET with no arguments.
^F (or ^S) typed during execution of an EDIT command is similar to
normal MDL ^S but returns to the EDIT Reader instead of the MDL LISTEN
loop.
O object -- Open
Equivalent to Q followed by <EDIT object>. Positions the cursor just to
the left of the first element of the entire object specified.
OT -- Open This
If the object to the right of the cursor is an ATOM, or a FORM whose
first element is an ATOM, and the ATOM's value is openable, then it is
opened. This command is useful when tracing a calling sequence through
several functions.
3.2.2.3. Movement Commands
UT -- Up to the Top
Places the cursor at the position it had following an O.
R fix -- Right
Moves the cursor fix objects to the right, by default one. If fix is too
large, i.e., there are not that many positions to the right of the current
position, EDIT prints an error comment and the cursor stays where it is.
B -- Back
Moves the cursor as far to the right as possible.
L fix -- Left
Moves the cursor fix positions to the left, by default one. If fix is too
large, EDIT prints an error message.
F -- Front
Moves the cursor as far to the left as possible.
DL -- Down Left
Positions the cursor just to the right of the rightmost element within the
object to the left of the cursor, if that object is of PRIMTYPE LIST.
Visually, the cursor moves left over one 'close bracket'.
DR -- Down Right
Positions the cursor just to the left of the leftmost element within the
object to the right of the cursor, if that object is of PRIMTYPE LIST.
Visually, the cursor moves right over one 'open bracket'. If the cursor is
to the left of an element that is not of PRIMTYPE LIST, EDIT prints an
error message.
D -- Down
Equivalent to DR.
UR fix -- Up Right
Positions the cursor just to the right of the object the cursor is currently within. Does so fix times, by default once.
UL fix -- Up Left
Positions the cursor just to the left of the object the cursor is currently within. Does so fix times, by default once.
U fix -- Up
Identical to UL.
S object -- Search
Does a depth-first, left-first tree-walk, (i.e., left-to-right) starting
with the object to the right of the cursor, until the cursor is just to the
right of an object structurally equal (i.e., =?) to its argument. An
occurrence of the object will not be found if it is inside anything not of
PRIMTYPE LIST. On failure, the cursor does not move. If the argument
is omitted, the last object searched for is used.
SR object -- Search Right
Same as S.
SL object -- Search Left
Same as S, but the tree-walk is depth-first, right-first (i.e.,
right-to-left) and you end up to the left of the object for which you were
searching.
3.2.2.4. Printing Commands
(the empty command)
Causes the normal 'ampersand print' to be done. This is principally useful
when you are in 'silent' mode: see the V command. By the way, an 'empty'
command is typed by typing ESC without having typed any visible characters
before it.
P -- Print
PPRINTs (not 'ampersand prints') the object to the right of the cursor.
PU -- Print Up
PPRINTs the object the cursor is in. This is similar to doing a U and
then a P, although the cursor is not moved.
PT -- Print Top
PPRINTs the whole object you have open.
V -- Verbosity
Toggles the verbosity mode between 'verbose' (most commands cause ampersand
printing) and 'silent' (printing of any sort is done only when some explicit
print command is used, or when an error occurs). The current state of
verbosity is the GVAL of E-VERBOSE.
In silent mode, absolutely nothing is printed after each command, not even new-lines or prompts. However, normal MDL evaluation still causes normal MDL printing.
3.2.2.5. Editing Commands
I any ... -- Insert
Inserts all its arguments immediately to the right of the cursor. None of
its arguments are evaluated; you can insert unevaluated FORMs without
using QUOTE. The cursor ends up to the right of the last object inserted.
G any ... -- Get
Same as I, but its arguments are evaluated. This is useful in conjunction
with the X command (see below).
I: type:atom fix -- Insert Type
Grabs fix objects to the right of the cursor, inserts them into a newly
created object of TYPE type, deletes them from the original structure, and
inserts the newly created object in their place. In other words, it
'inserts' the appropriate open and close brackets for type at the cursor and
fix objects to the right.
By default fix is one, type is LIST. An error message is printed if fix is
larger than the number of objects to the right of the cursor.
There is no way to directly insert or delete single parentheses, brackets,
etc., using EDIT. Instead, use K: (see below) to remove pairs of
brackets, and I: to insert them.
I* indicator:atom new-structure -- Imbed
Imbed looks for all occurrences of indicator in new-structure and replaces these occurrences with objects taken and deleted from the right of the cursor. It then inserts the result.
If only new-structure is given, the indicator is the ATOM *. If there
aren't enough objects to the right of the cursor to replace each indicator,
remaining indicators are left untouched and a warning message is printed.
If no indicators are found, the new structure is inserted, but a warning
message is printed.
I* is generally used to insert one or more structures into another complex
structure in one operation, instead of several. For example:
<SET X ▮ <12 .Y>>
I* <COND (<NOT <LENGTH? .Y 11>> *)>$
<SET X <COND (<NOT <LENGTH? .Y 11>> <12 .Y>)> ▮>
places a protective conditional around an NTH to prevent an out-of-bounds
error.
IG any ... -- Insert into Group
Inserts into a group. IG is similar to I, but assumes that the object
you are in is a group (as produced by GROUP-LOAD). Arguments to IG which
are not ATOMs are inserted as in I. Objects which are ATOMs and which
have a value insert a FORM which DEFINEs, SETGs, or SETs the ATOM
as appropriate. Thus, to add a new function F to a group G, one could
type
O G$IG F$Q$
K fix -- Kill
Deletes fix objects to the right of the cursor. Defaults to one. Negative fix causes deletion to the left of the cursor.
C any -- Change
Changes the one object to the right of the cursor to its single argument.
Does not move the cursor. Does not evaluate its argument. C is more
efficient than K plus I.
C: type:atom -- Change Type
Changes the type of object to the right of the cursor to type. Attempts to
do something reasonable for every type change. If you tell it to change a
STRING to a LIST, you get a LIST of CHARACTERs. If you attempt to
change a structure whose elements are other than CHARACTERs and STRINGs
to a STRING, you will get a MDL error.
K: -- Kill Type
Deletes the brackets around the object to the right of the cursor. I.e., kills the object and inserts its elements into the structure of which it was a part.
SU new old -- Substitute
The Substitute command takes two arguments. All occurrences of old from the current location to the end of the open object (actually a search-right is done) are replaced by new. Once the search for old fails, the command terminates, and the number of substitutions performed is printed. The cursor is left after the last object replaced.
X atom -- Transfer
SETs the atom to the object to the right of the cursor. X can be used
with K and G to move things around within the object being edited.
SW -- Swap
Swaps the two objects to right of the cursor, leaving the cursor pointing at
the same object. The effect is to move the cursor and the object it points
at one object to the right. Repeated SWs move cursor and object further
and further to the right.
3.2.2.6. Macro Facility
M macro -- Macro
Takes either a STRING or something which EVALs to a STRING and
performs all of the commands in the STRING. For complete assurance that
your commands will be done properly, put an ESC between commands.
IT fix macro -- Iterate
This command (also called DO) takes a fix and macro as if an argument to
M. This command will loop through the macro fix times or until an error
is generated. When the iteration ends, the user is told how many complete
passes have been made of the macro.
In both of the above commands, if an EDIT error is generated, the macro
will be terminated, and the macro itself will be printed, with an arrow
pointing to the offending command. The cursor will remain at the place where
the last legal command left it.
The SU command is, internally:
DO * "S old$L$C new$"
3.2.2.7. Cursors
Cursors are locations in objects being EDITed. In addition to the main
cursor, which is where editing occurs, other locations (also called cursors)
may be remembered. The main cursor may be moved to another cursor in a
single operation, potentially saving many motion commands. In large
FUNCTIONs cursors may also reduce confusion by distinguishing among
several similar areas of code.
UC -- Use Cursors
The PACKAGE for dealing with cursors is not normally loaded in an initial
MDL, so the UC command loads it and makes the cursor commands available.
The PACKAGE loaded is "CURSOR".
CU atom -- Cursor
CU takes an ATOM argument and SETs the ATOM to an object of type
CURSOR, which tries to be clever in the event you change the object.
Also, if you use the X command to name a substructure and then move copy
it with G or I, the cursors in the substructure will follow to the new
location.
There are some restrictions. Cursors in empty LISTs are okay but they
will not follow the object to new locations. Also this 'following' feature
is effective only at the first G or I after the X. To move the
substructure again you have to X again.
I* is somewhat incompatible with CURSORs. Cursors in Imbedded
structures will sometimes disappear.
GO cursor -- Go
GO takes a cursor (normally the LVAL of an ATOM previously given as an
argument to CU) and GOes to that position. If the cursor is illegal
(not in the current top-level structure), an error message will be printed
and you will remain in your previous position.
KC atom -- Kill Cursor
Kill the cursor assigned to atom.
PC -- Print Cursors
Prints all cursors in the structure to the right of the main cursor.
PA -- Print All Cursors
Prints all cursors in the currently open structure.
3.2.2.8. Breakpoints
BK predicate any ... -- Breakpoint
Inserts a breakpoint 'around' the object to the right of the cursor. Takes any number of arguments. Subsequently, whenever that object would have been evaluated, you instead hit a breakpoint function which:
- Evaluates predicate. If the value is
FALSE, evaluation continues as if there were no breakpoint. If the value is non-FALSE, or ifBKwas given no arguments: - Types
**BREAK**. - For each argument after the first that you gave
BK, typesarg = EVAL of arg - Enters
LISTEN.
You continue by applying ERRET to one argument, just as from an ERROR;
the argument's value is ignored.
Breakpoints are implemented by inserting a BREAKR (a PRIMTYPE LIST
with APPLYTYPE FORM) which consists of the function BREAKR and
arguments, including the object breakpointed. A breakpoint prints as a
glyph similar to the cursor:
▯object
If the ATOM SHORT-PRINT is assigned and FALSE, the actual BREAKR
LIST is printed.
The breakpoint function returns EVAL of the thing it is put 'around,' and
there are cases where this does not work. There are always equivalent
places that do work.
- Breakpoint on the first element of a
FORMdoes not work. Put it on the wholeFORM. - Breakpoint on a
LISTwhich is an argument to aCONDdoes not work. Put it on the firstFORMin theLIST.
BA predicate any ... -- Break After
Similar to BK, but puts the break point after the object at the cursor.
Its action is like that of BK except that the break occurs after the object
it is on is EVALed.
This sort of breakpoint prints like the 'before' sort, but with the glyph after the object broken:
object▯
The predicate for a BA breakpoint may check the value returned by EVAL
for the object the breakpoint is on. This value is assigned by BREAKR to
the ATOM VALUE.
KT -- Kill This
Removes the breakpoint (if any) from the object to the right of the cursor.
KB -- Kill Breakpoints
Removes all breakpoints in the currently open object.
3.2.2.9. Edit Monitors
There are several commands in EDIT which provide a simple interface to the
"MONITOR" PACKAGE. These allow placing of monitors on references to or
modifications of LVALs in interpreted MDL code.
For a more complete discussion of the use of monitors, see section 3.7.
UM -- Use Monitors
The PACKAGEs for dealing with monitors are not normally loaded in an
initial MDL, so the UM command loads them and makes the three commands for
creating monitors available. The PACKAGEs loaded are "MONITR", which is
the general monitor PACKAGE, and "EMONIT", which is the interface between
EDIT and "MONITR".
RW atom predicate any ... -- Read-write Monitor
The most general type of monitor that can be set is a read-write monitor.
It will catch any reference to or attempt to modify the LVAL of the atom
specified. The restrictions on placement of breakpoints also apply to
monitors, with the addition that a monitor on an LVAL must be placed after
that LVAL has become ASSIGNED?.
The second, third (and so on) arguments to RW are the same as those for
BK. The predicate may be dependent on either the new or old value of the
variable: These are available as the LVALs of NEWVAL and OLDVAL,
respectively.
When a monitor is triggered, it prints the type of monitor, the variable
being monitored, and any other information requested by the user, and then
calls LISTEN.
A monitor prints as yet another glyph:
▯[atom]object
where atom is the ATOM being monitored, and object is the object on which
the call to MONITOR is placed.
Edit monitors are objects of type BREAKR, and thus they are killed by the
same commands that kill normal breakpoints: KB, KT, and so on.
RM atom predicate any ... -- Read Monitor
RM is analogous to RW, but is only triggered by reading the variable.
WM atom predicate any ... -- Write Monitor
WM is analogous to RW, but is only triggered by writing the variable.
3.2.2.10. User-defined Edit Commands
It is possible to add user-defined commands to EDIT. The value of
EDIT-TABLE should be a VECTOR of STRINGs (commands) and APPLICABLE
objects. EDIT will search EDIT-TABLE before its own command table. If a
match is found, the APPLICABLE will be applied to three arguments: the
command string, the LOCATIVE containing the item currently being edited
(the immediately surrounding object) and the position in that item.
Note that user-defined commands should not be added except by constructing a
new value of EDIT-TABLE from the commands to be added and the old value.
Otherwise, any existing user-defined commands may be lost when new ones are
added.
The Monitor commands described in section 3.2.2.9 are effectively
'installed' user-defined commands. They add elements to EDIT-TABLE when
loaded by the UM command.
3.2.3. Examples
3.2.3.1. Simple Editing
Suppose you have the FUNCTION
#FUNCTION (('A) <EVAL .A>)
as the global value of the ATOM SIMP, and you wish to change it to
#FUNCTION (("BIND" B 'A) (<EVAL .A .B> .A))
using EDIT. The following example does just that; it includes doing the
editing and applying of SIMP to an argument. Console input and output are
shown below exactly as they would be in non-silent mode. (Console input
consists of those characters to the left of every $). Note that there is
nothing in SIMP which is big enough to warrant use of an &.
<EDIT SIMP>$
V$
#FUNCTION ( ▮ ('A) <EVAL .A>)
D$
( ▮ 'A)
I "BIND" B$
("BIND" B ▮ 'A)
S .A$
<EVAL .A ▮ >
I .B$
<EVAL .A .B ▮ >
UR$
#FUNCTION (("BIND" B 'A) <EVAL .A .B> ▮ )
I .A$
#FUNCTION (("BIND" B 'A) <EVAL .A .B> .A ▮ )
L 2$
#FUNCTION (("BIND" B 'A) ▮ <EVAL .A .B> .A)
I: LIST 2$
#FUNCTION (("BIND" B 'A) ▮ (<EVAL .A .B> .A))
<SIMP <+ 1 2>>$
(3 <+ 1 2>)
#FUNCTION (("BIND" B 'A) ▮ (<EVAL .A .B> .A))
Q$T
3.2.3.2. X and G Commands
In this example we have the FUNCTION
<DEFINE F (X)
<G .X 10>
<H 23 <- .X 1>>>$
By applying the X and G commands to the appropriate FORMs, we are able
to swap the FORMs within the FUNCTION.
<DEFINE F (X)
<G .X 10>
<H 23 <- .X 1>>>$
F
<EDIT F>$
$
#FUNCTION ( ▮ (X) <G .X 10> <H 23 <- .X 1>>)
R$$
#FUNCTION ((X) ▮ <G .X 10> <H 23 <- .X 1>>)
X MOVER$
#FUNCTION ((X) ▮ <G .X 10> <H 23 <- .X 1>>)
K$$
#FUNCTION ((X) ▮ <H 23 <- .X 1>>)
R$$
#FUNCTION ((X) <H 23 <- .X 1>> ▮ )
G .MOVER$$
#FUNCTION ((X) <H 23 <- .X 1>> <G .X 10> ▮ )
Q$T
.MOVER$
<G .X 10>
3.2.3.3. Unconditional Breakpoints
To insert unconditional breakpoints into the FUNCTION in the next example,
do the following:
- Define
FIBand test theFUNCTIONa few times. - Enter
EDITand position the cursor appropriately. - Insert the breakpoint.
- Leave
EDITand run theFUNCTIONagain for the value 3. The breakpoint is exercised 5 times during this run.
<DEFINE FIB (X)
<COND (<L=? .X 1> .X)
(ELSE <+ <FIB <- .X 2>> <FIB <- .X 1>>>)>>$
FIB
<FIB 5>$
5
<FIB 6>$
8
<FIB 10>$
55
<EDIT FIB>$
R$$
#FUNCTION ((X) ▮ <&>)
BK T .X$Q$T
<FIB 3>$
**BREAK**
.X = 3
LISTENING-AT-LEVEL 2 PROCESS 1
<ERRET T>$
**BREAK**
.X = 1
LISTENING-AT-LEVEL 2 PROCESS 1
<ERRET T>$
**BREAK**
.X = 2
LISTENING-AT-LEVEL 2 PROCESS 1
<ERRET T>$
**BREAK**
.X = 0
LISTENING-AT-LEVEL 2 PROCESS 1
<ERRET T>$
**BREAK**
.X = 1
LISTENING-AT-LEVEL 2 PROCESS 1
<ERRET T>$
2
3.2.3.4. Conditional Breakpoints
We continue from the previous example and demonstrate conditional breakpoints with the following:
- Enter
EDITand kill the breakpoint from the previous example. - Position the cursor and insert a conditional breakpoint with a predicate
of
<0? .X>. - Leave
EDITand run theFUNCTIONagain for the value 10. - Enter
EDITand remove the breakpoint.
<EDIT>$
$
#FUNCTION ((X) ▮ ▯<&>)
KB$$
#FUNCTION ((X) ▮ <&>)
BK <0? .X> <TIME>$Q$T
<FIB 10>$
**BREAK**
<TIME> = 14.794538
LISTENING-AT-LEVEL 2 PROCESS 1
.X$
0
<ERRET T>$
**BREAK**
<TIME> = 15.252382
LISTENING-AT-LEVEL 2 PROCESS 1
.X$
0
<ERRET T>$
**BREAK**
<TIME> = 15.716037
LISTENING-AT-LEVEL 2 PROCESS 1
and so on. Eventually we reach the last breakpoint, and re-enter EDIT
<EDIT>$
$
#FUNCTION ((X) ▮ ▯<&>)
KB$Q$T
<ERRET T>$
55
3.2.4. Edit Command Summary
| name | args | meaning |
|---|---|---|
? |
none | type out short summary |
?? |
none | type out this summary |
O |
any | Open object or the value of an atom |
OT |
none | Open object at the cursor |
Q |
none | Quit and return to MDL |
QR |
fix | Quit and Retry frame |
V |
none | toggle Verbosity |
Movement commands
| name | args | meaning |
|---|---|---|
L |
fix | move Left fix objects |
R |
fix | move Right fix objects |
U |
fix | move Up fix levels |
D |
none | move Down one level |
B |
none | move to Back of object |
F |
none | move to Front of object |
UR |
fix | move Up fix objects and to the Right |
DL |
fix | move Down fix objects and to the Left |
UT |
none | Up Top -- go to the place you were after you did O |
Editing commands
| name | args | meaning |
|---|---|---|
I |
any... | Insert arguments to the right of cursor |
I: |
type,fix | make next n objects into a type |
I* |
atom,object | Imbed command: replace all occurrences of atom (default *) in object with objects to right of cursor |
IG |
any... | Insert into group |
SU |
new,old | SUbstitute new for old |
X |
atom | set the atom to the object to right of cursor |
G |
any... | Get EVAL of arguments, insert to right of cursor |
SW |
none | SWap the two objects to the right of cursor |
C |
any | Change the next object to arg |
C: |
type | Change the type of the next object to type |
K |
fix | Kill (delete) the next fix objects |
K: |
none | Kill (remove) the 'brackets' around the next object |
Search commands
| name | args | meaning |
|---|---|---|
S/SR |
any | Search (Right) until match (=?) is found for any |
SL |
any | Search Left as above |
Macro commands
| name | args | meaning |
|---|---|---|
M |
string | execute the string as if typed to EDIT |
IT/DO |
fix,string | ITerate the execute string fix times |
Printing commands
| name | args | meaning |
|---|---|---|
P |
none | PPRINT the next object |
PU |
none | PPRINT the next Upper level |
PT |
none | PPRINT the whole object open |
Cursor commands
| name | args | meaning |
|---|---|---|
UC |
none | Use Cursors |
CU |
atom | set atom to CUrrent cursor position |
GO |
cursor | GO to the specified cursor position |
PC |
none | Print Cursor positions in the current object |
PA |
none | Print All cursor positions in the top-level object |
KC |
atom | Kill the Cursor assigned to the atom |
Debugging commands
| name | args | meaning |
|---|---|---|
BK |
pred,any... | set BreaKpoint at next object; if pred evaluates to FALSE, don't break; rest of arguments are printed out at break |
BA |
pred,any... | set Breakpoint After next object |
KB |
none | Kill all Breakpoints in open object |
KT |
none | Kill This breakpoint in the object to the right of cursor |
Monitor commands
| name | args | meaning |
|---|---|---|
UM |
none | Use Monitors |
RW |
atom,pred,any... | set Read-Write monitor on atom |
RM |
atom,pred,any... | set Read Monitor on atom |
WM |
atom,pred,any... | set Write Monitor on atom |
^F and ^S return you to EDIT from a higher level.
The ATOM * may be used as a fix argument whose value is the largest
legal value for that command.
3.3. Debugging and the Interpreter
Before continuing the discussion of the various packages that are used in
the debugging of MDL code, we will expand on the discussion of ERROR,
FRAME, (and so on) in Chapter 16 of [3]. To summarize that chapter,
whenever an ATOM is bound or a FUNCTION or RSUBR is MCALLed in MDL,
information is added to the control stack. This information, normally
'invisible', may be examined using the functions described in a previous
section (FRAMES, FR&, FRLVAL, etc.). An invocation of ERROR puts MDL
into a LISTEN-like loop. Successive ERRORs stack up and are reflected in
the LISTENING-AT-LEVEL message printed whenever ERROR or LISTEN is
called.
In addition to being examined, the stack may be modified as part of the
debugging procedure. For example, the SUBRs SET and LVAL take an
optional second argument which may be (among several possible TYPEs) a
FRAME. EVALing
<SET X 10 <FRM n>>
would change the LVAL of X in the nearest binding lower in the stack than
the FRAME n FRAMEs lower than the most recent call to ERROR or
LISTEN. Similarly
<LVAL X <FRM n>>
examines the LVAL in a particular FRAME.
The most common use of the MDL interpreter in debugging is to invoke the
SUBR ERRET. With no arguments, it drops all the way to the bottom of the
stack and then calls LISTEN: It says 'I give up' (although side effects are
not undone). More commonly, ERRET is given a single argument, which causes
the last invocation of ERROR or LISTEN to return that argument. For
example, suppose a program contains ,FOO but FOO has no GVAL. MDL would
respond
*ERROR*
UNASSIGNED-VARIABLE
FOO
GVAL
LISTENING-AT-LEVEL 2 PROCESS 1
You could give up, saying <ERRET>, but it is often more reasonable to say
'Oh, yes, FOO was supposed to be 1000', and then
<ERRET 1000>
Still better is
<ERRET <SETG FOO 1000>>
which will prevent future ERRORs from the same cause.
Finally, ERRET may be given a second argument of a FRAME, which means to
return the first argument as the value of the invocation of that FRAME. In
the previous example, the programmer might look at the stack (with FR& or
FRAMES) and see
1 GVAL [FOO]
2 EVAL [,FOO]
3 EVAL [<+ .X .Y ,FOO>]
4 EVAL [<LOSER .A .B>]
5 EVAL [</ ,GOOD-GVAL <LOSER .A .B>>]
6 EVAL [<WINNER 1.0 2.0>]
7 LISTEN []
After some thought, he may just say 'Well, LOSER apparently needs some
debugging, but for now I'm interested in WINNER', in which case he can
'fake' a reasonable return from LOSER by typing
<ERRET 342.0 <FRM 4>>
which returns 342.0 exactly as though LOSER had returned it.
More complex errors are sometimes more difficult to fix, requiring the use of
EDIT (at least). In the above example, the programmer might decide to
debug LOSER after all. There are two ways to go about this: First, if the
problem is localized, the FRAME itself may be edited (which is to say, the
contents of the FRAME may be edited). Changes will show up in the
FUNCTION from which the FRAME's contents were derived. The newly
corrected FRAME may then be RETRYed. For example,
<EDIT 3>$
...various editing commands
QR$
Second, the function itself may be edited. In the process, it may be so
changed that the FORM which caused the ERROR no longer even exists.
Often, the easiest solution is to retry the invocation of the EDITed
FUNCTION from scratch: in this case
<RETRY <FRM 4>>$
As always, the major restriction to remember is that side-effects are not
undone by RETRY.
Measured, MDL 55, 2026-08-31. Chapter 19 has the error handler's behaviour form by form, measured in one stateful session (chapter 37): levels are counted per process and the banner names the process, so an error in a fresh process is
LISTENING-AT-LEVEL 1 PROCESS 2;RETRYre-signals rather than re-evaluates; and^Gat the listener is a real error,*ERROR* CONTROL-G?and a new level, not a printed imitation of one. The banner'sLISTENING-AT-LEVEL 2 PROCESS 1in this chapter's transcripts is the 55's own shape.
3.4. Loading and Dumping
GROUP-LOAD and GROUP-DUMP are used to load and dump files of MDL programs
in such a way that the contents of the file are made available in a MDL
structure called a group. Many other PACKAGEs in the MDL environment
operate on or change groups: Among them are "EDIT", "GLUE", "PDUMP", and the
MDL compiler.
GROUP-LOAD and GROUP-DUMP are almost as widely used as FLOAD as a way
of dealing with groups of MDL functions. Consequently, they are already
loaded in most initial MDLs, as part of the package "GRLOAD".
<GROUP-LOAD file-name:string
group-name:atom>
file-name:string is the file to load.
group-name:atom is the name to give the group. It is optional and by
default the ATOM formed by PARSE of the first name of the file to load.
The group will be stored as the LVAL of group-name.
GROUP-DUMP is the opposite of GROUP-LOAD. It outputs the group from the
MDL to the file given as its first argument. Functions unchanged since the
last GROUP-LOAD are copied from the original input file. Functions that
have been edited are output using the routine given as the third argument to
GROUP-DUMP.
<GROUP-DUMP file-name:string
group-name:atom
print-routine
kill-breakpoints?>
file-name:string is the only required argument. It is the file to which to output the group.
group-name:atom is optional, and defaults as it does for GROUP-LOAD, but
of course gives an ERROR if the group doesn't already exist.
print-routine is optional, and defaults to ,PPRINT unless the group
contained NBIN format RSUBRs, in which case ,PRINC is used.
kill-breakpoints? is optional, by default T, in which case GROUP-DUMP
kills all EDIT breakpoints and monitors in objects being dumped. Giving a
fourth argument of a FALSE to GROUP-DUMP prevents this.
On the surface, it appears that little happens in the process of loading a file and making it into a group. However, a great deal of information about the group has been stored away in associations for later use. Some of this information is of use to the MDL programmer:
- On an association between group-name and the
ATOMCHANNELis stored aLISTgiving the name of the file that wasGROUP-LOADed to form the group. Removing this association beforeGROUP-DUMPing has the effect of making the entire group be output from core rather than copied from the original source. - On an association between group-name and the
ATOMMAGIC-RSUBRtheATOMTis stored if the group contained anyRSUBRs in fast (NBIN) format. It is this association which is used to determine the default print-routine inGROUP-DUMP. - The
OBLISTpath in effect at any time during the load is available. The original path is stored on an association between group-name and theATOMBLOCK. Within the group, the path changes are stored in an association between the groupRESTed to the point of change and theATOMBLOCK. - If the second element of a
FUNCTIONdefinition is not anATOM, the actualFUNCTIONname gotten byEVALof that element is stored as an association between the original element and theATOMVALUE. - The location of a function within the input file is stored as a
LISTof the starting and ending offsets (in characters) of the function, under an association between a locative to theGVALof theFUNCTIONname and the indicatorDEFINE. This association is removed byEDIT(and other editors) to indicate that theFUNCTIONhas been changed.
There are additionally several switches that affect the operation of
GROUP-LOAD:
.KEEP-FIXUPS
If the LVAL of KEEP-FIXUPS is true (and GROUP-LOAD binds it that way
during loading), the fixups of RSUBRs GROUP-LOADed will be kept.
.EXPFLOAD
If the LVAL of EXPFLOAD is true, FLOADs will be expanded. That is, the
objects in the file FLOADed will be added to the group in place of the
FLOAD. The initial setting of EXPFLOAD is a FALSE.
.EXPSPLICE
If the LVAL of EXPSPLICE is true, any objects returned within SPLICEs
will be inserted directly into the group as described above. The initial
setting of EXPSPLICE is a FALSE.
<GROUP-LOAD "fact.mud" FACTG> ⇒ FACTG
<TYPE .FACTG> ⇒ LIST
<LENGTH .FACTG> ⇒ 3
<1 .FACTG> ⇒ <SETG LIMIT 10>
<GET FACTG CHANNEL> ⇒ ("fact" "mud" "DSK" "")
<GET FACTG MAGIC-RSUBR> ⇒ #FALSE ()
<TYPE <1 <GET FACTG BLOCK>>> ⇒ OBLIST
<FACT 5> ⇒ 120
<FACTS 4> ⇒ (1 2 6 24)
<GROUP-DUMP "fact2.mud" FACTG> ⇒ FACTG
pymdl's own. The group is
grload.mud, the era's, and the associations are the ones the manual lists; theCHANNELassociation holds the file name as the channel parsed it under pymdl's file root (chapter 2), so the "device" isDSKand the directory is empty.GROUP-DUMPcopies unchanged functions from the source text by the character offsets of item 5, exactly as described, and the dumpedfact2.mudis the source again.GROUP-LOADis also the front door of the era compiler here:FILE-COMPILEGROUP-LOADs the input in the compiler session andGROUP-DUMPs the result as anNBIN(34.7).
3.5. The One-step Debugger
The MDL One-step debugger allows the user to step through the evaluation of
any MDL expression one 'operation' at a time. Between steps, variables may
be examined or changed, functions edited, and so on. This is possible
because the debugger runs in a different MDL PROCESS than the expression
being stepped, and a MDL PROCESS may 1STEP another [3]. To load the
Debugger, <USE "DEBUGR">.
The MDL Debugger can be in any of three states. In the initial state, OFF,
no one-stepping occurs and the Debugger does not listen for any special
interrupt characters. The Debugger is, therefore, completely inactive. By
typing <DEBUG> to MDL, you leave the OFF state and enter the READY
state. In the READY state no one-stepping occurs, however the Debugger
does listen for interrupt characters. By typing the interrupt character
^B, you enter the ON state and one-stepping begins. In addition, if you
were stopped at an EDIT breakpoint when the ^B was typed, the breakpoint
will automatically be exited and evaluation continued in the one-stepping
state.
While in the ON state, the Debugger will proceed through the execution of
any MDL objects one step at a time. In essence, the Debugger stops just
before and just after every call to EVAL. At each step the Debugger will
indicate its current condition as follows. If EVAL is recursively entered
at level, n, with input, object, the display will be:
n=> object
(where object is ampersand printed). If EVAL is returning from level, n,
with result, object, the display will be:
n<= object
(where object is ampersand printed).
The Debugger will stop at each such step and wait for directions. There are
four interrupt characters that may be typed to proceed further in the
program: ^N, ^O, ^R and ^A. They each take an optional prefix
argument that serves as a repeat count.
^N causes the Debugger to perform the next step of the current evaluation.
^O causes the current object to be completely evaluated without any
one-stepping and then stops with the result of that evaluation. ^O is
useful for stepping over COND predicates that you know will not succeed, or
more generally, uninteresting parts of a program.
^A is similar to ^O, but specific to the evaluation of the argument list
of a FUNCTION, PROG, or REPEAT. Typing ^A during such evaluation
allows the rest of the argument list to be evaluated without one-stepping and
then stops before evaluating the body of a FUNCTION, PROG, or REPEAT or
returning of a result.
^R is most effectively used in a REPEAT or PROG loop. Typing ^R
causes evaluation to proceed until control returns to the point in the body
of the REPEAT/PROG at which ^R was typed. It thus allows you to go
once around a loop.
It should be noticed that, when stopped at one of these steps, you can
examine and modify program variables, do a FRAMES or FR&, EDIT
FUNCTIONs and set breakpoints, and in general perform any valid MDL
operations. Also, when you stop, the LVAL of the ATOM LAST-OUT will be
set to the object the Debugger last typed out. This is useful if the &
performed by the Debugger did not show a particular detail that you are
interested in.
Use the interrupt character ^E to leave the ON state and return to the
READY state. Use the interrupt character ^Q to leave either the ON
state or the READY state and return to the OFF state. When leaving the
ON state as described, the execution currently being one-stepped will be
finished in the usual manner.
The function REPAIR attempts to fix any errors in the Debugger that you
might happen to invoke. These errors are easily distinguished since they
never occur in MDL's MAIN PROCESS. Therefore, you will see:
LISTENING-AT-LEVEL m PROCESS n
(where n is not 1). REPAIR turns off the Debugger and returns you to
running in the MAIN PROCESS (no longer one-stepping). Because REPAIR
turns off the Debugger, you must do <DEBUG> again if you wish to try any
further one-stepping.
pymdl's own.
DEBUGRis complete here:debugr.mudloads,DEBUGmakes it ready, and the one-step display --n=> object,n<= result, then: short = valuelines of 3.5.2 -- appears at real frame depths with a stop at each step. It is driven headless in the tests by handing each stop's listener the interrupt the^Nkey would raise (<INTERRUPT ,CHAR-INT!-IDEBUGR ,NEXT-CHAR!-IDEBUGR ,INCHAN>). Getting the era's debugger to run on pymdl took six engine repairs, each a fact about MDL in its own right:EVLINandEVLOUTareROOTatoms; a#TYPE atomliteral read from sourceCHTYPEs a copy, so#DISMISS Tdoes not retypeTitself;<DISMISS value>with no activation exits the handler's application; a handler given aPROCESSruns on that process's stack (chapter 23);<FRAME process>is that process's topmost frame (chapter 22); and a stepped evaluation pushes realEVAL-named frames soFRAME-COUNTis the depth the display shows. An older stepper,MEND(mend.mud), is in the tree too: it spawns a process,1STEPs the resumer, and shows each level with a|cursor at the argument being evaluated and evaluated arguments substituted in place --<* | .X>,<* 6 |>,<* 6 6>,36.
3.5.1. MDL Debugger Command Summary
<USE "DEBUGR">loads the Debugger.<DEBUG>makes the Debugger ready.^Bbegins one-stepping.^Nperforms the next step of the computation.^Osteps completely over the next computation, then stops and continues one-stepping.^Aevaluates the arguments of the current object then stops and continues one-stepping through the body.^Rcontinues evaluation until you return to this point.^Eends one-stepping.^Qquits one-stepping and makes the Debugger unready (turned off).<HELP>prints a command summary.<REPAIR>attempts to repair any Debugger errors you might invoke.
3.5.2. MDL Debugger Special Features
The following flags have special importance to the Debugger:
,INDENT-INC
is the amount by which to indent for each level (by default 2 spaces).
,INDENT-MOD
The indentation-level is the real level taken modulo this number. The default is 10. Indentation 'restarts' when level gets here. If you don't like this feature, make the number large.
,INDENT-DIF
is the minimum amount of free space to reserve on each line that indentation must not touch (by default 20). Therefore at level L the indentation is exactly:
<MIN <* ,INDENT-INC <MOD .L ,INDENT-MOD>>
<- <13 ,OUTCHAN> ,INDENT-DIF>>
,OUT-FAST
if true the Debugger will not stop when leaving a level with a result. The
default is T.
,OUT-UNIQUE
if both this and previous flag are true successive 'outs' of the same item
will not be displayed (defaults to T).
,SELF-FAST
if true the Debugger will not stop when entering a level with an object
which EVALs to itself (e.g. ATOMs, FIXes, STRINGs). The default is
T. The display will be:
n: object
,FORM-FAST
if true the Debugger will not stop when entering a level with any of the
'short' FORMs (e.g. <>, .FOO, ,BAR, 'ANYTHING). The default is
T. The display will be:
n: .FOO = lval
Any of these flags can be SETGed by you to tailor the Debugger to your own
tastes.
3.6. Execution Tracing
The "TRACE" PACKAGE provides a facility for observing the arguments and
returned values of selected FUNCTIONs and RSUBRs. It is possible to
print the arguments on entry to the function, print the value returned, and
to break on entry to and exit from the function. All actions may be
performed conditionally. To load TRACE, type
<USE "TRACE">
3.6.1. Using TRACE
TRACE is invoked by
<TRACE what options>
what is either an ATOM or a LIST of ATOMs, naming the things to be
traced. These may include SUBRs, FUNCTIONs, and RSUBRs; however,
anything which is traced must EVAL all of its arguments. options
specifies the behavior of TRACE with respect to the specified function.
There are five switches, as follows:
IN-BREAK means break (cause a MDL ERROR) before calling the function.
Normally off.
IN-PRINT means & function arguments on entry. Normally on.
OUT-PRINT means & function value on exit. Normally on.
OUT-BREAK break after executing the function call. Normally off.
VERBOSE means & the arguments to the function one per line. This is
useful if the arguments are long. Normally off.
To cause a given option to be unconditionally on, include its name (an
ATOM) in the options TUPLE. To cause an option to be unconditionally
off, include a two-element LIST, composed of the option name and a
FALSE. If the second element of the LIST is neither FALSE nor an
ATOM, it will be EVALed each time TRACE examines the setting of the
given option for the function. This allows conditional breakpoints, for
example.
Thus:
<TRACE FOO (OUT-PRINT <>)>
will cause FOO's arguments to be printed on entry, but the value will not
be printed.
<TRACE FOO (OUT-PRINT '<G? <TIME> 4.0>)>
will cause printing of the value after four seconds of cpu time have been
used. Printing of the arguments will occur each time FOO is called.
UNTRACE turns off tracing of the specified functions:
<UNTRACE what:atom-or-list>
what defaults to a LIST of all functions which have been traced.
<GROUP-LOAD "fact.mud" FACTG> ⇒ FACTG
<USE "TRACE">
<TRACE FACT> ⇒ FACT
<FACT 2> ⇒ 2
<UNTRACE> ⇒ (FACT)
<FACT 2> ⇒ 2
The traced call prints, in the era package's own words,
Entering FACT with [2]
Entering FACT with [1]
Leaving FACT with 1
Leaving FACT with 2
3.6.2. Understanding TRACE
TRACE works by CHTYPEing the specified functions to new types which have
an APPLYTYPE associated with them. This means that one cannot trace calls
to RSUBRs or RSUBR-ENTRYs which are already linked. In addition, it
means that UNTRACE must be used to get the old value back. To determine
the status of a function with respect to tracing, say
<GET applicable TRACE>
This returns FALSE if applicable is not traced; otherwise, it returns an
object which describes the settings of the various options. The object has
a PRINTTYPE which associates the name of each option with its setting:
<GET ,FOO TRACE>$
FOO
IN-BREAK: #FALSE ()
IN-PRINT: T
OUT-PRINT: <G? <TIME> 4.0>
OUT-BREAK: #FALSE ()
VERBOSE: #FALSE ()
Individual settings for a particular function may be changed by PUTting
into this structure:
<PUT <GET ,FOO TRACE> ,IN-BREAK T>
causes a break whenever FOO is called.
3.7. Monitors
A common problem in debugging is the mysterious 'clobbering' of some value or element of a data structure. MDL has imbedded in it a mechanism for triggering interrupts on references, either for reading or writing, to values of variables and elements of structures.
The "MONITOR" PACKAGE is designed to be a readily accessible user interface
to these "READ" and "WRITE" interrupts in the MDL interpreter.
To obtain "MONITOR",
<USE "MONITOR">
There are three basic kinds of 'things' which can be monitored: values of
ATOMs, elements of STRUCTUREDs (the TYPE of the element is not
important), and ASSOCIATIONs.
For ATOMs, the LVAL or the GVAL may be monitored. If the LVAL is to
be monitored, the ATOM must be ASSIGNED?. For the GVAL, the ATOM
must be GBOUND?. If these conditions cannot be met, a monitor cannot be
generated.
For STRUCTUREDs, the monitor is on the nth element, where n is specified
when the monitor is created. Remember, the monitor is on a slot of the
STRUCTURED, not on the contents of that slot!
For ASSOCIATIONs, the monitor is on the association itself.
3.7.1. Monitor Internals
This section expands on the discussion of monitors in the MDL document itself [3].
MDL defines two types of monitors: Read and Write. These are implemented in
the language by two interrupts, READ!-INTERRUPTS and WRITE!-INTERRUPTS,
respectively. In addition, the "MONITOR" PACKAGE can allow read-write
monitors. The "MONITOR" PACKAGE is at base a set of functions to create
and handle these interrupts. A monitor is triggered in the following cases:
Read monitor:
- For
LVALs -- viaLVAL - For
GVALs -- viaGVAL - For
STRUCTUREDs -- viaNTH - For
ASSOCIATIONs -- viaGETandGETPROP
Write monitor:
- For
LVALs -- viaSETor"AUX"bindings - For
GVALs -- viaSETG - For
STRUCTUREDs -- viaPUT,SUBSTRUC - For
ASSOCIATIONs -- viaPUTandPUTPROP
Note that PUTRESTs of LISTs which may alter the nth element of a LIST,
do not access the old nth element of the LIST and therefore do not cause a
write monitor to trigger.
Internally, MDL performs monitoring on LOCATIVEs to STRUCTUREDs. In
fact, LVAL and GVAL are really pointers to an internal structure. This
need not concern the user except in the case of LVALs of ATOMs. In this
case, MDL will monitor a LOCATIVE to that (exactly that unique) binding
of the ATOM. When that binding becomes invalid, or more precisely,
<NOT <LEGAL? locative>>
a function in the "MONITOR" PACKAGE will make the monitor vanish. Illegal
monitors print as #MONITOR [ILLEGAL] (if you ever get a pointer to one).
Remember that if you want to monitor the LVAL of an ATOM bound in a
FUNCTION (or PROG, etc.), you must create a new monitor each time, as a
new binding is created each time. One way to do this is to edit into the
FUNCTION a call to MONITOR (see below) after the ATOM becomes
ASSIGNED?. Fortunately, EDIT (see section 3.2.2.9) has commands to do
exactly that.
3.7.2. Creating MONITORs
Creation of all monitors is done through a call to MONITOR (which returns
an object of TYPE MONITOR), as follows:
<MONITOR type:string
object
where
predicate
todo:tuple>
where:
type is one of "READ", "WRITE", or "RW".
object is either an ATOM or a STRUCTURED, or an ASSOCIATION item.
where is either LVAL or GVAL (if object is an ATOM) or a FIX (if
object is a STRUCTURED), or an ASSOCIATION INDICATOR.
predicate is something which is EVALed to determine whether the monitor is
to be triggered; this defaults to T. The "MONITOR" PACKAGE defines
three variables which can be referenced in the test:
OLDVALis the old value of the object monitored.NEWVALis the new value of the object monitored.MONOBJis the object monitored (ATOMorSTRUCTURED).
Here value means LVAL, GVAL, or element. Obviously, NEWVAL is not set
for "READ" monitors.
todo is any number of things to be EVALed and PRINTed when the monitor
is triggered.
Note that predicate and todo are identical to the analogous arguments of the
EDIT BK command.
3.7.3. Monitor Events
When a monitor is triggered, the following is printed (remember the
predicate is evaluated before this), and then LISTEN is called. To
continue, <ERRET T>.
Read:
**READ of where of object**
Value: oldval
todo1 = result1
todo2 = result2
...
Write:
**WRITE of where of object**
Old value: oldval
New value: newval
todo1 = result1
todo2 = result2
...
A slightly different first line format is used for associations.
3.7.4. Killing Monitors
Killing a MONITOR is accomplished by calling KILL-MONITOR as follows:
<KILL-MONITOR monitor>
or
<KILL-MONITOR type object where>
In the latter case, type, object, and where are as given in the original
call to MONITOR.
To kill all MONITORs, use
<KILL-ALL-MONITORS>
3.7.5. Other Monitor Routines
<MONOBJ monitor>
returns the object monitored.
<MONSPEC monitor>
returns the where of the MONITOR.
<CLEAN-MONITORS>
flushes invalid MONITORs from the MONITOR LIST. This is done
internally and need not be called routinely.
,MONITORS
is a LIST of all current MONITORs.
3.7.6. What You Can't Do with Monitors
You can't monitor the LVAL of something BOUND? but not ASSIGNED?.
E.g.,
<DEFINE WRONG ("AUX" BAR)
<MONITOR "READ" BAR LVAL>
..&.. >
You can't expect compiled code to cause monitors to be triggered.
Naturally, you can't place monitors in compiled code; however, a compiled
reference to a monitored ATOM will not usually cause the monitor to trigger
either.
pymdl's own. The
"READ"and"WRITE"interrupts monitors rest on are the interpreter's (chapter 23), and the package ismonitr.mudover them, loaded by<USE "MONITOR">;EMONIT, theEDITinterface, loads withPP,EDITandMONITORbefore it, as theUMcommand loads them. The last paragraph holds here for era-compiled code on the emulator, which reads a value cell directly, and for the native compiler's code, which reads a local as a Python local (34.5.3); a compiledGVALread does pass through the interpreter's cell and will trigger aGVALmonitor, which is one more thing compiled code does here that it did not there.
3.8. FINDATOM
The "FINDATOM" PACKAGE is intended to reduce the problems caused by
multiple OBLISTs and lengthy ATOM names in MDL. It allows one to find
all ATOMs whose PNAMEs match some specification, which need not be
exact; in addition, one may place constraints on the values of the ATOMs
found.
FINDATOM is invoked as:
<FINDATOM specstr:string
searchlist
constraints
outobl:list>
specstr is a STRING describing the PNAMEs of the ATOMs one wishes to
find. Three special characters are recognized in this STRING:
*: matches anything, including an empty string=: matches any single character^Q: quotes the following character
Search strings may be an arbitrary concatenation of normal and special characters. For example:
"*SDM*": matches anyATOMcontaining "SDM" anywhere in itsPNAME."*=SDM*": matches anyATOMcontaining "SDM" in itsPNAME, provided that at least one character precedes the "SDM"."^Q*": matches anyATOMwithPNAME"*"."*": matches anyATOM.
If ^Q is the only special character in the string, it need not be quoted:
"^Q" searches for ATOMs with PNAME "^Q".
searchlist specifies the OBLISTs to search. Possible values are:
#FALSE (): search allOBLISTs in.OBLIST#FALSE (oblists-or-forms): search all but theOBLISTs specified.- oblist: search only this
OBLIST. - list-of-oblists: search only the
OBLISTs in this list. - else: search all
OBLISTs. This is the default.
constraints is a TUPLE describing the value of each ATOM found. It may
consist of any number of valid TYPE names, along with arbitrary structures
and the following special objects:
T: if present, overrides any other constraints; if no other constraints are specified, this is assumed. AnyATOMmatching specstr will be accepted.ANY: overrides any constraint other thanT. AnyATOMmatching specstr which has a value (eitherGVALorLVAL) will be accepted.<>: anyATOMwhich has no value will be accepted. Note that giving bothANYand<>is equivalent to givingT.LINK: anyLINKwill be accepted.- If other constraints are provided, they work as follows: all valid
TYPEnames given (ones for whomVALID-TYPE?returnsT) are stored in a structure; when a value is encountered, itsTYPEisMEMQed on this structure. If theATOMdoes not succeed here, it is next checked against the 'arbitrary structures.' - Anything in constraints which is neither one of the above 'special
objects' nor a valid type is treated as a
DECLspecification. All such objects are put in aFORMstarting withOR, which has the effect of generating a singleDECLspecification. When a value is found,DECL?is called with the value as its first argument and the generatedFORMas its second. IfDECL?returnsT, meaning that theFORMis valid as aDECLfor theVALUE, theATOMis accepted.
Examples:
ATOM FALSE '<LIST [REST FIX]>
specifies that any ATOM accepted must have either a GVAL or an LVAL
which is of type ATOM or FALSE, or which is a LIST of FIXes.
'<OR ATOM FALSE> '<LIST [REST OBLIST]>
specifies that any ATOM accepted must match the DECL
<OR <OR ATOM FALSE> <LIST [REST OBLIST]>>
outobl, if present, is a LIST of OBLISTs which is the LVAL of OBLIST
when FINDATOM prints things. Thus, one may force all ATOMs to be
printed with full trailers by providing an empty LIST here. The last
argument given to FINDATOM, provided it is a LIST, is assumed to be
outobl.
FINDATOM prints the name of each ATOM it accepts, followed by the
STRING "Gassigned" and the type of GVAL if the ATOM has one; this will
be followed by the STRING "Assigned" and the type of the LVAL if the
ATOM has one. It prints the number of ATOMs found when it finishes.
<GROUP-LOAD "fact.mud" FACTG>$
FACTG
<USE "FINDATOM">$
<FINDATOM "FACT*">$
FACTS Gassigned FUNCTION
FACT Gassigned FUNCTION
(Found 2)
3.9. "PINFO"
"PINFO" is an informational PACKAGE. It is used to examine the OBLISTs
of the PACKAGEs loaded into an MDL. There are two major entries in
PINFO.
<PCK-INFO package:string
internal?:boolean>
Both arguments to PCK-INFO are optional. If neither argument is given,
the names of the PACKAGEs loaded into the MDL are listed. If a package is
given, the contents of the package's ENTRY OBLIST are listed, as well as
information about the VALUE of each ENTRY. If internal? is provided and
non-FALSE the contents of the internal OBLIST are also listed.
PCK-INFO prints an error message if package is not loaded.
<PCK-USES package:string>
lists the names of PACKAGEs USEd by package or returns a FALSE if
package is not loaded.
<USE "TRACE" "PINFO">$
<PCK-INFO "TRACE">$
PACKAGE TRACE
Uses nothing.
TRACE!-PACKAGE Oblist:
IN-BREAK Gassigned FIX
IN-PRINT Gassigned FIX
INDENT
INDENT-MOD Gassigned FIX
ITRACE
OUT-BREAK Gassigned FIX
OUT-PRINT Gassigned FIX
TFUNCTION
TRACE Gassigned FUNCTION
TRACE-ARGS
TRACE-VAL
TRACELIST Gassigned LIST
TRSUBR
TRSUBR-ENTRY
TSTRUC
TSUBR
UNTRACE Gassigned FUNCTION
VERBOSE Gassigned FIX
18
3.10. Debugging in a Run-time Environment
A fairly common occurrence when running 'debugged' code is to find that it
was not after all completely debugged. It is useful to be able to load
interpreted versions of some FUNCTIONs in a PACKAGE into the compiled
environment for debugging. "DFL", "RDFL", and "UNLINK" are PACKAGEs
written to simplify this procedure.
3.10.1. DFL
The "DFL" ('Debugging Fload') PACKAGE is a set of routines for loading and
dumping of small numbers of FUNCTIONs from a larger file. It is useful in
debugging already running systems, or ones which have not been
GROUP-LOADed. To get "DFL"
<USE "DFL">
The main entry of the "DFL" PACKAGE is DFL:
<DFL func-names file-name:string unlink?:boolean>
where all arguments are optional and
func-names is the name(s) of the DEFINEd FUNCTION(s) to be obtained from
this file. It may be an ATOM, a STRING, or a structure of ATOMs or
STRINGs; if ATOMs are given, their SPNAMEs are used. The default is
the argument last given to DFL or RDFL.
file-name is the file to obtain the FUNCTION(s) from. The default is the
last file DFLed or RDFLed. An ATOM may be given, in which case its
SPNAME is used for the first file name.
unlink? If this is true, and if one or more of the values replaced by the
DFLed FUNCTIONs were RSUBRs or RSUBR-ENTRYs, the reference VECTORs
of all RSUBRs, including pure ones, will be searched for occurrences of
the old value; such occurrences will be replaced by the ATOM. This is the
inverse of RSUBR-LINKing. Pure structures will be unpurified; this does
not change their address in core, but simply makes the page they live in
read/write.
In the normal case, if an RSUBR or RSUBR-ENTRY is being replaced,
unlinking will occur automatically in garbage-collector space only if
RSUBR-LINK is T. Also, remember that unlinking is not the same as
substituting: only RSUBRs stored at top level in reference VECTORs are
found; if the old value itself was in a structure (such as a dispatch table),
it will not be replaced.
3.10.2. RDFL
RDFL is similar to DFL but is for reloading RSUBRs rather than
FUNCTIONs. RDFL is contained in the PACKAGE "RDFL".
<RDFL func-names file-name unlink? glue?>
The first three arguments are as for DFL. The only difference between
RDFL and DFL (barring the effect of the fourth argument) is that RDFL
searches in the file for '<SETG ' rather than '<DEFINE '.
glue? If non-FALSE, RDFL will READ and EVAL the next object in the
file following each RSUBR read. This will in the normal case obtain the
'glue bits' for the RSUBR (see section 6.1). The default for glue? is
<AND <ASSIGNED? GLUE!- > .GLUE!- >
This is the FORM used in NBIN files to determine whether glue bits should
be kept.
Note that RDFL will work to reload any SETGed object, not just RSUBRs.
RDFLing an RSUBR-ENTRY does not work and may well be fatal: you must
RDFL the RSUBR in which the RSUBR-ENTRY is an entry, as well.
3.10.3. UN-DFL
UN-DFL is for writing out DFLed FUNCTIONs after EDITing.
<UN-DFL atoms filnam force?>
atoms is an ATOM or a list of ATOMs, which will be UN-DFLed. The
FUNCTIONs defined must all be from the same file, or UN-DFL will not
work. UN-DFL can only UN-DFL things which were previously loaded by
DFL.
filnam The default is the file the ATOMs originally came from.
force? Normally, UN-DFL will object if there is a version between the file
the FUNCTIONs came from and the file which UN-DFL will create: it thinks
it will likely destroy useful information. Providing an ATOM here causes
this scruple to be ignored. It is almost always unwise to do so. For
example:
<DFL (FOO BAR)> <UN-DFL FOO> <UN-DFL BAR>
will cause UN-DFL to fail. Moral: DFL and UN-DFL your FUNCTIONs
together.
pymdl's own.
DFL,RDFLandUN-DFLare the era'sdfl.mud, and all three work on pymdl's files:DFLfinds a<DEFINEby name in the source and loads just that function,RDFLreloads aSETGed object, andUN-DFLwrites an edited function back over its place in the file (tests/test_mudbug.py). Loadingdfl.mudwanted a helper the era got from a hand-assembledREADST, anddfl.fbin's pure code for it runs here on the emulator -- it is the1DFLblock whose disassembly chapter 35 checks -- with a Python stand-in only if the binary is not reachable.DFLof a name that is already defined stops atALREADY-DEFINED-ERRET-NON-FALSE-TO-REDEFINE, exactly as a secondDEFINEdoes (chapter 12): setREDEFINEfirst, orERRETpast it.
3.10.4. UNLINK
The "UNLINK" PACKAGE contains three entries: UNLINK, PURE?, and
UNPURIFY. UNLINK is sometimes called by DFL; PURE? and UNPURIFY
are good ways to figuratively defeat the safety 'interlock' of MDL.
UNLINK is used to unlink RSUBRs after they have been linked. (See the
discussion of RSUBR-LINK in [3]).
<UNLINK atoms pure?>
atoms is a list of the ATOMs to be unlinked, or a FALSE, meaning unlink
every RSUBR in the MDL, or a group-name, meaning unlink calls to all
FUNCTIONs and RSUBRs in the group.
pure? is optional and defaults to FALSE, but if true, even pure RSUBRs
will be searched. UNLINK examines all the OBLISTs in the MDL, looking
for RSUBRs; if an RSUBR exists only in a structure, and not at top level
in any RSUBR's reference VECTOR, it will not be found.
<PURE? object:any>
PURE? takes an object and determines if the right half of the value word
is greater than the number contained in the MDL location PURBOT, which is
the lowest pure location in MDL. Ergo, 'Is the object I gave you pure?' It
is only meaningful for structures.
<UNPURIFY pure-object:any>
UNPURIFY takes a single argument, which must be of PRIMTYPE VECTOR or
UVECTOR (i.e., it must have an AOBJN pointer for its value word). It
causes the pages in which that object lives to become impure, and returns
T.
Because there is no way on ITS to make a read-only page an impure page
directly, the following algorithm is used by UNPURIFY:
- Is the object pure, according to
PURE?If not, leave. - Is
UNPURIFY-PAGE!-IUNLINKGASSIGNED?If not, get a page from the interpreter, andSETGthe aforementionedATOMto its number. I.e., the page is more or less permanently taken for use ofUNPURIFY. - For each page occupied by the object: a) If the page is already impure,
do nothing; b) otherwise, map the page on top of
UNPURIFY-PAGE; c) create a new, impure page where the old page was. d) copy the contents ofUNPURIFY-PAGEback to the old, now impure page.
Thus, no pointers are changed: as far as MDL is concerned, in fact, nothing has changed. The unpurified pages are still pure, according to its page map. However, you may freely change the unpurified object.
If your change to the newly unpurified object consists of PUTing a pointer
into garbage-collected space into the object, you may lose completely unless
the pointer points to a frozen object. The MDL garbage collector does not
examine unpurified objects. UNLINK can only use UNPURIFY because all
ATOMs referenced by pure RSUBRs are indeed frozen.
For the above reason, use of UNPURIFY is not recommended for the general
user.
pymdl's own. Purity is a fact about the era's address space: pure code lived above
PURBOTin read-only pages shared from theSAVfiles (33.13). pymdl keeps the observable: anRSUBRloaded from the pure code database answersPURE?true,UNPURIFYmakes it writable and answersT, andUNLINKputs theATOMs back into reference vectors anRSUBR-LINKhad replaced (tests/test_mudbug.py); the page games of step 3 have nothing to act on and are not performed. The library's ownPURE?is a packageENTRY; a pymdl built-in of the same name once sat onROOTand made<USE "UNLINK">fail at thatENTRY, which is theALREADY-USED-ELSEWHEREof chapter 28.
3.11. CRITIC
"CRITIC" is a PACKAGE designed to aid the user in debugging (and perhaps
increasing the efficiency of) his programs. It accumulates and prints in a
readable format information about the interactions of the various
FUNCTIONs (and LVALs and GVALs) in a group. It also warns the user
about various conditions it considers to be either non-optimal or erroneous,
such as incorrect use of SPECIAL, forgetting to QUOTE some structure,
and so on. Like most critics, it is sometimes wrong, but it tries to
perform a useful service. To load "CRITIC" say
<USE "CRITIC">
There are two major entries, one of which prints more information than the other.
<CRITIC group-name
output-file>
where group-name is the ATOM returned by a GROUP-LOAD, and the optional
output-file is a STRING giving the name of the file to output to (by
default with second file name "CRITIC"). This can also be a CHANNEL if
you are planning to do several CRITICs into one file. CRITIC prints
information about interactions among the FUNCTIONs in a group (as
described below).
<CRITIC-NOTES group-name
output-file>
is similar but only prints 'errors' and 'warnings' -- things that might be
problems with the FUNCTIONs in the group.
The output format (for each FUNCTION and for the group as a whole) is as
follows:
function (object number of function in group)
Called-by: a list of all the functions which call function
Calls: a list of all the functions called by function
SETG: external globals SETGed by function
GVAL: external globals referenced by function
SET: external variables SET by function
LVAL: external variables referenced by function
SPECIAL: variables declared SPECIAL by function
USE-DATUM: DATUMs used by function
The above table is printed by CRITIC but not by CRITIC-NOTES. 'External'
as used above means 'External to function'.
CRITIC-NOTES and CRITIC both print information about possible defects or
errors in each FUNCTION. These can be any or all of the following
(explanations follow where needed).
Found along the way.
CRITICnever ran under declaration checking on real MDL, and cannot here either: its own source declaresFATHER-LISTwith a#DECLthat names theFATHERNEWTYPEbefore thatNEWTYPEexists, so withDECL-CHECKon -- pymdl's default -- the first analysis diesTYPE-MISMATCHinside the critic.<DECL-CHECK <>>before<CRITIC MYGRP ,OUTCHAN>is the era's own working condition, and then the review prints as the manual describes:CRITIC's Review of Group, the call graph withCalled-by,Internal Functions never called,Internal Globals never used,Calls undefined function NOSUCH-FN,FUNCTION has no DECL(tests/test_mudbug.py). The same class of file property asCELEST's in chapter 2: an era program that depended on a checker being off.
3.11.1. Global Problems with the Group
FLOAD in file.
This is pretty minor: FLOADs at top level are discouraged if you can avoid
them.
BLOCK or ENDBLOCK at top level in PACKAGE.
PACKAGEs should not have to resort to this.
atom-name: MANIFESTed structure.
The ATOM given is a structure but was MANIFESTed. Since a MANIFEST is
copied within the reference VECTOR of any RSUBR that uses it, it is
usually not a good idea.
ENTRYs not bound, assumed locals: atom-list
The ATOMs given were made ENTRYs in the PACKAGE, but were not bound,
so CRITIC has assumed they are locals, for lack of something better to do.
Packages USEd but never referenced: package-names
These PACKAGEs were in USE statements but no ATOM was ever found which
fell on their OBLISTs. There will sometimes be incorrect entries in this
list if you USE a PACKAGE which sets up a funny ENTRY OBLIST
(RPACKAGEs included) or no OBLISTs at all.
Internal functions unused: atom-list
These are FUNCTIONs DEFINEd but apparently never referenced and not
entries. There will sometimes be incorrect entries in this list if you have
FUNCTIONs invoked only by funny dispatching methods, such as APPLYing or
EVALing an element of a structure.
Internal globals unused: atom-list
ATOMs SETGed at top level but never referenced.
Internal manifests unused: atom-list
ATOMs SETGed and MANIFESTed at top level but never referenced.
3.11.2. Parameter List Problems
ATOM atom-name used twice in parameter list.
The ATOM named was bound twice in the same parameter LIST within the
FUNCTION. MDL doesn't worry about this, but you might.
Untasteful re-use of ATOM atom-name in ROOT.
An ATOM was bound which happened to be in the ROOT OBLIST and happened
to have a GVAL that is a SUBR or FSUBR. This is reported because the
ATOM will have to be unpurified, which is expensive.
"BIND" illegally located.
A "BIND" was found other than at the beginning of a parameter LIST.
"CALL"/"ARGS" illegally located.
A "CALL" or "ARGS" was found after the "AUX" in a parameter LIST.
"OPTIONAL" illegally located.
"OPTIONAL" was found after "AUX" in a parameter LIST.
"TUPLE" illegally located.
"TUPLE" was found after "AUX" in a parameter LIST.
atom "AUX" illegally QUOTEd.
The ATOM named was given as a quoted argument in the "AUX" part of the
parameter LIST.
External locals set but unbound and unDECLed: atom-list
External locals set but unbound: atom-list
Two different classes of hacking an external local. In both cases it means
that the ATOMs did not appear to be improperly SPECIALed, since no one
bound them higher in the call tree (or at top level). These are most often
indications of misspelling or forgetting to put a temporary in the parameter
LIST.
External locals used but unbound and unDECLed: atom-list
External locals used but unbound: atom-list
A reference to an external local which was not bound anywhere is probably a
misspelling of a SPECIAL bound elsewhere or the result of forgetting to
put the ATOMs in the FUNCTION's parameter LIST.
External locals set but unDECLed: atom-list
External locals used but unDECLed: atom-list
An external used but not DECLed usually means that the compiler will
produce poorer code.
3.11.3. Unused ATOMs
Argument unused: atom-list
The arguments listed were never referenced.
Unused: atom-list
The ATOMs listed were bound at top level of the FUNCTION and never
referenced.
Unused in PROG: atom-list
Similar to the above, but the ATOMs were bound within a PROG.
Unused in REPEAT: atom-list
Similar to the above, but the ATOMs were bound within a REPEAT.
Unused in FUNCTION: atom-list
Similar to the above, but the ATOMs were bound within a nameless
FUNCTION, such as the second argument to a MAPF/MAPR.
Unused SPECIALs: atom-list
The same as above (including '... in FUNCTION', etc.), except that the
ATOM was SPECIAL. This message results from really looking down the
call tree, so it is more accurate about this problem than the compiler,
which only looks at the FUNCTION in which the ATOM is bound.
3.11.4. Function Calling Errors
Calls undefined function atom.
The FUNCTION calls an undefined FUNCTION (undefined at the time CRITIC
ran).
Calls function with too few arguments.
Calls function with too many arguments.
External FUNCTION function
The FUNCTION named is called but doesn't seem to fall on any of the
OBLISTs associated with the group.
3.11.5. SPECIAL/UNSPECIAL Problems
SPECIALs never used as SPECIALs: atom-list
The ATOMs were made SPECIAL but never used outside the FUNCTION in
which they were bound.
atom-name is unused or should be SPECIAL.
A very specific error which means that the ATOM given (always one of
INCHAN, OUTCHAN, or OBLIST) was bound but never referenced within the
FUNCTION, and was not SPECIAL: Either you bound it for effect and forgot
to SPECIAL it, or you didn't need to bind it.
atom unbound in paths: path-list
If the FUNCTION is called by one of the paths given, the atom will be
unbound. A path is just a list of calls CRITIC has found are possible,
such as (FOO BAR BLECH), meaning 'FOO is called by BAR which is called
by BLECH'.
The ATOM atom used in fcn1 should be special in fcn2.
This note will appear with both FUNCTIONs mentioned. It means that atom
is referenced in fcn1 and the nearest FUNCTION that binds it and calls
down to fcn1 is fcn2.
3.11.6. DECLing Problems
RSUBR has no DECL.
FUNCTION has no DECL.
Parameters not DECLed: atom-list
The ATOMs given were bound but not DECLed in the parameter list of a
FUNCTION, PROG, or REPEAT.
No DECL in DECL for: atom-list
The ATOMs in the atom-list given had no associated declarations.
NEWTYPE not DECLed: type-name
A NEWTYPE of a structured type was made but no DECL argument was
included. In a structured NEWTYPE, including a DECL of the interior can
greatly increase the efficiency of compiled code.
Illegal DECL: atom-list decl reason
The DECL pair given had illegal syntax for the reason given. These can
include:
"Not a legal type": An object appeared in aDECLthat was not anATOM,FORM, orSEGMENT."Type-name not a type: atom": Something other than a type-name or special symbol (such asANY) appeared where a type was expected. This is sometimes caused by not having your environment completely set up whenCRITICis run."FORM/SEGMENT too short": AFORM/SEGMENTconstruction of only one element was found."SPECIAL/UNSPECIAL with three or more elements""Bad PRIMTYPE type": The type given in aPRIMTYPEwas not a type-name."PRIMTYPE with three or more elements""Bad type of structured type": The type-name given as the type of a structured type was not a type. For example,<FOO FIX>whereFOOis not a type."Bad BYTES specification": ABYTESspecification was not of the form<BYTES fix fix>, or the byte size was greater than 36."BYTES DECL too short": ABYTESconstruction of only one element was encountered."BYTES DECL too long": ABYTESconstruction of more than three elements was encountered."VECTOR in OR specification": AnNTH/REST/OPTconstruction was found at top level of anOR."Nth/REST/OPT too short": A one-elementNTH/REST/OPT."Only REST or OPT may follow OPT": Something other than aRESTorOPTwas found after anOPT."REST must terminate DECL": Something was found after aRESTin theDECL.
3.11.7. Miscellaneous
Possibly should be QUOTEd: structure.
The structure given will be =? to itself if EVALed. CRITIC lists
these under the assumption that you might have forgotten to QUOTE a
structure that should have been. It says "possibly" because you obviously
want to build new structure sometimes. One way to do this without offending
CRITIC is to build new structure with explicit calls to LIST, VECTOR,
etc.
3.12. Program Environments
The ENV PACKAGE makes it easier to load programs into different
environments. It allows certain actions to be taken during loading only if
a given 'feature' is present. ENV has three ENTRYs, and is preloaded.
<FEATURES features:tuple>
If given no arguments, FEATURES returns the current feature LIST. If its
first argument is not a FALSE, the arguments are added to the feature
LIST. If the first argument is FALSE, the remaining arguments are
removed from the feature LIST. Thus,
<FEATURES "COMPILER">
says that we are currently in a compiler. All of the 'feature' arguments
may be either STRINGs or ATOMs; internally features are stored as
STRINGs to avoid OBLIST problems.
<FEATURE? features:tuple>
returns T if any of its arguments is on the feature LIST.
<EVAL-WHEN features
consequences:tuple>
uses the first argument to decide whether to evaluate the remaining arguments.
features specifies which feature(s) to look for. It may be a single feature
or a LIST of features. In the latter case, if the first element is a
FALSE, what is checked for is the absence of the features listed. Note
that this argument is often a LIST created out of arguments to FEATURE?.
consequences are things to be evaluated only if the features are present (or
absent, in the FALSE case).
For example,
<EVAL-WHEN GLUE <SETG FOO 1>>
would perform the SETG only if it's evaluated in a GLUE (or some other
environment defining that feature).
<EVAL-WHEN (<> COMPILER) <SETG BAR 2>>
would not perform the SETG in the compiler environment.
Unfortunately, the ENV PACKAGE is a relatively recent innovation, and so
many programs do not set up appropriate environments.
<FEATURES> ⇒ ()
<FEATURE? "COMPILER"> ⇒ #FALSE ()
<EVAL-WHEN (<> COMPILER) 42> ⇒ 42
<FEATURES "COMPILER"> ⇒ ("COMPILER")
<FEATURE? "COMPILER"> ⇒ T
<EVAL-WHEN COMPILER 42> ⇒ 42
pymdl's own. A fresh pymdl declares no features;
ENVisenv.mud, loaded on first reference as the era's "preloaded" allows. The era compiler's image declares"COMPILER"for itself, which is what the second example of the manual is for, and pymdl'sFILE-COMPILEmarks its scratch load the era's other way, withFILE-COMPILEonROOT(34.5.2), because that is the test the surviving programs make.