The pymdl book
5. Read, evaluate, and print
2.1. General
Once you type $ and all brackets are correctly paired and nested, the
current contents of the input buffer go through processing by three functions
successively: first READ, which passes its output to EVAL ("evaluate"),
which passes its output to PRINT, whose output is typed on the terminal.
[Actually, the sequence is more like READ, CRLF, EVAL, PRIN1, CRLF
(explained in chapter 11); MDL gives you a carriage-return line-feed when the
READ is complete, that is, when all brackets are paired.]
Functionally:
READ: printable representations → MDL objectsEVAL: MDL objects → MDL objectsPRINT: MDL objects → printable representations
That is, READ takes ASCII text, such as is typed in at a terminal, and
creates the MDL objects represented by that text. PRINT takes MDL objects,
creates ASCII text representations of them, and types them out. EVAL,
which is the really important one, performs transformations on MDL objects.
pymdl's own. Those are three modules here:
reader.py,eval.py,printer.py, and the loop that joins them isrepl.py. The three are alsoSUBRs an MDL program can call, exactly as the manual means them, and chapter 14 is where the manual documents them as such.
2.2. Philosophy (TYPEs)
In a general sense, when you are interacting with MDL, you are dealing with a world inhabited only by a particular set of objects: MDL objects.
MDL objects are best considered as abstract entities with abstract
properties. The properties of a particular MDL object depend on the class of
MDL objects to which it belongs. This class is the TYPE of the MDL object.
Every MDL object has a TYPE, and every TYPE has its own peculiarities.
There are many different TYPEs in MDL; they will gradually be introduced
below, but in the meantime here is a representative sample: SUBR (the
TYPE of READ, EVAL, and PRINT), FSUBR, LIST, VECTOR, FORM,
FUNCTION, etc. Since every object has a TYPE, one often abbreviates "an
object of TYPE type" by saying "a type".
The laws of the MDL world are defined by EVAL. In a very real sense, EVAL
is the only MDL object which "acts", which "does something". In "acting",
EVAL is always "following the directions" of some MDL object. Every MDL
object should be looked upon as supplying a set of directions to EVAL; what
these directions are depends heavily on the TYPE of the MDL object.
Since EVAL is so ever-present, an abbreviation is in order: "evaluates to
something" or "EVALs to something" should be taken as an abbreviation for
"when given to EVAL, causes EVAL to return something".
As abstract entities, MDL objects are, of course, not "visible". There is,
however, a standard way of representing abstract MDL objects in the real
world. The standard way of representing any given TYPE of MDL object will
be given below when the TYPE is introduced. These standard representations
are what READ understands, and what PRINT produces.
Measured, MDL 55, 2026-08-12. "Depends heavily on the
TYPE" is exact, and exactly implemented:EVALdispatches on an object'sTYPE, not on the Python class that carries it, because the era'sEVALis a dispatch on the type word. So aLISTCHTYPEd toFORMevaluates as a form, and a userTYPEwhosePRIMTYPEis one of the element-wise ones is evaluated element-wise and rebuilt under its own type, while one whosePRIMTYPEis not evaluates to itself -- which is what keeps a cyclic structure of a user type from recursing for ever. Chapters 9 and 10 have the rules.<TYPE <EVAL <CHTYPE (+ 1 2) FORM>>> ⇒ FIX <EVAL <CHTYPE (+ 1 2) FORM>> ⇒ 3 <NEWTYPE GRITCH LIST> ⇒ GRITCH <EVAL <CHTYPE (A <+ 1 5>) GRITCH>> ⇒ #GRITCH (A 6) <NEWTYPE TXT STRING> ⇒ TXT <EVAL <CHTYPE "ab" TXT>> ⇒ #TXT "ab"
The listener's own place in this is chapter 19's, not this chapter's: the
banner, the levels, and ERRET are the error handler's, and READ at a
terminal is chapter 14's.
2.3. Example (TYPE FIX)
1 ⇒ 1
<TYPE 1> ⇒ FIX
The following has occurred:
First, READ recognized the character 1 as the representation for an object
of TYPE FIX, in particular the one which corresponds to the integer one.
(FIX means integer, because the decimal point is understood always to be in
a fixed position: at the right-hand end.) READ built the MDL object
corresponding to the decimal representation typed, and returned it.
Then EVAL noted that its input was of TYPE FIX. An object of TYPE
FIX evaluates to itself, so EVAL returned its input undisturbed.
Then PRINT saw that its input was of TYPE FIX, and printed on the
terminal the decimal character representation of the corresponding integer.
2.4. Example (TYPE FLOAT)
1.0 ⇒ 1.0
<TYPE 1.0> ⇒ FLOAT
What went on was entirely analogous to the preceding example, except that the
MDL object was of TYPE FLOAT. (FLOAT means a real number (of limited
precision), because the decimal point can float around to any convenient
position: an internal exponent part tells where it "really" belongs.)
pymdl's own. A
FLOATis a Python double here, where the era's was a PDP-10 single-precision word: 27 bits of fraction against 52. That is platform substrate, like the machine word of chapter 21, and it is visible in exactly two places -- the precision of a printed value, and theFLOATs that pass through an 18-bitTEMPLATEfield (chapter 31). The emulator of chapter 36 does carry the real 36-bit format, because compiled code computes in it.
2.5. Example (TYPE ATOM, PNAME)
GEORGE ⇒ GEORGE
<TYPE GEORGE> ⇒ ATOM
<SPNAME GEORGE> ⇒ "GEORGE"
This time a lot more has happened.
READ noted that what was typed had no special meaning, and therefore assumed
that it was the representation of an identifier, that is, an object of TYPE
ATOM. ("Atom" means more or less indivisible.) READ therefore
attempted to look up the representation in a table it keeps for such purposes
[a LIST of OBLISTs, available as the local value of the ATOM OBLIST].
If READ finds an ATOM in its table corresponding to the representation,
that ATOM is returned as READ's value. If READ fails in looking up, it
creates a new ATOM, puts it in the table with the representation read
[INSERT into <1 .OBLIST> usually], and returns the new ATOM. Nothing
which could in any way be referenced as a legal "value" is attached to the new
ATOM. The initially-typed representation of an ATOM becomes its PNAME,
meaning its name for PRINT. One often abbreviates "object of TYPE ATOM
with PNAME name" by saying "ATOM name".
EVAL, given an ATOM, returned just that ATOM.
PRINT, given an ATOM, typed out its PNAME.
At the end of this chapter, the question "what is a legal PNAME" will be
considered. Further on, the methods used to attach values to ATOMs will be
described.
Found along the way. "That
ATOMis returned" is a stronger statement than it looks, and pymdl was wrong about it for a long time. AnATOMis an object with an identity, and two atoms that share aPNAMEon different oblists are different variables with different value cells: MDL 55, measured, answers 1 to.FOOand 2 to.FOO!-INTERRUPTSafter<SET FOO 1>and<SET FOO!-INTERRUPTS 2>. pymdl keyed local values by the bare name, so it had oneFOO; it keys by the atom's qualified name now. Chapter 18 has the five measurements that settled it, and chapter 28 the consequences for packages.
2.6. FIXes, FLOATs, and ATOMs versus READ: Specifics
2.6.1. READ and FIXed-point Numbers
READ considers any grouping of characters which are solely digits to be a
FIX, and the radix of the representation is decimal by default. A -
(hyphen) immediately preceding such a grouping represents a negative FIX.
The largest FIX representable on the PDP-10 is two to the 35th power minus
one, or 34,359,738,367 (decimal); the smallest is one less than the negative
of that number. If you attempt to type in a if a program you write attempts to produce
a FIX outside that range,
READ converts it to a FLOAT;FIX outside that range, an overflow error will occur (unless it is
disabled).
Measured, MDL 55, 2026-08-16. The struck clause is wrong about the implementation the manual describes. Typing a
FIXoutside the range does not convert it to aFLOATthere; it refuses to read it:34359738368 *ERROR* NUMBER-OUT-OF-RANGE READ -34359738368 *ERROR* NUMBER-OUT-OF-RANGE READ-- the second because the digits are read as a magnitude first, and 2^35 is already past the largest
FIX, so the smallestFIXcannot be typed in decimal at all (chapter 21 has the octal that reaches it).34359738367and-34359738367read asFIXes and1e20reads as aFLOAT, so it is the integer path alone that refuses. pymdl followed the manual and answered3.4359738E+10until that was measured; where the manual and the era disagree, this project follows the era.34359738367 ⇒ 34359738367 -34359738367 ⇒ -34359738367 34359738368 ⇒ *ERROR* NUMBER-OUT-OF-RANGE <TYPE 1e20> ⇒ FLOAT
The radix used by READ and PRINT is changeable by the user; however, there
are two formats for representations of FIXes which cause READ to use a
specified radix independent of the current one. These are as follows:
- If a group of digits is immediately followed by a period (
.),READinterprets that group as the decimal representation of aFIX. For example,10.is always interpreted byREADas the decimal representation of ten. - If a group of digits is immediately enclosed on both sides with asterisks
(
*),READinterprets that group as the octal representation of aFIX. For example,*10*is always interpreted byREADas the octal representation of eight.
10. ⇒ 10
*10* ⇒ 8
*377* ⇒ 255
Measured, MDL 55, 2026-08-16. The changeable radix is not symmetric, and the asymmetry is arithmetic rather than tabular.
'0' + dwith no letter table, so a radix above ten prints characters that are not digits at all;READ's digits are0to9at every radix, so it cannot read those characters back.<UNPARSE 10 16> ⇒ ":" <UNPARSE 26 16> ⇒ "1:" <UNPARSE 255 16> ⇒ "??" <TYPE <PARSE "FF" 16>> ⇒ ATOM <TYPE <PARSE "1:" 16>> ⇒ ATOMASCII
0is 060 octal, so ten lands on:and fifteen on?. A radix above ten is therefore print-only on both machines: the era can print aFIXit cannot read back. And the reading arithmetic checks nothing at all -- not that a digit is below the radix, not that the radix is sane:<PARSE "123" 3> ⇒ 18 <PARSE "123" 1> ⇒ 6 <PARSE "123" 0> ⇒ 3which is
val = val*radix + (ch - '0')run over the token and nothing else: for radix 3,1*3+2is 5 and5*3+3is 18, the digit3taken though it is not a digit in radix 3. pymdl clamped a radix below two up to ten and rejected an out-of-range digit until this was measured.
2.6.2. READ and PRINT versus FLOATing-point Numbers
PRINT can produce, and READ can understand, two different formats for
objects of TYPE FLOAT. The first is "decimal-point" notation, the second
is "scientific" notation. Decimal radix is always used for representations of
FLOATs.
"Decimal-point" notation for a FLOAT consists of an arbitrarily long string
of digits containing one . (period) which is followed by at least one digit.
READ will make a FLOAT out of any such object, with a limit of precision
of one part in 2 to the 27th power.
"Scientific" notation consists of:
- a number,
- immediately followed by
Eore(upper or lower case letter E), - immediately followed by an exponent,
where a "number" is an arbitrarily long string of digits, with or without a
decimal point (see following note), and an "exponent" is up to two digits
worth of FIX. This notation represents the "number" to the "exponent" power
of ten. Note: if the "number" as above would by itself be a FIX, and if the
"exponent" is positive, and if the result is within the allowed range of
FIXes, then the result will be a FIX. For example, READ understands
10E1 as 100 (a FIX), but 10E-1 as 1.0000000 (a FLOAT).
The largest-magnitude FLOAT which can be handled without overflow is
1.7014118E+38 (decimal radix). The smallest-magnitude FLOAT which can be
handled without underflow is .14693679E-38.
1.2345 ⇒ 1.2345000
10E1 ⇒ 100
<TYPE 10E1> ⇒ FIX
10E-1 ⇒ 1.0
<TYPE 10E-1> ⇒ FLOAT
pymdl's own.
1.2345prints as1.2345000because the printer writes the era's seven significant figures, which is what a 27-bit fraction is worth; the value behind it is a double (2.4). The two magnitude limits are the PDP-10 single-precision format's and are not enforced here, since a double reaches far past both.
2.6.3. READ and PNAMEs
The question "what is a legal PNAME?" is actually not a reasonable one to
ask: any non-empty string of arbitrary characters can be the PNAME
of an ATOM. However, some PNAMEs are easier to type to READ than
others. But even the question "what are easily typed PNAMEs?" is not too
reasonable, because READ decides that a group of characters is a PNAME by
default; if it can't possibly be anything else, it's a PNAME. So, the
rules governing the specification of PNAMEs are messy, and best expressed in
terms of what is not a PNAME. For simplicity, you can just consider any
uninterrupted group of upper- and lower-case letters and (customarily) hyphens
to be a PNAME; that will always work. If you are neither a perfectionist
nor a masochist, skip to the next chapter.
2.6.3.1. Non-PNAMEs
A group of characters is not a PNAME if:
- It represents a
FLOATor aFIX, as described above -- that is, it is composed wholly of digits, or digits and a single.(period), or digits and a.and the letterEore(with optional minus signs in the right places). - It begins with a
.(period). - It contains -- if typed interactively -- any of the characters which have
special interactive effects:
^@,^D,^L,^G,^O,$(ESC), rubout. - It contains a format character -- space, carriage-return, line-feed, form-feed, horizontal tab, vertical tab.
- It contains a
,(comma) or a#(number sign) or a'(single quote) or a;(semicolon) or a%(percent sign). - It contains any variety of bracket --
(or)or[or]or<or>or{or}or".
In addition, the character \ (backslash) has a special interpretation, as
mentioned below. Also the pair of characters !- (exclamation-point hyphen)
has an extremely special interpretation, which you will reach at chapter 18.
The characters mentioned in cases 4 through 6 are "separators" -- that is,
they signal to READ that whatever it was that the preceding characters
represented, it's done now. They can also indicate the start of a new
object's representation (all the opening "brackets" do just that).
Measured, MDL 55, 2026-08-16. Case 3 is about interactive effects, but one of those characters separates tokens wherever it is read, not only at a terminal: ESC. Reading
1<ESC>Cfrom a file channel, the era gives theFIX1 and then theATOMC; reading1^ACfrom the same channel gives the singleATOM1C, so the other control characters do not break a token there. ESC is the activation character, and the editor's macro facility depends on its separating ("put an ESC between commands", chapter 29).
2.6.3.2. Examples
The following examples are not in the "standard format" of "line typed in$
result printed", because they are not, in some cases, completed objects; hence
READ would continue waiting for the brackets to be closed. In other cases,
they will produce errors during EVALuation if other -- currently irrelevant
-- conditions are not met. Instead, the right-hand column states just what
READ thought the input in the left-hand column really was.
| input | explanation |
|---|---|
ABC$ |
an ATOM of PNAME ABC |
abc$ |
an ATOM of PNAME abc |
ARBITRARILY-LONG-PNAME$ |
an ATOM of PNAME ARBITRARILY-LONG-PNAME |
1.2345$ |
a FLOAT, PRINTed as 1.2345000 |
1.2.345$ |
an ATOM of PNAME 1.2.345 |
A.or.B$ |
an ATOM of PNAME A.or.B |
.A.or.B$ |
not an ATOM, but (as explained later) a FORM containing an ATOM of PNAME A.or.B |
MORE THAN ONE$ |
three ATOMs, with PNAMEs MORE, and THAN, and ONE |
ab(cd$ |
an ATOM of PNAME ab, followed by the start of something else (the something else will contain an ATOM of PNAME beginning cd) |
12345A34$ |
an ATOM of PNAME 12345A3512345A34 (if the A had been an E, the object would have been a FLOAT) |
The struck 12345A35 is a typographical error in the print: the input has a
4 at the end.
<TYPE 1.2.345> ⇒ ATOM
<SPNAME 1.2.345> ⇒ "1.2.345"
<TYPE A.or.B> ⇒ ATOM
<TYPE 12345A34> ⇒ ATOM
<SPNAME 12345A34> ⇒ "12345A34"
<TYPE '.A.or.B> ⇒ FORM
<TYPE <1 '.A.or.B>> ⇒ ATOM
<SPNAME <2 '.A.or.B>> ⇒ "A.or.B"
2.6.3.3. \ (Backslash) in ATOMs
If you have a strange, uncontrollable compulsion to have what were referred to
as "separators" above as part of the PNAMEs of your ATOMs, you can do so
by preceding them with the character \ (backslash). \ will also magically
turn an otherwise normal FIX or FLOAT into an ATOM if it appears amongst
the digits. In fact, backslash in front of any character changes it from
something special to "just another character" (including the character \).
It is an escape character.
When PRINT confronts an ATOM which had to be backslashed in order to be an
ATOM, it will dutifully type out the required \s. They will not, however,
necessarily be where you typed them; they will instead be at those positions
which will cause READ the least grief. For example, PRINT will type out a
PNAME which consists wholly of digits by first typing a \ and then typing
the digits -- no matter where you originally typed the \ (or \s).
2.6.3.4. Examples of Awful ATOMs
The following examples illustrate the amount of insanity that can be
perpetrated by using \. The format of the examples is again non-standard,
this time not because anything is unfinished or in error, but because
commenting is needed: PRINT doesn't do it full justice.
| input | explanation |
|---|---|
a\ one\ and\ a\ two$ |
one ATOM, whose PNAME has four spaces in it |
1234\56789$ |
an ATOM of PNAME 123456789, which PRINTs as \123456789 |
123\ $ |
an ATOM of PNAME 123space, which PRINTs as \123\ , with a space on the end |
\\$ |
an ATOM whose PNAME is a single backslash |
<LENGTH <SPNAME a\ one\ and\ a\ two>> ⇒ 15
<SPNAME 1234\56789> ⇒ "123456789"
<SPNAME \\> ⇒ "\\"
<TYPE 1234\56789> ⇒ ATOM
(<SPNAME \\> answers a one-character STRING holding a backslash; the
printed representation doubles it, as a STRING's printed representation
must.)
pymdl's own. The printer's rule -- put the backslashes where
READwill have least grief, not where they were typed -- is implemented, and so is the harder half of it: anATOMprinted at a moment when the oblist path does not reach its home gets the minimal trailer that would read it back (chapter 18), which is a property of the path and not of the atom. The reader is the place where an interpreter can silently stop being faithful, so it is measured hard: the manual's own examples are tests, the era batteries of chapter 37 compared 710 forms against MDL 55, and the read tables and macro characters of chapter 20 have their own.