The pymdl book
10. Structured objects
This chapter discusses structured objects in general and the five basic
structured PRIMTYPEs. [We defer detailed discussion of the structured
PRIMTYPEs TUPLE (section 12.2) and STORAGE (section 22.2.2).]
7.1. Manipulation
The following SUBRs operate uniformly on all structured objects and generate
an error if not applied to a structured object. Hereafter, structured
represents a structured object.
7.1.1. LENGTH
<LENGTH structured>
evaluates to the number of elements in structured.
7.1.2. NTH
<NTH structured fix>
evaluates to the fix'th element of structured. An error occurs if fix is less
than 1 or greater than <LENGTH structured>. fix is optional, 1 by default.
7.1.3. REST
<REST structured fix>
evaluates to structured without its first fix elements. fix is optional, 1 by default.
Obscure but important side effect: REST actually returns structured
"CHTYPEd" (but not through application of CHTYPE) to its PRIMTYPE. For
example, REST of a FORM is a LIST. REST with an explicit second
argument of 0 has no effect except for this TYPE change.
<LENGTH '(1 2 3)> ⇒ 3
<NTH '(1 2 3) 2> ⇒ 2
<REST '(1 2 3)> ⇒ (2 3)
<REST '<+ 1 2>> ⇒ (1 2)
<REST '<+ 1 2> 0> ⇒ (+ 1 2)
<TYPE <REST '<+ 1 2> 0>> ⇒ LIST
pymdl's own. "Obscure but important" is right on both counts, and there is a third thing
RESTdoes not do that the section never says: it does not copy. TheRESTof aPRIMTYPELISTshares cells with the original, so writing through one is visible through the other:<SET L (1 2 3)> ⇒ (1 2 3) <SET R <REST .L>> ⇒ (2 3) <PUT .R 1 99> ⇒ (99 3) .L ⇒ (1 99 3)That is exact here, and it has to be, because the era's own programs are built on it. Section 7.7.4 states the same fact from the other side, for
SEGMENTs.
7.1.4. PUT
<PUT structured fix anything-legal>
first makes anything-legal the fix'th element of structured, then evaluates to
structured. anything-legal is anything which can legally be an element of
structured; often, this is synonymous with "any MDL object", but see below. An
error occurs if fix is less than 1 or greater than <LENGTH structured>.
(PUT is actually more general than this -- chapter 16.)
7.1.5. GET
<GET structured fix>
evaluates the same as <NTH structured fix>. It is more general than NTH,
however (chapter 16), and is included here only for symmetry with PUT.
7.1.6. APPLYing a FIX
EVAL understands the application of an object of TYPE FIX as a
"shorthand" call to NTH or PUT, depending on whether it is given one or
two arguments, respectively [unless the APPLYTYPE of FIX is changed]. That
is, EVAL considers the following two to be identical:
<fix structured>
<NTH structured fix>
and these:
<fix structured object>
<PUT structured fix object>
[However, the compiler cannot generate efficient code from the longer forms
unless it is sure that fix is a FIX (section 9.10). The two constructs are
not identical even to EVAL, if the order of evaluation is significant: for
example, these two:
<NTH .X <LENGTH <SET X .Y>>> <<LENGTH <SET X .Y>> .X>
are not identical.]
<SET V [10 20 30]> ⇒ [10 20 30]
<2 .V> ⇒ 20
<2 .V 99> ⇒ [10 99 30]
7.1.7. SUBSTRUC
SUBSTRUC ("substructure") facilitates the construction of structures that
are composed of sub-parts of existing structures. A special case of this
would be a "substring" function.
<SUBSTRUC from:structured rest:fix amount:fix to:structured>
copies the first amount elements of <REST from rest> into another object and
returns the latter. All arguments are optional except from, which must be of
PRIMTYPE LIST, VECTOR, TUPLE (treated like a VECTOR), STRING,
BYTES, or UVECTOR. rest is 0 by default, and amount is all the elements
by default. to, if given, receives the copied elements, starting at its
beginning; it must be an object whose TYPE is the PRIMTYPE of from (a
VECTOR if from is a TUPLE). If to is not given, a new object is returned,
of TYPE <PRIMTYPE from> (a VECTOR if from is a TUPLE), which never
shares with from. The copying is done in one fell swoop, not an element at a
time. Note: due to an implementation restriction, if from is of PRIMTYPE
LIST, it must not share any elements with to.
7.2. Representation of Basic Structures
7.2.1. LIST
( element-1 element-2 ... element-N )
represents a LIST of N elements.
7.2.2. VECTOR
[ element-1 element-2 ... element-N ]
represents a VECTOR of N elements. [A TUPLE is just like a VECTOR, but
it lives on the control stack.]
7.2.3. UVECTOR
![ element-1 element-2 ... element-N !]
represents a UVECTOR (uniform vector) of N elements. The second !
(exclamation-point) is optional for input. [A STORAGE is an archaic kind of
UVECTOR that is not garbage-collected.]
7.2.4. STRING
"characters"
represents a STRING of ASCII text. A STRING containing the character "
(double-quote) is represented by placing a \ (backslash) before the
double-quote inside the STRING. A \ in a STRING is represented by two
consecutive backslashes.
7.2.5. BYTES
#n {element-1 element-2 ... element-N}
represents a string of N uniformly-sized bytes of size n bits.
7.2.6. TEMPLATE
{ element-1 element-2 ... element-N }
represents a TEMPLATE of N elements when output, not input -- when input, a
# and a TYPE must precede it.
pymdl's own. Chapter 31 is where
TEMPLATEs are made, since the interpreter cannot make them (7.6.8) and the package that can was compiled code.
7.3. Evaluation of Basic Structures
This section and the next two describe how EVAL treats the basic structured
TYPEs [in the absence of any modifying EVALTYPE calls (section 6.4.4)].
EVAL of a STRING [or BYTES or TEMPLATE] is just the original object.
EVAL acts exactly the same with LISTs, VECTORs, and UVECTORs: it
generates a new object with elements equal to EVAL of the elements it is
given. This is one of the simplest means of constructing a structure.
However, see section 7.7.
7.4. Examples
(1 2 <+ 3 4>) ⇒ (1 2 7)
<SET FOO [5 <- 3> <TYPE "ABC">]> ⇒ [5 -3 STRING]
<2 .FOO> ⇒ -3
<TYPE <3 .FOO>> ⇒ ATOM
<SET BAR ![("meow") (.FOO)]> ⇒ ![("meow") ([5 -3 STRING])!]
<LENGTH .BAR> ⇒ 2
<REST <1 <2 .BAR>>> ⇒ [-3 STRING]
[<SUBSTRUC <1 <2 .BAR>> 0 2>] ⇒ [[5 -3]]
<PUT .FOO 1 SNEAKY> ⇒ [SNEAKY -3 STRING]
.BAR ⇒ ![("meow") ([SNEAKY -3 STRING])!]
<SET FOO <REST <1 <1 .BAR>> 2>> ⇒ "ow"
.BAR ⇒ ![("meow") ([SNEAKY -3 STRING])!]
The PUT is the one to watch, as the manual's own comment says: .FOO was an
element of .BAR, not a copy of one, so changing .FOO changed .BAR too.
The last two lines make the opposite point -- SETting FOO to something else
does not disturb .BAR, because SET rebinds a name and does not write
through a structure.
7.5. Generation of Basic Structures
Since LISTs, VECTORs, UVECTORs, and STRINGs [and BYTESes] are all
generated in a fairly uniform manner, methods of generating them will be
covered together here. [TEMPLATEs cannot be generated by the interpreter
itself.]
7.5.1. Direct Representation
Since EVAL of a LIST, VECTOR, or UVECTOR is a new LIST, VECTOR, or
UVECTOR with elements which are EVAL of the original elements, simply
evaluating a representation of the object you want will generate it. (Care
must be taken when representing a UVECTOR that all elements have the same
TYPE.) This method of generation was exclusively used in the examples of
section 7.4. Note that new STRINGs [and BYTESes] will not be generated in
this manner, since the contents of a STRING are not interpreted or copied by
EVAL. The same is true of any other TYPE whose TYPEPRIM happens to be
LIST, VECTOR, or UVECTOR [again, assuming it neither has been EVALTYPEd
nor has a built-in EVALTYPE, as do FORM and SEGMENT].
7.5.2. QUOTE
QUOTE is an FSUBR of one argument which returns its argument unevaluated.
READ and PRINT understand the character ' (single-quote) as an
abbreviation for a call to QUOTE, the way period and comma work for LVAL
and GVAL. Examples:
<+ 1 2> ⇒ 3
'<+ 1 2> ⇒ <+ 1 2>
Any LIST, VECTOR, or UVECTOR in a program that is constant and need not
have its elements evaluated should be represented directly and inside a call
to QUOTE. This technique prevents the structure from being copied each
time that portion of the program is executed. Examples hereafter will adhere
to this dictum. (Note: one should never modify a QUOTEd object. The
compiler will one day put it in read-only (pure) storage.)
pymdl's own. It did: chapter 31's purification is that day arriving, and chapter 33's pure-code database is where such storage ended up. The warning is still exactly as sharp here, for a reason the manual could not give -- a modified quoted constant is a modified program, and pymdl runs era programs whose constants must stay as the tape has them.
7.5.3. LIST, VECTOR, UVECTOR, and STRING (the SUBRs)
Each of the SUBRs LIST, VECTOR, UVECTOR, and STRING takes any number
of arguments and returns an object of the appropriate TYPE whose elements are
EVAL of its arguments. There are limitations on what the arguments to
UVECTOR and STRING may EVAL to, due to the nature of the objects
generated. See sections 7.6.5 and 7.6.6.
LIST, VECTOR, and UVECTOR are generally used only in special cases, since
Direct Representation usually produces exactly the same effect (in the absence
of errors), and the intention is more apparent. [Note: if .L is a LIST,
<LIST !.L> makes a copy of .L whereas (!.L) doesn't; see section 7.7.]
STRING, on the other hand, produces effects very different from literal
STRINGs.
Examples:
<LIST 1 <+ 2 3> ABC> ⇒ (1 5 ABC)
(1 <+ 2 3> ABC) ⇒ (1 5 ABC)
<STRING "A" <2 "QWERT"> <REST "ABC"> "hello"> ⇒ "AWBChello"
"A <+ 2 3> (5)" ⇒ "A <+ 2 3> (5)"
7.5.4. ILIST, IVECTOR, IUVECTOR, and ISTRING
Each of the SUBRs ILIST, IVECTOR, IUVECTOR, and ISTRING ("implicit"
or "iterated" whatever) creates and returns an object of the obvious TYPE.
The format of an application of any of them is
< Ithing number-of-elements:fix expression:any >
where Ithing is one of ILIST, IVECTOR, IUVECTOR, or ISTRING. An object
of LENGTH number-of-elements is generated, whose elements are EVAL of
expression.
expression is optional. When it is not specified, ILIST, IVECTOR, and
IUVECTOR return objects filled with objects of TYPE LOSE (PRIMTYPE
WORD) as place holders, a TYPE which can be passed around and have its
TYPE checked, but otherwise is an illegal argument. If expression is not
specified in ISTRING, you get a STRING made up of ^@ characters.
When expression is supplied as an argument, it is re-EVALuated each time a
new element is generated. (Actually, EVAL of expression is re-EVALuated,
since all of these are SUBRs.) See the last example for how this argument
may be used.
[By the way, in a construct like <IUVECTOR 9 '.X>, even if the LVAL of X
evaluates to itself, so that the ' could be omitted without changing the
result, the compiler is much happier with the ' in place.]
IUVECTOR and ISTRING again have limitations on what expression may EVAL
to; again, see sections 7.6.5 and 7.6.6.
Examples:
<ILIST 5 6> ⇒ (6 6 6 6 6)
<IVECTOR 2> ⇒ [#LOSE *000000000000* #LOSE *000000000000*]
<SET A 0> ⇒ 0
<IUVECTOR 9 '<SET A <+ .A 1>>> ⇒ ![1 2 3 4 5 6 7 8 9!]
7.5.5. FORM and IFORM
Sometimes the need arises to create a FORM without EVALing it or making it
the body of a FUNCTION. In such cases the SUBRs FORM and IFORM
("implicit form") can be used (or QUOTE can be used). They are entirely
analogous to LIST and ILIST. Example:
<DEFINE INC-FORM (A)
<FORM SET .A <FORM + 1 <FORM LVAL .A>>>> ⇒ INC-FORM
<INC-FORM FOO> ⇒ <SET FOO <+ 1 .FOO>>
pymdl's own. Building a
FORMand evaluating it later is what aMACROdoes (chapter 20), and it is how the assembler's pseudo-operations work (chapter 35):<DPUSH ac args>is aFUNCTIONthat returns aSPLICEof twoFORMs. It is worth noticing here how small the machinery is.
7.6. Unique Properties of Primitive TYPEs
7.6.1. LIST (the PRIMTYPE)
An object of PRIMTYPE LIST may be considered as a "pointer chain"
(appendix H). Any MDL object may be an element of a PRIMTYPE LIST. It is
easy to add and remove elements of a PRIMTYPE LIST, but the higher N is,
the longer it takes to refer to the Nth element. The SUBRs which work only
on objects of PRIMTYPE LIST are these:
7.6.1.1. PUTREST
<PUTREST head:primtype-list tail:primtype-list>
changes head so that <REST head> is tail (actually <CHTYPE tail LIST>),
then evaluates to head. Note that this actually changes head; it also changes
anything having head as an element or a value. For example:
<SET BOW [<SET ARF (B W)>]> ⇒ [(B W)]
<PUTREST .ARF '(3 4)> ⇒ (B 3 4)
.BOW ⇒ [(B 3 4)]
PUTREST is probably most often used to splice lists together. For example,
given that .L is of PRIMTYPE LIST, to leave the first m elements of it
intact and take out the next n elements of it,
<PUTREST <REST .L <- m 1>> <REST .L <+ m n>>>. Specifically,
<SET NUMS (1 2 3 4 5 6 7 8 9)> ⇒ (1 2 3 4 5 6 7 8 9)
<PUTREST <REST .NUMS 3> <REST .NUMS 7>> ⇒ (4 8 9)
.NUMS ⇒ (1 2 3 4 8 9)
7.6.1.2. CONS
<CONS new list>
("construct") adds new to the front of list, without copying list, and returns
the resulting LIST. References to list are not affected.
[Evaluating <CONS .E .LIST> is equivalent to evaluating (.E !.LIST)
(section 7.7) but is less preferable to the compiler.]
<CONS 1 '(2 3)> ⇒ (1 2 3)
7.6.2. "Array" PRIMTYPEs
VECTORs, UVECTORs, and STRINGs [and BYTESes and TEMPLATEs] may be
considered as "arrays" (appendix H). It is easy to refer to the Nth element
irrespective of how large N is, and it is relatively difficult to add and
delete elements. The following SUBRs can be used only with an object of
PRIMTYPE VECTOR, UVECTOR, or STRING [or BYTES or TEMPLATE]. (In
this section array represents an object of such a PRIMTYPE.)
7.6.2.1. BACK
<BACK array fix>
This is the opposite of REST. It evaluates to array, with fix elements put
back onto its front end, and changed to its PRIMTYPE. fix is optional, 1 by
default. If fix is greater than the number of elements which have been
RESTed off, an error occurs. Example:
<SET ZOP <REST '![1 2 3 4] 3>> ⇒ ![4!]
<BACK .ZOP 2> ⇒ ![2 3 4!]
<SET S <REST "Right is might." 15>> ⇒ ""
<BACK .S 6> ⇒ "might."
7.6.2.2. TOP
<TOP array>
"BACKs up all the way" -- that is, evaluates to array, with all the elements
which have been RESTed off put back onto it, and changed to its PRIMTYPE.
Example:
<SET ZOP <REST '![1 2 3 4] 3>> ⇒ ![4!]
<TOP .ZOP> ⇒ ![1 2 3 4!]
7.6.3. "Vector" PRIMTYPEs
7.6.3.1. GROW
<GROW vu end:fix beg:fix>
adds/removes elements to/from either or both ends of vu, and returns the
entire (TOPped) resultant object. vu can be of PRIMTYPE VECTOR or
UVECTOR. end specifies a lower bound for the number of elements to be added
to the end of vu; beg specifies the same for the beginning. A negative
fix specifies removal of elements.
The number of elements added to each respective end is end or beg increased
to an integral multiple of X, where X is 32 for PRIMTYPE VECTOR and 64 for
PRIMTYPE UVECTOR (1 produces 32 or 64; -1 produces 0). The elements
added will be LOSEs if vu is of PRIMTYPE VECTOR, and "empty"
whatever-they-are's if vu is of PRIMTYPE UVECTOR. An "empty" object of
PRIMTYPE WORD contains zero. An "empty" object of any other PRIMTYPE has
zero in its "value word" (appendix H) and is not safe to play with: it should
be replaced via PUT.
Note that, if elements are added to the beginning of vu, previously-existing
references to vu will have to use TOP or BACK to get at the added elements.
Caution: GROW is a very expensive operation; it requires a
garbage collection (section 22.4) every time it is used. It should be
reserved for very special circumstances, such as where the pattern of
shared elements is terribly important.
<SET A '![1]> ⇒ ![1!]
<LENGTH <GROW .A 0 1>> ⇒ 65
<LENGTH <GROW <IVECTOR 1> 1 0>> ⇒ 33
.A ⇒ ![1!]
The manual prints the first GROW's whole answer -- sixty-four zeros and then
the 1 -- which is quoted here only by its LENGTH. The last line is the
point of the note above: the reference in .A still sees the old front.
pymdl's own. The rounding is the era's, 64 for a
UVECTORand 32 for aVECTOR, because those are the storage quanta the collector worked in. pymdl has no such quanta, and keeps the arithmetic anyway: a program thatGROWs and then indexes by the resultingLENGTHgets the answer it got there. The "requires a garbage collection" half is not reproduced -- nothing here needs one -- which makesGROWmerely expensive rather than catastrophic, the only way this chapter is kinder than the original.
7.6.3.2. SORT
This SUBR will sort PRIMTYPEs VECTOR, UVECTOR and TUPLE (section
12.2). It works most efficiently if the sort keys are of PRIMTYPE WORD,
ATOM or STRING. However, the keys may be of any TYPE, and SORT will
still work. SORT acts on fixed-length records which consist of one or more
contiguous elements in the structure being sorted. One element in the record
is declared to be the sort key. Also, any number of additional structures can
be rearranged based on how the main structure is sorted.
<SORT pred s1 l1 off s2 l2 s3 l3 sN lN>
where:
pred is either (see chapter 11 for information about predicates):
TYPEFALSE, in which case theTYPEs of all the sort keys must be the same; they must be ofPRIMTYPEWORD,STRINGorATOM; and a radix-exchange sort is used; or- something applicable to two sort keys which returns
TYPEFALSEif the first is not bigger than the second, in which case a shell sort is used. For example,,G?sorts numbers in ascending order,,L?in descending order. Note: if your pred is buggy, theSORTmay never terminate.
s1 ... sN are the (PRIMTYPE) VECTORs, UVECTORs or TUPLEs being sorted,
and s1 contains the sort keys;
l1 ... lN are the corresponding lengths of sort records (optional, one by default); and
off is the offset from start of record to sort key (optional, zero by default).
SORT returns the sorted s1 as a value.
Note: the SUBR SORT calls the RSUBR (chapter 33) SORTX; if the RSUBR
must be loaded, you may see some output from the loader on your terminal.
Examples:
<SET V [1 MONEY 2 SHOW 3 READY 4 GO]> ⇒ [1 MONEY 2 SHOW 3 READY 4 GO]
<SORT <> .V 2 1> ⇒ [4 GO 1 MONEY 3 READY 2 SHOW]
<SORT ,L? .V 2> ⇒ [4 GO 3 READY 2 SHOW 1 MONEY]
.V ⇒ [4 GO 3 READY 2 SHOW 1 MONEY]
<SORT <> ![2 1 4 3 6 5 8 7] 1 0 .V> ⇒ ![1 2 3 4 5 6 7 8!]
.V ⇒ [GO 4 READY 3 SHOW 2 MONEY 1]
The first sort was based on the ATOMs' PNAMEs, considering records to be
two elements. The second one sorted based on the FIXes. The third
interchanged pairs of elements of each of its structured arguments.
pymdl's own.
SORTis aSUBRhere, as the manual says, and it is the only documented primitive among the names pymdl's compiled packages supply -- so it stays aSUBRwhere they areRSUBRs (chapter 30). The loader chatter the note warns of does not appear, since nothing has to be loaded.
7.6.4. VECTOR (the PRIMTYPE)
Any MDL object may be an element of a PRIMTYPE VECTOR. A PRIMTYPE
VECTOR takes two words of storage more than an equivalent PRIMTYPE LIST,
but takes it all in a contiguous chunk, whereas a PRIMTYPE LIST may be
physically spread out in storage (appendix H). There are no SUBRs or
FSUBRs which operate only on PRIMTYPE VECTOR.
7.6.5. UVECTOR (the PRIMTYPE)
The difference between PRIMTYPEs UVECTOR and VECTOR is that every element
of a PRIMTYPE UVECTOR must be of the same TYPE. A PRIMTYPE UVECTOR
takes approximately half the storage of a PRIMTYPE VECTOR or PRIMTYPE
LIST and, like a PRIMTYPE VECTOR, takes it in a contiguous chunk
(appendix H).
[Note: due to an implementation restriction (appendix H), PRIMTYPE STRINGs,
BYTESes, LOCDs (chapter 15), and objects on the control stack (chapter 24)
may not be elements of PRIMTYPE UVECTORs.]
The "same TYPE" restriction causes an equivalent restriction to apply to
EVAL of the arguments to either of the SUBRs UVECTOR or IUVECTOR. Note
that attempting to say
![1 .A!]
will cause READ to produce an error, since you're attempting to put a FORM
and a FIX into the same UVECTOR. On the other hand,
<UVECTOR 1 .A>
is legal, and will EVAL to the appropriate UVECTOR without error if .A
EVALs to a TYPE FIX.
The following SUBRs work on PRIMTYPE UVECTORs alone.
7.6.5.1. UTYPE
<UTYPE primtype-uvector>
("uniform type") evaluates to the TYPE of every element in its argument.
Example:
<UTYPE '![A B C]> ⇒ ATOM
7.6.5.2. CHUTYPE
<CHUTYPE uv:primtype-uvector type>
("change uniform type") changes the UTYPE of uv to type, simultaneously
changing the TYPE of all elements of uv, and returns the new, changed, uv.
This works only when the PRIMTYPE of the elements of uv can remain the same
through the whole procedure. (Exception: a uv of UTYPE LOSE can be
CHUTYPEd to any type (legal in a UVECTOR of course); the resulting elements
are "empty", as for GROW.)
CHUTYPE actually changes uv; hence all references to that object will
reflect the change. This is quite different from CHTYPE.
Examples:
<SET LOST <IUVECTOR 2>> ⇒ ![#LOSE *000000000000* #LOSE *000000000000*!]
<UTYPE .LOST> ⇒ LOSE
<CHUTYPE .LOST FORM> ⇒ ![<> <>!]
.LOST ⇒ ![<> <>!]
<CHUTYPE .LOST LIST> ⇒ ![() ()!]
Found along the way. That last line was
![#LIST <> #LIST <>!]here until 2026-09-10, and finding it is what this chapter was for. The elements really wereLISTs --<TYPE <1 .LOST>>said so -- but the printer chose their brackets from the Python class carrying them, which was still the oneCHUTYPEing toFORMhad left. So the same value printed two different ways depending on how it had been made: built straight fromLOSEtoLISTit printed(). The printer dispatches on theTYPEnow, which is the ruleEVALhas always followed (chapter 5) and which 6.2 states for printing too: the#typeprefix is for aTYPEwith no representation of its own, andLIST,FORMandSEGMENTeach have one.
7.6.6. STRING (the PRIMTYPE) and CHARACTER
The best mental image of a PRIMTYPE STRING is a PRIMTYPE UVECTOR of
CHARACTERs -- where CHARACTER is the MDL TYPE for a single ASCII
character. The representation of a CHARACTER, by the way, is
!\any-ASCII-character
That is, the characters !\ (exclamation-point backslash) preceding a single
ASCII character represent the corresponding object of TYPE CHARACTER
(PRIMTYPE WORD). (The characters !" (exclamation-point double-quote)
preceding a character are also acceptable for inputting a CHARACTER, for
historical reasons.)
The SUBR ISTRING will produce an error if you give it an argument that
produces a non-CHARACTER. STRING can take either CHARACTERs or
STRINGs.
There are no SUBRs which uniquely manipulate PRIMTYPE STRINGs, but some
are particularly useful in connection with them:
7.6.6.1. ASCII
<ASCII fix-or-character>
If its argument is of TYPE FIX, ASCII evaluates to the CHARACTER with
the 7-bit ASCII code of its argument. If its argument is of TYPE
CHARACTER, ASCII evaluates to the FIXed-point number which is its
argument's 7-bit ASCII code.
[Actually, a FIX can be CHTYPEd to a CHARACTER (or vice versa) directly,
but ASCII checks in the former case that the FIX is within the permissible
range.]
<ASCII 65> ⇒ !\A
<ASCII !\Z> ⇒ 90
7.6.6.2. PARSE
<PARSE string radix:fix>
PARSE applies to its argument READ's algorithm for converting ASCII
representations to MDL objects and returns the first object created. The
remainder of string, after the first object represented, is ignored. radix
(optional, ten by default) is used for converting any FIXes that occur. [See
also sections 15.7.2 and 17.1.3 for additional arguments.]
7.6.6.3. LPARSE
LPARSE ("list parse") is exactly like PARSE (above), except that it parses
the entire string and returns a LIST of all objects created. If
given an empty STRING or one containing only separators, LPARSE returns an
empty LIST, whereas PARSE gets an error.
<PARSE "1 2 3"> ⇒ 1
<LPARSE "1 2 3"> ⇒ (1 2 3)
<LPARSE ""> ⇒ ()
7.6.6.4. UNPARSE
<UNPARSE any radix:fix>
UNPARSE applies to its argument PRINT's algorithm for converting MDL
objects to ASCII representations and returns a STRING which contains the
CHARACTERs PRINT would have typed out. [However, this STRING will
not contain any of the gratuitous carriage-returns PRINT adds to
accommodate a CHANNEL's finite line-width (section 11.2.8).] radix
(optional, ten by default) is used for converting any FIXes that occur.
Chapter 5 has what the radix argument does above ten, which is not what one would guess.
7.6.7. BYTES
A (PRIMTYPE) BYTES is a string of uniformly-sized bytes. The bytes can be
any size between 1 and 36 bits inclusive. A BYTES is similar in some ways to
a UVECTOR of FIXes and in some ways to a STRING of non-seven-bit bytes.
The elements of a BYTES are always of TYPE FIX.
The SUBRs BYTES and IBYTES are similar to STRING and ISTRING,
respectively, except that each of the former takes a first argument giving the
size of the bytes in the generated BYTES. BYTES takes one required
argument which is a FIX specifying a byte size and any number of PRIMTYPE
WORDs. It returns an object of TYPE BYTES with that byte size containing
the objects as elements. These objects will be ANDBed with the appropriate
mask of 1-bits to fit in the byte size. IBYTES takes two required FIXes
and one optional argument. It uses the first FIX to specify the byte size
and the second to specify the number of elements. The third argument is
repeatedly evaluated to generate FIXes that become elements of the BYTES
(if it is omitted, bytes filled with zeros are generated). The analog to
UTYPE is BYTE-SIZE. Examples:
<BYTES 3 <+ 2 2> 9 -1> ⇒ #3 {4 1 7}
<SET A 0> ⇒ 0
<IBYTES 3 9 '<SET A <+ .A 1>>> ⇒ #3 {1 2 3 4 5 6 7 0 1}
<IBYTES 3 4> ⇒ #3 {0 0 0 0}
<BYTE-SIZE <BYTES 1>> ⇒ 1
The masking is worth reading off the first example: in three-bit bytes, 4
stays 4, 9 becomes 1 and -1 becomes 7. In the second, the count
runs 1 to 7 and then wraps.
7.6.8. TEMPLATE
A TEMPLATE is similar to a PL/I "structure" of one level: the elements are
packed together and reduced in size to save storage space, while an auxiliary
internal data structure describes the packing format and the elements' real
TYPEs (appendix H). The interpreter is not able to create objects of
PRIMTYPE TEMPLATE; however, it can apply the standard built-in Subroutines
to them, with the same effects as with other "arrays".
7.7. SEGMENTs
Objects of TYPE SEGMENT (whose TYPEPRIM is LIST) look very much like
FORMs. SEGMENTs, however, undergo a non-standard evaluation designed to
ease the construction of structured objects from elements of other structured
objects.
7.7.1. Representation
The representation of an object of TYPE SEGMENT is the following:
!< func arg-1 arg-2 ... arg-N !>
where the second ! (exclamation-point) is optional, and func and arg-1
through arg-N are any legal constituents of a FORM (that is, anything). The
pointed brackets can be implicit, as in the period and comma notation for
LVAL and GVAL.
All of the following are SEGMENTs:
!<3 .FOO> !.FOO !,FOO
7.7.2. Evaluation
A SEGMENT is evaluated in exactly the same manner as a FORM, with the
following three exceptions:
- It had better be done inside an
EVALof a structure; otherwise an error occurs. (See special case ofFORMs in section 7.7.5.) - It had better
EVALto a structured object; otherwise an error occurs. - What actually gets inserted into the structure being built are the elements
of the structure returned by the
FORM-like evaluation.
7.7.3. Examples
<SET ZOP '![2 3 4]> ⇒ ![2 3 4!]
<SET ARF (B 3 4)> ⇒ (B 3 4)
(.ARF !.ZOP) ⇒ ((B 3 4) 2 3 4)
![!.ZOP !<REST .ARF>!] ⇒ ![2 3 4 3 4!]
<SET S "STRUNG."> ⇒ "STRUNG."
(!.S) ⇒ (!\S !\T !\R !\U !\N !\G !\.)
<SET NIL ()> ⇒ ()
[!.NIL] ⇒ []
7.7.4. Note on Efficiency
Most of the cases in which it is possible to use SEGMENTs require EVAL to
generate an entire new object. Naturally, this uses up both storage and time.
However, there is one case which it is possible to handle without copying, and
EVAL uses it. When the structure being built is a PRIMTYPE LIST, and the
segment value of a PRIMTYPE LIST is the last (rightmost) element being
concatenated, that last PRIMTYPE LIST is not copied. This case is similar
to CONS and is the principal reason why PRIMTYPE LISTs have their
structures more easily varied than PRIMTYPE VECTOR or UVECTOR.
This does not copy ARF:
<SET ARF (B 3 4)> ⇒ (B 3 4)
(1 2 !.ARF) ⇒ (1 2 B 3 4)
These do:
<SET ARF (B 3 4)> ⇒ (B 3 4)
(1 !.ARF 2) ⇒ (1 B 3 4 2)
[1 2 !.ARF] ⇒ [1 2 B 3 4]
(1 2 !.ARF !<REST '(1)>) ⇒ (1 2 B 3 4)
-- respectively because the segment is not the last element, because the
structure being built is not a PRIMTYPE LIST, and because the segment is
still not the last element.
Note the following, which occurs because copying does not take place:
<SET ARF (B 3 4)> ⇒ (B 3 4)
<SET DOG (A !.ARF)> ⇒ (A B 3 4)
<PUT .ARF 1 "BOWOW"> ⇒ ("BOWOW" 3 4)
.DOG ⇒ (A "BOWOW" 3 4)
<PUT .DOG 3 "WOOF"> ⇒ (A "BOWOW" "WOOF" 4)
.ARF ⇒ ("BOWOW" "WOOF" 4)
Since ARF was not copied, it was literally part of DOG. Hence, when an
element of ARF was changed, DOG was changed. Similarly, when an element of
DOG which ARF shared was changed, ARF was changed too.
pymdl's own. The sharing is exact here, and it has to be: the era's own programs are built on it, and so is the
RESTnote of 7.1.3. Chapter 36 is where sharing stops being free -- an object crossing into the emulator is rendered into machine words, and keeping the two sides in step is the whole of that chapter's boundary section,LISTcells included (they are rendered read-only, and the chapter says why).
7.7.5. SEGMENTs in FORMs
When a SEGMENT appears as an element of a FORM, the effect is approximately
the same as if the elements of the EVAL of the SEGMENT were in the FORM.
Example:
<SET A '![1 2 3 4]> ⇒ ![1 2 3 4!]
<+ !.A 5> ⇒ 15
Note: the elements of the structure segment-evaluated in a FORM are not
re-evaluated if the thing being applied is a SUBR. Thus if .A were
(1 2 <+ 3 4> 5), the above example would produce an error: you can't add up
FORMs.
You could perform the same summation of 5 and the elements of A by using
<SET A '![1 2 3 4]> ⇒ ![1 2 3 4!]
<EVAL <CHTYPE (+ !.A 5) FORM>> ⇒ 15
(Note that EVAL must be explicitly called as a SUBR; if it were not so
called, you would just get the FORM <+ 1 2 3 4 5> -- not its "value".)
However, the latter is more expensive both in time and in storage: when you use
the SEGMENT directly in the FORM, a new FORM is, in fact, not
generated as it is in the latter case. (The elements are put on "the control
stack" with the other arguments.)
7.8. Self-referencing Structures
It is possible for a structured object to "contain" itself, either as a subset
or as an element, as an element of a structured element, etc. Such an object
cannot be PRINTed, because recursion begins and never terminates. Warning:
if you try the examples in this section with a live MDL, be sure you know how
to use ^S (section 4.2) to save PRINT from endless agony. (Certain
constructs with ATOMs can give PRINT similar trouble: see chapters 15 and
18.)
7.8.1. Self-subset
If head is a subset of tail, that is, if <REST tail fix> is the same object
as <REST head 0> for some fix, then both head and tail will be "circular"
(and thus self-referencing) after the PUTREST. Example:
<SET WALTZ (1 2 3)>$
(1 2 3)
<PUTREST <REST .WALTZ 2> .WALTZ>$
(3 1 2 3 1 2 3 1 2 3 1 2 3 ...
7.8.2. Self-element
If s1 is the same object as s2, then it will "contain" itself (and thus be
self-referencing) after the PUT. Examples:
<SET S <LIST 1 2 3>> ;"or VECTOR"$
(1 2 3)
<PUT .S 3 .S>$
(1 2 (1 2 (1 2 (1 2 ...
<SET U ![![]]>$
![![!]!]
<PUT .U 1 .U>$
![![![![![![...
Test your reaction time or your terminal's bracket-maker. Amaze your friends.
pymdl's own. The two sections loop for two different reasons, and pymdl reproduces them differently.
The self-subset of 7.8.1 is circular in width: printing it emits
3 1 2 3 1 2forever in constant memory, exactly as there, and<LENGTH>of it never terminates either. Nothing catches that here, because there is nothing to catch -- no recursion is deepening.NTHandRESTwalk the cycle transparently (<NTH .WALTZ 7>is1), and the manual's own bounded escape isLENGTH?(section 8.2.4), which answers#FALSE ()rather than counting forever.The self-element of 7.8.2 is circular in depth, and that one pymdl stops: the printer has a depth limit, and reaching it signals an error where the era reached the same place by exhausting its stack. The limit is a knob,
set_print_limit, which can instead truncate with...or be turned off for the era's own unbounded descent. It is a limit on recursion and not on size -- a legitimate deep structure prints in full, and<LENGTH <ILIST 100000>>is 100000 with no false cycle reported.So
^Sis still the remedy for the first kind, and the second fails loudly instead of quietly forever, which is the better default when the session is likelier to be a pipe than a terminal.