The pymdl book
14. Input and output
The MDL interpreter can transmit information between an object in MDL and an
external device in three ways. Historically, the first way was to convert
an object into a string of characters, or vice versa. The transformation is
nearly one-to-one (although some MDL objects, for example TUPLEs, cannot be
input in this way) and is similar in style to Fortran's formatted I/O. It is
what READ and PRINT do, and it is the normal method for terminal I/O.
The second way is used for the contents of MDL objects rather than the objects themselves. Here an image of numbers or characters within an object is transmitted, similar in style to Fortran's unformatted I/O.
The third way is to dump an object in a clever format so that it can be reproduced exactly when input the next time. Exact reproduction means that any sharing between structures or self-reference is preserved; only the garbage collector itself can do I/O in this way.
pymdl's own. pymdl supports conversion, image, and dump I/O.
GC-DUMPpreserves sharing between objects when they are read back (section 11.5).SAVErecords a session in pymdl's own format (section 11.6). Historical MDL saved images have a different layout; chapter 38 explains how they are inspected.
11.1. Conversion I/O
All conversion-I/O SUBRs in MDL take an optional argument which directs their
attention to a specific I/O channel. This section will describe SUBRs
without their optional arguments. In this situation, they all refer to a
particular channel by default, initially the terminal running the MDL. When
given an optional argument, that argument follows any arguments indicated here.
Some of these SUBRs also have additional optional arguments, relevant to
conversion, discussion of which will be deferred until later.
11.1.1. Input
All of the following input Subroutines, when directed at a terminal, hang until
$ (ESC) is typed and allow normal use of rubout, ^D, ^L and ^@.
11.1.1.1. READ
<READ>
This returns the entire MDL object whose character representation is next in
the input stream. Successive <READ>s return successive objects. This is
precisely the SUBR READ mentioned in chapter 5. See also sections 11.3,
18.7.1, and 20.1.3 for optional arguments.
11.1.1.2. READCHR
<READCHR>
("read character") returns the next CHARACTER in the input stream.
Successive <READCHR>s return successive CHARACTERs.
11.1.1.3. NEXTCHR
<NEXTCHR>
("next character") returns the CHARACTER which READCHR will return the next
time READCHR is called. Multiple <NEXTCHR>s, with no input operations
between them, all return the same thing.
11.1.2. Output
If an object to be output requires (or can tolerate) separators within it (for
example, between the elements in a structured object or after the TYPE name
in "# notation"), these conversion-output SUBRs will use a
carriage-return/line-feed separator to prevent overflowing a line. Overflow is
detected in advance from elements of the CHANNEL in use (section 11.2.8).
11.1.2.1. PRINT
<PRINT any>
This outputs, in order,
- a carriage-return line-feed,
- the character representation of
EVALof its argument (PRINTis aSUBR), and - a space
and then returns EVAL of its argument. This is precisely the SUBR PRINT
mentioned in chapter 5.
11.1.2.2. PRIN1
<PRIN1 any>
outputs just the representation of, and returns, EVAL of any.
11.1.2.3. PRINC
<PRINC any>
("print characters") acts exactly like PRIN1, except that
- if its argument is a
STRINGor aCHARACTER, it suppresses the surrounding"s or initial!\respectively; or - if its argument is an
ATOM, it suppresses any\s orOBLISTtrailers (chapter 18) which would otherwise be necessary.
If PRINC's argument is a structure containing STRINGs, CHARACTERs, or
ATOMs, the service mentioned will be done for all of them. Ditto for the
ATOM used to name the TYPE in "# notation".
11.1.2.4. TERPRI
<TERPRI>
("terminate printing") outputs a carriage-return line-feed and then returns
#FALSE ()!
11.1.2.5. CRLF
("carriage-return line-feed") outputs a carriage-return line-feed and then
returns T.
<TERPRI> ⇒ #FALSE ()
<CRLF> ⇒ T
The exclamation point in 11.1.2.4 is the manual's own, and it is a warning:
section 8.4.1 told you to watch out for TERPRI inside an AND, and this is
why. CRLF exists to be the same operation with a usable value.
11.1.2.6. FLATSIZE
<FLATSIZE any max:fix radix:fix>
does not actually cause any output to occur and does not take a CHANNEL
argument. Instead, it compares max with the number of characters PRIN1 would
take to print any. If max is less than the number of characters needed
(including the case where any is self-referencing), FLATSIZE returns
#FALSE (); otherwise, it returns the number of characters needed by
PRIN1 any. radix (optional, ten by default) is used for converting any
FIXes that occur.
<FLATSIZE "ABC" 100> ⇒ 5
<FLATSIZE 12345 3> ⇒ #FALSE ()
<FLATSIZE '(1 2) 100> ⇒ 5
<FLATSIZE 255 100 8> ⇒ 3
<FLATSIZE !\A 10> ⇒ 3
"ABC" is five characters because PRIN1 would print its quotation marks, and
!\A is three for the same reason. The 255 in radix eight is 377.
This SUBR is especially useful in conjunction with (section 11.2.8) those
elements of a CHANNEL which specify the number of characters per output line
and the current position on an input line.
pymdl's own. "Including the case where any is self-referencing" is the one clause
FLATSIZEcannot answer by counting, and it is the same problem chapter 10's box on section 7.8 describes. The bound is what makes it answerable at all:FLATSIZEstops as soon as it passes max, so a circular structure gives#FALSE ()rather than counting forever. It isLENGTH?for printing.
11.2. CHANNEL (the TYPE)
I/O channels are dynamically assigned in MDL, and are represented by an object
of TYPE CHANNEL, which is of PRIMTYPE VECTOR. The format of a
CHANNEL will be explained later, in section 11.2.8. First, how to generate
and use them.
<TYPE ,OUTCHAN> ⇒ CHANNEL
<TYPEPRIM CHANNEL> ⇒ VECTOR
11.2.1. OPEN
<OPEN mode file-spec>
or
<OPEN mode name1 name2 device dir>
OPEN is a SUBR which creates and returns a CHANNEL. All its arguments
must be of TYPE STRING, and all are optional. The preceding statement
is false when the device is "INT" or "NET"; see sections 11.9 and 11.10. If
the attempted opening of an operating-system I/O channel fails, OPEN returns
#FALSE (reason:string file-spec:string status:fix), where the reason and the
status are supplied by the operating system, and the file-spec is the standard
name of the file (after any name transformations by the operating system) that
MDL was trying to open.
<OPEN "READ" "no-such-file.txt"> ⇒ #FALSE ("FILE NOT FOUND" "DSK:no-such-file txt" 1048576)
The choice of mode is usually determined by which SUBRs will be used on the
CHANNEL, and whether or not the device is a terminal. The following table
tells which SUBRs can be used with which modes, where OK indicates an
allowed use:
SUBRs |
"READ" |
"PRINT" |
"READB" |
"PRINTB", "PRINTO" |
|---|---|---|---|---|
READ READCHR NEXTCHR READSTRING FILECOPY FILE-LENGTH LOAD |
OK | OK | ||
PRINT PRIN1 PRINC IMAGE CRLF TERPRI FILECOPY PRINTSTRING BUFOUT NETS RENAME |
OK | OK* | ||
READB GC-READ |
OK | |||
PRINTB GC-DUMP |
OK | |||
ACCESS |
OK | OK | OK | |
RESET |
OK | OK | OK | OK |
ECHOPAIR |
OK | OK | ||
TTYECHO TYI |
OK |
* PRINTing (or PRIN1ing) an RSUBR (chapter 33) on a "PRINTB" or
"PRINTO" CHANNEL has special effects.
(The manual prints this table with its column headings in the last column and
its SUBR names in the header row; it is transposed here so the SUBR names
label the rows they describe.)
"PRINTB" differs from "PRINTO" in that the latter mode is used to update a
"DSK" file without copying it. "READB" and "PRINTB" are not used with
terminals. "READ" is the mode used by default.
The next one to four arguments to OPEN specify the file involved. If only
one STRING is used, it can contain the entire specification, according to
standard operating-system syntax. Otherwise, the string(s) are interpreted as
follows:
name1 is the first file name, that part to the left of the space (in the ITS
version) or period (in the Tenex and Tops-20 versions). The name used by
default is <VALUE NM1>, if any, otherwise "INPUT".
name2 is the second file name, that part to the right of the space (ITS) or
period (Tenex and Tops-20). The name used by default is <VALUE NM2>, if any,
otherwise ">" or "MUD" and highest version number (Tenex) or generation
number (Tops-20).
device is the device name. The name used by default is <VALUE DEV>, if any,
otherwise "DSK". (Devices about which MDL has no special knowledge are
assumed to behave like "DSK".)
dir is the disk-directory name. The name used by default is <VALUE SNM>, if
any, otherwise the "working-directory" name as defined by the operating system.
Examples:
<OPEN "PRINT" "TPL:"> opens a conversion-output channel to the TPL device.
<OPEN "PRINT" "DUMMY" "NAMES" "TPL"> does the same.
<OPEN "PRINT" "TPL"> opens a CHANNEL to the file DSK:TPL > (ITS version)
or DSK:TPL.MUD (Tenex and Tops-20 versions).
<OPEN "READ" "FOO" ">" "DSK" "GUEST"> opens up a conversion-input CHANNEL
to the given file.
<OPEN "READ" "GUEST;FOO"> does the same in the ITS version.
pymdl's own. The ITS name syntax is understood here -- a
CHANNELrecords the"DSK"device and the two name halves, and theFALSEabove reports"DSK:no-such-file txt"in the era's own spelling, with a space where a modern path has a dot. But the file it actually opens is a host file, resolved under a file root the session sets, so the book's own examples can ship files beside them. Chapter 2 has how that root is chosen; chapter 38 has the ITS directory format for the times a real one must be read.
11.2.2. OPEN-NR
OPEN-NR is the same as OPEN, except that the date and time of last
reference of the opened file are not changed.
11.2.3. CHANNEL (the SUBR)
CHANNEL is called exactly like OPEN, but it always returns an unopened
CHANNEL, which can later be opened by RESET (below) just as if it had once
been open.
11.2.4. FILE-EXISTS?
FILE-EXISTS? tests for the existence of a file without creating a CHANNEL,
which occupies about a hundred machine words of storage. It takes file-name
arguments just like OPEN (but no mode argument) and returns either T or
#FALSE (reason:string status:fix).
11.2.5. CLOSE
<CLOSE channel>
closes channel and returns its argument, with its "state" changed to "closed".
If channel is for output, all buffered output is written out first. No harm is
done if channel is already CLOSEd.
11.2.6. CHANLIST
<CHANLIST>
returns a LIST whose elements are all the currently open CHANNELs. The
first two elements are usually .INCHAN and .OUTCHAN (see below). A
CHANNEL not referenced by anything except <CHANLIST> will be CLOSEd
during garbage collection.
<LENGTH <CHANLIST>> ⇒ 2
Two, in a fresh interpreter, and they are the terminal's input and output.
11.2.7. INCHAN and OUTCHAN
The channel used by default for input SUBRs is the local value of the ATOM
INCHAN. The channel used by default for output SUBRs is the local value of
the ATOM OUTCHAN.
You can direct I/O to a CHANNEL by SETting INCHAN or OUTCHAN
(remembering their old values somewhere), or by giving the SUBR you wish to
use an argument of TYPE CHANNEL. (These actually have the same effect,
because READ binds INCHAN to an explicit argument, and PRINT binds
OUTCHAN similarly. Thus the CHANNEL being used is available for READ
macros (section 20.1) and PRINTTYPEs (section 6.4.4).)
By the way, a good trick for playing with INCHAN and OUTCHAN values within
a function is to use the ATOMs INCHAN and OUTCHAN as "AUX" variables,
re-binding their local values to the CHANNEL you want. When you leave, of
course, the old LVALs are restored (which is the whole point). The ATOMs
must be declared SPECIAL (chapter 17) for this trick to compile correctly.
INCHAN and OUTCHAN also have global values, initially the CHANNELs
directed at the terminal running MDL. Initially, INCHAN's and OUTCHAN's
local and global values are the same.
pymdl's own. That trick is used throughout the era's own programs, and it is also the one that most needs the
SPECIALwarning: an"AUX"rebinding ofOUTCHANis invisible to compiled code unless theATOMis declared, because compiled code keeps unspecial locals in machine registers rather than in the binding stack. Chapter 17 has theDECL, and chapter 34 has the same hazard in pymdl's native compiler, where the registers are Python locals -- which is why it refuses to compile aFUNCTIONliteral that closes over one.
11.2.8. Contents of CHANNELs
The contents of an object of TYPE CHANNEL are referred to by the I/O
SUBRs each time such a SUBR is used. If you change the contents of a
CHANNEL (for example, with PUT), the next use of that CHANNEL will be
changed accordingly. Some elements of CHANNELs, however, should be played
with seldom, if ever, and only at your own peril. These are marked below with
an * (asterisk). Caveat user.
There follows a table of the contents of a CHANNEL, the TYPE of each
element, and an interpretation.
11.2.8.1. Output CHANNELs
| element | type | interpretation |
|---|---|---|
| -1 | LIST |
transcript channel(s) (see below) |
| * 0 | varies | device-dependent information |
| * 1 | FIX |
channel number (ITS) or JFN (Tenex and Tops-20), 0 for internal or closed |
| * 2 | STRING |
mode |
| * 3 | STRING |
first file name argument |
| * 4 | STRING |
second file name argument |
| * 5 | STRING |
device name argument |
| * 6 | STRING |
directory name argument |
| * 7 | STRING |
real first file name |
| * 8 | STRING |
real second file name |
| * 9 | STRING |
real device name |
| * 10 | STRING |
real directory name |
| * 11 | FIX |
various status bits |
| * 12 | FIX |
PDP-10 instruction used to do one I/O operation |
| 13 | FIX |
number of characters per line of output |
| 14 | FIX |
current character position on a line |
| 15 | FIX |
number of lines per page |
| 16 | FIX |
current line number on a page |
| 17 | FIX |
access pointer for file-oriented devices |
| 18 | FIX |
radix for FIX conversion |
| 19 | FIX |
sink for an internal CHANNEL |
The four unstarred slots are the ones a program may usefully read, and they do what the table says:
<SET C <OPEN "PRINT" "ex.txt">>
<13 .C> ⇒ 80
<14 .C> ⇒ 0
<PRINC "abcd" .C> ⇒ "abcd"
<14 .C> ⇒ 4
<18 .C> ⇒ 10
<2 .C> ⇒ "PRINT"
<1 <CLOSE .C>> ⇒ 0
The line width is 80, the character position tracks what has been written, the
radix is ten, and CLOSE really does put 0 in the channel-number slot, as the
table says it does for a closed channel.
N.B.: The elements of a CHANNEL below number 1 are usually invisible but are
obtainable via <NTH <TOP channel> fix>, for some appropriate fix.
The transcript-channels slot has this meaning: if this slot contains a LIST
of CHANNELs, then anything input or output on the original CHANNEL is
output on these CHANNELs. Caution: do not use a CHANNEL as its own
transcript channel; you probably won't live to tell about it.
11.2.8.2. Input CHANNELs
The contents of the elements up to number 12 of a CHANNEL used for input are
the same as that for output. The remaining elements are as follows ("same"
indicates that the use is the same as that for output):
| element | type | interpretation |
|---|---|---|
| 13 | varies | object evaluated when end of file is reached |
| * 14 | FIX |
one "look-ahead" character, used by READ |
| * 15 | FIX |
PDP-10 instruction executed waiting for input |
| 16 | LIST |
queue of buffers for input from a terminal |
| 17 | FIX |
access pointer for file-oriented devices (same) |
| 18 | FIX |
radix for FIX conversion (same) |
| 19 | STRING |
buffer for input or source for internal CHANNEL |
Slot 13 changing meaning between an output and an input CHANNEL -- from the
line width to the end-of-file routine -- is not a misprint, and it is the next
section's subject.
11.3. End-of-File "Routine"
As mentioned above, an explicit CHANNEL is the first optional argument of all
SUBRs used for conversion I/O. The second optional argument for
conversion-input SUBRs is an "end-of-file routine" -- that is, something
for the input SUBR to EVAL and return, if it reaches the end of the file it
is reading. A typical end-of-file argument is a QUOTEd FORM which applies a
function of yours. The value of this argument used by default is a call to
ERROR. Note: the CHANNEL has been CLOSEd by the time this argument is
evaluated.
Example: the following FUNCTION counts the occurrences of a character in a
file, according to its arguments. The file names, device, and directory are
optional, with the usual names used by default.
<DEFINE COUNT-CHAR
(CHAR "TUPLE" FILE "AUX" (CNT 0) (CHN <OPEN "READ" !.FILE>))
<COND (.CHN ;"If CHN is FALSE, bad OPEN: return the FALSE
so result can be tested by another FUNCTION."
<REPEAT ()
<AND <==? .CHAR <READCHR .CHN '<RETURN>>>
<SET CNT <+ 1 .CNT>>>>
;"Until EOF, keep reading and testing a character at a time."
.CNT ;"Then return the count.")>> ⇒ COUNT-CHAR
<COUNT-CHAR !\a "banana.txt"> ⇒ 3
<COUNT-CHAR !\z "banana.txt"> ⇒ 0
<COUNT-CHAR !\a "no-such-file.txt"> ⇒ #FALSE ("FILE NOT FOUND" "DSK:no-such-file txt" 1048576)
(banana.txt is one of the files this book ships beside its examples; it
contains the word.)
Found along the way. That third answer was a bare
#FALSE ()here until 2026-09-10, and thisFUNCTION's own comment is what found the bug: "return theFALSEso result can be tested by anotherFUNCTION" is a promise aboutCOND, not aboutOPEN. Section 8.3 states it plainly -- "If it can't find a non-FALSE, it returns the lastFALSEit saw" -- and pymdl'sCONDwas returning a fresh emptyFALSEinstead of the one it saw, throwing away every reason a failing test had attached. A single-clauseCONDused as a guard, which is what this is, therefore lost exactly the information it was written to pass along.
<COND (.F YES)>where.Fis#FALSE ("R")is now#FALSE ("R"), and section 8.1's box onFALSEs carrying reasons is true all the way through rather than only until the firstCOND.
11.4. Imaged I/O
11.4.1. Input
11.4.1.1. READB
<READB buffer:uvector-or-storage channel eof:any>
The channel must be open in "READB" mode. READB will read as many 36-bit
binary words as necessary to fill the buffer (whose UTYPE must be of
PRIMTYPE WORD), unless it hits the end of the file. READB returns the
number of words actually read, as a FIXed-point number. This will normally be
the length of the buffer, unless the end of file was read, in which case it will
be less, and only the beginning of buffer will have been filled (SUBSTRUC may
help). An attempt to READB again, after buffer is not filled, will evaluate
the end-of-file routine eof, which is optional, a call to ERROR by default.
11.4.1.2. READSTRING
<READSTRING buffer:string channel stop:fix-or-string eof>
is the STRING analog to READB, where buffer and eof are as in READB, and
channel is any input CHANNEL (.INCHAN by default). stop tells when to stop
inputting: if a FIX, read this many CHARACTERs (fill up buffer by default);
if a STRING, stop reading if any CHARACTER in this STRING is read (don't
include this CHARACTER in final STRING).
Note what READSTRING returns and where the characters go:
<SET W <OPEN "PRINT" "rs.txt">>
<PRINC "abcd" .W> ⇒ "abcd"
<CLOSE .W>
<SET R <OPEN "READ" "rs.txt">>
<SET S <ISTRING 4>> ;"four ^@ characters -- 7.5.4"
<READSTRING .S .R> ⇒ 4
.S ⇒ "abcd"
<CLOSE .R>
The answer is the count; the characters are written into the buffer you
supplied, which is why ISTRING is how you make one.
11.4.2. Output
11.4.2.1. PRINTB
<PRINTB buffer:uvector-or-storage channel>
This call writes the entire contents of the buffer into the specified channel
open in "PRINTB" or "PRINTO" mode. It returns buffer.
<SET B <OPEN "PRINTB" "words.bin">>
<PRINTB '![1 2 3] .B> ⇒ ![1 2 3!]
<CLOSE .B>
<SET RB <OPEN "READB" "words.bin">>
<SET BUF <IUVECTOR 3 0>> ⇒ ![0 0 0!]
<READB .BUF .RB> ⇒ 3
.BUF ⇒ ![1 2 3!]
<CLOSE .RB>
11.4.2.2. PRINTSTRING
<PRINTSTRING buffer:string channel count:fix>
is analogous to READSTRING. It outputs buffer on channel, either the whole
thing or the first count characters, and returns the number of characters
output.
11.4.2.3. IMAGE
<IMAGE fix channel>
is a rather special-purpose SUBR. When any conversion-output routine outputs
an ASCII control character (with special exceptions like carriage-returns,
line-feeds, etc.), it actually outputs two characters: ^ (circumflex),
followed by the upper-case character which has been control-shifted. IMAGE,
on the other hand, always outputs the real thing: that ASCII character whose
ASCII 7-bit code is fix. It is guaranteed not to give any gratuitous
linefeeds or such. channel is optional, .OUTCHAN by default, and its slots
for current character position (number 14) and current line number (16) are not
updated. IMAGE returns fix.
11.5. Dumped I/O
11.5.1. Output: GC-DUMP
<GC-DUMP any printb:channel-or-false>
dumps any on printb in a clever format so that GC-READ (below) can reproduce
any exactly, including sharing. any cannot live on the control stack, nor can
it be of PRIMTYPE PROCESS or LOCD or ASOC (which see). any is returned
as a value.
If printb is a CHANNEL, it must be open in "PRINTB" or "PRINTO" mode. If
printb is a FALSE, GC-DUMP instead returns a UVECTOR (of UTYPE
PRIMTYPE WORD) that contains what it would have output on a CHANNEL. This
UVECTOR can be PRINTBed anywhere you desire, but, if it is changed in any
way, GC-READ will not be able to input it. Probably the only reason to get
it is to check its length before output.
Except for the miniature garbage collection required, GC-DUMP is about twice
as fast as PRINT, but the amount of external storage used is two or three
times as much.
11.5.2. Input: GC-READ
<GC-READ readb:channel eof:any>
returns one object from the channel, which must be open in "READB" mode. The
file must have been produced by GC-DUMP. eof is optional. GC-READ is about
ten times faster than READ.
Here is the property that makes all this worth having. Build a structure whose two elements are the same object, dump it, read it back, and ask:
<SET SHARED '(1 2)> ⇒ (1 2)
<SET OBJ (.SHARED .SHARED)> ⇒ ((1 2) (1 2))
<SET GB <OPEN "PRINTB" "dump.bin">>
<GC-DUMP .OBJ .GB> ⇒ ((1 2) (1 2))
<CLOSE .GB>
<SET GR <OPEN "READB" "dump.bin">>
<SET BACK <GC-READ .GR>> ⇒ ((1 2) (1 2))
<CLOSE .GR>
<==? <1 .BACK> <2 .BACK>> ⇒ T
PRINT and READ would have given #FALSE () for that last question, because
they write two copies of something that was one object. The FALSE variant
works too, and answers a UVECTOR you can measure:
<TYPE <GC-DUMP '(1 2) <>>> ⇒ UVECTOR
pymdl's own. The dump format here is pymdl's, not the era's: it records the same graph, and it round-trips, but a file written by MDL 55's
GC-DUMPis a picture of that machine's storage words and is read by chapter 36's machinery instead. What the manual promises aboutGC-DUMP-- exact reproduction, sharing preserved, self-reference preserved -- is what is reproduced, and the test above is how it is checked.
11.6. SAVE Files
The entire state of MDL can be saved away in a file for later restoration: this
is done with the SUBRs SAVE and RESTORE. This is a very different form of
I/O from any mentioned up to now; the file used contains an actual image of your
MDL address space and is not, in general, "legible" to other MDL routines.
RESTOREing a SAVE file is much faster than re-READing the objects it
contains.
Since a SAVE file does not contain all extant MDL objects, only the impure and
PURIFYed (section 22.9.2) ones, a change to the interpreter has the result of
making all previous SAVE files unusable. To prevent errors from arising from
this, the interpreter has a release number, which is incremented whenever
changes are installed. The current release number is printed out on initially
starting up the program and is available as the GVAL of the ATOM MUDDLE.
This release number is written out as the very first part of each SAVE file.
If RESTORE attempts to re-load a SAVE file whose release number is not the
same as the interpreter being used, an error is produced. If desired, the
release number of a SAVE file can be obtained by doing a READ of that file.
Only that initial READ will work; the rest of the file is not ASCII.
,MUDDLE ⇒ 55
Fifty-five, because that is the interpreter this book measures against, and chapter 1 says why.
11.6.1. SAVE
<SAVE file-spec:string gc?:false-or-any>
or
<SAVE name1 name2 device dir gc?:false-or-any>
saves the entire state of your MDL away in the file specified by its arguments,
and then returns "SAVED". All STRING arguments are optional, with
"MUDDLE", "SAVE", "DSK", and <VALUE SNM> used by default. gc? is
optional and, if supplied and of TYPE FALSE, causes no garbage collection to
occur before SAVEing. (FSAVE is an alias for SAVE that may be seen in old
programs.)
If, after restoring, RESTORE finds that <VALUE SNM> is the null STRING
(""), it will ask the operating system for the name of the "working
directory" and call SNAME with the result. This mechanism is handy for
"public" SAVE files, which should not point the user at a particular disk
directory.
In the ITS version, the file is actually written with the name _MUDS_ > and
renamed to the argument(s) only when complete, to prevent losing a previous
SAVE file if a crash occurs. In the Tenex and Tops-20 versions,
version/generation numbers provide the same safety.
Example:
<DEFINE SAVE-IT ("OPTIONAL"
(FILE '("PUBLIC" "SAVE" "DSK" "GUEST"))
"AUX" (SNM ""))
<SETUP>
<COND (<=? "SAVED" <SAVE !.FILE>> ;"See below."
<CLEANUP>
"Saved.")
(T
<CRLF>
<PRINC "Amazing program at your service.">
<CRLF>
<START-RUNNING>)>>
11.6.2. RESTORE
<RESTORE file-spec>
or
<RESTORE name1 name2 device dir>
replaces the entire current state of your MDL with that SAVEd in the file
specified. All arguments are optional, with the same values used by default as
by SAVE.
RESTORE completely replaces the contents of the MDL, including the state of
execution existing when the SAVE was done and the state of all open I/O
CHANNELs. If a file which was open when the SAVE was done does not exist
when the RESTORE is done, a message to that effect will appear on the
terminal.
A RESTORE never returns (unless it gets an error): it causes a SAVE done
some time ago to return again (this time with the value "RESTORED"), even
if the SAVE was done in the midst of running a program. In the latter case,
the program will continue its execution upon RESTOREation.
That is the whole trick of SAVE-IT above: the COND tests for "SAVED"
because a RESTORE months later makes the very same SAVE call return
"RESTORED", and the program takes the other branch and starts running.
pymdl's own. This is the one part of the chapter that cannot be literally reproduced, and it is worth being exact about why. A
SAVEfile is an image of a PDP-10 address space; pymdl has no address space to image, and the release-number check the manual describes would reject every file it wrote anyway. What pymdl writes is aSAVEof its own, restoring the same observables -- globals, oblists, open channels, the pending computation -- and refusing an era file rather than pretending. An eraSAVEfile is not lost, though: chapter 33 reads one, because the emulator does have an address space, and chapter 31'sPURIFYis the other half of what such a file contains.
11.7. Other I/O Functions
11.7.1. LOAD
<LOAD input:channel look-up>
eventually returns "DONE". First, however, it READs and EVALs every MDL
object in the file pointed to by input, and then CLOSEs input. Any
occurrences of rubout, ^@, ^D, ^L, etc., in the file are given no special
meaning; they are simply ATOM constituents.
look-up is optional, used to specify a LIST of OBLISTs for the READ.
.OBLIST is used by default (chapter 18).
11.7.2. FLOAD
<FLOAD file-spec look-up>
or
<FLOAD name1 name2 device dir look-up>
("file load") acts just like LOAD, except that it takes arguments (with values
used by default) like OPEN, OPENs the CHANNEL itself for reading, and
CLOSEs the CHANNEL when done. look-up is optional, as in LOAD. If the
OPEN fails, an error occurs, giving the reason for failure.
<SET W <OPEN "PRINT" "prog.mud">>
<PRINC "<SETG LOADED-OK 42>" .W> ⇒ "<SETG LOADED-OK 42>"
<CLOSE .W>
<FLOAD "prog.mud"> ⇒ "DONE"
,LOADED-OK ⇒ 42
Note the asymmetry the two sections describe: a failed OPEN inside FLOAD is
an error, while the same failure from OPEN itself is a FALSE. Section
8.4.1's idiom exists because of that difference.
11.7.3. SNAME
<SNAME string> ("system name", a hangover from ITS) is identical in effect
with <SETG SNM string>, that is, it causes string to become the dir argument
used by default by all SUBRs which want file specifications (in the absence of
a local value for SNM). SNAME returns its argument.
<SNAME> is identical in effect with <GVAL SNM>, that is, it returns the
current dir used by default.
<SNAME "GUEST"> ⇒ "GUEST"
<SNAME> ⇒ "GUEST"
,SNM ⇒ "GUEST"
11.7.4. ACCESS
<ACCESS channel fix>
returns channel, after making the next character or binary word (depending on
the mode of channel, which should not be "PRINT") which will be input from or
output to channel the (fix+1)st one from the beginning of the file. channel
must be open to a randomly accessible device ("DSK", "USR", etc.). A fix of
0 positions channel at the beginning of the file.
11.7.5. FILE-LENGTH
<FILE-LENGTH input:channel>
returns a FIX, the length of the file open on input. This information is
supplied by the operating system, and it may not be available, for example, with
the "NET" device (section 11.10). If input's mode is "READ", the length is
in characters (rounded up to a multiple of five); if "READB", in binary words.
If ACCESS is applied to input and this length or more, then the next input
operation will detect the end of file.
ACCESS and FILE-LENGTH together are how a file is re-read without
re-opening it:
<SET W <OPEN "PRINT" "seek.txt">>
<PRINC "abcd" .W> ⇒ "abcd"
<CLOSE .W>
<SET R <OPEN "READ" "seek.txt">>
<FILE-LENGTH .R> ⇒ 5
<READSTRING <ISTRING 2> .R> ⇒ 2
<ACCESS .R 0>
<SET S <ISTRING 4>> ;"four ^@ characters -- 7.5.4"
<READSTRING .S .R> ⇒ 4
.S ⇒ "abcd"
<CLOSE .R>
The length is 5 for four characters, which is the "rounded up to a multiple of
five" of the text above: a PDP-10 word holds five seven-bit characters, so a
file's length in characters is only known to the nearest word.
Found along the way. That rounding is not cosmetic and pymdl keeps it deliberately, because a program can see it.
<ACCESS .R <FILE-LENGTH .R>>is the documented way to seek to the end, and ifFILE-LENGTHanswered the exact byte count the seek would land one to four characters short of where an era program expects. It is the same principle asGROW's 32 and 64 in chapter 10: the quantum is gone, the arithmetic stays.
11.7.6. FILECOPY
<FILECOPY input:channel output:channel>
copies characters from input to output until the end of file on input (thus
closing input) and returns the number of characters copied. Both arguments are
optional, with .INCHAN and .OUTCHAN used by default, respectively. The
operation is essentially a READSTRING-PRINTSTRING loop. Neither CHANNEL
need be freshly OPENed, and output need not be immediately CLOSEd.
Restriction: internally a <FILE-LENGTH input> is done, which must succeed;
thus FILECOPY might lose if input is a "NET" CHANNEL.
11.7.7. RESET
<RESET channel>
returns channel, after "resetting" it. Resetting a CHANNEL is like OPENing
it afresh, with only the file-name slots preserved. For an input CHANNEL,
this means emptying all input buffers and, if it is a CHANNEL to a file, doing
an ACCESS to 0 on it. For an output CHANNEL, this means returning to the
beginning of the file -- which implies, if the mode is not "PRINTO",
destroying any output done to it so far. If the opening fails (for example, if
the mode slot of channel says input, and if the file specified in its real-name
slots does not exist), RESET (like OPEN) returns
#FALSE (reason:string file-spec:string status:fix).
11.7.8. BUFOUT
<BUFOUT output:channel>
causes all internal MDL buffers for output to be written out and returns its
argument. This is helpful if the operating system or MDL is flaky and you want
to attempt to minimize your losses. The output may be padded with up to four
extra spaces, if output's mode is "PRINT".
11.7.9. RENAME
RENAME is for renaming and deleting files. It takes three kinds of arguments:
- (a) two file names, in either single- or multi-
STRINGformat, separated by theATOMTO, - (b) one file name in either format, or
- (c) a
CHANNELand a file name in either format (only in the ITS version).
Omitted file-name parts use the same values by default as does OPEN. If the
operation is successful, RENAME returns T, otherwise
#FALSE (reason:string status:fix).
In case (a) the file specified by the first argument is renamed to the second argument. For example:
<RENAME "FOO 3" TO "BAR"> ;"Rename FOO 3 to BAR >."
In case (b) the single file name specifies a file to be deleted. For example:
<RENAME "FOO FOO DSK:HARRY;"> ;"Delete FOO FOO on DSK:HARRY;."
(The manual repeats the previous line's comment here; the second case deletes.)
In case (c) the CHANNEL must be open in either "PRINT" or "PRINTB" mode,
and a rename while open for writing is attempted. The real-name slots in the
CHANNEL are updated to reflect any successful change.
Both the rename and the delete work here, and the delete is the one to be careful with:
<SET W <OPEN "PRINT" "one.txt">>
<PRINC "x" .W> ⇒ "x"
<CLOSE .W>
<RENAME "one.txt" TO "two.txt"> ⇒ T
<FILE-EXISTS? "two.txt"> ⇒ T
<RENAME "two.txt"> ⇒ T
<FILE-EXISTS? "two.txt"> ⇒ #FALSE ("FILE NOT FOUND" 0)
11.8. Terminal CHANNELs
MDL behaves like the ITS version of the text editor Teco with respect to typing
in carriage-return, in that it automatically adds a line-feed. In order to type
in a lone carriage-return, a carriage-return followed by a rubout must be typed.
Also PRINT, PRIN1 and PRINC do not automatically add a line-feed when a
carriage-return is output. This enables overstriking on a terminal that lacks
backspacing capability. It also means that what goes on a terminal and what
goes in a file are more likely to look the same.
In the ITS version, MDL's primary terminal output channel (usually ,OUTCHAN)
is normally not in "display" mode, except when PRINCing a STRING. Thus
errors will rarely occur when a user is typing in text containing display-mode
control codes.
In the ITS version, MDL can start up without a terminal, give control of the
terminal away to an inferior operating-system process or get it back while
running. Doing a RESET on either of the terminal channels causes MDL to find
out if it now has the terminal; if it does, the terminal is reopened and the
current screen size and device parameters are updated. If it doesn't have the
terminal, an internal flag is set, causing output to the terminal to be ignored
and attempted input from the terminal to make the operating-system process go to
sleep.
In the ITS version, there are some peculiarities associated with
pseudo-terminals ("STY" and "STn" devices). If the CHANNEL given to
READCHR is open in "READ" mode to a pseudo-terminal, and if no input is
available, READCHR returns -1, TYPE FIX. If the CHANNEL given to
READSTRING is open in "READ" mode to a pseudo-terminal, reading also stops
if and when no more characters are available, that is, when READCHR would
return -1.
11.8.1. ECHOPAIR
<ECHOPAIR terminal-in:channel terminal-out:channel>
returns its first argument, after making the two CHANNELs "know about each
other" so that rubout, ^@, ^D and ^L on terminal-in will cause the
appropriate output on terminal-out.
11.8.2. TTYECHO
<TTYECHO terminal-input:channel pred>
turns the echoing of typed characters on channel off or on, according to whether
or not pred is TYPE FALSE, and returns channel. It is useful in conjunction
with TYI (below) for a program that wants to do character input and echoing in
its own fashion.
11.8.3. TYI
<TYI terminal-input:channel>
returns one CHARACTER from channel (optional, .INCHAN by default) when it is
typed, rather than after $ (ESC) is typed, as is the case with READCHR. The
following example echoes input characters as their ASCII values, until a
carriage-return is typed:
<REPEAT ((FOO <TTYECHO .INCHAN <>>))
<AND <==? 13 <PRINC <ASCII <TYI .INCHAN>>>>
<RETURN <TTYECHO .INCHAN T>>>>
pymdl's own. This whole section is about a real terminal, and pymdl runs about as often attached to a pipe. The terminal
SUBRs exist and do the sensible thing when there is a terminal; when there is not,TYIreads a character without waiting for an ESC, which is the behaviour the era's programs actually depend on. Chapter 29's debugger is the best example of a program built on this section, and chapter 37 is where the distinction matters most, since driving MDL 55 under the emulator means being its terminal.
11.9. Internal CHANNELs
If the device specified in an OPEN is "INT", a CHANNEL is created which
does not refer to any I/O device outside MDL. In this case, the mode must be
"READ" or "PRINT", and there is another argument, which must be a function.
For a "READ" CHANNEL, the function must take no arguments. Whenever a
CHARACTER is desired from this CHANNEL, the function will be applied to no
arguments and must return a CHARACTER. This will occur once per call to
READCHR using this CHANNEL, and several times per call to READ. In the
ITS version, the function can signal that its "end-of-file" has been reached by
returning <CHTYPE *777777000003* CHARACTER> (-1 in left half, control-C in
right), which is the standard ITS end-of-file signal. In the Tenex and Tops-20
versions, the function should return either that or
<CHTYPE *777777000032* CHARACTER> (-1 and control-Z), the latter being their
standard end-of-file signal.
For a "PRINT" CHANNEL, the function must take one argument, which will be a
CHARACTER. It can dispose of its argument in any way it pleases. The value
returned by the function is ignored.
Example: <OPEN "PRINT" "INT:" ,FCN> opens an internal output CHANNEL with
,FCN as its character-gobbler.
<SET ACC ""> ⇒ ""
<SET IC <OPEN "PRINT" "INT:"
<FUNCTION (CH) <SET ACC <STRING .ACC <STRING .CH>>>>>>
<PRINC "hi" .IC> ⇒ "hi"
.ACC ⇒ "hi"
pymdl's own. An internal
CHANNELis the cleanest thing in this chapter, because it needs no operating system at all: it is aCHANNELwhose device is an MDLFUNCTION. Chapter 34's compiler uses one to capture what a compilation prints without touching a file, and it is howUNPARSEcould be written in MDL if it were not aSUBR.
11.10. The "NET" Device: the ARPA Network
The "NET" device is different in many ways from conventional devices. In the
ITS version, it is the only device besides "INT" that does not take all
strings as its arguments to OPEN, and it must take an additional optional
argument to specify the byte size of the socket. The format of a call to open a
network socket is
<OPEN mode:string local-socket:fix "NET" foreign-socket:fix
foreign-host:fix byte-size:fix>
where:
- mode is the mode of the desired
CHANNEL. This must be either"READ","PRINT","READB"or"PRINTB". - local-socket is the local socket number. If it is
-1, the operating system will generate a unique local socket number. If it is not, in the Tenex and Tops-20 versions, the socket number is "fork-relative". - foreign-socket is the foreign socket number. If it is
-1, this is anOPENfor "listening". - foreign-host is the foreign host number. If it is an
OPENfor listening, this argument is ignored. - byte-size is the optional byte size. For
"READ"or"PRINT"this must be either7(used by default) or8. For"READB"or"PRINTB", it can be any integer from1to36(used by default).
(The manual's call line omits foreign-socket though the list below it describes one; it is restored above.)
In the Tenex and Tops-20 versions, OPEN can instead be given a STRING
argument of the form "NET:...". In this case the local socket number can be
"directory-relative".
Like any other OPEN, either a CHANNEL or a FALSE is returned. Once open,
a network CHANNEL can be used like any other CHANNEL, except that
FILE-LENGTH, ACCESS, RENAME, etc., cannot be done. The "argument"
first-name, second-name, and directory-name slots in the CHANNEL are used for
local socket, foreign socket, and foreign host (as specified in the call to
OPEN), respectively. The corresponding "real" slots are used somewhat
differently. If a channel is OPENed with local socket -1, the "real"
first-name slot will contain the unique socket number generated by the operating
system. If a listening socket is OPENed, the foreign socket and host numbers
of the answering host are stored in the "real" second-name and directory-name
slots of the CHANNEL when the Request For Connection is received.
An interrupt (chapter 24) can be associated with a "NET"-device CHANNEL, so
that a program will know that the CHANNEL has or needs data, according to its
mode.
There also exist several special-purpose SUBRs for the "NET" device. These
are described next.
11.10.1. NETSTATE
<NETSTATE network:channel>
returns a UVECTOR of three FIXes. The first is the state of the connection,
the second is a code specifying why a connection was closed, and the last is the
number of bits available on the connection for input. The meanings of the state
and close codes are installation-dependent and so are not included here.
11.10.2. NETACC
<NETACC network:channel>
accepts a connection to a socket that is open for listening and returns its
argument. It will return a FALSE if the connection is in the wrong state.
11.10.3. NETS
<NETS network:channel>
returns its argument, after forcing any system-buffered network output to be
sent. ITS normally does this every half second anyway. Tenex and Tops-20 do
not do it unless and until NETS is called. NETS is similar to BUFOUT for
normal CHANNELs, except that even operating-system buffers are emptied
now.
pymdl's own. The
"NET"device is the one part of this chapter that is documented here without being implemented. It described sockets on the ARPANET of 1979, reached through operating-system calls that have no modern counterpart worth guessing at, and no program in the corpus of chapter 38 opens one.OPENon"NET"therefore answers aFALSErather than pretending, which is what the section itself says an unavailable connection looks like. It is reproduced in full because this book is also a record of what MDL was, and a network device in a 1979 language manual is worth reading.