Chapter 11. Input/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,
TUPLEs, cannot be input in this way) and is similar in
style to Fortran's formatted I/O. It is what
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.
11.1. Conversion I/O
SUBRs in MDL take an optional argument which
directs their attention to a specific I/O channel. This section will
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
also have additional optional arguments, relevant to conversion,
discussion of which will be deferred until later.
All of the following input Subroutines, when directed at a terminal,
$ (ESC) is typed and allow normal use of
rubout, ^D, ^L and ^@.
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
READ mentioned in chapter 2.
See also sections 11.3, 15.7.1, and 17.1.3 for optional arguments.
("read character") returns the next
CHARACTER in the input stream.
<READCHR>s return successive
("next character") returns the
return the next time
READCHR is called. Multiple
no input operations between them, all return the same thing.
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
CHANNEL in use (section 11.2.8).
This outputs, in order,
- a carriage-return line-feed,
- the character representation of
EVALof its argument (
- a space
and then returns
EVAL of its argument. This is precisely the
outputs just the representation of, and returns,
EVAL of any.
("print characters") acts exactly like
PRIN1, except that
- if its argument is a
CHARACTER, it suppresses the surrounding
"s or initial
- if its argument is an
ATOM, it suppresses any
OBLISTtrailers (chapter 15) which would otherwise be necessary.
PRINC's argument is a structure containing
ATOMs, the service mentioned will be done for all
of them. Ditto for the
ATOM used to name the
TYPE in "#
("terminate printing") outputs a carriage-return line-feed and then
# FALSE ()!
("carriage-return line-feed") outputs a carriage-return line-feed and
<FLATSIZE any max:fix radix:fix>
does not actually cause any output to occur and does not take a
CHANNEL argument. Instead, or compares max with the number of
PRIN1 would take to print any. If max is less than
the number of characters needed (including the case where any is
#FALSE (); otherwise, it
returns the number of characters needed by
PRIN1 any. radix
(optional, ten by default) is used for converting any
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.
11.2. CHANNEL (the TYPE)
I/O channels are dynamically assigned in MDL, and are represented by
an object of
CHANNEL, which is of
format of a
CHANNEL will be explained later, in section 11.2.8.
First, how to generate and use them.
<OPEN mode file-spec>
<OPEN mode name1 name2 device dir>
OPEN is a
SUBR which creates and returns a
CHANNEL. All its
arguments must be of
STRING, and all are optional. The
preceding statement is false when the device is
see sections 11.9 and 11.10. If the attempted opening of an
operating-system I/O channel fails,
(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.
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,
OK indicates an allowed use:
|"READ"||"PRINT"||"READB"||"PRINTB", "PRINTO"||mode / SUBRs|
* PRINTing (or
RSUBR (chapter 19) on a
CHANNEL has special effects.
"PRINTB" differs from
"PRINTO" in that the latter mode is used to
"DSK" file without copying it.
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
name2 is the second fail 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
"MUD" and highest version
number (Tenex) or generation number (Tops-20).
device is the device name. The name used by default is
DEV>, if any, otherwise
"DSK". (Devices about which MDL has no
special knowledge are assumed to behave like
dir is the disk-directory name. The name used by default is
SNM>, if any, otherwise the "working-directory" name as defined by
her operating system.
<OPEN "PRINT" "TPL:"> opens a conversion-output channel to the TPL
<OPEN "PRINT" "DUMMY" "NAMES" "IPL"> does the same.
<OPEN "PRINT" "TPL"> opens a
CHANNEL to the file
DSK:TPL > (ITS
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.
OPEN-NR is the same as
OPEN, except that the date and time of
last reference of the opened file are not changes.
11.2.3. CHANNEL (the SUBR)
CHANNEL is called exactly like
OPEN, but it always return an
CHANNEL, which can later be opened by
RESET (below) just
as if it had once been open.
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, `#FALSE (reason:string status:fix),
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
LIST whose elements are all the currently open
CHANNELs. The first two elements are usually
.OUTCHAN (see below). A
CHANNEL not referenced by anything except
<CHANLIST> will be
CLOSEd during garbage collection.
11.2.7. INCHAN and OUTCHAN
The channel used by default for input
SUBRs is the local value of
INCHAN. The channel used by default for output SUBRs is
the local value of the
You can direct I/O to a
(remembering their old values somewhere), or by giving the
with to use an argument of
CHANNEL. (These actually have the
same effect, because
INCHAN to an explicit argument,
OUTCHAN similarly. Thus the
CHANNEL being used
is available for
READ macros (section 17.1), or by giving the
SUBR you wish to use an argument of
CHANNEL. Thus the
CHANNEL being used is available for
READ macros (section 17.1)
PRINTTYPEs (section 6.4.4).)
By the way, a good trick for playing with
values within a function is to use the
"AUX" variables, re-binding their local values to the
you want. When you leave , of course, the old
LVALs are expanded
(which is the whole point). The
ATOMs must be declared
(chapter 14) for this trick to compile correctly.
OUTCHAN also have global values, initially the
CHANNELs directed at the terminal running
OUTCHANs local and global values are the same.
11.2.8. Contents of CHANNELs
The contents of an object of
CHANNEL are referred to by the
SUBRs each time such a
SUBR is used. If you change the
contents of a
CHANNEL (for example, with
PUT), the next use of
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
There follows a table of the contents of a
each element, and an interpretation. The format used is the
element-number: type interpretation
184.108.40.206 Output CHANNELs
The contents of a CHANNEL used for output are as follows:
||transcript channel(s) (see below)|
|* 0||varies||device-dependent information|
||channel number (ITS) or JFN (Tenex and Tops-20),
||first file name argument|
||second file name argument|
||device name argument|
||directory name argument|
||real first file name|
||real second file name|
||real device name|
||real directory name|
||various status bits|
||PDP-10 instruction used to do one I/O operation|
||number of characters per line of output|
||current character position on a line|
||number of lines per page|
||current line number on a page|
||access pointer for file-oriented devices|
||sink for an internal
N.B.: The elements of a
CHANNEL below number 1 are usually
invisible but are obtainable via
<NTH <TOP channel> fix>, for some
The transcript-channels slot has this meaning: if this slot contains
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.
220.127.116.11. 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
|13||varies||object evaluated when end of file is reached|
||one "look-ahead" character, used by
||PDP-10 instruction executed waiting for input|
||queue of buffers for input from a terminal|
||access pointer for file-oriented devices (same)|
||buffer for input or source for internal
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
EVAL and return, if
it reaches the end of the file it is reading. A typical end-of-file
argument is a
FORM which applies a function of yours. The
value of this argument used by default is a call to
CHANNEL has been
CLOSEd by the time this argument is
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
<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.")>>
11.4. Imaged I/O
<READB buffer:uvector-or-storage channel eof:any>
The channel must be open in
READB will read as
many 36-bit binary words as necessary to fill the buffer (whose
UTYPE must be of
WORD), unless it hits the end of the
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.
<READSTRING buffer:string channel stop:fix-or-string eof>
STRING analog to
READB, where buffer and eof are as in
READB, and channel is any input
.INCHAN by default).
stop tells when to stop inputting: if a
FIX, read this many
CHARACTERs (fill up buffer by default); if a
reading if any
CHARACTER in this
STRING is read (don't include
CHARACTER in final
<PRINTB buffer:uvector-or-storage channel>
This call writes the entire contents of the buffer into the
specified channel open in
"PRINTO" mode. It returns
<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.
<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
^ (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
can reproduce any exactly, including sharing. any cannot live on
the control stack, nor can it be of
ASOC (which see). any is returned as a value.
If printb is a
CHANNEL, it must be open in
"PRINTO" mode. If printb is a
GC-DUMP instead returns
WORD) that contains what it
would have output on a
UVECTOR can be
anywhere you desire, but, if it is changed in any way,
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,
about twice as fast as
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-READ is about ten times faster than
11.6. SAVE Files
The entire state of MDL can be saved away in a file for later
restoration: this is done with the
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.
SAVE file is much faster than re-
READing the objects it
SAVE file does not contain all extant MDL objects, only the
PURIFYed (section 22.9.2) ones, a change to the
interpreter has the result of making all previous
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
MUDDLE. This release number is written out as the very first part
SAVE file. If
RESTORE attempts to re-load a
whose release number is not the same as the interpreter being used,
an error is produced. If desired, the release number of a
can be obtained by doing a
READ of that file. Only that initial
READ will work; the rest of the file is not ASCII.
<SAVE file-spec:string gc?:false-or-any>
<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
STRING arguments are
<VALUE SNM> used
by default. gc? is optional and, if supplied and of
causes no garbage collection to occur before
an alias for
SAVE that may be seen in old programs.)
If, after restoring,
RESTORE finds that
<VALUE SNM> is the null
""), 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
<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>)>>
<RESTORE name1 name2 device dir>
replaces the entire current state of your MDL with that
in the file specified. All arguments are optional, with the same
values used by default as by
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
done does not exist when the
RESTORE is done, a message to that
effect will appear on the terminal.
RESTORE never returns (unless it gets an error): it causes a
SAVE done some time ago to return again (this time with the
"RESTORED"), even if the
SAVE was done in the midst of
running a program. In the latter case, the program will continue its
11.7. Other I/O Functions
<LOAD input:channel look-up>
"DONE". First, however, it
every MDL object in the file pointed to by input, and then
input. Any occurrences of rubout, ^@,
^D, ^L, etc., in the file are given no special
meaning; they are simply
look-up is optional, used to specify a
OBLISTs for the
.OBLIST is used by default (chapter 15).
<FLOAD file-spec look-up>
<FLOAD name1 name2 device dir look-up>
("file load") acts just like
LOAD, except that it takes arguments
(with values used by default) like
itself for reading, and
CHANNEL when done. look-up
is optional, as in
LOAD. If the
OPEN fails, an error occurs,
giving the reason for failure.
<SNAME string> ("system name", a hangover from ITS) is identical in
<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
SNAME returns its argument.
<SNAME> is identical in effect with
<GVAL SNM>, that is, it
returns the current dir used by default.
<ACCESS channel fix>
returns channel, after making the next character or binary word
(depending on the mode of channel, which should not be
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 (
"USR", etc.). A fix of
channel at the beginning of the file.
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
applied to input and this length or more, then the next input
operation will detect the end of file.
<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
used by default, respectively. The operation is essentially a
PRINTSTRING loop. Neither
CHANNEL need be freshly
OPENed, and output need not be immediately
<FILE-LENGTH input> is done, which must succeed; thus
FILECOPY might lose if input is a
returns channel, after "resetting" it. Resetting a
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
0 on it. For an
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),
#FALSE (reason:string file-spec:string status:fix).
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
RENAME is for renaming and deleting files. It takes three kinds of
- (a) two file names, in either single- or multi-
STRINGformat, separated by the
- (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,
#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;"> ;"Rename FOO 3 to BAR >."
In case (c) the
CHANNEL must be open in either
"PRINTB" mode, and a rename while open for writing is attempted.
The real-name slots in the
CHANNEL are updated to reflect any
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
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
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
"STn" devices). If the
READCHR is open in
"READ" mode to a pseudo-terminal, and
if no input is available,
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
<ECHOPAIR terminal-in:channel terminal-out:channel>
returns its first argument, after making the two
about each other" so that rubout, ^@,
^D and ^L on terminal-in will cause the
appropriate output on terminal-out.
<TTYECHO terminal-input:channel pred>
turns the echoing of typed characters on channel off or on,
according to whether or not pred is
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
CHARACTER from channel (optional,
default) when it is typed, rather than after
$ (ESC) is
typed, as is the case with
READCHR. The following example echos
input characters as their ASCII values, until a carriage-return is
<REPEAT ((FOO <TTYECHO .INCHAN <>>)) <AND <==? 13 <PRINC <ASCII <TYI .INCHAN>>>> <RETURN <TTYECHO .INCHAN T>>>>
11.9. Internal CHANNELs
If the device specified in an
created which does not refer to any I/O device outside MDL. In this
case, the mode must be
"PRINT", and there is another
argument, which must be a function.
CHANNEL, the function must take no arguments.
CHARACTER is desired from this
CHANNEL, the function
will be applied to no arguments and must return a
will occur once per call to
READCHR using this
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.
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.
<OPEN "PRINT" "INT:" ,FCN> opens an internal output
,FCN as its character-gobbler.
11.10. The "NET" Device: the ARPA Network
"NET" device is different in many ways from conventional
devices. In the ITS version, it is the only device besides
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-host:fix byte-size:fix>
- mode is the mode of the desired
CHANNEL. This must be either
- 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 an
- 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
"PRINT"this must be either
7(used by default) or
"PRINTB", it can be any integer from
36(used by default).
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
RENAME, etc., cannot be done.
The "argument" first-name, second-name, and directory-name slots in
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
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 21) can be associated with a
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
device. These are described next.
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 meaning of the state and close codes are
installation-dependent and so are not included here.
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.
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.