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. ,NULL is the ATOM the manual describes -- its PNAME is <ASCII 127>, and PPRINT returns it -- and it lives on ROOT there, so a program that names it finds the interpreter's; pymdl once handed it back without interning it, which <USE "PP"> noticed: the package's ENTRY NULL stopped with ALREADY-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:

  1. An & appearing between some variety of brackets indicates that there is a big object of the indicated TYPE there.
  2. 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 &1 were pure RSUBRs on the era, compiled from MUDBUG;FRAND, and here they are frand.mud interpreted, loaded as the FR& tool. A minimal & is built in for a bare interpreter, and the loader clears it before FR& loads so the package's ENTRY can take the name (chapter 28). The 1975 compiled FR& itself, MUDBUG;FR& NBIN, also runs here on the emulator, end to end over the bridged FRAME internals (tests/test_era_binaries.py), which is how one of the boundary's coherence rules was found: it reads a CHANNEL's column back out of the channel vector with MOVE 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 FRAMES worked, and it worked because it was compiled. The surviving PPRINT source reads <TYPE? ,FUNF FSUBR> where the line after it reads <AND <ASSIGNED? .FUNF> <TYPE? ..FUNF FSUBR>> -- the LVAL of the LVAL -- and nothing in the archive ever SETGs FUNF; every revision from 1976 to 1983 carries the line. Interpreted, FRAMES therefore signals UNASSIGNED-VARIABLE FUNF on the first frame whose function is a global, which is every one of them, here and on MDL 55. Disassembling MIT's pprint.nbin with the era's own UNASSM (chapter 35) showed the binary is not a fix -- the compiled FRAMES carries 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 an FSUBR". pymdl reproduces both halves. Compiled with the native compiler (<PYCOMPILE FRAMES>, chapter 34), FRAMES walks the chain and prints what MDL 55 printed, measured at an error level (its real use: the walk starts at the last ERROR or LISTEN frame, so a bare mid-computation <FRAMES> sees only TOPLEVEL below); 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, FRTYPE and FRLVAL have no such line and work either way:

<USE "FRMSP">
<FR& 2>              ⇒ TOPLEVEL
<SET X 3>            ⇒ 3
<FRLVAL X 5>
<TYPE <FRM 0>>       ⇒ FRAME

(FR& prints 0 LISTEN [] before answering; FRLVAL prints its two-column table under a Frame----Value heading, and at the listener X'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. The GVAL (preferably) or the LVAL of the ATOM is opened. If it has no value, EDIT returns a FALSE.
  • A PRIMTYPE LIST. The PRIMTYPE LIST is 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. EDIT reads its commands from .INCHAN like 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: PT then Q on a FUNCTION prints the function with the cursor; R 1 C 99 Q on (1 2 3) leaves ,L as (1 99 3), the change made in place; a macro string runs through M. Under the ITS personality edit.mud sets E-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 ^S also work normally.

  • All EDIT commands 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. EDIT commands may be typed in either upper or lower case.

  • If you type something that EDIT does 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 the OBLIST containing the ATOM whose value is the function), EDIT causes the OBLIST path to be set up to what it was in the environment of that PACKAGE. This has the advantage of reducing the number of trailers printed, and causes newly entered ATOMs to fall on the correct OBLIST (the internal OBLIST of the PACKAGE). It has the slight disadvantage that it disables the dynamic loader (which depends on unbound variables falling on the INITIAL OBLIST). If the GVAL of E-PKG is a FALSE, this feature is disabled, and the normal OBLIST path 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:

  1. Evaluates predicate. If the value is FALSE, evaluation continues as if there were no breakpoint. If the value is non-FALSE, or if BK was given no arguments:
  2. Types **BREAK**.
  3. For each argument after the first that you gave BK, types arg = EVAL of arg
  4. 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.

  1. Breakpoint on the first element of a FORM does not work. Put it on the whole FORM.
  2. Breakpoint on a LIST which is an argument to a COND does not work. Put it on the first FORM in the LIST.

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:

  1. Define FIB and test the FUNCTION a few times.
  2. Enter EDIT and position the cursor appropriately.
  3. Insert the breakpoint.
  4. Leave EDIT and run the FUNCTION again 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:

  1. Enter EDIT and kill the breakpoint from the previous example.
  2. Position the cursor and insert a conditional breakpoint with a predicate of <0? .X>.
  3. Leave EDIT and run the FUNCTION again for the value 10.
  4. Enter EDIT and 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; RETRY re-signals rather than re-evaluates; and ^G at the listener is a real error, *ERROR* CONTROL-G? and a new level, not a printed imitation of one. The banner's LISTENING-AT-LEVEL 2 PROCESS 1 in 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:

  1. On an association between group-name and the ATOM CHANNEL is stored a LIST giving the name of the file that was GROUP-LOADed to form the group. Removing this association before GROUP-DUMPing has the effect of making the entire group be output from core rather than copied from the original source.
  2. On an association between group-name and the ATOM MAGIC-RSUBR the ATOM T is stored if the group contained any RSUBRs in fast (NBIN) format. It is this association which is used to determine the default print-routine in GROUP-DUMP.
  3. The OBLIST path in effect at any time during the load is available. The original path is stored on an association between group-name and the ATOM BLOCK. Within the group, the path changes are stored in an association between the group RESTed to the point of change and the ATOM BLOCK.
  4. If the second element of a FUNCTION definition is not an ATOM, the actual FUNCTION name gotten by EVAL of that element is stored as an association between the original element and the ATOM VALUE.
  5. The location of a function within the input file is stored as a LIST of the starting and ending offsets (in characters) of the function, under an association between a locative to the GVAL of the FUNCTION name and the indicator DEFINE. This association is removed by EDIT (and other editors) to indicate that the FUNCTION has 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; the CHANNEL association holds the file name as the channel parsed it under pymdl's file root (chapter 2), so the "device" is DSK and the directory is empty. GROUP-DUMP copies unchanged functions from the source text by the character offsets of item 5, exactly as described, and the dumped fact2.mud is the source again. GROUP-LOAD is also the front door of the era compiler here: FILE-COMPILE GROUP-LOADs the input in the compiler session and GROUP-DUMPs the result as an NBIN (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. DEBUGR is complete here: debugr.mud loads, DEBUG makes it ready, and the one-step display -- n=> object, n<= result, the n: short = value lines 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 ^N key 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: EVLIN and EVLOUT are ROOT atoms; a #TYPE atom literal read from source CHTYPEs a copy, so #DISMISS T does not retype T itself; <DISMISS value> with no activation exits the handler's application; a handler given a PROCESS runs on that process's stack (chapter 23); <FRAME process> is that process's topmost frame (chapter 22); and a stepped evaluation pushes real EVAL-named frames so FRAME-COUNT is 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.
  • ^B begins one-stepping.
  • ^N performs the next step of the computation.
  • ^O steps completely over the next computation, then stops and continues one-stepping.
  • ^A evaluates the arguments of the current object then stops and continues one-stepping through the body.
  • ^R continues evaluation until you return to this point.
  • ^E ends one-stepping.
  • ^Q quits 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 -- via LVAL
  • For GVALs -- via GVAL
  • For STRUCTUREDs -- via NTH
  • For ASSOCIATIONs -- via GET and GETPROP

Write monitor:

  • For LVALs -- via SET or "AUX" bindings
  • For GVALs -- via SETG
  • For STRUCTUREDs -- via PUT, SUBSTRUC
  • For ASSOCIATIONs -- via PUT and PUTPROP

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:

  • OLDVAL is the old value of the object monitored.
  • NEWVAL is the new value of the object monitored.
  • MONOBJ is the object monitored (ATOM or STRUCTURED).

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 is monitr.mud over them, loaded by <USE "MONITOR">; EMONIT, the EDIT interface, loads with PP, EDIT and MONITOR before it, as the UM command 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 compiled GVAL read does pass through the interpreter's cell and will trigger a GVAL monitor, 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 any ATOM containing "SDM" anywhere in its PNAME.
  • "*=SDM*": matches any ATOM containing "SDM" in its PNAME, provided that at least one character precedes the "SDM".
  • "^Q*": matches any ATOM with PNAME "*".
  • "*": matches any ATOM.

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 all OBLISTs in .OBLIST
  • #FALSE (oblists-or-forms): search all but the OBLISTs 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. Any ATOM matching specstr will be accepted.
  • ANY: overrides any constraint other than T. Any ATOM matching specstr which has a value (either GVAL or LVAL) will be accepted.
  • <>: any ATOM which has no value will be accepted. Note that giving both ANY and <> is equivalent to giving T.
  • LINK: any LINK will be accepted.
  • If other constraints are provided, they work as follows: all valid TYPE names given (ones for whom VALID-TYPE? returns T) are stored in a structure; when a value is encountered, its TYPE is MEMQed on this structure. If the ATOM does 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 DECL specification. All such objects are put in a FORM starting with OR, which has the effect of generating a single DECL specification. When a value is found, DECL? is called with the value as its first argument and the generated FORM as its second. If DECL? returns T, meaning that the FORM is valid as a DECL for the VALUE, the ATOM is 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, RDFL and UN-DFL are the era's dfl.mud, and all three work on pymdl's files: DFL finds a <DEFINE by name in the source and loads just that function, RDFL reloads a SETGed object, and UN-DFL writes an edited function back over its place in the file (tests/test_mudbug.py). Loading dfl.mud wanted a helper the era got from a hand-assembled READST, and dfl.fbin's pure code for it runs here on the emulator -- it is the 1DFL block whose disassembly chapter 35 checks -- with a Python stand-in only if the binary is not reachable. DFL of a name that is already defined stops at ALREADY-DEFINED-ERRET-NON-FALSE-TO-REDEFINE, exactly as a second DEFINE does (chapter 12): set REDEFINE first, or ERRET past it.

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:

  1. Is the object pure, according to PURE? If not, leave.
  2. Is UNPURIFY-PAGE!-IUNLINK GASSIGNED? If not, get a page from the interpreter, and SETG the aforementioned ATOM to its number. I.e., the page is more or less permanently taken for use of UNPURIFY.
  3. 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 of UNPURIFY-PAGE back 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 PURBOT in read-only pages shared from the SAV files (33.13). pymdl keeps the observable: an RSUBR loaded from the pure code database answers PURE? true, UNPURIFY makes it writable and answers T, and UNLINK puts the ATOMs back into reference vectors an RSUBR-LINK had replaced (tests/test_mudbug.py); the page games of step 3 have nothing to act on and are not performed. The library's own PURE? is a package ENTRY; a pymdl built-in of the same name once sat on ROOT and made <USE "UNLINK"> fail at that ENTRY, which is the ALREADY-USED-ELSEWHERE of 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. CRITIC never ran under declaration checking on real MDL, and cannot here either: its own source declares FATHER-LIST with a #DECL that names the FATHER NEWTYPE before that NEWTYPE exists, so with DECL-CHECK on -- pymdl's default -- the first analysis dies TYPE-MISMATCH inside 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 with Called-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 as CELEST'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 a DECL that was not an ATOM, FORM, or SEGMENT.
  • "Type-name not a type: atom": Something other than a type-name or special symbol (such as ANY) appeared where a type was expected. This is sometimes caused by not having your environment completely set up when CRITIC is run.
  • "FORM/SEGMENT too short": A FORM/SEGMENT construction of only one element was found.
  • "SPECIAL/UNSPECIAL with three or more elements"
  • "Bad PRIMTYPE type": The type given in a PRIMTYPE was 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> where FOO is not a type.
  • "Bad BYTES specification": A BYTES specification was not of the form <BYTES fix fix>, or the byte size was greater than 36.
  • "BYTES DECL too short": A BYTES construction of only one element was encountered.
  • "BYTES DECL too long": A BYTES construction of more than three elements was encountered.
  • "VECTOR in OR specification": An NTH/REST/OPT construction was found at top level of an OR.
  • "Nth/REST/OPT too short": A one-element NTH/REST/OPT.
  • "Only REST or OPT may follow OPT": Something other than a REST or OPT was found after an OPT.
  • "REST must terminate DECL": Something was found after a REST in the DECL.

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; ENV is env.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's FILE-COMPILE marks its scratch load the era's other way, with FILE-COMPILE on ROOT (34.5.2), because that is the test the surviving programs make.