From 52c514c43b3ee690d148bf42c23793dfdaad39ad Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 4 Apr 2023 10:11:46 +0100 Subject: [PATCH 1/7] Further documentation and research around property lists --- doc/further_reading.md | 7 ++ doc/lisp1.5.md | 245 ++++++++++++++--------------------------- doc/values.md | 61 +++++++++- 3 files changed, 151 insertions(+), 162 deletions(-) create mode 100644 doc/further_reading.md diff --git a/doc/further_reading.md b/doc/further_reading.md new file mode 100644 index 0000000..bcf4720 --- /dev/null +++ b/doc/further_reading.md @@ -0,0 +1,7 @@ +# Further Reading + +1. [CODING for the MIT-IBM 704 COMPUTER, October 1957](http://bitsavers.org/pdf/mit/computer_center/Coding_for_the_MIT-IBM_704_Computer_Oct57.pdf) +2. [MIT AI Memo 1, John McCarthy, September 1958](https://www.softwarepreservation.org/projects/LISP/MIT/AIM-001.pdf) +3. [Lisp 1 Programmer's Manual, Phyllis Fox, March 1960](https://bitsavers.org/pdf/mit/rle_lisp/LISP_I_Programmers_Manual_Mar60.pdf) +4. [Lisp 1.5 Programmer's Manual, Michael I. Levin, August 1962](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=81) +4. [Early LISP History (1956 - 1959)](https://dl.acm.org/doi/pdf/10.1145/800055.802047#page=3) diff --git a/doc/lisp1.5.md b/doc/lisp1.5.md index 7e53b6b..fc57a20 100644 --- a/doc/lisp1.5.md +++ b/doc/lisp1.5.md @@ -1626,19 +1626,13 @@ ables bound outside of the errorset have not been altered by using cset or set, damage has been done by pseudo-functions, it may be possible to continue computation in a different direction when one path results in an error. -``` -VII. LIST STRUCTURES -In other sections of this manual, lists have been discussed by using the LISP input- -output language. In this section, we discuss the representation of lists inside the com- -puter, the nature of property lists of atomic symbols, representation of numbers, and -the garbage collector. -``` +## VII. LIST STRUCTURES -7. 1 Representation of List Structure - Lists are not stored in the computer as sequences of BCD characters, but as struc- - tural forms built out of computer words as parts of trees. - In representing list structure, a computer word will be depicted as a rectangle - divided into two sections, the address and decrement. +In other sections of this manual, lists have been discussed by using the LISP input-output language. In this section, we discuss the representation of lists inside the computer, the nature of property lists of atomic symbols, representation of numbers, and the garbage collector. + +### 7.1 Representation of List Structure + +Lists are not stored in the computer as sequences of BCD characters, but as structural forms built out of computer words as parts of trees. In representing list structure, a computer word will be depicted as a rectangle divided into two sections, the address and decrement. add. I dec. @@ -1660,16 +1654,14 @@ in the decrement. Following are some diagrammed S-expressions, shown as they would appear in the computer. It is convenient to indicate NIL by -- - -- - instead of -- -- -F]. -``` It is possible for lists to make use of common subexpressions. ((M. N) X (M. N)) could also be represented as -``` -``` + Circular lists are ordinarily not permitted. They may not be read in; however, they can occur inside the computer as the result of computations involving certain functions. Their printed representation is infinite in length. For example, the structure -``` + ``` will print as (A B C A B C A. .. @@ -1689,9 +1681,7 @@ The advantages of list structures for the storage of symbolic expressions are: 1. The size and even the number of expressions with which the program will have to deal cannot be predicted in advance. Therefore, it is difficult to arrange blocks of -``` -37 -``` +page 37 storage of fixed length to contain them. @@ -1702,7 +1692,8 @@ available. 3. An expression that occurs as a subexpression of several expressions need be represented in storage only once, -7.2 Construction of List Structure +### 7.2 Construction of List Structure + The following simple example has been included to illustrate the exact construction of list structures. Two types of list structures are shown, and a function for deriving one from the other is given in LISP. @@ -1740,26 +1731,27 @@ So rnltgrp applied to the list P1 takes each threesome, (X Y Z), in turn and app to it to put it in the new form, (X (Y Z)) until the list P1 has been exhausted and the new list P2 achieved. -7.3 Property Lists +### 7.3 Property Lists + In other sections, atomic symbols have been considered only as pointers. In this section the property lists of atomic symbols that begin at the appointed locations are described. + Every atomic symbol has a property list. When an atomic symbol is read in for the first time, a property list is created for it. + A property list is characterized by having the special constant 777778 (i. e., minus 1) as the first element of the list. The rest of the list contains various properties of the atomic symbol. Each property is preceded by an atomic symbol which is called its indicator. Some of the indicators are: -``` -PNAME - the BCD print name of the atomic symbol for input-output use. -EXPR - S-expression defining a function whose name is the atomic symbol -on whose property list the EXPR appears. -SUBR - Function defined by a machine language subroutine. -APVAL - Permanent value for the atomic symbol considered as a variable. -``` +| Indicator | Description | +| --------- | ------------------------------------------------------------ | +| PNAME | the BCD print name of the atomic symbol for input-output use. | +| EXPR | S-expression defining a function whose name is the atomic symbol on whose property list the EXPR appears. | +| SUBR | Function defined by a machine language subroutine. | +| APVAL | Permanent value for the atomic symbol considered as a variable. | -``` The atomic symbol NIL has two things on its property list - its PNAME, and an APVAL that gives it a value of NIL. Its property list looks like this: ``` @@ -1805,14 +1797,13 @@ TXL 37721,, 2 1 ``` The indicator EXPR points to an S-expression defining a function. The function define puts EXPR1s on property lists. After defining ff, its property list would look like this -``` -1 I LAMBDA I The function get[x;i] can be used to find a property of x whose indicator is i. The -value of get[~~;~G~] would be (LAMBDA (X) (COW... +value of get[X; G] would be (LAMBDA (X) (COW... A property with its indicator can be removed by remprop[x;i]. The function deflist[x;i] can be used to put any indicator on a property list. The first argument is a list of pairs as for define, the second argument is the indicator to @@ -1826,45 +1817,38 @@ and what type it is, and a pointer to the number itself in the decrement of this Unlike atomic symbols, numbers are not stored uniquely. For example, the decimal number 15 is represented as follows: -7.4 List Structure Operators +### 7.4 List Structure Operators + +The theory of recursive functions developed in Section I will be referred to as elementary LISP. Although this language is universal in terms of computable functions of symbolic expressions, it is not convenient as a programming system without additional tools to increase its power. + +In particular, elementary LISP has no ability to modify list structure. The only basic function that affects list structure is `cons`, and this does not change existing lists, but creates new lists. Functions written in pure LISP such as `subst` do not actually modify their arguments, but make the modifications while copying the original. -The theory of recursive functions developed in Section I will be referred to as ele- -mentary LISP. Although this language is universal in terms of computable functions of -symbolic expressions, it is not convenient as a programming system without additional -tools to increase its power. -In particular, elementary LISP has no ability to modify list structure. The only -basic function that affects list structure is cons, and this does not change existing lists, -but creates new lists. Functions written in pure LISP such as subst do not actually mod- -ify their arguments, but make the modifications while copying the original. LISP is made general in terms of list structure by means of the basic list operators -rplaca and rplacd. These operators can be used to replace the address or decrement +`rplaca` and `rplacd`. These operators can be used to replace the address or decrement or any word in a list. They are used for their effect, as well as for their value, and are called pseudo-functions. -rplaca[x;y] replaces the address of x with y. Its value is x, but x is something -different from what it was before. In terms of value, rplaca can be described by the -equation + +`rplaca[x;y]` replaces the address of `x` with `y`. Its value is `x`, but `x` is something different from what it was before. In terms of value, rplaca can be described by the equation rpla~a[x;~] = c~ns[~;cdr[x]] But the effect is quite different: there is no cons involved and a new word is not created. -rplacd[x;y] replaces the decrement of x with y. + +`rplacd[x;y]` replaces the decrement of `x` with `y`. + These operators must be used with caution. They can permanently alter existing definitions and other basic memory. They can be used to create circular lists, which -can cause infinite printing, and look infinite to functions that search, such as equal and -subst. -As an example, consider the function mltgrp of section 7.2. This is a list-altering +can cause infinite printing, and look infinite to functions that search, such as `equal` and +`subst`. -function that alters a copy of its argument. The subfunction - grp rearranges a subgroup +As an example, consider the function mltgrp of section 7.2. This is a list-altering function that alters a copy of its argument. The subfunction - grp rearranges a subgroup + +The original function does this by creating new list structures, and uses four cons's. Because there are only three words in the original, at least one cons is necessary, but -``` -The original function does this by creating new list structures, and uses four cons's. -Because there are only three words in the original, at leaSt one cons is necessary, but -``` - grp can be rewritten by using rplaca and rplacd. The modification is -``` -The new word is created by cons[cadr[x];cddr[x]]. A pointer to it is provided by -rplaca[cdr[x];cons[cadr[x];cddr[x]]]. +The new word is created by cons[cadr[x];cddr[x]]. A pointer to it is provided by rplaca[cdr[x];cons[cadr[x];cddr[x]]]. + The other modification is to break the pointer from the second to the third word. This is done by rplacd[cdr[x];~l~]. pgrp - is now defined as @@ -1876,64 +1860,29 @@ pmltgrp[l] = [null[l] -. NIL; T -- ~rog2[~g~~[car[~Il~~~~tgr~[cdr[~1111 prog2 is a function that evaluates its two arguments. Its value is the second argument. The value of pmltgrp is NIL. pgrp - and - pmltgrp are pseudo-functions. -``` -``` -7.5 The Free-Storage List and the Garbage Collector -``` -At any given time only a part of the memory reserved for list structures will actually -be in use for storing S-expressions. The remaining registers are arranged in a single -list called the free-storage list. A certain register, FREE, in the program contains the -location of the first register in this list. When a word is required to form some addi- -tional list structure, the first word on the free-storage list is taken and the number in -register FREE is changed to become the location of the second word on the free-storage +### 7.5 The Free-Storage List and the Garbage Collector -``` -list. No provision need be made for the user to program the return of registers to the -free-storage list. -This return takes place automatically whenever the free -storage list has been -exhausted during the running of a LISP program. The program that retrieves the storage -is called the garbage collector. -Any piece of list structure that is accessible to programs in the machine is consid- -ered an active list and is not touched by the garbage collector. The active lists are -accessible to the program through certain fixed sets of base registers, such as the reg- -isters in the list of atomic symbols, the registers that contain partial results of the -LISP computation in progress, etc. The list structures involved may be arbitrarily -long but each register that is active must be connected to a base register through a car- -cdr - chain of registers. Any register that cannot be so reached is not accessible to any -program and is nonactive; therefore its contents are no longer of interest. -The nonactive, i. e. , inaccessible, registers are reclaimed for the free-storage list -by the garbage collector as follows. First, every active register that can be reached -through a car-cdr chain is marked by setting its sign negative. Whenever a negative -register is reached in a chain during this process, the garbage collector knows that the -rest of the list involving that register has already been marked. Then the garbage col- -lector does a linear sweep of the free-storage area, collecting all registers with a posi- -tive sign into a new free-storage list, and restoring the original signs of the active -registers. -Sometimes list structure points to full words such as BCD print names and numbers. -The garbage collector cannot mark these words because the sign bit may be in use. The -garbage collector must also stop tracing because the pointers in the address and decre- -ment of a full word are not meaningful. -These problems are solved by putting full words in a reserved section of memory -called full-word space. The garbage collector stops tracing as soon as it leaves the -``` +At any given time only a part of the memory reserved for list structures will actually be in use for storing S-expressions. The remaining registers are arranged in a single list called the free-storage list. A certain register, FREE, in the program contains the location of the first register in this list. When a word is required to form some additional list structure, the first word on the free-storage list is taken and the number in register FREE is changed to become the location of the second word on the free-storage list. No provision need be made for the user to program the return of registers to the free-storage list. ---- free-storage space. Marking in full-word space is accomplished by a bit table. +This return takes place automatically whenever the free -storage list has been exhausted during the running of a LISP program. The program that retrieves the storage is called the garbage collector. -``` -VIII. A COMPLETE LISP PROGRAM - THE WANG ALGORITHM FOR THE -PROPOSITIONAL CALCULUS -``` +Any piece of list structure that is accessible to programs in the machine is considered an active list and is not touched by the garbage collector. The active lists are accessible to the program through certain fixed sets of base registers, such as the registers in the list of atomic symbols, the registers that contain partial results of the LISP computation in progress, etc. The list structures involved may be arbitrarily long but each register that is active must be connected to a base register through a car-cdr - chain of registers. Any register that cannot be so reached is not accessible to any program and is nonactive; therefore its contents are no longer of interest. -This section gives an example of a complete collection of LISP function definitions -which were written to define an algorithm. The program was then run on several test -cases. The algorithm itself is explained, and is then written in M-expressions. The -complete input card deck an'd the printed output of the run are reprinted here. -The Wang Algorithm^1 is a method of deciding whether or not a formula in the prop- -oditional calculus is a theorem. The reader will need to know something about the prop- -ositional calculus in order to understand this discussion. -We quote from pages 5 and 6 of Wangls paper: +The nonactive, i. e. , inaccessible, registers are reclaimed for the free-storage list by the garbage collector as follows. First, every active register that can be reached through a car-cdr chain is marked by setting its sign negative. Whenever a negative register is reached in a chain during this process, the garbage collector knows that therest of the list involving that register has already been marked. Then the garbage collector does a linear sweep of the free-storage area, collecting all registers with a positive sign into a new free-storage list, and restoring the original signs of the active registers. + +Sometimes list structure points to full words such as BCD print names and numbers. The garbage collector cannot mark these words because the sign bit may be in use. The garbage collector must also stop tracing because the pointers in the address and decrement of a full word are not meaningful. + +These problems are solved by putting full words in a reserved section of memory called full-word space. The garbage collector stops tracing as soon as it leaves the free-storage space. Marking in full-word space is accomplished by a bit table. + +### VIII. A COMPLETE LISP PROGRAM - THE WANG ALGORITHM FOR THE PROPOSITIONAL CALCULUS + +This section gives an example of a complete collection of LISP function definitions which were written to define an algorithm. The program was then run on several test cases. The algorithm itself is explained, and is then written in M-expressions. The complete input card deck and the printed output of the run are reprinted here. + +The [Wang Algorithm](https://dl.acm.org/doi/abs/10.1147/rd.41.0002) is a method of deciding whether or not a formula in the propositional calculus is a theorem. The reader will need to know something about the propositional calculus in order to understand this discussion. + +We quote from pages 5 and 6 of Wang's paper: "The propositional calculus (System P) Since we are concerned with practical feasibility, it is preferable to use more logical connectives to begin with when we wish actually to apply the procedure to concrete cases. @@ -1958,13 +1907,11 @@ vinced that these rules are indeed correct. Later on, a proof will be given of t pleteness, i. e., all intuitively valid sequents are provable, and of their consistency, i. e. , all provable sequents are intuitively valid. -``` "PI. Initial rule: if h, 5 are strings of atomic formulae, then h -. 5 is a theorem if some atomic formula occurs on both sides of the arrow. "In the ten rules listed below, h and 5 are always strings (possibly empty) of atomic formulae. As a proof procedure in the usual sense, each proof begins with a finite set of cases of P1 and continues with successive consequences obtained by the other rules .I1 -``` 1. Wang, Hao. "Toward Mechanical Mathematics," IBM J. Res. Develop., Vo1.4, No. 1. January 1960. @@ -2554,30 +2501,14 @@ FIN END OF LISP RUN M948-1207 LEVIN END OF LISP JOB ``` -``` -APPENDIX A -``` -``` -FUNCTIONS AND CONSTANTS IN THE LISP SYSTEM -``` +## APPENDIX A : FUNCTIONS AND CONSTANTS IN THE LISP SYSTEM + +This appendix contains all functions available in the LISP System as of August 1962. Each entry contains the name of the object, the property under which it is available (e. g., EXPR, FEXPR, SUBR, FSUBR, or APVAL), whether it is a pseudo-function, functional (function having functions as arguments), or predicate, and in some cases a definition of the function as an M-expression. In the case of APVALts, the value is given. + +The LISP Library is a file of BCD cards distributed with the LISP System. It is not intended to be used as input to the computer without being edited first. Have the Library file punched out, and then list the cards. Each Library function is preceded by a title card that must be removed. Some Library entries are in the form of a DEFINE, while some are in the form of an assembly in LAP. Note that some of them have auxiliary functions that must be included. -``` -This appendix contains all functions available in the LISP System as of August 1962. -Each entry contains the name of the object, the property under which it is available -(e. g., EXPR, FEXPR, SUBR, FSUBR, or APVAL), whether it is a pseudo-function, -functional (function having functions as arguments), or predicate, and in some cases -a definition of the function as an M-expression. In the case of APVALts, the value is -given. -The LISP Library is a file of BCD cards distributed with the LISP System. It is not -intended to be used as input to the computer without being edited first. Have the Library -file punched out, and then list the cards. Each Library function is preceded by a title -card that must be removed. Some Library entries are in the form of a DEFINE, while -some are in the form of an assembly in LAP. Note that some of them have auxiliary -functions that must be included. -``` -``` Elementary Functions car - [x] SUBR cdr - [x] SUBR @@ -2678,9 +2609,8 @@ TRUE TRANSFER IF EQUAL OTHERWISE VALUE IS NIL VALUE IS *T* -``` -equal[^;^] - SUBR predicate +equal[x; y] - SUBR predicate * equal is true if its arguments are the same S-expression, although they do not have to be identical list structure in the computer. It uses 7 eq on the atomic level and is @@ -2702,32 +2632,28 @@ CLA TRUE TRA 1,4 TRUE OCT 1000000 ``` +page 58 -* rplaca[x;y] SUBR pseudo-function +#### rplaca[x; y] : SUBR pseudo-function + +#### rplacd[x; y] : SUBR pseudo-function -``` -rplacd[x;y] SUBR pseudo-function These list operators change list structure and can damage the system memory if not -used properly. See page^41 for a description of usage. -``` +used properly. See [page 41](#page41) for a description of usage. -``` -Logical Connectives -``` +### Logical Connectives -- and[x ;x2... ;xn] : FSUBR predicate +#### and[x1; x2; ... ; xn] : FSUBR predicate -``` The arguments of are evaluated in sequence, from left to right, until one is found -that is false, or until the end of the list is reached. The value of & is false or true +that is false, or until the end of the list is reached. The value of and is false or true respectively. -``` -- 0r[x1;x2... ;xn] : FSUBR predicate - The arguments of or are evaluated in sequence from left to right, until one is found - that is true, or until the end of the list is reached. The value of 2 is true or - false respectively. -* not [x] SUBR predicate +#### or[x1; x2; ... ; xn] : FSUBR predicate + +The arguments of or are evaluated in sequence from left to right, until one is found that is true, or until the end of the list is reached. The value of or is true or false respectively. + +#### not [x]: SUBR predicate The value of not is true if its argument is false, and false otherwise. @@ -2748,24 +2674,21 @@ where each `u` is a name and each `v` is a λ-expression for a function . > define[x] = deflist[x; EXPR] +#### deflist [x; ind] : EXPR pseudo-function +The function `deflist` is a more general defining function. Its first argument is a list of pairs as for define. Its second argument is the indicator that is to be used. After `deflist` has been executed with (ui vi) among its first argument, the property list of ui will begin: -deflist [x; ind] EXPR pseudo-function -The function deflist is a more general defining function. Its first argument is a list -of pairs as for define. Its second argument is the indicator that is to be used. After -deflist has been executed with (ui vi) among its first argument, the property list of ui -will begin: +If `deflist` or `define` is used twice on the same object with the same indicator, the old value will be replaced by the new one. + +#### attrib[x;e] : SUBR pseudo-function + +The function attrib concatenates its two arguments by changing the last element of its first argument to point to the second argument. Thus it is commonly used to tack something onto the end of a property list. The value of attrib is the second argument. -If deflist or define is used twice on the same object with the same indicator, the old -value will be replaced by the new one. -attrib[x;e] - SUBR pseudo-function -The function attrib concatenates its two arguments by changing the last element of -its first argument to point to the second argument. Thus it is commonly used to tack -something onto the end of a property list. The value of attrib is the second argument. For example attrib[~~; (EXPR (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR x))))))] would put EXPR followed by the LAMBDA expression for FF onto the end of the prop- erty list for FF. + ``` - pr~p[x;~;u] SUBR functional diff --git a/doc/values.md b/doc/values.md index 2c73959..5e75113 100644 --- a/doc/values.md +++ b/doc/values.md @@ -47,6 +47,22 @@ O14 (read lines O and 1) Of course, this isn't proof. If `CAR` and `CDR` used here are standard IBM 704 assembler mnemonics -- as I believe they are -- then what is `CONS`? It's used in a syntactically identical way. If it also is an assembler mnemonic, then it's hard to believe that, as legend relates, it is short for 'construct'; on the other hand, if it's a label representing an entry point into a subroutine, then why should `CAR` and `CDR` not also be labels? +----- + +**Edited 3rd April to add:** I've found a document, not related to Lisp (although John McCarthy is credited as one of the authors), which does confirm -- or strictly, amend -- the story. This is the [CODING for the MIT-IBM 704 COMPUTER](http://bitsavers.org/pdf/mit/computer_center/Coding_for_the_MIT-IBM_704_Computer_Oct57.pdf), dating from October 1957. The registers of the 704 were divided into four parts, named respectively the prefix part, the address part, the tag part, and the decrement part, of 3, 15, 3, and 15 bits respectively. The decrement part was not used in addressing; that part of the folklore I was taught isn't right. But the names are correct. Consider [this excerpt](http://bitsavers.org/pdf/mit/computer_center/Coding_for_the_MIT-IBM_704_Computer_Oct57.pdf#page=145) : + +> The address, tag and decrement parts of symbolic instructions are given in that order. In some cases the decrement, tag or address parts are not necessary; therefore the following combinations where OP represents the instruction abbreviation are permissible. + +This doesn't prove there were individual machine instructions with the mnemonics `CAR` and `CDR`; in fact, I'm going to say with some confidence that there were not, by reference to [the table of instructions](http://bitsavers.org/pdf/mit/computer_center/Coding_for_the_MIT-IBM_704_Computer_Oct57.pdf#page=170) appended to the same document. The instructions do have three letter mnemonics, and they do use 'A' and 'D' as abbreviations for 'address' and 'decrement' respectively, but `CAR` and `CDR` are not included. + +So it seems probable that `CAR` and `CDR` were labels for subroutines, as I hypothesised above. But they were quite likely pre-existing subroutines, in use before the instantiation of the Lisp project, because they would be generally useful; and the suggestion that they are contractions of 'contents of the address part' and 'contents of the decrement part', respectively, seem confirmed. + +And, going further down the rabbit hole, [there's this](https://dl.acm.org/doi/pdf/10.1145/800055.802047#page=3). In 1957, before work on the Lisp project started, McCarthy was writing functions to add list processing to the then-new FORTRAN language, on the very same IBM 704 machine. + +> in this time any function that delivered integer values had to have a first letter X. Any function (as opposited to subroutines) had to have a last letter F in its name. Therefore the functions selecting parts of the IBM704 memory register (word) were introduced to be XCSRF, XCPRF, XCDRF, XCTRF and XCARF + +----- + I think that the answer has to be that if `CAR` and `CDR` had been named by the early Lisp team -- John McCarthy and his immediate colleagues -- they would not have been named as they were. If not `FRST` and `REST`, as in more modern Lisps, then something like `P1` and `P2`. `CAR` and `CDR` are distinctive and memorable (and therefore in my opinion worth preserving) because they very specifically name the parts of a cons cell and of nothing else. Let's be clear, here: when `CAR` and `CDR` are used in Lisp, they are returning pointers, certainly -- but not in the sense that one points to a page and the other to a word. Each is an offset into a cell array, which is almost certainly an array of single 36 bit words held on a single page. So both are in effect being used as decrements. Their use in Lisp is an overload onto their original semantic meaning; they are no longer being used for the purpose for which they are named. @@ -262,6 +278,49 @@ Lisp 1.5 doesn't have `PUT`, `PUTPROP` or `DEFUN` because setting properties ind ----- +## Deeper delving + +After writing, and publishing, this essay, I went on procrastinating, which is what I do when I'm sure I'm missing something; and to procrastinate, I went on reading the earliest design documents of Lisp I could find. And so I came across the MIT AI team's first ever memo, written by John McCarthy in September 1958. And in that, I find this: + +> 3.2.1. First we have those that extract parts of a 704 word and form a word from parts. We shall distinguish the following parts of a word and indicate each of them by a characteristic letter. +> +> | Letter | Description | +> | ---- | ---------------------------- | +> | w | the whole word | +> | p | the prefix (bits s, 1, 2) | +> | i | the indicator (bits 1 and 2) | +> | s | the sign bit | +> | d | the decrement (bits 3-17) | +> | t | the tag (bits 18-20) | +> | a | the address (bits 21-35) | + +In the discussion of functions which access properties on [page 58 of the Lisp 1.5 programmer's manual](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=66), the word 'indicator' is used in preference to 'symbol' for the name of a property: for example + +> The function `deflist` is a more general defining function. Its first argument is a list of pairs as for define. Its second argument is the *indicator* that is to be used. After `deflist` has been executed with (ui vi) among its first argument, the property list of ui will begin: +> +> If `deflist` or `define` is used twice on the same object with the same *indicator*, the old value will be replaced by the new one. + +(my emphasis). + +That use of 'indicator' has been nagging at me for a week. It looks like a term of art. If it's just an ordinary atomic symbol, why isn't it called a symbol? + +Is it an indicator in the special sense of the indicator part of the machine word? If it were, then the property list could just be a flat list of values. And what's been worrying and surprising me is that property lists are shown in the manual as flat lists. Eureka? I don't *think* so. + +The reason I don't think so is that there are only two bits in the indicator part of the word, so only four distinct values; whereas we know that Lisp 1.5 has (at least) five distinct indicator values, `APVAL`, `EXPR`, `FEXPR`, `SUBR` and `FSUBR`. + +Furthermore, on [page 39](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=47), we have: + +> A property list is characterized by having the special constant 777778 (i. e., minus 1) +> as the first element of the list. The rest of the list contains various properties of the +> atomic symbol. Each property is preceded by an *atomic symbol* which is called its +> *indicator*. + +(again, my emphasis) + +But I'm going to hypothesise that the properties were originally intended to be discriminated by the indicator bits in the cons cell, that they were originally coded that way, and that there was some code which depended on property lists being flat lists; and that, when it was discovered that four indicators were not enough and that something else was going to have to be used, the new format of the property list using atomic symbols as indicators was bodged in. + +----- + So what this is about is I've spent most of a whole day procrastinating, because I'm not exactly sure how I'm going to make the change I've got to make. Versions of Beowulf up to and including 0.2.1 used the naive understanding of the architecture; version 0.3.0 *should* use the corrected version. But before it can, I need to be reasonably confident that I understand what the correct solution is. -I *shall* implement `PUT`, even though it isn't in the spec, because it's a useful building block on which to build `DEFINE` and `DEFLIS`, both of which are. And also, because `PUT` would have been very easy for the Lisp 1.5 implementers to implement, if it had been relevant to their working environment. +I *shall* implement `PUT`, even though it isn't in the spec, because it's a useful building block on which to build `DEFINE` and `DEFLIS`, both of which are. And also, because `PUT` would have been very easy for the Lisp 1.5 implementers to implement, if it had been relevant to their working environment. And I shall implement property list as flat lists of interleaved 'indicator' symbols and values, even with that nonsense 777778 as a prefix, because now I know (or think I know) that it was a bodge, it seems right in the spirit of historical reconstruction to reconstruct the bodge. From 31d8f0b8f9df93d4dfc658ba8409f26f899c3f33 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 4 Apr 2023 11:02:30 +0100 Subject: [PATCH 2/7] More reformatting of programmers manual --- doc/lisp1.5.md | 556 +++++++++------------------ resources/mexpr/properties.mexpr.lsp | 0 2 files changed, 188 insertions(+), 368 deletions(-) create mode 100644 resources/mexpr/properties.mexpr.lsp diff --git a/doc/lisp1.5.md b/doc/lisp1.5.md index fc57a20..8bc052d 100644 --- a/doc/lisp1.5.md +++ b/doc/lisp1.5.md @@ -2689,17 +2689,14 @@ attrib[~~; (EXPR (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR x))))))] would put EXPR followed by the LAMBDA expression for FF onto the end of the prop- erty list for FF. + +#### prop[x; y; u] : SUBR functional + +The function `prop` searches the list `x` for an item that is `eq` to `y`. If such an element is found, the value of `prop` is the rest of the list beginning immediately after the element. Otherwise the value is `u[]`, where u is a function of no arguments. ``` - -- pr~p[x;~;u] SUBR functional - The function prop - searches the list x for an item that is - eq to y. If such an element - is found, the value of prop is the rest of the list beginning immediately after the element. - Otherwise the value is u[ 1, where 2 is a function of no arguments. - -##### prop[^;^; u] = [null[x] - u[ ];eq[car[x];y] -cdr[x] - -###### T - prop[cdr [x];y ;u]] - +prop[x; y; u] = [null[x] -> u[ ]; + eq[car[x];y] -> cdr[x] + T -> prop[cdr[x]; y; u]] ``` SUBR ``` @@ -2772,14 +2769,11 @@ sassoc[x;y;u] SUBR functional The function sassoc searches y, which is a list of dotted pairs, for a pair whose first element that is x. If such a pair is found, the value of sassoc is this pair. Otherwise the function u of no arguments is taken as the value of sassoc. -``` -* subst[x; y;z] SUBR +#### subst[x; y; z] : SUBR -``` The function subst has as value the result of substituting x for all occurrences of the S-expression y in the S-expression z. -``` ###### subst[x;y;z] = [equal[y;z] - x @@ -2804,18 +2798,17 @@ k[[j]; equal[y;caar[j]]]; k[[j]; cdar[j]]; k[[j];[atom[y] - y; T -c cons [sublis [x;car [y]];sublis [x;cdr [y]]]]]]] -List Handling Functions ``` -``` -append [x;y] SUBR +### List Handling Functions + +#### append [x;y] : SUBR The function append combines its two arguments into one new list. The value of append is the resultant list. For example, append[(^ B) (a1 = (A B C) ``` - -##### append [x;y] = [null[x] -. y ; T - cons [car [x]; append[cdr [x]; y I]] - +append [x;y] = [null[x] -. y ; T - cons [car [x]; append[cdr [x]; y I]] +``` Note that append copies the top level of the first list; append is like - nconc except that nconc does not copy its first argument. @@ -2861,59 +2854,55 @@ reverse[(^ B (C. D))] = ((C D) B A)) reverse[t] = prog[[v]; u: =t; ``` - -##### A [null[u] - return[v]] +A [null[u] - return[v]] ``` v:=cons[car[u];v]; u:=cdr[u]; so[AlI -``` -``` -member[x; 8 ] SUBR predicate -If the S-expression x is a member of the list 1, then the value of member is *T*. +#### member[x; l ] : SUBR predicate + +If the S-expression x is a member of the list l, then the value of member is T. Otherwise, the value is NIL. -``` ##### member[x;l] = [null[l] - ~;equal[x;car[L]] -- T ##### T - member[x;cdr[l]]] -``` -length[x] SUBR -``` +#### length[x] : SUBR -``` The value of length is the number of items in the list x. The list ( ) or NIL has length 0. -``` -* efface[x;Q] SUBR pseudo-function +#### efface[x; Q] : SUBR pseudo-function -``` The function efface deletes the first appearance of the item x from the list 8. +``` efface[x;l ] = [null[l] -. NIL; /' equal[x;car[8]] .-. cdr[8]; T -- rplacd[k ;effac e[x;cdr [8]]]] -These four functionals apply a function, f, to x, then to cdr[x], then to cddr[x], etc. -Functionals or Functions with Functions as Arguments -maplis t [x; f ] SUBR functional -The function maplist is a mapping of the list x onto a new list f[x]. ``` +These four functionals apply a function, f, to x, then to cdr[x], then to cddr[x], etc. + +Functionals or Functions with Functions as Arguments + +#### maplist [x; f ] : SUBR functional + +The function maplist is a mapping of the list x onto a new list f[x]. + ##### maplist [x; f] = [null[x] - NIL; T - cons[f [x]; maplist [cdr [x]; f]]] -``` -map on [x; f ] SUBR pseudo-functional +#### mapcon [x; f ] : SUBR pseudo-functional + The function mapcon is like the function maplist except that the resultant list is a concatenated one instead of having been created by cons-ing. ``` - -##### mapcon[x; f ] = [null[x] - NIL; T - nconc [f [x]; mapcon[cdr [x]; f I]] - +mapcon[x; f ] = [null[x] -> NIL; T - nconc[f [x]; mapcon[cdr [x]; f ]]] ``` -map[x; f ] SUBR functional + +#### map[x; f ] : SUBR functional The function map - is like the function maplist except that the value of map is NIL, and map does not do a cons of the evaluated functions. map is used only when the action of doing f[x] is important. @@ -2924,21 +2913,16 @@ LOOP [null[m] -. returnl~~~]]; f [m]; m:= cdr[m]; go[~oopl1 -``` -- sear~h[x;~;f;u] : SUBR functional - The function search looks through a list x for an element that has the property p, - and if such an element is found the function f of that element is the value of search. - If there is no such element, the function u of one argument x is taken as the value of - search (in this case x is, of course, NIL). +#### search[x; p; f; u] : SUBR functional + +The function search looks through a list x for an element that has the property p, and if such an element is found the function f of that element is the value of search. If there is no such element, the function u of one argument x is taken as the value of search (in this case x is, of course, NIL). -``` Arithmetic Functions These are discussed at length in Section IV. function type number of args plus FSUBR indef. minus SUBR 1 -``` - value @@ -2966,7 +2950,6 @@ logand logxor leftshift -``` SUBR FSUBR SUBR @@ -2990,9 +2973,7 @@ FSUBR FSUBR FSUBR SUBR -``` -``` predicate predicate predicate @@ -3014,32 +2995,22 @@ indef. indef. indef. 1 2 2 2 1 1 1 1 1 1 -``` -``` indef. indef. indef. 2 -``` -``` Xl'X2'... *X n list [x/~; remainder] -``` -``` remainder of x/~ -``` -``` largest of xi smallest of xi [fixp[x]-0; ~-.quotient[l ;XI] XY -``` -``` x is negative x is a number x is a fixed point number @@ -3049,63 +3020,55 @@ x1Ax2A... A xn ANA x ,*x24... Vxn ERA x 2Y array SUBR 1 declares arrays -``` -The Compiler and Assembler +### The Compiler and Assembler + +#### compile[x] : SUBR pseudo-function -``` -compile[x] SUBR pseudo-function The list x contains the names of previously defined functions. They are compiled. -special[x] SUBR pseudo-function -The list x contains the names of variables that are to be declared SPECIAL. -``` -uns pec ial[x] SUBR pseudo-function +#### special[x] : SUBR pseudo-function + +The list x contains the names of variables that are to be declared SPECIAL. + +#### unspecial[x] : SUBR pseudo-function The list x contains the names of variables that are no longer to be considered SPECIAL by the compiler. -c ommon[x] SUBR pseudo-function +#### common[x] : SUBR pseudo-function -``` The list x contains the names of variables that are to be declared COMMON. -``` -unc ommon[x] : SUBR pseudo-function +#### uncommon[x] : SUBR pseudo-function The list x contains the names of variables that are no longer to be considered COMMON by the compiler. -``` -lap[list; - table] : SUBR pseudo-function +#### lap[list; - table] : SUBR pseudo-function + The assembler LAP is discussed in appendix C. opd ef ine [x] EXPR pseudo-function opdefine defines new symbols for the assembler LAP. The argument is a list of dotted pairs, each pair consisting of symbol and value. -``` -``` -readlap[ ] EXPR pseudo-function +#### readlap[ ] : EXPR pseudo-function + readlap reads assembly language input and causes it to be assembled using LAP. The input follows the STOP card of the packet containing the readlap. Each function to be read in consists of a list of the two arguments of 2. These are read in successively until a card containing NIL is encountered. readlap uses remob to remove unwanted atomic symbols occurring in the listing. For this reason, it should only be used to read cards that have been produced by punchlap. -``` -``` -Input and Output -``` +### Input and Output -- read[ ] SUBR pseudo-function +#### read[] : SUBR pseudo-function -``` The execution of read causes one list to be read from SYSPIT, or from the card reader. The list that is read is the value of read. -``` -- print [x] SUBR pseudo-function +#### print[x] : SUBR pseudo-function The execution of - print causes the S-expression x to be printed on SYSPOT and/or the on-line printer. The value of print is its argument. punchrx] - SUBR pseudo.-function @@ -3115,47 +3078,38 @@ reader. The list that is read is the value of read. * prinl prints an atomic symbol without terminating the print line. The argument of - prini must be an atomic symbol. -terpri[ - ] SUBR pseudo-function +#### terpri[] : SUBR pseudo-function -``` terpri terminates the print line. -``` -``` The character reading, sorting and printing functions are discussed in appendix F. -``` -``` startread pack opc har error1 numob advance unpack dash mknam endread digit clearbuff liter -``` -``` -Functions for System Control, Debugging, and Error Processing -``` +#### Functions for System Control, Debugging, and Error Processing -- trace[xl EXPR pseudo-function +#### trace[x] : EXPR pseudo-function -``` The argument of trace is a list of functions. After trace has been executed, the arguments and values of these functions are printed each time the function is entered recursively. This is illustrated in the printed output of the Wang Algorithm example. The value of trace is NIL. Special forms cannot be traced. -untrac e [x] EXPR pseudo-function -This removes +he tracing from all functions in the list x. The value of untrace is NIL. + +#### untrace [x] : EXPR pseudo-function + +This removes the tracing from all functions in the list x. The value of untrace is NIL. The following pseudo-functions are described in the section on running the LISP system: -``` -``` --- count, uncount, speak, error, errorset. -Miscellaneous Functions ' + +### Miscellaneous Functions ' prog2 [x;~] SUBR The value of prog2 is its second argument. It is used mainly to perform two pseudo- functions. -``` - CPl [XI SUBR * cpA copies its argument which must be a list of a very special type. @@ -3164,8 +3118,8 @@ functions. The copied list is the value of cpi. -``` -gens~m[ 1 SUBR +#### gensym[ ] : SUBR + The function gensym has no arguments. Its value is a new, distinct, and freshly- created atomic symbol with a print name of the form G00001, G00002,... , G99999. This function is useful for creating atomic symbols when one is needed; each one @@ -3176,35 +3130,33 @@ The qits in select are evaluated in sequence from left to right until one is fou qi = and the value of select is the value of the corresponding ei. If no such qi is found the value of select is that of e. -``` -``` -t empus -fugit [ ] : SUBR pseudo-function +#### tempus -fugit [ ] : SUBR pseudo-function + Executing this will cause a time statement to appear in the output. The value is NIL. (tempus-fugit is for MIT users only.) -``` -- load[ ] : SUBR pseudo-function +#### load[] : SUBR pseudo-function -``` Program control is given to the LISP loader which expects octal correction cards, 704 row binary cards, and a transfer card. -``` -- plb [ I SUBR pseudo-function +#### plb[] : SUBR pseudo-function -``` This is equivalent to pushing "LOAD CARDS " on the console in the middle of a LISP program. -reclaim[ ] SUBR pseudo-function -Executing this will cause a garbage collection to occur. The value is NIL. -``` -``` -pause[ 1 SUBR pseudo-function +#### reclaim[] : SUBR pseudo-function + +Executing this will cause a garbage collection to occur. The value is NIL. + +#### pause[] : SUBR pseudo-function + Executing this will cause a program halt. Pushing START will cause the program to continue, returning the value NIL. -excise[x] : SUBR pseudo-function + +#### excise[x] : SUBR pseudo-function + If x is NIL, then the compiler will be overwritten with free storage, If x is *T*, then both the compiler and LAP will be overwritten by free storage. excise may be executed more than once. The effect of excise[W'*] is somewhat unreliable. It is @@ -3224,10 +3176,11 @@ This removes the atom x from the object list. It causes the symbol and all its properties to be lost unless the symbol is referred to by active list structure. When an atomic symbol has been removed, subsequent reading of its name from input will create a different atomic symbol. -The LISP Library + +### The LISP Library + The LISP Library is distributed as the second file on the LISP setup tape. To use any part of it, punch out the entire library and remove the part you wish to use. Be -``` sure to strip off comment cards, unnecessary DEFINE cards, and unnecessary cards that close a define with )). @@ -3265,27 +3218,20 @@ atoms in the EXPRs and FEXPRs of punchlapped functions must not contain class C characters. pr intpr op[x] EXPR pseudo-function -``` If x is an atomic symbol, all of its properties will be printed in the output. Nothing is changed by printprop. -``` punchdef [x] EXPR pseudo-function -``` If x is a list of atomic symbols, each one having an EXPR or FEXPR will have its definition punched out. Nothing is changed. -``` APVAL1s The following is a list of all atoms with APVALts on their property lists in the basic system and their values. -``` APVAL -``` -``` BLANK CHARCOUNT COMMA @@ -3305,13 +3251,9 @@ SLASH STAR T *T* -``` -``` value -``` -``` (BCD blank) (character count during reading of characters) J @@ -3319,40 +3261,28 @@ J $ $EOF $ $EOR$ -``` -``` NIL ( NIL (bucket sorted object list) -``` 1. The entire set of objects (atomic symbols) existing in the system can be printed out by performing EVAL (OBLIST NIL). -``` -APPENDIX B -``` +## APPENDIX B : THE LISP INTERPRETER -``` -THE LISP INTERPRETER -``` - -``` This appendix is written in mixed M-expressions and English. Its purpose is to describe as closely as possible the actual working of the interpreter and PROG feature. The functions evalquote, ---- apply, eval, evlis, evcon, and the PROG feature are defined by using a language that follows the M-expression notation as closely as possible and contains some insertions in English. -``` ###### evalquote[fn;args]=[get [fn; FEXPR] vget [fn; FSUBR] - eval[cons [ fn; args]; NIL] -``` This definition shows that evalquote is capable of handling special forms as a sort of exception. Apply cannot handle special forms and will give error A2 if given one as its first argument. @@ -3368,15 +3298,12 @@ apply[fn;args;a]=[ null [fn]-NIL; at ~rn[fn]-[~et [fn; EXPR]-~~~~~ [expr^1 ; args ; a]; spread[args]; -``` -``` T-apply[cdr[sassoc [fn;a;~[[];error [~2]]]];ar~s ;a]; eq[car[fn]; ~~~~~]-a~~l~[caddr[fn];ar~s;cons[cons[cadr[fn];caddr[fn]];a]]; eq[car[fn]; ~~~~~~]-a~~1~[cadr [fn]; args; caddr [fn]]; eq[car [fn]; LAMBDA]-eval[caddr[fn]; nconc [pair[cadr[fn]; args]; a]]; ~-a~~ly[eval[fn;a];ar~s ;a]] -``` 1. The value of get is set aside. This is the meaning of the apparent free or unde- fined variable - @@ -3464,13 +3391,7 @@ piled programs and the interpreter. 2. & as distinct from setq can only be used to set common variables. 1. See Appendix D for an explanation of variable declaration. -``` -APPENDIX C -``` - -``` -THE LISP ASSEMBLY PROGRAM (LAP) -``` +APPENDIX C : THE LISP ASSEMBLY PROGRAM (LAP) lap is a two-pass assembler. It was specifically designed for use by the new com- piler, but it can also be used for defining functions in machine language, and for making @@ -3511,13 +3432,9 @@ pointer to a word containing TXL, the first location of the program just assembl the address, and the number n in the decrement. type is usually either SUBR or FSUBR. n is the number of arguments which the subroutine expects. -Sv mbols +Symbols -``` Atomic symbols appearing on the listing (except NIL or the first item on the listing) -``` - -``` are treated as location symbols. The appearance of the symbol defines it as the location of the next instruction in the listing. During pass one, these symbols and their values are made into a pair list, and appended to the initial symbol table to form the final sym- @@ -3527,14 +3444,11 @@ Symbols occurring on this table are defined only for the current assembly. The symbol table is discarded after each assembly. Permanent symbols are defined by putting the indicator SYM followed by a pointer to a value on their property lists. -``` Instructions -``` Each instruction is a list of from zero to four fields. Each field is evaluated in the same manner; however, the fields are combined as follows. -``` 1. The first field is taken as a full word. 2. The second field is reduced algebraically modulo 2 15, and is OR1ed into the @@ -3566,30 +3480,23 @@ literal will not be created if it is equal to one that already exists. 4. If the field is of the form (SPECIAL x), then the value is the address of the SPECIAL cell on the property list of x. If one does not already exist, it will be created. -``` The SPECIAL cell itself (but not the entire atom) is protected against garbage collection. -``` 5. In all other cases, the field is assumed to be a list of subfields, and their sum is taken. The subfields must be of types 14 above. -``` Error Diagnostics *L 1* Unable to determine origin. No assembly. *L 2* Out of binary program space. Second pass cancelled. *L 3 * Undefined symbol. Assembly incomplete. *L 4* Type five field contains type five fields inside itself. Assembly incomplete. -``` -``` Opdef ine opdefine is a pseudo-function for defining new quantities for LAP. It puts a SYM on the property list of the symbol that is being defined. Its argument is a list of pairs. Each pair is a symbol and its numerical value. Note that these pairs are not "dotted pairs. -``` -``` Example OPDEFINE ( ( (CLA 500Q8) (TRA 2Q9) @@ -3604,9 +3511,6 @@ LXD PAX PDX Examples of the Use of LAP -``` - -``` PXA PXD STD @@ -3614,9 +3518,6 @@ ST0 STQ STR STZ -``` - -``` SUB SXA SXD @@ -3624,9 +3525,6 @@ TIX Trn TNX TNZ -``` - -``` TRA TSX TXH @@ -3634,24 +3532,19 @@ TXI TXL TZE XCA -``` -``` Example 1: A LISP function The predicate greater induces an arbitrary canonical order among atomic symbols. LAP ( ( (GREATER SUBR 2) (TI& (* 3)) (PXA 0 0) (TRA 1 4) (CLA (QUOTE *T* ) ) (TRA 1 4) )NIL) Example 2: A patch -``` The instruction TSX 6204Q must be inserted after location 62 17Q.^62 17Q contains CIA 6243Q and this instruction must be moved to the patch. -``` LAP ( (6217Q (TRA NIL) )NIL) LAP ( (NIL (CLA A) (TSX 6204Q) (TRA B) ) ( (A 6243Q) (B 6220Q) ) ) -``` APPENDIX D THE LISP COMPILER @@ -3699,7 +3592,6 @@ compiler will print this fact and proceed with the next function. Second, the as not otherwise accessible, one gains as free storage the total space formerly occupied by the S-expression definition. -``` language program is assembled by LAP. Finally, if no error has occurred, then the EXPR or FEXPR is removed from the property list. When certain errors caused by undeclared free variables occur, the compiler will print a diagnostic and continue. @@ -3708,7 +3600,6 @@ compiled. When writing a large LISP program, it is better to debug the individual function defi- nitions by using the interpreter, and compile them only when they are known to work. Persons planning to use the compiler should note the following points: -``` 1. It is not necessary to compile all of the functions that are used in a particular run. The interpreter is designed to link with compiled functions. Compiled functions @@ -3724,24 +3615,18 @@ compiled. This is discussed at length in this appendix. Excise -``` The compiler and the assembler LAP can be removed from the system by using the pseudo-function excise. If excise [NIL] is executed, then the compiler will be removed. If excise [*T*] is executed, then the compiler and LAP will both be excised. One may execute excise [NIL] and then excise [*T*] at a later time. When a portion of the system is excised, the region of memory that it occupied is converted into addi- tional free-storage space. -``` -``` Free Variables A variable is bound in a particular function when it occurs in a list of bound vari- ables following the word LAMBDA or PROG. Any variable that is not bound is free. -``` -``` Example -``` (LAMBDA (A) (PROG (B) S (SETQ B A) @@ -3789,32 +3674,27 @@ functions. COMMON variables are declared by common[a], where a is a list of variable names. The declaration can be removed by uncommon[a], where a is a list of variable names. -``` Functional Constants Consider the following definition of a function dot by using an S-expression: (YDOT (LAMBDA (X Y) (MAPLIST X (FUNCTION (LAMBDA (J) (CONS (CAR J) Y)) )))) -``` Following the word FUNCTION is a functional constant. If we consider it as a sep- arate function, it is evident that it contains a bound variable "Jtt, and a free variable "Yfl. This free variable must be declared SPECIAL or COMMON, even though it is bound in YDOT. -Functional Arguments +### Functional Arguments -``` MAPLIST can be defined in S-expressions as follows: (MAPLIST (LAMBDA (L FN) (COND ((NULL L) NIL) (T (CONS (FN L) (MAPLIST (CDR L) FN))) ))) The variable FN is used to bind a functional argument. That is, the value of FN is a function definition. This type of variable must be declared COMMON. -``` -- Link +### Link -``` Link is the routine that creates all linkage of compiled functions at run time. The normal procedure for calling a compiled function is to place the arguments in the AC, MQj $ARG3,. .. and then to TSX FN,4. However, the first time any call is @@ -3824,11 +3704,8 @@ ments that are being transmitted, respectively. The tag contains a 7. If there i SUBR or FSUBR for the function that is being called, then link will call the interpreter that may find an EXPR or FEXPR. If there is a subroutine available, then link will form the instruction TSX and plant this on top of the STR. -``` -``` -Tracing Compiled Functions -``` +### Tracing Compiled Functions - trace will work for compiled functions, subject to the following restrictions. 1. The trace must be declared after the function has been compiled. @@ -3836,11 +3713,7 @@ Tracing Compiled Functions 2. Once a direct TSX link is made, this particular calling point will not be traced. (Link will not make a TSX as long as the called function is being traced. ) -``` -APPENDIX E -``` - -OVERLORD - THE MONITOR +## APPENDIX E : OVERLORD - THE MONITOR Overlord is the monitor of the LISP System. It controls the handling of tapes, the reading and writing of entire core images, the historical memory of the system, and @@ -3874,56 +3747,68 @@ function definitions and other memory changes are preserved. Card Format -``` Octal correction cards can alter up to 4 words of memory per card. Each change specifies an address (5 octal digits) and a word to be placed there (12 octal digits). The card columns to use are as follows. -``` -``` address data word -``` -``` Overlord cards have the Overlord direction beginning in column 8. If the card has no other field, then comments may begin in column 16. Otherwise, the other fields of the card begin in column 16 and are separated by commas. The comments may begin after the first blank past column 16. -``` -Overlord Cards -TAPE SYSPPT, B4 +### Overlord Cards + +#### TAPE SYSPPT, B4 + The TAPE Overlord card defines the actual drives to be assigned to the tapes. The system uses five tapes designated by the names SYSTAP, SYSTMP, SYSPIT, SYSPOT, and SYSPPT. The actual tape units may range from A0 through C9. -SIZE N1, N2, N3, N4 + +#### SIZE N1, N2, N3, N4 + The size card specifies the amount of storage to be allocated to binary program space, push-down, full words, and free storage in that order. The SIZE card must be used only once at the time when the system is created from a binary card deck. The fields are octal or decimal integers. -DUMP Ll,L2,0 + +#### DUMP Ll,L2,0 + This Overlord card causes an octal dump of memory to be printed. The first two fields are octal or decimal integers specifying the range of the dump. The third field specifies the mode. 0 mode specifies a straight dump. 1 mode specifies that if the prefix and tag areas of a word are zero, then the complements of the address and decre- ment are dumped instead. -TEST + +#### TEST + Specifies that a packet is to follow and that memory is to be restored from SYSTMP after the packet has been evaluated. -TST + +#### TST + Same as TEST -SET + +#### SET + The SET card specifies that a packet is to follow and that the memory state following the evaluation of the packet is to be set onto SYSTMP. If an error occurs during the evaluation of the packet, then the memory is to be restored from SYSTMP instead. -SETSET + +#### SETSET + The SETSET card is like SET except that it sets even if there has been an error. -DEBUG + +#### DEBUG + This direction is like TEST except that after the doublets have been read in the entire object list is thrown away, making it impossible to do any further reading (except of numbers). This makes a considerable amount of free storage available but may cause trouble if certain atoms that are needed are not protected in some manner. -FIN + +#### FIN + Causes the computer to halt. An end of file mark is written on SYSPOT. An end of file is written on SYSPPT only if it has been used. If the FIN card was read on-line, the computer halts after doing these things. If the FIN card came from SYSPIT, then @@ -3951,13 +3836,7 @@ become a system tape containing the basic system plus any changes that have been onto it. It may be mounted on the SYSTAP drive for some future run to use definitions that have been set onto it. -``` -APPENDIX F -``` - -``` -LISP INPUT AND OUTPUT -``` +## APPENDIX F : LISP INPUT AND OUTPUT This appendix describes the LISP read and write programs and the character- manipulation programs. The read and write programs allow one to read and write @@ -3998,12 +3877,10 @@ b. The first two characters must not be $ $. c. It must be delimited on either side by a character from class C. There is a provision for reading in atomic symbols containing arbitrary characters. -``` This is done by punching the form $$dsd, where s is any string of up to 30 characters, and d is any character not contained in the string s. Only the string s is used in forming the print name of the atomic symbol; d and the dollar signs will not appear when the atomic symbol is printed out. -``` Examples Input will print as @@ -4028,8 +3905,8 @@ prinl is a pseudo-function that prints its argument, which must be an atomic sym bol, and does not terminate the print line (unless it is full). terpri prints what is left in the print buffer, and then clears it. -``` -Characters and Character Objects +### Characters and Character Objects + Each of the sixty-four 6-bit binary numbers corresponds to a BCD character, if we include illegal characters. Therefore, in order to manipulate these characters via LISP functions, each of them has a corresponding object. Of the 64 characters, 48 corre- @@ -4043,8 +3920,6 @@ to Z. Each letter is a legitimate atomic symbol, and therefore may be referred t a straightforward way, without ambiguity. The second group of legal characters consists of the digits from 0 to 9. These must be handled with some care because if a digit is considered as an ordinary integer -``` - rather than a character a new nonunique object will be created corresponding to it, and this object will not be the same as the character object for the same digit, even though it has the same print name. Since the character-handling programs depend on the char- @@ -4077,9 +3952,8 @@ STAR BLANK EQSIGN -``` + ) -``` i @@ -4095,12 +3969,9 @@ Each example consists of a doublet for evalquote followed by the result. Examples -``` EVAL (DOLLAR NIL) value is " $ EVAL ((PRINT PERIOD) NIL) value is ". and If. is also printed. -``` -``` The remaining characters are all illegal as far as the key punch is concerned. The two characters corresponding to 12 and 72 have been reserved for end-of-file and end- of-record, respectively, The end-of-file character has print name $EOF$ and the end- @@ -4115,9 +3986,9 @@ sponding object or conversely by a single addition or subtraction. This speeds u character-handling considerably, because it isn't necessary to search property lists of character objects for their print names; the names may be deduced from the object locations. -``` -Packing and Unpacking Characters +### Packing and Unpacking Characters + When a sequence of characters is to be made into either a print name or a numerical object, the characters must be put one by one into a buffer called BOFFO. BOFFO is used to store the characters until they are to be combined. It is not available explicitly @@ -4152,12 +4023,10 @@ whose print name is in BOFFO. 6. h~tern[~name] : SUBR pseudo-function This function has as argument a pointer to a PNAME type structure such as - -``` Its value is the atomic symbol having this print name. If it does not already exist, then a new atomic symbol will be created. -``` -The Character-Classifying Predicates +### The Character-Classifying Predicates *. - liter [c]: SUBR predicate * liter has as argument a character object. Its value is T if the character @@ -4176,7 +4045,7 @@ lently. - dash has as argument a character object. Its value is T if the character is either an 11-punch minus or an 84 punch minus, and F otherwise. -The Character -Reading Functions +### The Character -Reading Functions The character-reading functions make it possible to read characters one by one from input. @@ -4189,14 +4058,12 @@ CURCHAR. There are three functions which affect the value of CURCHAR: startread is a function of no arguments which causes a new card to be read. The value of startread is the first character on that card, or more precisely, -``` the object corresponding to the first character on the card. If an end-of-file condition exists, the value of startread is $EOF$. The value of CURCHAR becomes the same as the output of startread, and the value of CHARCOUNT becomes 1. Both CURCHAR and CHARCOUNT are undefined until a startread is performed. A startread may be performed before the current card has been completely read. -``` 2. advance [ 1: SUBR pseudo -function advance is a function of no arguments which causes the next character to be @@ -4217,7 +4084,6 @@ completely read. Diagnostic Function -``` error 1 [ 1: SUBR pseudo-function errorL is a function of no arguments and has value NIL. It should be executed only while reading characters from a card (or tape). Its effect is to mark the char- @@ -4235,105 +4101,79 @@ the current card has been completed will cause the error1 printout to be lost. T card is considered to have been completed when CURCHAR has been set to $EOR$. Successive endreads will cause the error l printout to be reprinted. Any number of characters in a given line may be marked by error1. -``` -``` -APPENDIX G -``` +## APPENDIX G : MEMORY ALLOCATION AND THE GARBAGE COLLECTOR -``` -MEMORY ALLOCATION AND THE GARBAGE COLLECTOR -``` - -``` The following diagram shows the way in which space is allocated in the LISP System. -``` -``` Loader LAP -``` -``` Compiler -``` -``` Free Storage -``` -``` Full Words -``` -``` Push-Down List -``` -``` Binary Program Space -``` -``` Interpreter, I/O, Read Print, Arithmetic, Overlord, Garbage Collector, and other system coding -``` The addresses in this chart are only approximate. The available space is divided among binary program space, push-down list, full-word space, and free-storage space as specified on the SIZE card when the system is made. + When the compiler and LAP are not to be used again, they may be eliminated by executing the pseudo-function excise. This part of the memory is then converted into free storage. + Free storage is the area in the computer where list structures are stored. This includes the property lists of atomic symbols, the definitions of all EXPRts and FEXPR1s, evalquote doublets waiting to be executed, APVALts, and partial results of the computation that is in progress. -Full-word space is filled with the BCD characters of PNAMEts, the actual numbers +Full-word space is filled with the BCD characters of PNAMEts, the actual numbers of numerical atomic structures, and the TXL words of SUBRtsB FSUBRts, and SYMts. All available words in the free-storage area that are not in use are strung together in one long list called the free-storage list. Every time a word is needed (for example, by s) the first word on the free-storage list is used, and the free-storage list is set to & of what it formerly was. + Full-word space is handled in the same way. No use is made of consecutive storage -in either of these areas of memory. They are both scrambled. ' +in either of these areas of memory. They are both scrambled. + When either of these lists is exhausted in the middle of a computation, the garbage collector is called automatically. Unless the computation is too large for the system, there are many words in free storage and full-word space that are no longer needed. The garbage collector locates these by marking those words that are needed. In free storage, the sign bit is used for marking. In full-word space, there is no room in the word itself. Marking is done in a bit table which is next to full-word space. + Since it is important that all needed lists be marked, the garbage collector starts marking from several base positions including the following: -1. The object list that includes all atomic symbols except numbers and generated -names. This protects the atomic symbols, and all S-expressions that hang on the prop- -erty lists of atomic symbols. -2. The portion of the push-down list that is currently being used. This protects -partial results of the computation that is in progress. -3. The temlis, which is a list of registers scattered throughout the memory where -binary programs store list structures that must be protected. +1. The object list that includes all atomic symbols except numbers and generated names. This protects the atomic symbols, and all S-expressions that hang on the property lists of atomic symbols. +2. The portion of the push-down list that is currently being used. This protects partial results of the computation that is in progress. +3. The temlis, which is a list of registers scattered throughout the memory where binary programs store list structures that must be protected. + Marking proceeds as follows. If the cell is in full-word space, then the bit table is marked. If the cell is in free storage, then the sign is set minus, and car and & of the cell are marked. If the cell is anywhere else, then nothing is done. After the marking is done, the new available word lists are made by stringing all unmarked words together. Finally, the signs in free storage are set plus. + A garbage collection takes approximately 1 second on the IBM 7090 computer. It can be recognized by the stationary pattern of the MQ lights. Any trap that prevents completion of a garbage collection will create a panic condition in memory from which there is no recovery. -``` -APPENDIX H -``` - -``` -RECURSION AND THE PUSH-DOWN LIST -``` +## APPENDIX H : RECURSION AND THE PUSH-DOWN LIST One of the most powerful resources of the LISP language is its ability to accept function definitions that make use of the very function that is being defined. This may @@ -4341,75 +4181,74 @@ come about either directly by using the name of the function, or indirectly thro chain of function definitions that eventually return to the original ones. A definition of this type is called recursive. Its power lies in its ability to define an algorithm in terms of itself. + A recursive definition always has the possibility of not terminating and of being infinitely regressive. Some recursive definitions may terminate when given certain inputs and not terminate for others. It is theoretically impossible to determine whether a definition will terminate in the general case; however, it is often possible to show that particular cases will or will not terminate. + LISP is designed in such a way that all functions for which the possibility of recursion can exist are in fact recursive. This requires that all temporary stored results related to the computation that is in progress be set aside when a piece of coding is to be used recursively, and that they be later restored. This is done autorrlatically and need not be programmed explicitly. + All saving of temporary results in LISP is performed on a linear block of storage called the push-down list. Each set of stored data that is moved onto the push-down list is in a block labeled with its size and the name of the subroutine from which it came. Since it is in the nature of recursion that the first block to be saved is always the last block to be restored, it is possible to keep the push-down list compact. + The frontier of the push-down list can always be found by referring to the cell CPPI. The decrement of this cell contains the complementary address of the first available unused location on the push-down list. Index register 1 also contains this quantity, except during certain nonrecursive subroutines; in these last cases it must be restored upon leaving these routines. + There are two types of blocks to be found on the push-down list, those put there by -SAVE, and those put there by *MOVE. SAVE blocks are moved from fixed locations +SAVE, and those put there by MOVE. SAVE blocks are moved from fixed locations in certain subroutines onto the push-down list, and then moved back to the place where they came from by UNSAVE. Each block contains parameters that tell UNSAVE how many words are to be moved, and where they are to be moved to. + Functions compiled by the LISP compiler do not make use of storage cells located near the actual programming. All data are stored directly on the push-down list and -referenced by using index register 1.*MOVE is used to update CPPI and index regis- +referenced by using index register 1. MOVE is used to update CPPI and index regis- ter 1, to place the arguments on the push-down list, and to set up the parameters for the push-down block. -Because pointers to list structures are normally stored on the push-down list, the +Because pointers to list structures are normally stored on the push-down list, the garbage collector must mark the currently active portion of the push-down list during a garbage collection. Sometimes quantities are placed on the push- down list which should not be marked. In this case, the sign bit must be negative. Cells on the active portion of the push-down list having a negative sign bit will not be marked. + When an error occurs, an examination of the push-down list is an excellent indica- tion of what was occurring at the time of the error. Since each block on the push-down list has the name of the function to which it belongs, it is possible to form a list of these names. This is called the backtrace, and is normally printed out after error diagnostics. -``` -APPENDIX I -``` +## APPENDIX I : LISP FOR SHARE DISTRIBUTION -``` -LISP FOR SHARE DISTRIBUTION -``` - -``` The Artificial Intelligence Project at Stanford University has produced a version of LISP 1.5 to be distributed by SHARE. In the middle of February 1965 the system is complete and is available from Stanford. The system should be available from SHARE by the end of March 1965. + SHARE LISP differs somewhat from the LISP 1.5 system described in the LISP 1.5 Programmer's Manual, but only in (generally) inessential details. It is hoped that the changes will be widely hailed as improvements. -``` -``` -Verbos and the Garbage Collector +### Verbos and the Garbage Collector + The garbage collector now prints its message in a single-spaced format; thus, the amount of paper generated by a program with many constes is somewhat less than for- merly. Furthermore, the garbage collector printout may be suspended by executing "VERBOS(N1L)"; and the printout may be reinstated by executing flVERBOS(*T*)tI. -``` -Flap Trap +### Flap Trap Every now and then a state of affairs known as floating-point trap occurs - this re- sults when a floating-point arithmetic instruction generates a number whose exponent @@ -4421,7 +4260,7 @@ stores a floating -point zero in the accumulator when an underflow occurs. (Ther has, as yet, been no request to have "infinityIt stored in the accumulator when an overflow occurs.) -Time +### Time The new system prints the time upon entering and leaving evalquote. In fact, two times are printed, but in a neat, concise, impersonal manner which, it is felt, is @@ -4442,11 +4281,10 @@ prints (again in the evalquote time printout format) the time since the last execution of "TIME()" and the time since the last execution of "TIMEl()". The use of the time and time1 functions has no effect on the times recorded by evalquote. -``` -Lap and Symtab +### Lap and Symtab + Heretofore, lap has not only returned the symbol table as its value but has printed it out as well. This phenomenon is familiar to those who have much at all to do with -``` -- lap or the compiler. The lap in the new system always prints the function name and the octal location in which the first word of the assembled function is stored. (If the @@ -4457,57 +4295,54 @@ The value of - lap is still the symbol table, but the printing of the symbol tab be suspended by executing llSYMTAB(NIL)lt; and the printing may be restored by ex- ecuting uSYMTAB(*T*)ll. -``` -Non -Printing Compiler +### Non-Printing Compiler + The problem of the verbosity of the compiler is only slightly abated by the symtab function. The remainder of the trouble may be cured by executing "LISTING(NIL)ll. This turns off the printout of the lap code generated by the compiler. And, of course, the printout may be reinstated by executing llLISTING(*T*)ll. Thus, for a perfectly quiet compilation (except for the origin printout by lap), one need only execute I1SYMTAB(NIL)l1 and LISTING(NIL)I1 before compiling. -``` -``` -Tracecount (Alarm-Clock Trace) +### Tracecount (Alarm-Clock Trace) + The trace feature of LISP is quite useful; but, with very little encouragement, it can be made to generate wastebaskets full of useless output. Often a programmer will find that his output (without tracing) consists of many lines of garbage collector printout, an error message, and a few cryptic remarks concerning the condition of the push-down list at the time the error occurred. In such a situation, one wishes he could begin tracing only a short time before the occurrence of the error. The -tracecount function permits exactly this. " TRACECOUNT(X)~~ causes the tracing +tracecount function permits exactly this. " TRACECOUNT(X) causes the tracing (of those functions designated to be traced by the trace function call) to begin after x number of function entrances. Furthermore, when the tracecount mecha- nism has been activated, by execution of ltTRACECOUNT(x)ll, some of the blank space in the garbage collector printout will be used to output the number of function entrances which have taken place up to the time of the garbage collection; each time -``` - -``` the arguments or value of a traced function are printed the number of function en- trances will be printed; and if an error occurs, the number of function entrances ac- complished before the error will be printed. + The tracecount feature (or alarm-clock trace, as it is called by Marvin Minsky of M. I. T.) enables a programmer to run a job (preceding the program by "TRACE- COUNT(O)It), estimate the number of function entrances that occur before the pro- gram generates an error condition or a wrong answer, and then run the job again, tracing only the pertinent portion of the execution. -``` -``` -Space and Eject +### Space and Eject + A small amount of additional control over the form of the data printed by LISP has been provided in the space and eject functions. + ttSPACE(*T*)tt causes all output to be double-spaced. nSPACE(NIL)u restores the spacing to its original status; in particular, the output of the print routine reverts to single -spacing, and the "END OF EVALQUOTE OPERATORnt printout again ejects the page before printing. + "EJECT()tt causes a blank line with a carriage control character of 1 to be printed on the output tape. The result is a skip to the top of the next page of output. -``` -``` -Untime +### Untime + This routine is not available to the programmer, but its mention here may prevent some anxiety. In the event that the program time estimate is exceeded during system I/O, using the old system, one finds himself in the position of having part of one sys- @@ -4520,19 +4355,15 @@ write operation (in a machine with a millisecond core clock this is the case - m chines with 1/60 second core clocks add 50 seconds, but this is easily changed). A clock trap that would normally have occurred during the execution of the read or write will be executed before the I/O operation takes place. -``` -``` -Tape +### Tape + A few programmers with very large programs have long bemoaned the inability of LISP to communicate between different systems. The functions tape, -- rewind, -``` - -- mprht, mread, and backspace have been designed to alleviate this difficulty. ttTAPE(s)tt, where s is a list, allows the user to specify up to ten scratch tapes; if more than ten are specified, only the first ten are used. The value of tape is its argument. The initial tape settings are, from one to ten, A4, A5, A6, A7, A8, B2, - B3, B4, B5, B6. The tapes must be specified by the octal number that occurs in the address portion of a machine-language instruction to rewind that tape; that is, a four- digit octal number is required - the first (high-order) digit is a 1 if channel A is de- @@ -4542,16 +4373,15 @@ one, two, and three are to be tapes A4, B1, and A5, respectively, execute "TAPE ((1204Q 2201Q 1205Q))I1. Only the low-order fifteen bits of the numbers in +he tape list are used by the tape routines, so it is possible to use decimal integers or floating- point numb.ers in the tape list without generating errors. -. -Rewind +### Rewind llREWIND(x)w rewinds scratch tape x, as specified in the most recently exe- cuted tape function. For example, if the last tape function executed was 'ITAPE ((1 204Q 2201Q))n, then wREWIND(2)11 will cause tape B1 to be rewound. The value of rewind is NIL. -Mprint +### Mprint "MPRINT(x s)I1 prints the S-expression s on scratch tape x. The format of the output is identical to the normal LISP output, except that sequence numbers are @@ -4560,12 +4390,12 @@ printed in the rightmost eight columns of the output line and the output line is is suitable for punching or being read by mread. The value of mprint is the list printed. -Mread +### Mread NMREAD(x)lq reads one S-expression from scratch tape x. The value of mread is the S-expression read. -Backspace +### Backspace llBACKSPACE(x)" causes scratch tape x to be backspaced one record. Cau- tion in the use of this function is recommended, for if an S-expression to be read @@ -4573,13 +4403,13 @@ from tape contains more than 72 characters, then it will occupy more than one re on the tape, and single backspace will not move the tape all the way back to the be- ginning of the S-expression. The value of backspace is NIL. -Evalquote +### Evalquote Evalquote is available to the programmer as a LISP function - thus, one may now write I1(EVALQUOTE APPEND ((A)(B C D)))I1, rather than "(EVAL (QUOTE (APPEND (A)(B C D))) NIL)", should one desire to do so. -Bac ktrace +### Backtrace This function was copied (not quite literally) from M. I. T.'s LISP system on the time-shared 7094. Backtrace is a function of no arguments in which the manner of @@ -4589,29 +4419,30 @@ rors occur. Thereafter, the value of "BACKTRACE NILu is the backtrace for the most recent error; and "BACKTRACE xtl, for x not NIL, restores the backtrace printout to the error routine. Backtrace should always be evaluated by evalquote. -Read-In Errors +### Read-In Errors + A common cause of free-storage or circular list printouts is an error (in paren- thesis count, usually) during the initial read-in of a packet. The new system causes the accumulator to be cleared if an error occurs during the initial read-in, so that the contents of the accumulator are printed as ttNIL1t. -Obkeep +### Obkeep Anyone desperate for a few more words of free storage may make up a list, s, of all atom names he wants to retain in his personal LISP systems, then execute (in a SET packet) "OBKEEP(s)". All atoms except those which are members of s will be eliminated from the object list. -``` -Reserved +### Reserved + "RESERVED NILtt prints the names of the atoms on the object list in alphabetical order, along with the indicators (not alphabetized, and flags may be missed) on their property lists. This function should help to solve some of the problems that arise involving mysterious behavior of compiled functions that worked fine when inter- preted. -``` -Gensym and Symnam +### Gensym and Symnam + Gensym now omits leading zeroes from the numeric portions of the print-names of the symbols it generates; thus, what once looked like ltGOOOO1tt now prints as ltGln. Furthermore, it is possible to specify a heading word of from zero to six characters @@ -4637,20 +4468,17 @@ step s until u do begin dl... dn endN. The value of the for statement is the val of dn the last time it was evaluated. The final value of index is available outside the for function because cset is used to set the index. -Sublis +### Sublis Sublis has been re-hand-compiled so that it behaves as if it were defined as fol- lows : -###### null[x] - e - -###### eq[caar[x];e] - cdar[x] - -###### T - subb[cdr[x]] - ``` +null[x] -> e + eq[caar[x];e] -> cdar[x] +T -> subb[cdr[x]] + 111~~1; -``` ###### T - @@ -4663,18 +4491,13 @@ T -. cons[u;v] The differences between the new sublis and the old one, as far as the programmer is concerned, are that the new model is faster and the result shares as much storage as possible with e. -``` -``` + Characteristics of the System -``` -``` The set-up deck supplied with the SHARE LISP system produces a system tape with the following properties: -``` -``` Size (in words) - Binary Program Space 14000 octal Push-Down List 5000 octal @@ -4685,11 +4508,9 @@ System Temporary Tape (SYSTMP) B6 System Input Tape (SYSPIT) A2 System Output Tape (SYSPOT) A3 System Punch Tape (SYSPPT) A3 -``` The console switches may be used to obtain special results: -``` SW1 on for LISP input from on-line card reader SW2 has no effect SW3 on for LISP output on on-line printer @@ -4697,7 +4518,6 @@ SW4 has no effect SW5 on to suppress SYSPOT output SW6 on to return to overlord after accumulator printout resulting from error *I? 5*. SW6 off for error printout. -``` ## Index diff --git a/resources/mexpr/properties.mexpr.lsp b/resources/mexpr/properties.mexpr.lsp new file mode 100644 index 0000000..e69de29 From 64a27be8e580dc40204763b2e1676807d817d56c Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 4 Apr 2023 21:20:29 +0100 Subject: [PATCH 3/7] That's probably the new property list functions done... but too tired to test! --- doc/lisp1.5.md | 370 ++++++++++++++++++---------------- resources/lisp1.5.lsp | 9 +- src/beowulf/host.clj | 112 +++++++--- src/beowulf/reader/parser.clj | 3 +- 4 files changed, 284 insertions(+), 210 deletions(-) diff --git a/doc/lisp1.5.md b/doc/lisp1.5.md index 8bc052d..f4b0946 100644 --- a/doc/lisp1.5.md +++ b/doc/lisp1.5.md @@ -331,7 +331,7 @@ it is not A. The main application of conditional expressions is in defining functions recursively. -#### Example +#### Example - recursive function `ff[x] = [atom[x] -> x; T -> ff[car[x]]]` @@ -373,7 +373,7 @@ The conditional expression is useful for defining numerical computations, as wel ``` |x| = [x<0 -> -x; T -> x] ``` -The factorial of a nonhnegative integer can be defined by +The factorial of a non-negative integer can be defined by ``` n! = [n=0 -> 1; T -> n.[n-l]!] ``` @@ -420,7 +420,7 @@ Using the lambda notation, we can write `ff =` λ`[x] = [atom[x] -> x; T -> ff[car[x]]]` -The equality sign in these identities is actually not part of the LISP meta-languageand is only a crutch until we develop the correct notation. The right side of the last equation cannot serve as an expression for the function `ff` because there is nothing to indicate that the occurrence of `ff` inside it stands for the function that is being defined. +The equality sign in these identities is actually not part of the LISP meta-language and is only a crutch until we develop the correct notation. The right side of the last equation cannot serve as an expression for the function `ff` because there is nothing to indicate that the occurrence of `ff` inside it stands for the function that is being defined. In order to be able to write expressions that bear their own name, we introduce the label notation. If ε is an expression, and α is its name, we write label[α; ε]. @@ -569,26 +569,32 @@ This can be translated into the following S-expression: , ((QUOTE T)(QUOTE F))))) ``` -- sub st[^;^; z] - This function gives the result of substituting the S-expression x for all occurrences - of the atomic symbol y in the S-expression z. It is defined by +#### subst[x; y; z] -###### s~bst[x;~;z] = [eq~al[~;z] -- x;atom[z] - z;T - cons[subst +This function gives the result of substituting the S-expression x for all occurrences of the atomic symbol y in the S-expression z. It is defined by ``` -[x; y; car[z]]; subst[x;y; cdr[z]]]] +subst[x; y; z] = [equal[y; z] -> x; + atom[z] - z; + T - cons[subst + [x; y; car[z]]; subst[x; y; cdr[z]]]] ``` As an example, we have -SU~S~[(X. A);B;((A. B). c)] = ((A. (X. A)). C) -null[x] -This predicate is useful for deciding when a list is exhausted. It is true if and -only if its argument is NIL. +```lisp +SUBST[(X . A); B; ((A . B) . c)] = ((A . (X . A)) . C) +``` + +#### null[x] +This predicate is useful for deciding when a list is exhausted. It is true if and only if its argument is NIL. + The following functions are useful when S-expressions are regarded as lists. -1. append[x; y] -append[x; y] = [n~ll[x]-~; T-cons[car [x]; append[cdr [x]; y I]] +#### 1. append[x; y] +``` +append[x; y] = [null[x] -> y; T -> cons[car [x]; append[cdr [x]; y]]] +``` An example is @@ -596,175 +602,193 @@ An example is append[(A B);(C D E)] = (A B C D E) ``` -2. member[^;^] -This predicate is true if the S-expression x occurs among the elements of the -list y. We have -memberlx; y] = [null[y ]--F; -equal[x; car [y ]I--T; -T-member [x; cdr [y I]] +#### 2. member[x; y] -This function gives the list of pairs of corresponding elements of the lists x and -y, and appends this to the list a. The resultant list of pairs, which is like a table with +This predicate is true if the S-expression `x` occurs among the elements of the list `y`. We have +``` +member[x; y] = [null[y] -> F; + equal[x; car [y ]] ->T; + T -> member[x; cdr [y ]]] +``` + +#### 3. pairlis[x; y; a] + +page 12 + +This function gives the list of pairs of corresponding elements of the lists `x` and +`y`, and appends this to the list `a`. The resultant list of pairs, which is like a table with two columns, is called an association list. We have ``` -pairlis [x; y; a] = [null[x]--a; T-cons[cons[car[x]; car[y]]; -pairlis[cdr[x]; cdr [y]; a]]] +pairlis [x; y; a] = [null[x] -> a; + T -> cons[cons[car[x]; car[y]]; + pairlis[cdr[x]; cdr [y]; a]]] ``` An example is ``` -pairlis[(A B C);(U V w);((D. X) (E. Y))] = -((A. U) (B. V) (C. W)(D. X) (E. Y)) +pairlis[(A B C);(U V W);((D . X) (E . Y))] = +((A . U) (B . V) (C . W)(D . X) (E . Y)) ``` -4. assoc[x; a] -If a is an association list such as the one formed by pairlis in the above example, -then assoc will produce the first pair whose first term is x. Thus it is a table searching +#### 4. assoc[x; a] + +If `a` is an association list such as the one formed by `pairlis` in the above example, +then `assoc` will produce the first pair whose first term is `x`. Thus it is a table searching function. We have +``` +assoc[x; a] = [equal[caar[a]; x] -> car[a]; T -> assoc[x; cdr[a]]] +``` + An example is ``` -assoc[~;((A. (M N)), (B. (CAR X)), (C. (QUOTE M)), (C. (CDR x)))] -= (B. (CAR x)) +assoc[B; ((A . (M N)), (B . (CAR X)), (C . (QUOTE M)), (C . (CDR x)))] += (B . (CAR X)) ``` -5. sublisla; y] -Here a is assumed to be an association list of the form ((ul. vl)... (un. v,)), -where the u1 s are atomic, and y is any S-expression. What sublis does, is to treat -the u1 s as variables when they occur in y, and to substitute the corresponding v1 s -from the pair list. In order to define sublis, we first define an auxiliary function. -We have -sub2[a; z] = [null[a]+z;eq[caar[a]; z]-cdar[a];~- -sub%[cdr[a]; z]] +##### 5. sublis[a; y] + +Here `a` is assumed to be an association list of the form ((u1. v1)... (un . vn)), +where the `u`s are atomic, and `y` is any S-expression. What `sublis` does, is to treat +the `u`s as variables when they occur in `y`, and to substitute the corresponding `v`s +from the pair list. In order to define `sublis`, we first define an auxiliary function. We have + +``` +sub2[a; z] = [null[a] -> z; eq[caar[a]; z] -> cdar[a]; + T -> sub2[cdr[a]; z]] +``` and -sublis[a; y] = [at0rn[~]-sub2[a;~]; T-cons[sublis[a; car[^]]; -sublis[a; cdr [Y]]]] + +``` +sublis[a; y] = [atom[y] -> sub2[a; y]; + T -> cons[sublis[a; car[y]]; sublis[a; cdr[y]]]] +``` + An example is + +``` sublis[((X. SHAKESPEARE) (Y. (THE TEMPEST)));(X WROTE Y)] = (SHAKESPEARE WROTE (THE TEMPEST)) -The universal function evalquote that is about to be defined obeys the following -identity. Let f be a function written as an M-expression, and let fn be its translation. -(& is an S-expression. ) Let f be a function of n arguments and let args=(argl... -argn), a list of the n S-expressions being used as arguments. Then - ``` + +The universal function `evalquote` that is about to be defined obeys the following identity. Let `f` be a function written as an M-expression, and let `fn` be its translation. (`fn` is an S-expression. ) Let `f` be a function of n arguments and let args=(arg1... argn), a list of the `n` S-expressions being used as arguments. Then + +`evalquote[fn; args] = f[arg`1`... arg`n`]` + +page 13 + if either side of the equation is defined at all. + Example -fi ~[[x;~];cons[car[x];y]] -fn: (LAMBDA (X Y) (CONS (CAR X) Y)) -argl: (A B) -arg2: (C D) -args: ((A B) (C D)) -evalquote[(LAMBDA (X Y) (CONS (CAR X) Y)); ((A B) (C D))] = -~[[x;y];cons[car[x];y]][(A B);(C Dl]= -(A C D) -evalquote is defined by using two main functions, called eval and apply. apply -handles a function and its arguments, while eval handles forms. Each of these func- -tions also has another argument that is used as an association list for storing the val- -ues of bound variables and f unction names. + +| | | +| --------------- | -------------------------------- | +| f | λ[[x; y];cons[car[x]; y]] | +| fn | (LAMBDA (X Y) (CONS (CAR X) Y)) | +| arg1 | (A B) | +| arg2 | (C D) | +| args | ((A B) (C D)) | + +`evalquote[(LAMBDA (X Y) (CONS (CAR X) Y)); ((A B) (C D))] =` +λ`[[x;y];cons[car[x];y]][(A B);(C D)] =` +`(A C D)` + +`evalquote` is defined by using two main functions, called `eval` and `apply`. `apply` handles a function and its arguments, while `eval` handles forms. Each of these functions also has another argument that is used as an association list for storing the values of bound variables and function names. + +*note here that the environment -- the combination of the object list and the pushdown list -- is said to be an assoc list, where, importantly, it isn't. Of course, for the simplest possible Lisp, it would be -- But (to my surprise) Lisp 1.5 is nothing like the simplest possible Lisp.* + +```mexpr +evalquote[fn; x] = apply[fn; x; NIL] ``` -``` where -apply [fn;x; a] = +```mexpr +apply[fn; x; a] = + [atom[fn] -> [eq[fn; CAR] -> caar[x] + eq[fn; CDR] -> cdar[x]; + eq[fn; CONS] -> cons[car[x]; cadr[x]]; + eq[fn; ATOM] -> atom[car[x]]; + eq[fn; EQ] -> eq[car[x]; cadr[x]]; + T -> apply[eval[fn; a]; x; a]] + eq[car[fn]; LAMBDA] -> eval[caddr[fn]; pairlis[cadr[fn]; x; a]]; + eq[car[fn]; LABEL] -> apply[caddr [fn]; x; cons[cons[cadr [fn]; + caddr[fn]]; a]]] + +eval[e;a] = [atom[e] -> cdr[assoc[e;a]]; + atom[car[e]] -> [eq[car[e]; QUOTE] -> cadr[e]; + eq[car[e]; COND] -> evcon[cdr[e]; a]; + T -> apply[car[e]; evlis[cdr[el; a]; a]]; + T -> apply[car[e]; evlis [cdr[e]; a]; a]] ``` -##### [atom[fn] - [eq[fn;~~~] - caar[x] +`pairlis` and `assoc` have been previously defined. -``` -eq[fn;~~~] -- cdar[x]; -eq[fn; CONS] -- cons[car[x]; cadr[x]]; -eq[fn;~~~~] -- atom[car[x]]; -eq[fn; EQ] - eq[car[x]; cadr[x]]; +```mexpr +evcon[c; a] = [eval[caar[c]; a] -> eval[cadar[c]; a]; + T -> evcon[cdr [c];a]] ``` -###### T - apply[eval[fn;a];x;a]] - -eq[car[fn]; LAMBDA] -- eval[caddr [fn]; pairlis[cadr[fn];x;a]]; - -###### eq[car [fn]; LABEL] - apply [caddr [fn]; x; cons [cons[cadr [fn] - -``` -c addr [f n]]; a]]] -eval[e;a] = [atom[e] - cdr[assoc[e;a]]; -``` - -###### atom[car[e]] - - -``` -[eq[car QUOTE] - cadr [el; -eq[car[e]; COND] - evcon[cdr [el; a]; -T -- apply[car [el; evlis[cdr [el; a]; a]]; -T - apply[car [el; evlis [cdr [el; a]; a]] -``` - -pairlis and assoc have been previously defined. - -``` -evcon[c; a] = [eval[caar [c]; a] -- eval[cadar [c]; a]; -T -- evcon[cdr [c];a]] and + +```mexpr +evlis[m; a] = [null[m] -> NIL; + T -> cons [eval[car [m];a];evlis[cdr [m];a]]] ``` -###### evlis[m;a] = [null[m] - NIL - -##### T - cons [eval[car [m];a];evlis[cdr [m];a]]] +page 14 We shall explain a number of points about these definitions. -The first argument for - apply is a function. If it is an atomic symbol, then there -are two possibilities. One is that it is an elementary function: car, cdr, cons, eq, -or atom. In each case, the appropriate function is applied to the argument(s). If it is -not one of these, then its meaning has to be looked up in the association list. -If it begins with LAMBDA, then the arguments are paired with the bound variables, -and the form is given to -1 to evaluate. -If it begins with LABEL, then the function name and definition are added to the as- -sociation list, and the inside function is evaluated by apply. -The first argument of is a form. If it is atomic, then it must be a variable, -and its value is looked up on the association list. -If =of the form is QUOTE, then it is a constant, and the value is cadr of the form -itself. -If car of the form is CGND, then it is a conditional expression, and evcon evaluates -the propositional terms in order, and choses the form following the first true predicate. -In all other cases, the form must be a function followed by its arguments. The ar- -guments are then evaluated, and the function is given to apply. -The LISP Programming System has many added features that have not been de- -scribed thus far. These will be treated hereafter. At this point, it is worth noting the -following points. -1. In the pure theory of LISP, all functions other than the five basic ones need to -be defined each time they are to be used. This is unworkable in a practical sense. -The LISP programming system has a larger stock of built-in functions known to the in- -terpreter, and provision for adding as many more as the programmer cares to define. -2. The basic functions car. and cdr were said to be undefined for atomic arguments. -In the system, they always have a value, although it may not always be meaningful. -Similarly, the basic predicate eq - always has a value. The effects of these functions +The first argument for `apply` is a function. If it is an atomic symbol, then there are two possibilities. One is that it is an elementary function: `car`, `cdr`, `cons`, `eq`, or `atom`. In each case, the appropriate function is applied to the argument(s). If it is not one of these, then its meaning has to be looked up in the association list. + +If it begins with `LAMBDA`, then the arguments are paired with the bound variables, and the form is given to `eval` to evaluate. + +If it begins with `LABEL`, then the function name and definition are added to the as- +sociation list, and the inside function is evaluated by apply. + +The first argument of `eval` is a form. If it is atomic, then it must be a variable, and its value is looked up on the association list. + +If `car` of the form is `QUOTE`, then it is a constant, and the value is `cadr` of the form +itself. + +If `car` of the form is `COND`, then it is a conditional expression, and `evcon` evaluates +the propositional terms in order, and choses the form following the first true predicate. + +In all other cases, the form must be a function followed by its arguments. The arguments are then evaluated, and the function is given to apply. + +The LISP Programming System has many added features that have not been described thus far. These will be treated hereafter. At this point, it is worth noting the following points. + +1. In the pure theory of LISP, all functions other than the five basic ones need to be defined each time they are to be used. This is unworkable in a practical sense. The LISP programming system has a larger stock of built-in functions known to the interpreter, and provision for adding as many more as the programmer cares to define. +2. The basic functions `car` and `cdr` were said to be undefined for atomic arguments. In the system, they always have a value, although it may not always be meaningful. +Similarly, the basic predicate `eq` always has a value. The effects of these functions in unusual cases will be understood after reading the chapter on list structures in the computer. -3. Except for very unusual cases, one never writes (QUOTE T) or (QUOTE F), +3. Except for very unusual cases, one never writes `(QUOTE T)` or `(QUOTE F)`, but T, and F respectively. -4. There is provision in LISP for computing with fixed and floating point numbers. -These are introduced as psuedo-atomic symbols. -The reader is warned that the definitions of apply and ~l given above are pedagogi- -cal devices and are not the same functions as those built into the LISP programming -system. Appendix B contains the computer implemented version of these functions and -should be used to decide questions about how things really work. +4. There is provision in LISP for computing with fixed and floating point numbers. These are introduced as psuedo-atomic symbols. -11. THE LISP INTERPRETER SYSTEM +The reader is warned that the definitions of `apply` and `eval` given above are pedagogical devices and are not the same functions as those built into the LISP programming system. Appendix B contains the computer implemented version of these functions and should be used to decide questions about how things really work. -The following example is a LISP program that defines three functions union, inter- -section, and member, and then applies these functions to some test cases. The functions -union and intersection are to be applied to "sets," each set being represented by a list -of atomic symbols. The functions are defined as follows. Note that they are all recur- -sive, and both union and intersection make use of member. +page 15 + +## II. THE LISP INTERPRETER SYSTEM + +The following example is a LISP program that defines three functions `union`, `intersection`, and `member`, and then applies these functions to some test cases. The functions `union` and `intersection` are to be applied to "sets," each set being represented by a list of atomic symbols. The functions are defined as follows. Note that they are all recursive, and both union and intersection make use of member. ``` -member[a;x] = [null[x]-~;e~[a;car[x]]-T;T- -member [a;cdr [x]]] -union[^;^] = [null[x]-.y;member[car[x];y]-union -[cdr [x];~]; T-cons [c ar [x];union[c dr [x];~]]] +member[a; x] = [null[x] -> F; eq[a; car[x]] -> T; + T -> member[a; cdr[x]]] + +union[x; y] = [null[x] -> y; + member[car[x];y] -> union[cdr[x]; y]; + T -> cons[car[x]; union[cdr[x]; y]]] + +intersection[x;y] = [null[x] -> NIL; + member[car[x]; y] -> cons[car[x]; intersection[cdr[x]; y]]; + T -> intersection[cdr[x]; y]] ``` To define these functions, we use the pseudo-function define. The program looks like @@ -2680,7 +2704,7 @@ The function `deflist` is a more general defining function. Its first argument i If `deflist` or `define` is used twice on the same object with the same indicator, the old value will be replaced by the new one. -#### attrib[x;e] : SUBR pseudo-function +#### attrib[x; e] : SUBR pseudo-function The function attrib concatenates its two arguments by changing the last element of its first argument to point to the second argument. Thus it is commonly used to tack something onto the end of a property list. The value of attrib is the second argument. @@ -2712,47 +2736,45 @@ cator is found, and NIL otherwise. This pseudo-function is used to create a constant by putting the indicator APVAL and a value on the property list of an atomic symbol. The first argument should be an atomic symbol; the second argument is the value is cons[val;N1~]. -``` -csetq[ob;val] - FEXPR pseudo-function +#### csetq[ob; val] : FEXPR pseudo-function + +csetq is like cset - except that it quotes its first argument instead of evaluating it. + +#### remprop[x; ind] : SUBR pseudo-function -* csetq is like cset - except that it quotes its first argument instead of evaluating it. -rempr op[x; ind] : SUBR pseudo-function f The pseudo-function remprop searches the list, x, looking for all occurrences of the indicator ind. When such an indicator is found, its name and the succeeding property are removed from the list. The two "endsn of the list are tied together as indicated by the dashed line below. -``` The value of remprop is NIL. + When an indicator appears on a property list without a property following it, then it is called a flag. An example of a flag is the indicator TRACE which informs the inter- preter that the function on whose property list it appears is to be traced. There are two pseudo-functions for creating and removing flags respectively. -``` -- flag [I; ind] EXPR pseudo-function +#### flag [I; ind] : EXPR pseudo-function -``` The pseudo-function flag puts the flag ind on the property list of every atomic symbol in the list 1. Note that d cannot be an atomic symbol, and must be a list of atomic sym- bols. The flag is always placed immediately following the first word of the property list, and the rest of the property list then follows. The value of flag is NIL. No property list ever receives a duplicated flag. -remflag[l; ind] : EXPR pseudo-function + +#### remflag[l; ind] : EXPR pseudo-function + remflag removes all occurrences of the indicator ind from the property list of each atomic symbol in the list 8. It does this by patching around the indicator with a rplacd in a manner similar to the way remprop works. -``` -``` -Table Buildinrr and Table Reference Functions -``` +### Table Building and Table Reference Functions + +#### pair [x; y] : SUBR + +The function pair has as value the list of pairs of corresponding elements of the lists x and y. The arguments x and y must be lists of the same number of elements. They should & be atomic symbols. The value is a dotted pair list, i. e. ((a (a p2)... -- pair [x; y] SUBR - The function pair has as value the list of pairs of corresponding elements of the lists - x and y. The arguments x and y must be lists of the same number of elements. They - should & be atomic symbols. The value is a dotted pair list, i. e. ((a (a p2)... pair[x;y] = [prog[u;v; m] u:= x; v:= y; @@ -2765,7 +2787,10 @@ m:= cons[cons[car[u];car[v]];m]; u:= cdr[u]; v:= cdr[v]; go[~Il -sassoc[x;y;u] SUBR functional +``` + +#### sassoc[x; y; u] : SUBR functional + The function sassoc searches y, which is a list of dotted pairs, for a pair whose first element that is x. If such a pair is found, the value of sassoc is this pair. Otherwise the function u of no arguments is taken as the value of sassoc. @@ -2781,12 +2806,12 @@ the S-expression y in the S-expression z. T .- cons[subst[x;y;car [z]];subst [x;y;cdr[e]]]] -* sublis [x ; y] SUBR +#### sublis [x ; y] : SUBR Here x is a list of pairs, -((ul vl) (u2 v2) (un vn)) -The value of sublis[x;y] is the result of substituting each v for the corresponding -u in y. +((u1 . v1) (u2 . v2) (un . vn)) +The value of `sublis[x; y]` is the result of substituting each `v` for the corresponding +`u` in `y`. Note that the following M-expression is different from that given in Section I, though the result is the same. @@ -2819,38 +2844,33 @@ nconc does not copy its first argument. * conc concatenates its arguments without copying them. Thus it changes existing list structure and is a pseudo-function. The value of conc is the resulting concatenated list. -nc - onc [x;y] SUBR pseudo-function +#### nconc [x; y] : SUBR pseudo-function -``` -The function nconc concatenates its arguments without copying the first one. The +The function `nconc` concatenates its arguments without copying the first one. The operation is identical to that of attrib except that the value is the entire result, (i. e. the modified first argument, x). + The program for nconc[x;y] has the program variable m and is as follows: +``` nconc [x; y ] = prog [[m]; +[null[x] - return[~]] ``` -##### [null[x] - ret~rn[~]] +#### COPY [X] : SUBR -* COPY [XI SUBR - -``` This function makes a copy of the list x. The value of copy is the location of the copied list. -``` -##### copy[x] = [null[x] - ~~~;atom[x] - x;T -- cons[copy[car[x]] +copy[x] = [null[x] - ~~~;atom[x] - x;T -- cons[copy[car[x]] ``` co~[cdr[xllIl ``` -``` -reverseit] SUBR -``` +#### reverse[t] : SUBR -``` This is a function to reverse the top level of a list. Thus -reverse[(^ B (C. D))] = ((C D) B A)) +reverse[(A B (C. D))] = ((C D) B A)) reverse[t] = prog[[v]; u: =t; ``` @@ -2915,7 +2935,7 @@ m:= cdr[m]; go[~oopl1 #### search[x; p; f; u] : SUBR functional - + The function search looks through a list x for an element that has the property p, and if such an element is found the function f of that element is the value of search. If there is no such element, the function u of one argument x is taken as the value of search (in this case x is, of course, NIL). Arithmetic Functions diff --git a/resources/lisp1.5.lsp b/resources/lisp1.5.lsp index 9c1bf99..038af02 100644 --- a/resources/lisp1.5.lsp +++ b/resources/lisp1.5.lsp @@ -147,4 +147,11 @@ ((ATOM Z) Z) ((QUOTE T) (CONS (SUBST X Y (CAR Z)) (SUBST X Y (CDR Z)))))) (SYSIN) - (SYSOUT) (TERPRI) (TIMES) (TRACE) (UNTRACE) (ZEROP LAMBDA (N) (EQ N 0))) + (SYSOUT) (TERPRI) (TIMES) (TRACE) + (UNION LAMBDA (X Y) + (COND + ((NULL X) Y) + ((MEMBER (CAR X) Y) (UNION (CDR X) Y)) + (T (CONS (CAR X) (UNION (CDR X) Y))))) + (UNTRACE) + (ZEROP LAMBDA (N) (EQ N 0))) diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj index dd2dd8f..05a3208 100644 --- a/src/beowulf/host.clj +++ b/src/beowulf/host.clj @@ -164,17 +164,32 @@ (number? value) (symbol? value) (= value NIL)) - (do + (try (.rplaca cell value) - cell) + cell + (catch Throwable any + (throw (ex-info + (str (.getMessage any) " in RPLACA: `") + {:cause :upstream-error + :phase :host + :function :rplaca + :args (list cell value) + :type :beowulf} + any)))) (throw (ex-info (str "Invalid value in RPLACA: `" value "` (" (type value) ")") {:cause :bad-value - :detail :rplaca}))) + :phase :host + :function :rplaca + :args (list cell value) + :type :beowulf}))) (throw (ex-info (str "Invalid cell in RPLACA: `" cell "` (" (type cell) ")") - {:cause :bad-value - :detail :rplaca})))) + {:cause :bad-cell + :phase :host + :function :rplaca + :args (list cell value) + :type :beowulf})))) (defn RPLACD "Replace the CDR pointer of this `cell` with this `value`. Dangerous, should @@ -189,17 +204,32 @@ (number? value) (symbol? value) (= value NIL)) - (do + (try (.rplacd cell value) - cell) + cell + (catch Throwable any + (throw (ex-info + (str (.getMessage any) " in RPLACD: `") + {:cause :upstream-error + :phase :host + :function :rplacd + :args (list cell value) + :type :beowulf} + any)))) (throw (ex-info (str "Invalid value in RPLACD: `" value "` (" (type value) ")") {:cause :bad-value - :detail :rplaca}))) + :phase :host + :function :rplacd + :args (list cell value) + :type :beowulf}))) (throw (ex-info (str "Invalid cell in RPLACD: `" cell "` (" (type cell) ")") - {:cause :bad-value - :detail :rplaca}))));; PLUS + {:cause :bad-cell + :phase :host + :detail :rplacd + :args (list cell value) + :type :beowulf}))));; PLUS (defn LIST [& args] @@ -394,38 +424,54 @@ (make-beowulf-list (map CAR @oblist)) NIL)) +(defn PUT + "Put this `value` as the value of the property indicated by this `indicator` + of this `symbol`. Return `value` on success. + + NOTE THAT there is no `PUT` defined in the manual, but it would have been + easy to have defined it so I don't think this fully counts as an extension." + [symbol indicator value] + (let [magic-marker (Integer/parseInt "777778" 8)] + (if-let [binding (ASSOC symbol @oblist)] + (if-let [prop (ASSOC indicator (CDDR binding))] + (RPLACD prop value) + (RPLACD binding + (make-cons-cell + magic-marker + (make-cons-cell + indicator + (make-cons-cell value (CDDR binding)))))) + (swap! + oblist + (fn [ob s p v] + (make-cons-cell + (make-beowulf-list (list s magic-marker p v)) + ob)) + symbol indicator value)))) + +(defn DEFLIST + "For each pair in this association list `a-list`, set the property with this + `indicator` of the symbol which is the first element of the pair to the + value which is the second element of the pair." + [a-list indicator] + (map + #(PUT (CAR %) indicator (CDR %)) + a-list)) + (defn DEFINE "Bootstrap-only version of `DEFINE` which, post boostrap, can be overwritten in LISP. - The single argument to `DEFINE` should be an assoc list which should be - nconc'ed onto the front of the oblist. Broadly, - (SETQ OBLIST (NCONC ARG1 OBLIST))" - [args] - (swap! - oblist - (fn [ob arg1] - (loop [cursor arg1 a arg1] - (if (= (CDR cursor) NIL) - (do - (.rplacd cursor @oblist) - (pretty-print a) - a) - (recur (CDR cursor) a)))) - (CAR args))) + The single argument to `DEFINE` should be an association list of symbols to + lambda functions" + [a-list] + (DEFLIST a-list 'EXPR)) (defn SET "Implementation of SET in Clojure. Add to the `oblist` a binding of the value of `var` to the value of `val`. NOTE WELL: this is not SETQ!" [symbol val] - (when - (swap! - oblist - (fn [ob s v] (if-let [binding (ASSOC symbol ob)] - (RPLACD binding v) - (make-cons-cell (make-cons-cell s v) ob))) - symbol val) - val)) + (PUT symbol 'APVAL val)) ;;;; TRACE and friends ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/beowulf/reader/parser.clj b/src/beowulf/reader/parser.clj index 60ff3f2..2c062c8 100644 --- a/src/beowulf/reader/parser.clj +++ b/src/beowulf/reader/parser.clj @@ -64,7 +64,8 @@ cond := lsqb (opt-space cond-clause semi-colon opt-space)* cond-clause rsqb; cond-clause := mexpr opt-space arrow opt-space mexpr opt-space; arrow := '->'; - args := mexpr | (opt-space mexpr semi-colon opt-space)* opt-space mexpr opt-space; + args := arg | (opt-space arg semi-colon opt-space)* opt-space arg opt-space; + arg := mexpr | sexpr; fn-name := mvar; mvar := #'[a-z][a-z0-9]*'; mconst := #'[A-Z][A-Z0-9]*'; From b5afb1ad442a371f5e39bb4f99b90f0ab1871bb2 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 4 Apr 2023 21:54:38 +0100 Subject: [PATCH 4/7] Actually, this isn't right (still) but too tired to continue. I'm backporting expectations from more modern Lisps onto Lisp 1.5; GET does not work the way I expect. --- src/beowulf/host.clj | 65 +++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj index 05a3208..a282a86 100644 --- a/src/beowulf/host.clj +++ b/src/beowulf/host.clj @@ -424,6 +424,10 @@ (make-beowulf-list (map CAR @oblist)) NIL)) +(def ^:private magic-marker + "The unexplained magic number which marks the start of a property list." + (Integer/parseInt "777778" 8)) + (defn PUT "Put this `value` as the value of the property indicated by this `indicator` of this `symbol`. Return `value` on success. @@ -431,28 +435,51 @@ NOTE THAT there is no `PUT` defined in the manual, but it would have been easy to have defined it so I don't think this fully counts as an extension." [symbol indicator value] - (let [magic-marker (Integer/parseInt "777778" 8)] - (if-let [binding (ASSOC symbol @oblist)] - (if-let [prop (ASSOC indicator (CDDR binding))] - (RPLACD prop value) - (RPLACD binding - (make-cons-cell - magic-marker - (make-cons-cell - indicator - (make-cons-cell value (CDDR binding)))))) - (swap! - oblist - (fn [ob s p v] - (make-cons-cell - (make-beowulf-list (list s magic-marker p v)) - ob)) - symbol indicator value)))) + (if-let [binding (ASSOC symbol @oblist)] + (if-let [prop (ASSOC indicator (CDDR binding))] + (RPLACD prop value) + (RPLACD binding + (make-cons-cell + magic-marker + (make-cons-cell + indicator + (make-cons-cell value (CDDR binding)))))) + (swap! + oblist + (fn [ob s p v] + (make-cons-cell + (make-beowulf-list (list s magic-marker p v)) + ob)) + symbol indicator value))) + +(defn GET + "From the manual: + + '`get` is somewhat like `prop`; however its value is car of the rest of + the list if the `indicator` is found, and NIL otherwise.' + + It's clear that `GET` is expected to be defined in terms of `PROP`, but + we can't implement `PROP` here because we lack `EVAL`; and we can't have + `EVAL` here because it depends on `GET`." + [symbol indicator] + (let [binding (ASSOC symbol @oblist)] + (cond + (= binding NIL) NIL + (= magic-marker (CADR binding)) (loop [b binding] + (cond (= b NIL) NIL + (= (CAR b) indicator) (CADR b) + :else (recur (CDR b)))) + :else (throw + (ex-info "Misformatted property list (missing magic marker)" + {:phase :host + :function :get + :args (list symbol indicator) + :type :beowulf}))))) (defn DEFLIST "For each pair in this association list `a-list`, set the property with this `indicator` of the symbol which is the first element of the pair to the - value which is the second element of the pair." + value which is the second element of the pair. See page 58 of the manual." [a-list indicator] (map #(PUT (CAR %) indicator (CDR %)) @@ -463,7 +490,7 @@ in LISP. The single argument to `DEFINE` should be an association list of symbols to - lambda functions" + lambda functions. See page 58 of the manual." [a-list] (DEFLIST a-list 'EXPR)) From 5b5ddb9444b7a78dc8854db94002fd5200ab650e Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 5 Apr 2023 23:35:41 +0100 Subject: [PATCH 5/7] This isn't working, but it's very close. --- doc/further_reading.md | 2 +- resources/lisp1.5.lsp | 352 ++++++++++++++++++++-------------- src/beowulf/bootstrap.clj | 266 +++---------------------- src/beowulf/host.clj | 12 +- src/beowulf/interop.clj | 129 +++++++++++++ src/beowulf/io.clj | 85 ++++++-- test/beowulf/interop_test.clj | 3 +- 7 files changed, 447 insertions(+), 402 deletions(-) create mode 100644 src/beowulf/interop.clj diff --git a/doc/further_reading.md b/doc/further_reading.md index bcf4720..9d97f5a 100644 --- a/doc/further_reading.md +++ b/doc/further_reading.md @@ -4,4 +4,4 @@ 2. [MIT AI Memo 1, John McCarthy, September 1958](https://www.softwarepreservation.org/projects/LISP/MIT/AIM-001.pdf) 3. [Lisp 1 Programmer's Manual, Phyllis Fox, March 1960](https://bitsavers.org/pdf/mit/rle_lisp/LISP_I_Programmers_Manual_Mar60.pdf) 4. [Lisp 1.5 Programmer's Manual, Michael I. Levin, August 1962](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=81) -4. [Early LISP History (1956 - 1959)](https://dl.acm.org/doi/pdf/10.1145/800055.802047#page=3) +5. [Early LISP History (1956 - 1959), Herbert Stoyan, August 1984](https://dl.acm.org/doi/pdf/10.1145/800055.802047#page=3) diff --git a/resources/lisp1.5.lsp b/resources/lisp1.5.lsp index 038af02..ddf36b2 100644 --- a/resources/lisp1.5.lsp +++ b/resources/lisp1.5.lsp @@ -1,157 +1,223 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Beowulf Sysout file generated at 2023-03-31T02:24:08.808 +;; Beowulf 0.3.0-SNAPSHOT Sysout file generated at 2023-04-05T23:30:32.954 ;; generated by simon ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -((NIL) - (T . T) - (F) - (ADD1) - (AND) - (APPEND LAMBDA - (X Y) (COND ((NULL X) Y) ((QUOTE T) (CONS (CAR X) (APPEND (CDR X) Y))))) - (APPLY) - (ASSOC LAMBDA (X L) - (COND - ((NULL L) (QUOTE NIL)) - ((AND (CONSP (CAR L)) (EQ (CAAR L) X)) (CAR L)) - ((QUOTE T) (ASSOC X (CDR L))))) - (ATOM) - (CAR) - (CAAAAR LAMBDA (X) (CAR (CAR (CAR (CAR X))))) - (CAAADR LAMBDA (X) (CAR (CAR (CAR (CDR X))))) - (CAAAR LAMBDA (X) (CAR (CAR (CAR X)))) - (CAADAR LAMBDA (X) (CAR (CAR (CDR (CAR X))))) - (CAADDR LAMBDA (X) (CAR (CAR (CDR (CDR X))))) - (CAADR LAMBDA (X) (CAR (CAR (CDR X)))) - (CAAR LAMBDA (X) (CAR (CAR X))) - (CADAAR LAMBDA (X) (CAR (CDR (CAR (CAR X))))) - (CADADR LAMBDA (X) (CAR (CDR (CAR (CDR X))))) - (CADAR LAMBDA (X) (CAR (CDR (CAR X)))) - (CADDAR LAMBDA (X) (CAR (CDR (CDR (CAR X))))) - (CADDDR LAMBDA (X) (CAR (CDR (CDR (CDR X))))) - (CADDR LAMBDA (X) (CAR (CDR (CDR X)))) - (CADR LAMBDA (X) (CAR (CDR X))) - (CDAAAR LAMBDA (X) (CDR (CAR (CAR (CAR X))))) - (CDAADR LAMBDA (X) (CDR (CAR (CAR (CDR X))))) - (CDAAR LAMBDA (X) (CDR (CAR (CAR X)))) - (CDADAR LAMBDA (X) (CDR (CAR (CDR (CAR X))))) - (CDADDR LAMBDA (X) (CDR (CAR (CDR (CDR X))))) - (CDADR LAMBDA (X) (CDR (CAR (CDR X)))) - (CDAR LAMBDA (X) (CDR (CAR X))) - (CDDAAR LAMBDA (X) (CDR (CDR (CAR (CAR X))))) - (CDDADR LAMBDA (X) (CDR (CDR (CAR (CDR X))))) - (CDDAR LAMBDA (X) (CDR (CDR (CAR X)))) - (CDDDAR LAMBDA (X) (CDR (CDR (CDR (CAR X))))) - (CDDDDR LAMBDA (X) (CDR (CDR (CDR (CDR X))))) - (CDDDR LAMBDA (X) (CDR (CDR (CDR X)))) - (CDDR LAMBDA (X) (CDR (CDR X))) - (CDR) - (CONS) - (CONSP) +((NIL 32767 APVAL NIL) + (T 32767 APVAL T) + (F 32767 APVAL NIL) + (ADD1 32767 SUBR (BEOWULF HOST ADD1)) + (AND 32767 SUBR (BEOWULF HOST AND)) + (APPEND + 32767 + EXPR + (LAMBDA + (X Y) (COND ((NULL X) Y) ((QUOTE T) (CONS (CAR X) (APPEND (CDR X) Y)))))) + (APPLY 32767 SUBR (BEOWULF BOOTSTRAP APPLY)) + (ASSOC + 32767 + EXPR + (LAMBDA + (X L) + (COND + ((NULL L) (QUOTE NIL)) + ((AND (CONSP (CAR L)) (EQ (CAAR L) X)) (CAR L)) + ((QUOTE T) (ASSOC X (CDR L))))) + SUBR (BEOWULF HOST ASSOC)) + (ATOM 32767 SUBR (BEOWULF HOST ATOM)) + (CAR 32767 SUBR (BEOWULF HOST CAR)) + (CAAAAR 32767 EXPR (LAMBDA (X) (CAR (CAR (CAR (CAR X)))))) + (CAAADR 32767 EXPR (LAMBDA (X) (CAR (CAR (CAR (CDR X)))))) + (CAAAR 32767 EXPR (LAMBDA (X) (CAR (CAR (CAR X))))) + (CAADAR 32767 EXPR (LAMBDA (X) (CAR (CAR (CDR (CAR X)))))) + (CAADDR 32767 EXPR (LAMBDA (X) (CAR (CAR (CDR (CDR X)))))) + (CAADR 32767 EXPR (LAMBDA (X) (CAR (CAR (CDR X))))) + (CAAR 32767 EXPR (LAMBDA (X) (CAR (CAR X)))) + (CADAAR 32767 EXPR (LAMBDA (X) (CAR (CDR (CAR (CAR X)))))) + (CADADR 32767 EXPR (LAMBDA (X) (CAR (CDR (CAR (CDR X)))))) + (CADAR 32767 EXPR (LAMBDA (X) (CAR (CDR (CAR X))))) + (CADDAR 32767 EXPR (LAMBDA (X) (CAR (CDR (CDR (CAR X)))))) + (CADDDR 32767 EXPR (LAMBDA (X) (CAR (CDR (CDR (CDR X)))))) + (CADDR 32767 EXPR (LAMBDA (X) (CAR (CDR (CDR X))))) + (CADR 32767 EXPR (LAMBDA (X) (CAR (CDR X)))) + (CDAAAR 32767 EXPR (LAMBDA (X) (CDR (CAR (CAR (CAR X)))))) + (CDAADR 32767 EXPR (LAMBDA (X) (CDR (CAR (CAR (CDR X)))))) + (CDAAR 32767 EXPR (LAMBDA (X) (CDR (CAR (CAR X))))) + (CDADAR 32767 EXPR (LAMBDA (X) (CDR (CAR (CDR (CAR X)))))) + (CDADDR 32767 EXPR (LAMBDA (X) (CDR (CAR (CDR (CDR X)))))) + (CDADR 32767 EXPR (LAMBDA (X) (CDR (CAR (CDR X))))) + (CDAR 32767 EXPR (LAMBDA (X) (CDR (CAR X)))) + (CDDAAR 32767 EXPR (LAMBDA (X) (CDR (CDR (CAR (CAR X)))))) + (CDDADR 32767 EXPR (LAMBDA (X) (CDR (CDR (CAR (CDR X)))))) + (CDDAR 32767 EXPR (LAMBDA (X) (CDR (CDR (CAR X))))) + (CDDDAR 32767 EXPR (LAMBDA (X) (CDR (CDR (CDR (CAR X)))))) + (CDDDDR 32767 EXPR (LAMBDA (X) (CDR (CDR (CDR (CDR X)))))) + (CDDDR 32767 EXPR (LAMBDA (X) (CDR (CDR (CDR X))))) + (CDDR 32767 EXPR (LAMBDA (X) (CDR (CDR X)))) + (CDR 32767 SUBR (BEOWULF HOST CDR)) + (CONS 32767 SUBR (BEOWULF HOST CONS)) + (CONSP 32767 SUBR (BEOWULF HOST CONSP)) (COPY - LAMBDA - (X) - (COND - ((NULL X) (QUOTE NIL)) - ((ATOM X) X) ((QUOTE T) (CONS (COPY (CAR X)) (COPY (CDR X)))))) - (DEFINE) - (DIFFERENCE) + 32767 + EXPR + (LAMBDA + (X) + (COND + ((NULL X) (QUOTE NIL)) + ((ATOM X) X) ((QUOTE T) (CONS (COPY (CAR X)) (COPY (CDR X))))))) + (DEFINE 32767 SUBR (BEOWULF HOST DEFINE)) + (DIFFERENCE 32767 SUBR (BEOWULF HOST DIFFERENCE)) (DIVIDE - LAMBDA (X Y) (CONS (QUOTIENT X Y) (CONS (REMAINDER X Y) (QUOTE NIL)))) - (DOC) - (EFFACE - LAMBDA (X L) (COND ((NULL L) (QUOTE NIL)) - ((EQUAL X (CAR L)) (CDR L)) - ((QUOTE T) (RPLACD L (EFFACE X (CDR L)))))) - (ERROR) - (EQ) - (EQUAL) - (EVAL) + 32767 + EXPR + (LAMBDA (X Y) (CONS (QUOTIENT X Y) (CONS (REMAINDER X Y) (QUOTE NIL))))) + (DOC 32767 SUBR (BEOWULF HOST DOC)) + (EFFACE + 32767 + EXPR + (LAMBDA + (X L) + (COND + ((NULL L) (QUOTE NIL)) + ((EQUAL X (CAR L)) (CDR L)) ((QUOTE T) (RPLACD L (EFFACE X (CDR L))))))) + (ERROR 32767 SUBR (BEOWULF HOST ERROR)) + (EQ 32767 SUBR (BEOWULF HOST EQ)) + (EQUAL 32767 SUBR (BEOWULF HOST EQUAL)) + (EVAL 32767 SUBR (BEOWULF BOOTSTRAP EVAL)) (FACTORIAL - LAMBDA (N) (COND ((EQ N 1) 1) (T (TIMES N (FACTORIAL (SUB1 N)))))) - (FIXP) - (GENSYM) + 32767 + EXPR (LAMBDA (N) (COND ((EQ N 1) 1) (T (TIMES N (FACTORIAL (SUB1 N))))))) + (FIXP 32767 SUBR (BEOWULF HOST FIXP)) + (GENSYM 32767 SUBR (BEOWULF HOST GENSYM)) (GET - LAMBDA - (X Y) - (COND - ((NULL X) (QUOTE NIL)) - ((EQ (CAR X) Y) (CAR (CDR X))) ((QUOTE T) (GET (CDR X) Y)))) - (GREATERP) - (INTEROP) + 32767 + EXPR + (LAMBDA + (X Y) + (COND + ((NULL X) (QUOTE NIL)) + ((EQ (CAR X) Y) (CAR (CDR X))) ((QUOTE T) (GET (CDR X) Y)))) + SUBR (BEOWULF HOST GET)) + (GREATERP 32767 SUBR (BEOWULF HOST GREATERP)) + (INTEROP 32767 SUBR (BEOWULF INTEROP INTEROP)) (INTERSECTION - LAMBDA - (X Y) - (COND - ((NULL X) (QUOTE NIL)) - ((MEMBER (CAR X) Y) (CONS (CAR X) (INTERSECTION (CDR X) Y))) - ((QUOTE T) (INTERSECTION (CDR X) Y)))) + 32767 + EXPR + (LAMBDA + (X Y) + (COND + ((NULL X) (QUOTE NIL)) + ((MEMBER (CAR X) Y) (CONS (CAR X) (INTERSECTION (CDR X) Y))) + ((QUOTE T) (INTERSECTION (CDR X) Y))))) (LENGTH - LAMBDA - (L) (COND ((EQ NIL L) 0) ((CONSP (CDR L)) (ADD1 (LENGTH (CDR L)))) (T 0))) - (LESSP) - (MAPLIST LAMBDA (L F) (COND ((NULL L) NIL) ((QUOTE T) (CONS (F (CAR L)) (MAPLIST (CDR L) F))))) + 32767 + EXPR + (LAMBDA + (L) + (COND ((EQ NIL L) 0) ((CONSP (CDR L)) (ADD1 (LENGTH (CDR L)))) (T 0)))) + (LESSP 32767 SUBR (BEOWULF HOST LESSP)) + (MAPLIST + 32767 + EXPR + (LAMBDA + (L F) + (COND + ((NULL L) NIL) ((QUOTE T) (CONS (F (CAR L)) (MAPLIST (CDR L) F)))))) (MEMBER - LAMBDA - (A X) - (COND - ((NULL X) (QUOTE F)) - ((EQ A (CAR X)) (QUOTE T)) ((QUOTE T) (MEMBER A (CDR X))))) - (MINUSP LAMBDA (X) (LESSP X 0)) - (NOT LAMBDA (X) (COND (X (QUOTE NIL)) ((QUOTE T) (QUOTE T)))) - (NULL LAMBDA (X) (COND ((EQUAL X NIL) (QUOTE T)) (T (QUOTE F)))) - (NUMBERP) - (OBLIST) - (ONEP LAMBDA (X) (EQ X 1)) + 32767 + EXPR + (LAMBDA + (A X) + (COND + ((NULL X) (QUOTE F)) + ((EQ A (CAR X)) (QUOTE T)) ((QUOTE T) (MEMBER A (CDR X)))))) + (MINUSP 32767 EXPR (LAMBDA (X) (LESSP X 0))) + (NOT 32767 EXPR (LAMBDA (X) (COND (X (QUOTE NIL)) ((QUOTE T) (QUOTE T))))) + (NULL + 32767 EXPR (LAMBDA (X) (COND ((EQUAL X NIL) (QUOTE T)) (T (QUOTE F))))) + (NUMBERP 32767 SUBR (BEOWULF HOST NUMBERP)) + (OBLIST 32767 SUBR (BEOWULF HOST OBLIST)) + (ONEP 32767 EXPR (LAMBDA (X) (EQ X 1))) (PAIR - LAMBDA - (X Y) - (COND - ((AND (NULL X) (NULL Y)) NIL) - ((NULL X) (ERROR (QUOTE F2))) - ((NULL Y) (ERROR (QUOTE F3))) - (T (CONS (CONS (CAR X) (CAR Y)) (PAIR (CDR X) (CDR Y)))))) - (PAIRLIS LAMBDA (X Y A) - (COND - ((NULL X) A) - ((QUOTE T) (CONS (CONS (CAR X) (CAR Y)) (PAIRLIS (CDR X) (CDR Y) A))))) - (PLUS) - (PRETTY) - (PRINT) + 32767 + EXPR + (LAMBDA + (X Y) + (COND + ((AND (NULL X) (NULL Y)) NIL) + ((NULL X) (ERROR (QUOTE F2))) + ((NULL Y) (ERROR (QUOTE F3))) + (T (CONS (CONS (CAR X) (CAR Y)) (PAIR (CDR X) (CDR Y))))))) + (PAIRLIS + 32767 + EXPR + (LAMBDA + (X Y A) + (COND + ((NULL X) A) + ((QUOTE T) (CONS (CONS (CAR X) (CAR Y)) (PAIRLIS (CDR X) (CDR Y) A))))) + SUBR (BEOWULF HOST PAIRLIS)) + (PLUS 32767 SUBR (BEOWULF HOST PLUS)) + (PRETTY 32767) + (PRINT 32767) (PROP - LAMBDA - (X Y U) - (COND - ((NULL X) (U)) ((EQ (CAR X) Y) (CDR X)) ((QUOTE T) (PROP (CDR X) Y U)))) - (QUOTE LAMBDA (X) X) - (QUOTIENT) - (RANGE LAMBDA (N M) (COND ((LESSP M N) (QUOTE NIL)) ((QUOTE T) (CONS N (RANGE (ADD1 N) M))))) - (READ) - (REMAINDER) + 32767 + EXPR + (LAMBDA + (X Y U) + (COND + ((NULL X) (U)) + ((EQ (CAR X) Y) (CDR X)) ((QUOTE T) (PROP (CDR X) Y U))))) + (QUOTE 32767 EXPR (LAMBDA (X) X)) + (QUOTIENT 32767 SUBR (BEOWULF HOST QUOTIENT)) + (RANGE + 32767 + EXPR + (LAMBDA + (N M) + (COND + ((LESSP M N) (QUOTE NIL)) ((QUOTE T) (CONS N (RANGE (ADD1 N) M)))))) + (READ 32767 SUBR (BEOWULF READ READ)) + (REMAINDER 32767 SUBR (BEOWULF HOST REMAINDER)) (REPEAT - LAMBDA (N X) (COND ((EQ N 0) NIL) (T (CONS X (REPEAT (SUB1 N) X))))) - (RPLACA) - (RPLACD) - (SET) - (SUB1 LAMBDA (N) (DIFFERENCE N 1)) - (SUB2 LAMBDA (A Z) - (COND - ((NULL A) Z) - ((EQ (CAAR A) Z) (CDAR A)) - ((QUOTE T) (SUB2 (CDAR A) Z)))) - (SUBLIS LAMBDA (A Y) (COND ((ATOM Y) (SUB2 A Y)) ((QUOTE T) (CONS)))) - (SUBST LAMBDA (X Y Z) - (COND - ((EQUAL Y Z) X) - ((ATOM Z) Z) - ((QUOTE T) (CONS (SUBST X Y (CAR Z)) (SUBST X Y (CDR Z)))))) - (SYSIN) - (SYSOUT) (TERPRI) (TIMES) (TRACE) - (UNION LAMBDA (X Y) - (COND - ((NULL X) Y) - ((MEMBER (CAR X) Y) (UNION (CDR X) Y)) - (T (CONS (CAR X) (UNION (CDR X) Y))))) - (UNTRACE) - (ZEROP LAMBDA (N) (EQ N 0))) + 32767 + EXPR + (LAMBDA (N X) (COND ((EQ N 0) NIL) (T (CONS X (REPEAT (SUB1 N) X)))))) + (RPLACA 32767 SUBR (BEOWULF HOST RPLACA)) + (RPLACD 32767 SUBR (BEOWULF HOST RPLACD)) + (SET 32767 SUBR (BEOWULF HOST SET)) + (SUB1 32767 EXPR (LAMBDA (N) (DIFFERENCE N 1)) SUBR (BEOWULF HOST SUB1)) + (SUB2 + 32767 + EXPR + (LAMBDA + (A Z) + (COND + ((NULL A) Z) ((EQ (CAAR A) Z) (CDAR A)) ((QUOTE T) (SUB2 (CDAR A) Z))))) + (SUBLIS + 32767 EXPR (LAMBDA (A Y) (COND ((ATOM Y) (SUB2 A Y)) ((QUOTE T) (CONS))))) + (SUBST + 32767 + EXPR + (LAMBDA + (X Y Z) + (COND + ((EQUAL Y Z) X) + ((ATOM Z) Z) + ((QUOTE T) (CONS (SUBST X Y (CAR Z)) (SUBST X Y (CDR Z))))))) + (SYSIN 32767 SUBR (BEOWULF IO SYSIN)) + (SYSOUT 32767 SUBR (BEOWULF IO SYSOUT)) + (TERPRI 32767) + (TIMES 32767 SUBR (BEOWULF HOST TIMES)) + (TRACE 32767 SUBR (BEOWULF HOST TRACE)) + (UNION + 32767 + EXPR + (LAMBDA + (X Y) + (COND + ((NULL X) Y) + ((MEMBER (CAR X) Y) (UNION (CDR X) Y)) + (T (CONS (CAR X) (UNION (CDR X) Y)))))) + (UNTRACE 32767 SUBR (BEOWULF HOST UNTRACE)) + (ZEROP 32767 EXPR (LAMBDA (N) (EQ N 0)))) diff --git a/src/beowulf/bootstrap.clj b/src/beowulf/bootstrap.clj index 24b3961..ad6aae7 100644 --- a/src/beowulf/bootstrap.clj +++ b/src/beowulf/bootstrap.clj @@ -9,18 +9,11 @@ ALLUPPERCASE are Lisp 1.5 functions (although written in Clojure) and that therefore all arguments must be numbers, symbols or `beowulf.cons_cell.ConsCell` objects." - (:require [clojure.string :as s] - [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell - pretty-print T F]] - [beowulf.host :refer [ADD1 AND ASSOC ATOM ATOM? CAR CDR CONS DEFINE - DIFFERENCE DOC EQ EQUAL ERROR FIXP GENSYM - GREATERP lax? LESSP LIST NUMBERP OBLIST PAIRLIS - PLUS - QUOTIENT REMAINDER RPLACA RPLACD SET - TIMES TRACE traced? UNTRACE]] - [beowulf.io :refer [SYSIN SYSOUT]] - [beowulf.oblist :refer [*options* oblist NIL]] - [beowulf.read :refer [READ]]) + (:require [beowulf.cons-cell :refer [make-cons-cell T]] + [beowulf.host :refer [ATOM CAAR CADAR CADDR CADR CAR CDR GET LIST + NUMBERP PAIRLIS traced?]] + [beowulf.interop :refer [to-clojure]] + [beowulf.oblist :refer [*options* NIL oblist]]) (:import [beowulf.cons_cell ConsCell] [clojure.lang Symbol])) @@ -51,171 +44,6 @@ [f] `(quote ~f)) -(defn uaf - "Universal access function; `l` is expected to be an arbitrary LISP list, `path` - a (clojure) list of the characters `a` and `d`. Intended to make declaring - all those fiddly `#'c[ad]+r'` functions a bit easier" - [l path] - (cond - (= l NIL) NIL - (empty? path) l - :else - (try - (case (last path) - \a (uaf (.first l) (butlast path)) - \d (uaf (.getCdr l) (butlast path)) - (throw (ex-info (str "uaf: unexpected letter in path (only `a` and `d` permitted): " (last path)) - {:cause :uaf - :detail :unexpected-letter - :expr (last path)}))) - (catch ClassCastException e - (throw (ex-info - (str "uaf: Not a LISP list? " (type l)) - {:cause :uaf - :detail :not-a-lisp-list - :expr l})))))) - -(defmacro CAAR [x] `(uaf ~x '(\a \a))) -(defmacro CADR [x] `(uaf ~x '(\a \d))) -(defmacro CDDR [x] `(uaf ~x '(\d \d))) -(defmacro CDAR [x] `(uaf ~x '(\d \a))) - -(defmacro CAAAR [x] `(uaf ~x '(\a \a \a))) -(defmacro CAADR [x] `(uaf ~x '(\a \a \d))) -(defmacro CADAR [x] `(uaf ~x '(\a \d \a))) -(defmacro CADDR [x] `(uaf ~x '(\a \d \d))) -(defmacro CDDAR [x] `(uaf ~x '(\d \d \a))) -(defmacro CDDDR [x] `(uaf ~x '(\d \d \d))) -(defmacro CDAAR [x] `(uaf ~x '(\d \a \a))) -(defmacro CDADR [x] `(uaf ~x '(\d \a \d))) - -(defmacro CAAAAR [x] `(uaf ~x '(\a \a \a \a))) -(defmacro CAADAR [x] `(uaf ~x '(\a \a \d \a))) -(defmacro CADAAR [x] `(uaf ~x '(\a \d \a \a))) -(defmacro CADDAR [x] `(uaf ~x '(\a \d \d \a))) -(defmacro CDDAAR [x] `(uaf ~x '(\d \d \a \a))) -(defmacro CDDDAR [x] `(uaf ~x '(\d \d \d \a))) -(defmacro CDAAAR [x] `(uaf ~x '(\d \a \a \a))) -(defmacro CDADAR [x] `(uaf ~x '(\d \a \d \a))) -(defmacro CAAADR [x] `(uaf ~x '(\a \a \a \d))) -(defmacro CAADDR [x] `(uaf ~x '(\a \a \d \d))) -(defmacro CADADR [x] `(uaf ~x '(\a \d \a \d))) -(defmacro CADDDR [x] `(uaf ~x '(\a \d \d \d))) -(defmacro CDDADR [x] `(uaf ~x '(\d \d \a \d))) -(defmacro CDDDDR [x] `(uaf ~x '(\d \d \d \d))) -(defmacro CDAADR [x] `(uaf ~x '(\d \a \a \d))) -(defmacro CDADDR [x] `(uaf ~x '(\d \a \d \d))) - -;;;; INTEROP feature ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn interop-interpret-q-name - "For interoperation with Clojure, it will often be necessary to pass - qualified names that are not representable in Lisp 1.5. This function - takes a sequence in the form `(PART PART PART... NAME)` and returns - a symbol in the form `PART.PART.PART/NAME`. This symbol will then be - tried in both that form and lower-cased. Names with hyphens or - underscores cannot be represented with this scheme." - [l] - (if - (seq? l) - (symbol - (s/reverse - (s/replace-first - (s/reverse - (s/join "." (map str l))) - "." - "/"))) - l)) - -(defn to-beowulf - "Return a beowulf-native representation of the Clojure object `o`. - Numbers and symbols are unaffected. Collections have to be converted; - strings must be converted to symbols." - [o] - (cond - (coll? o) (make-beowulf-list o) - (string? o) (symbol (s/upper-case o)) - :else o)) - -(defn to-clojure - "If l is a `beowulf.cons_cell.ConsCell`, return a Clojure list having the - same members in the same order." - [l] - (cond - (not (instance? beowulf.cons_cell.ConsCell l)) - l - (= (CDR l) NIL) - (list (to-clojure (CAR l))) - :else - (conj (to-clojure (CDR l)) (to-clojure (CAR l))))) - -(defn INTEROP - "Clojure (or other host environment) interoperation API. `fn-symbol` is expected - to be either - - 1. a symbol bound in the host environment to a function; or - 2. a sequence (list) of symbols forming a qualified path name bound to a - function. - - Lower case characters cannot normally be represented in Lisp 1.5, so both the - upper case and lower case variants of `fn-symbol` will be tried. If the - function you're looking for has a mixed case name, that is not currently - accessible. - - `args` is expected to be a Lisp 1.5 list of arguments to be passed to that - function. Return value must be something acceptable to Lisp 1.5, so either - a symbol, a number, or a Lisp 1.5 list. - - If `fn-symbol` is not found (even when cast to lower case), or is not a function, - or the value returned cannot be represented in Lisp 1.5, an exception is thrown - with `:cause` bound to `:interop` and `:detail` set to a value representing the - actual problem." - [fn-symbol args] - (if-not (:strict *options*) - (let - [q-name (if - (seq? fn-symbol) - (interop-interpret-q-name fn-symbol) - fn-symbol) - l-name (symbol (s/lower-case q-name)) - f (cond - (try - (fn? (eval l-name)) - (catch java.lang.ClassNotFoundException _ nil)) l-name - (try - (fn? (eval q-name)) - (catch java.lang.ClassNotFoundException _ nil)) q-name - :else (throw - (ex-info - (str "INTEROP: unknown function `" fn-symbol "`") - {:cause :interop - :detail :not-found - :name fn-symbol - :also-tried l-name}))) - args' (to-clojure args)] - (print (str "INTEROP: evaluating `" (cons f args') "`")) - (flush) - (let [result (eval (conj args' f))] ;; this has the potential to blow up the world - (println (str "; returning `" result "`")) - - (cond - (instance? beowulf.cons_cell.ConsCell result) result - (coll? result) (make-beowulf-list result) - (symbol? result) result - (string? result) (symbol result) - (number? result) result - :else (throw - (ex-info - (str "INTEROP: Cannot return `" result "` to Lisp 1.5.") - {:cause :interop - :detail :not-representable - :result result}))))) - (throw - (ex-info - (str "INTEROP not allowed in strict mode.") - {:cause :interop - :detail :strict})))) - (defn- traced-apply "Like `APPLY`, but with trace output to console." [function-symbol args lisp-fn environment depth] @@ -247,47 +75,10 @@ environment depth) (APPLY lisp-fn args environment depth)) - (case function-symbol ;; there must be a better way of doing this! - ADD1 (safe-apply ADD1 args) - AND (safe-apply AND args) - APPLY (APPLY (first args) (rest args) environment depth) - ATOM (ATOM? (CAR args)) - CAR (safe-apply CAR args) - CDR (safe-apply CDR args) - CONS (safe-apply CONS args) - DEFINE (DEFINE (CAR args)) - DIFFERENCE (DIFFERENCE (CAR args) (CADR args)) - DOC (DOC (first args)) - EQ (safe-apply EQ args) - EQUAL (safe-apply EQUAL args) - ERROR (safe-apply ERROR args) - ;; think about EVAL. Getting the environment right is subtle - FIXP (safe-apply FIXP args) - GENSYM (GENSYM) - GREATERP (safe-apply GREATERP args) - INTEROP (when (lax? INTEROP) (safe-apply INTEROP args)) - LESSP (safe-apply LESSP args) - LIST (safe-apply LIST args) - NUMBERP (safe-apply NUMBERP args) - OBLIST (OBLIST) - PLUS (safe-apply PLUS args) - PRETTY (when (lax? 'PRETTY) - (safe-apply pretty-print args)) - PRINT (safe-apply print args) - QUOTIENT (safe-apply QUOTIENT args) - READ (READ) - REMAINDER (safe-apply REMAINDER args) - RPLACA (safe-apply RPLACA args) - RPLACD (safe-apply RPLACD args) - SET (safe-apply SET args) - SYSIN (when (lax? 'SYSIN) - (safe-apply SYSIN args)) - SYSOUT (when (lax? 'SYSOUT) - (safe-apply SYSOUT args)) - TERPRI (println) - TIMES (safe-apply TIMES args) - TRACE (safe-apply TRACE args) - UNTRACE (safe-apply UNTRACE args) + (if function-symbol + (let [f (GET function-symbol 'SUBR)] + (when f + (apply @(resolve f) (to-clojure args)))) ;; else (ex-info "No function found" {:context "APPLY" @@ -309,7 +100,7 @@ {:context "APPLY" :function "NIL" :args args}))) - (= (ATOM? function) T) (apply-symbolic function args environment (inc depth)) + (= (ATOM function) T) (apply-symbolic function args environment (inc depth)) :else (case (first function) LABEL (APPLY (CADDR function) @@ -355,14 +146,13 @@ (EVAL (CAR args) env depth) (EVLIS (CDR args) env depth)))) -(defn- eval-symbolic [^Symbol s env] - (let [binding (ASSOC s env)] - (if (= binding NIL) - (throw (ex-info (format "No binding for symbol `%s`" s) - {:phase :eval - :symbol s})) - (CDR binding)))) - +;; (defn- eval-symbolic [^Symbol s env] +;; (let [binding (ASSOC s env)] +;; (if (= binding NIL) +;; (throw (ex-info (format "No binding for symbol `%s`" s) +;; {:phase :eval +;; :symbol s})) +;; (CDR binding)))) (defn EVAL "Evaluate this `expr` and return the result. If `environment` is not passed, @@ -376,7 +166,7 @@ ([expr env depth] (cond (= (NUMBERP expr) T) expr - (symbol? expr) (eval-symbolic expr env) + (symbol? expr) (GET expr 'APVAL) (string? expr) (if (:strict *options*) (throw (ex-info @@ -385,18 +175,16 @@ :detail :strict :expr expr})) (symbol expr)) - (= - (ATOM? (CAR expr)) - T) (case (CAR expr) - QUOTE (CADR expr) - FUNCTION (LIST 'FUNARG (CADR expr) ) - COND (EVCON (CDR expr) env depth) + (= (ATOM (CAR expr)) T) (case (CAR expr) + QUOTE (CADR expr) + FUNCTION (LIST 'FUNARG (CADR expr)) + COND (EVCON (CDR expr) env depth) ;; else - (APPLY - (CAR expr) - (EVLIS (CDR expr) env depth) - env - depth)) + (APPLY + (CAR expr) + (EVLIS (CDR expr) env depth) + env + depth)) :else (APPLY (CAR expr) (EVLIS (CDR expr) env depth) diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj index a282a86..738d806 100644 --- a/src/beowulf/host.clj +++ b/src/beowulf/host.clj @@ -424,9 +424,9 @@ (make-beowulf-list (map CAR @oblist)) NIL)) -(def ^:private magic-marker +(def magic-marker "The unexplained magic number which marks the start of a property list." - (Integer/parseInt "777778" 8)) + (Integer/parseInt "77777" 8)) (defn PUT "Put this `value` as the value of the property indicated by this `indicator` @@ -460,7 +460,13 @@ It's clear that `GET` is expected to be defined in terms of `PROP`, but we can't implement `PROP` here because we lack `EVAL`; and we can't have - `EVAL` here because it depends on `GET`." + `EVAL` here because both it and `APPLY` depends on `GET`. + + OK, It's worse than that: the statement of the definition of `GET` (and + of) `PROP` on page 59 says that the first argument to each must be a list; + But the in the definition of `ASSOC` on page 70, when `GET` is called its + first argument is always an atom. Since it's `ASSOC` and `EVAL` which I + need to make work, I'm going to assume that page 59 is wrong." [symbol indicator] (let [binding (ASSOC symbol @oblist)] (cond diff --git a/src/beowulf/interop.clj b/src/beowulf/interop.clj new file mode 100644 index 0000000..b993fbe --- /dev/null +++ b/src/beowulf/interop.clj @@ -0,0 +1,129 @@ +(ns beowulf.interop + (:require [beowulf.cons-cell :refer [make-beowulf-list]] + [beowulf.host :refer [CAR CDR]] + [beowulf.oblist :refer [*options* NIL]] + [clojure.string :as s :refer [last-index-of lower-case split + upper-case]])) + +;;;; INTEROP feature ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn listify-qualified-name + "We need to be able to print something we can link to the particular Clojure + function `subr` in a form in which Lisp 1.5 is able to read it back in and + relink it. + + This assumes `subr` is either + 1. a string in the format `#'beowulf.io/SYSIN` or `beowulf.io/SYSIN`; or + 2. something which, when coerced to a string with `str`, will have such + a format." + [subr] + (make-beowulf-list + (map + #(symbol (upper-case %)) + (remove empty? (split (str subr) #"[#'./]"))))) + + +(defn interpret-qualified-name + "For interoperation with Clojure, it will often be necessary to pass + qualified names that are not representable in Lisp 1.5. This function + takes a sequence in the form `(PART PART PART... NAME)` and returns + a symbol in the form `part.part.part/NAME`. This symbol will then be + tried in both that form and lower-cased. Names with hyphens or + underscores cannot be represented with this scheme." + ([l] + (symbol + (let [n (s/join "." + (concat (map #(lower-case (str %)) (butlast l)) + (list (last l)))) + s (last-index-of n ".")] + (if s + (str (subs n 0 s) "/" (subs n (inc s))) + n))))) + +(defn to-beowulf + "Return a beowulf-native representation of the Clojure object `o`. + Numbers and symbols are unaffected. Collections have to be converted; + strings must be converted to symbols." + [o] + (cond + (coll? o) (make-beowulf-list o) + (string? o) (symbol (s/upper-case o)) + :else o)) + +(defn to-clojure + "If l is a `beowulf.cons_cell.ConsCell`, return a Clojure list having the + same members in the same order." + [l] + (cond + (not (instance? beowulf.cons_cell.ConsCell l)) + l + (= (CDR l) NIL) + (list (to-clojure (CAR l))) + :else + (conj (to-clojure (CDR l)) (to-clojure (CAR l))))) + +(defn INTEROP + "Clojure (or other host environment) interoperation API. `fn-symbol` is expected + to be either + + 1. a symbol bound in the host environment to a function; or + 2. a sequence (list) of symbols forming a qualified path name bound to a + function. + + Lower case characters cannot normally be represented in Lisp 1.5, so both the + upper case and lower case variants of `fn-symbol` will be tried. If the + function you're looking for has a mixed case name, that is not currently + accessible. + + `args` is expected to be a Lisp 1.5 list of arguments to be passed to that + function. Return value must be something acceptable to Lisp 1.5, so either + a symbol, a number, or a Lisp 1.5 list. + + If `fn-symbol` is not found (even when cast to lower case), or is not a function, + or the value returned cannot be represented in Lisp 1.5, an exception is thrown + with `:cause` bound to `:interop` and `:detail` set to a value representing the + actual problem." + [fn-symbol args] + (if-not (:strict *options*) + (let + [q-name (if + (seq? fn-symbol) + (interpret-qualified-name fn-symbol) + fn-symbol) + l-name (symbol (s/lower-case q-name)) + f (cond + (try + (fn? (eval l-name)) + (catch java.lang.ClassNotFoundException _ nil)) l-name + (try + (fn? (eval q-name)) + (catch java.lang.ClassNotFoundException _ nil)) q-name + :else (throw + (ex-info + (str "INTEROP: unknown function `" fn-symbol "`") + {:cause :interop + :detail :not-found + :name fn-symbol + :also-tried l-name}))) + args' (to-clojure args)] + (print (str "INTEROP: evaluating `" (cons f args') "`")) + (flush) + (let [result (eval (conj args' f))] ;; this has the potential to blow up the world + (println (str "; returning `" result "`")) + (cond + (instance? beowulf.cons_cell.ConsCell result) result + (coll? result) (make-beowulf-list result) + (symbol? result) result + (string? result) (symbol result) + (number? result) result + :else (throw + (ex-info + (str "INTEROP: Cannot return `" result "` to Lisp 1.5.") + {:cause :interop + :detail :not-representable + :result result}))))) + (throw + (ex-info + (str "INTEROP not allowed in strict mode.") + {:cause :interop + :detail :strict})))) diff --git a/src/beowulf/io.clj b/src/beowulf/io.clj index cca8838..b97d8c7 100644 --- a/src/beowulf/io.clj +++ b/src/beowulf/io.clj @@ -15,8 +15,12 @@ oblist with data from that file. Hence functions SYSOUT and SYSIN, which do just that." - (:require [beowulf.cons-cell :refer [pretty-print]] - [beowulf.oblist :refer [*options* oblist]] + (:require [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell + pretty-print]] + [beowulf.host :refer [CADR CAR CDDR CDR]] + [beowulf.interop :refer [interpret-qualified-name + listify-qualified-name]] + [beowulf.oblist :refer [*options* NIL oblist]] [beowulf.read :refer [READ]] [clojure.java.io :refer [file resource]] [clojure.string :refer [ends-with?]] @@ -44,7 +48,7 @@ (def ^:constant default-sysout "resources/lisp1.5.lsp") -(defn- full-path +(defn- full-path [fp] (str (if (:filepath *options*) @@ -59,6 +63,24 @@ "" ".lsp"))) +;; (find-var (symbol "beowulf.io/SYSIN")) +;; (@(resolve (symbol "beowulf.host/TIMES")) 2 2) + +(defn safely-wrap-subr + [entry] + (cond (= entry NIL) NIL + (= (CAR entry) 'SUBR) (make-cons-cell + (CAR entry) + (make-cons-cell + (listify-qualified-name (CADR entry)) + (CDDR entry))) + :else (make-cons-cell + (CAR entry) (safely-wrap-subr (CDR entry))))) + +(defn safely-wrap-subrs + [objects] + (make-beowulf-list (map safely-wrap-subr objects))) + (defn SYSOUT "Dump the current content of the object list to file. If no `filepath` is specified, a file name will be constructed of the symbol `Sysout` and @@ -79,7 +101,38 @@ (println (format ";; generated by %s" (System/getenv "USER")))) (println (apply str (repeat 79 ";"))) (println) - (pretty-print @oblist))))) + (let [output (safely-wrap-subrs @oblist)] + (pretty-print output) + ))))) + +(defn- resolve-subr + "If this oblist `entry` references a subroutine, attempt to fix up that + reference." + [entry] + (cond (= entry NIL) NIL + (= (CAR entry) 'SUBR) (try + (make-cons-cell + (CAR entry) + (make-cons-cell + (interpret-qualified-name + (CADR entry)) + (CDDR entry))) + (catch Exception _ + (print "Warning: failed to resolve " + (CADR entry)) + (CDDR entry))) + :else (make-cons-cell + (CAR entry) (resolve-subr (CDR entry))))) + + +(defn- resolve-subroutines + "Attempt to fix up the references to subroutines (Clojure functions) among + these `objects`, being new content for the object list." + [objects] + (make-beowulf-list + (map + resolve-subr + objects))) (defn SYSIN "Read the contents of the file at this `filename` into the object list. @@ -100,14 +153,16 @@ ([] (SYSIN (or (:read *options*) default-sysout))) ([filename] - (let [fp (file (full-path (str filename))) - file (when (and (.exists fp) (.canRead fp)) fp) - res (try (resource filename) - (catch Throwable _ nil)) - content (try (READ (slurp (or file res))) - (catch Throwable any - (throw (ex-info "Could not read from file" - {:context "SYSIN" - :filepath fp} - any))))] - (swap! oblist #(when (or % (seq content)) content))))) + (let [fp (file (full-path (str filename))) + file (when (and (.exists fp) (.canRead fp)) fp) + res (try (resource filename) + (catch Throwable _ nil)) + content (try (READ (slurp (or file res))) + (catch Throwable any + (throw (ex-info "Could not read from file" + {:context "SYSIN" + :filepath fp} + any))))] + (swap! oblist + #(when (or % (seq content)) + (resolve-subroutines content)))))) diff --git a/test/beowulf/interop_test.clj b/test/beowulf/interop_test.clj index 98290f2..c1e70ea 100644 --- a/test/beowulf/interop_test.clj +++ b/test/beowulf/interop_test.clj @@ -1,6 +1,7 @@ (ns beowulf.interop-test (:require [clojure.test :refer [deftest is testing]] - [beowulf.bootstrap :refer [EVAL INTEROP]] + [beowulf.bootstrap :refer [EVAL]] + [beowulf.interop :refer [INTEROP]] [beowulf.read :refer [gsp]])) From 20b8f45db1570ec9bd1b207d41e66fed8ad5735f Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 6 Apr 2023 14:25:12 +0100 Subject: [PATCH 6/7] My monster, it lives! I'm not confident this is yet tidy, so I'm not yet closing the feature branch - but it's working. --- src/beowulf/bootstrap.clj | 244 ++++++++++++++++++++++---------------- src/beowulf/core.clj | 4 +- src/beowulf/host.clj | 50 +++++--- 3 files changed, 176 insertions(+), 122 deletions(-) diff --git a/src/beowulf/bootstrap.clj b/src/beowulf/bootstrap.clj index ad6aae7..9637a69 100644 --- a/src/beowulf/bootstrap.clj +++ b/src/beowulf/bootstrap.clj @@ -9,10 +9,9 @@ ALLUPPERCASE are Lisp 1.5 functions (although written in Clojure) and that therefore all arguments must be numbers, symbols or `beowulf.cons_cell.ConsCell` objects." - (:require [beowulf.cons-cell :refer [make-cons-cell T]] - [beowulf.host :refer [ATOM CAAR CADAR CADDR CADR CAR CDR GET LIST - NUMBERP PAIRLIS traced?]] - [beowulf.interop :refer [to-clojure]] + (:require [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell T]] + [beowulf.host :refer [ASSOC ATOM CAAR CADAR CADDR CADR CAR CDR GET + LIST NUMBERP PAIRLIS traced?]] [beowulf.oblist :refer [*options* NIL oblist]]) (:import [beowulf.cons_cell ConsCell] [clojure.lang Symbol])) @@ -39,51 +38,67 @@ (declare APPLY EVAL) -(defmacro QUOTE - "Quote, but in upper case for LISP 1.5" - [f] - `(quote ~f)) +(defn try-resolve-subroutine + "Attempt to resolve this `subr` with these `arg`." + [subr args] + (when (and subr (not= subr NIL)) + (try @(resolve subr) + (catch Throwable any + (throw (ex-info "Failed to resolve subroutine" + {:phase :apply + :function subr + :args args + :type :beowulf} + any)))))) -(defn- traced-apply - "Like `APPLY`, but with trace output to console." - [function-symbol args lisp-fn environment depth] - (let [indent (apply str (repeat depth "-"))] - (println (str indent "> " function-symbol " " args)) - (let [r (APPLY lisp-fn args environment depth)] - (println (str "<" indent " " r)) - r))) +(defn- trace-call + "Show a trace of a call to the function named by this `function-symbol` + with these `args` at this depth." + [function-symbol args depth] + (when (traced? function-symbol) + (let [indent (apply str (repeat depth "-"))] + (println (str indent "> " function-symbol " " args))))) -(defn- safe-apply - "We've a real problem with varargs functions when `args` is `NIL`, because - Clojure does not see `NIL` as an empty sequence." - [clj-fn args] - (let [args' (when (instance? ConsCell args) args)] - (apply clj-fn args'))) +(defn- trace-response + "Show a trace of this `response` from the function named by this + `function-symbol` at this depth." + [function-symbol response depth] + (when (traced? function-symbol) + (let [indent (apply str (repeat depth "-"))] + (println (str "<" indent " " function-symbol " " response)))) + response) + +(defn- value + "Seek a value for this symbol `s` by checking each of these indicators in + turn." + ([s] + (value s (list 'APVAL 'EXPR 'FEXPR 'SUBR 'FSUBR))) + ([s indicators] + (when (symbol? s) + (first (remove #(= % NIL) (map #(GET s %) + indicators)))))) (defn- apply-symbolic "Apply this `funtion-symbol` to these `args` in this `environment` and return the result." [^Symbol function-symbol args ^ConsCell environment depth] - (let [lisp-fn (try (EVAL function-symbol environment depth) - (catch Throwable any (when (:trace *options*) - (println any))))] - (if (and lisp-fn - (not= lisp-fn NIL)) (if (traced? function-symbol) - (traced-apply function-symbol - args - lisp-fn - environment - depth) - (APPLY lisp-fn args environment depth)) - (if function-symbol - (let [f (GET function-symbol 'SUBR)] - (when f - (apply @(resolve f) (to-clojure args)))) - ;; else - (ex-info "No function found" - {:context "APPLY" - :function function-symbol - :args args}))))) + (trace-call function-symbol args depth) + (let [lisp-fn ;; (try + (value function-symbol '(EXPR FEXPR)) + ;; (catch Exception any (when (traced? function-symbol) + ;; (println any)))) + subr (value function-symbol '(SUBR FSUBR)) + host-fn (try-resolve-subroutine subr args) + result (cond (and lisp-fn + (not= lisp-fn NIL)) (APPLY lisp-fn args environment depth) + host-fn (apply host-fn (when (instance? ConsCell args) args)) + :else (ex-info "No function found" + {:phase :apply + :function function-symbol + :args args + :type :beowulf}))] + (trace-response function-symbol result depth) + result)) (defn APPLY "Apply this `function` to these `arguments` in this `environment` and return @@ -93,33 +108,37 @@ All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects. See page 13 of the Lisp 1.5 Programmers Manual." [function args environment depth] - (cond - (= NIL function) (if (:strict *options*) - NIL - (throw (ex-info "NIL is not a function" - {:context "APPLY" - :function "NIL" - :args args}))) - (= (ATOM function) T) (apply-symbolic function args environment (inc depth)) - :else (case (first function) - LABEL (APPLY - (CADDR function) - args - (make-cons-cell - (make-cons-cell - (CADR function) - (CADDR function)) - environment) - depth) - FUNARG (APPLY (CADR function) args (CADDR function) depth) - LAMBDA (EVAL - (CADDR function) - (PAIRLIS (CADR function) args environment) depth) - (throw (ex-info "Unrecognised value in function position" - {:phase :apply - :function function - :args args - :type :beowulf}))))) + (trace-call 'APPLY (list function args environment) depth) + (let [result (cond + (= NIL function) (if (:strict *options*) + NIL + (throw (ex-info "NIL is not a function" + {:phase :apply + :function "NIL" + :args args + :type :beowulf}))) + (= (ATOM function) T) (apply-symbolic function args environment (inc depth)) + :else (case (first function) + LABEL (APPLY + (CADDR function) + args + (make-cons-cell + (make-cons-cell + (CADR function) + (CADDR function)) + environment) + depth) + FUNARG (APPLY (CADR function) args (CADDR function) depth) + LAMBDA (EVAL + (CADDR function) + (PAIRLIS (CADR function) args environment) depth) + (throw (ex-info "Unrecognised value in function position" + {:phase :apply + :function function + :args args + :type :beowulf}))))] + (trace-response 'APPLY result depth) + result)) (defn- EVCON "Inner guts of primitive COND. All `clauses` are assumed to be @@ -146,13 +165,25 @@ (EVAL (CAR args) env depth) (EVLIS (CDR args) env depth)))) -;; (defn- eval-symbolic [^Symbol s env] -;; (let [binding (ASSOC s env)] -;; (if (= binding NIL) -;; (throw (ex-info (format "No binding for symbol `%s`" s) -;; {:phase :eval -;; :symbol s})) -;; (CDR binding)))) +(defn- eval-symbolic + [expr env depth] + (let [v (value expr (list 'APVAL)) + indent (apply str (repeat depth "-"))] + (when (traced? 'EVAL) + (println (str indent ": EVAL: deep binding (" expr " . " (or v "nil") ")"))) + (if (and v (not= v NIL)) + v + (let [v' (ASSOC expr env)] + (when (traced? 'EVAL) + (println (str indent ": EVAL: shallow binding: " (or v' "nil")))) + (if (and v' (not= v' NIL)) + (.getCdr v') + (throw (ex-info "No binding for symbol found" + {:phase :eval + :function 'EVAL + :args (list expr env depth) + :type :lisp + :code :A8}))))))) (defn EVAL "Evaluate this `expr` and return the result. If `environment` is not passed, @@ -160,34 +191,41 @@ argument is part of the tracing system and should not be set by user code. All args are assumed to be numbers, symbols or `beowulf.cons-cell/ConsCell` - objects." + objects. However, if called with just a single arg, `expr`, I'll assume it's + being called from the Clojure REPL and will coerce the `expr` to `ConsCell`." ([expr] - (EVAL expr @oblist 0)) + (let [expr' (if (and (coll? expr) (not (instance? ConsCell expr))) + (make-beowulf-list expr) + expr)] + (EVAL expr' @oblist 0))) ([expr env depth] - (cond - (= (NUMBERP expr) T) expr - (symbol? expr) (GET expr 'APVAL) - (string? expr) (if (:strict *options*) - (throw - (ex-info - (str "EVAL: strings not allowed in strict mode: \"" expr "\"") - {:phase :eval - :detail :strict - :expr expr})) - (symbol expr)) - (= (ATOM (CAR expr)) T) (case (CAR expr) - QUOTE (CADR expr) - FUNCTION (LIST 'FUNARG (CADR expr)) - COND (EVCON (CDR expr) env depth) + (trace-call 'EVAL (list expr env depth) depth) + (let [result (cond + (= (NUMBERP expr) T) expr + (symbol? expr) (eval-symbolic expr env depth) + (string? expr) (if (:strict *options*) + (throw + (ex-info + (str "EVAL: strings not allowed in strict mode: \"" expr "\"") + {:phase :eval + :detail :strict + :expr expr})) + (symbol expr)) + (= (ATOM (CAR expr)) T) (case (CAR expr) + QUOTE (CADR expr) + FUNCTION (LIST 'FUNARG (CADR expr)) + COND (EVCON (CDR expr) env depth) ;; else - (APPLY - (CAR expr) - (EVLIS (CDR expr) env depth) - env - depth)) - :else (APPLY - (CAR expr) - (EVLIS (CDR expr) env depth) - env - depth)))) + (APPLY + (CAR expr) + (EVLIS (CDR expr) env depth) + env + depth)) + :else (APPLY + (CAR expr) + (EVLIS (CDR expr) env depth) + env + depth))] + (trace-response 'EVAL result depth) + result))) diff --git a/src/beowulf/core.clj b/src/beowulf/core.clj index 42e3e16..99b5a59 100644 --- a/src/beowulf/core.clj +++ b/src/beowulf/core.clj @@ -2,8 +2,8 @@ "Essentially, the `-main` function and the bootstrap read-eval-print loop." (:require [beowulf.bootstrap :refer [EVAL]] [beowulf.io :refer [default-sysout SYSIN]] + [beowulf.oblist :refer [*options* NIL]] [beowulf.read :refer [READ read-from-console]] - [beowulf.oblist :refer [*options* oblist]] [clojure.java.io :as io] [clojure.pprint :refer [pprint]] [clojure.string :refer [trim]] @@ -55,7 +55,7 @@ (defn- re "Like REPL, but it isn't a loop and doesn't print." [input] - (EVAL (READ input) @oblist 0)) + (EVAL (READ input) NIL 0)) (defn repl "Read/eval/print loop." diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj index 738d806..82821ce 100644 --- a/src/beowulf/host.clj +++ b/src/beowulf/host.clj @@ -2,14 +2,12 @@ "provides Lisp 1.5 functions which can't be (or can't efficiently be) implemented in Lisp 1.5, which therefore need to be implemented in the host language, in this case Clojure." - (:require [clojure.string :refer [upper-case]] - [beowulf.cons-cell :refer [F make-cons-cell make-beowulf-list - pretty-print T]] - ;; note hyphen - this is Clojure... + (:require [beowulf.cons-cell :refer [F make-beowulf-list make-cons-cell T]] ;; note hyphen - this is Clojure... [beowulf.gendoc :refer [open-doc]] - [beowulf.oblist :refer [*options* oblist NIL]]) - (:import [beowulf.cons_cell ConsCell] - ;; note underscore - same namespace, but Java. + [beowulf.oblist :refer [*options* NIL oblist]] + [clojure.set :refer [union]] + [clojure.string :refer [upper-case]]) + (:import [beowulf.cons_cell ConsCell] ;; note underscore - same namespace, but Java. )) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -290,9 +288,20 @@ In `beowulf.host` principally because I don't yet feel confident to define varargs functions in Lisp." [& args] - (if (empty? (filter #(or (= 'F %) (= NIL %) (nil? %)) args)) - 'T - 'F)) + (cond (= NIL args) T + (not (#{NIL F} (.getCar args))) (AND (.getCdr args)) + :else T)) + +(defn OR + "`T` if and only if at least one of my `args` evaluates to something other + than either `F` or `NIL`, else `F`. + + In `beowulf.host` principally because I don't yet feel confident to define + varargs functions in Lisp." + [& args] + (cond (= NIL args) F + (not (#{NIL F} (.getCar args))) T + :else (OR (.getCdr args)))) ;;;; Operations on lists ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; @@ -516,19 +525,26 @@ "Return `true` iff `s` is a symbol currently being traced, else `nil`." [s] (try (contains? @traced-symbols s) - (catch Throwable _))) + (catch Throwable _ nil))) (defn TRACE - "Add this symbol `s` to the set of symbols currently being traced. If `s` - is not a symbol, does nothing." + "Add this `s` to the set of symbols currently being traced. If `s` + is not a symbol or sequence of symbols, does nothing." [s] - (when (symbol? s) - (swap! traced-symbols #(conj % s)))) + (swap! traced-symbols + #(cond + (symbol? s) (conj % s) + (and (seq? s) (every? symbol? s)) (union % (set s)) + :else %))) (defn UNTRACE + "Remove this `s` from the set of symbols currently being traced. If `s` + is not a symbol or sequence of symbols, does nothing." [s] - (when (symbol? s) - (swap! traced-symbols #(set (remove (fn [x] (= s x)) %))))) + (cond + (symbol? s) (swap! traced-symbols #(set (remove (fn [x] (= s x)) %))) + (and (seq? s) (every? symbol? s)) (map UNTRACE s)) + @traced-symbols) ;;;; Extensions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From 10a4a6da71dc6b1422d1878850a192b5c1c938f0 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Thu, 6 Apr 2023 18:59:34 +0100 Subject: [PATCH 7/7] #1: Some polishing of the property lists fix; documentation work. --- doc/lisp1.5.md | 393 ++++++++++++++++++++++---------------- docs/index.html | 1 + resources/lisp1.5.lsp | 44 ++--- src/beowulf/bootstrap.clj | 2 + src/beowulf/host.clj | 2 +- 5 files changed, 258 insertions(+), 184 deletions(-) create mode 120000 docs/index.html diff --git a/doc/lisp1.5.md b/doc/lisp1.5.md index f4b0946..116cf53 100644 --- a/doc/lisp1.5.md +++ b/doc/lisp1.5.md @@ -387,7 +387,7 @@ The Euclidean algorithm for finding the greatest common divisor of two positive ``` gcd[x; y]=[x>y -> gcd[y; x]; - rem[y;x]=0 -> x] + rem[y;x]=0 -> x] ``` `rem[u; v]` is the remainder when `u` is divided by `v`. @@ -575,9 +575,9 @@ This function gives the result of substituting the S-expression x for all occurr ``` subst[x; y; z] = [equal[y; z] -> x; - atom[z] - z; - T - cons[subst - [x; y; car[z]]; subst[x; y; cdr[z]]]] + atom[z] - z; + T - cons[subst + [x; y; car[z]]; subst[x; y; cdr[z]]]] ``` As an example, we have @@ -607,8 +607,8 @@ append[(A B);(C D E)] = (A B C D E) This predicate is true if the S-expression `x` occurs among the elements of the list `y`. We have ``` member[x; y] = [null[y] -> F; - equal[x; car [y ]] ->T; - T -> member[x; cdr [y ]]] + equal[x; car [y ]] ->T; + T -> member[x; cdr [y ]]] ``` #### 3. pairlis[x; y; a] @@ -621,8 +621,8 @@ two columns, is called an association list. We have ``` pairlis [x; y; a] = [null[x] -> a; - T -> cons[cons[car[x]; car[y]]; - pairlis[cdr[x]; cdr [y]; a]]] + T -> cons[cons[car[x]; car[y]]; + pairlis[cdr[x]; cdr [y]; a]]] ``` An example is @@ -658,13 +658,13 @@ from the pair list. In order to define `sublis`, we first define an auxiliary fu ``` sub2[a; z] = [null[a] -> z; eq[caar[a]; z] -> cdar[a]; - T -> sub2[cdr[a]; z]] + T -> sub2[cdr[a]; z]] ``` and ``` sublis[a; y] = [atom[y] -> sub2[a; y]; - T -> cons[sublis[a; car[y]]; sublis[a; cdr[y]]]] + T -> cons[sublis[a; car[y]]; sublis[a; cdr[y]]]] ``` An example is @@ -707,35 +707,35 @@ evalquote[fn; x] = apply[fn; x; NIL] where ```mexpr apply[fn; x; a] = - [atom[fn] -> [eq[fn; CAR] -> caar[x] - eq[fn; CDR] -> cdar[x]; - eq[fn; CONS] -> cons[car[x]; cadr[x]]; - eq[fn; ATOM] -> atom[car[x]]; - eq[fn; EQ] -> eq[car[x]; cadr[x]]; - T -> apply[eval[fn; a]; x; a]] - eq[car[fn]; LAMBDA] -> eval[caddr[fn]; pairlis[cadr[fn]; x; a]]; - eq[car[fn]; LABEL] -> apply[caddr [fn]; x; cons[cons[cadr [fn]; - caddr[fn]]; a]]] + [atom[fn] -> [eq[fn; CAR] -> caar[x] + eq[fn; CDR] -> cdar[x]; + eq[fn; CONS] -> cons[car[x]; cadr[x]]; + eq[fn; ATOM] -> atom[car[x]]; + eq[fn; EQ] -> eq[car[x]; cadr[x]]; + T -> apply[eval[fn; a]; x; a]] + eq[car[fn]; LAMBDA] -> eval[caddr[fn]; pairlis[cadr[fn]; x; a]]; + eq[car[fn]; LABEL] -> apply[caddr [fn]; x; cons[cons[cadr [fn]; + caddr[fn]]; a]]] eval[e;a] = [atom[e] -> cdr[assoc[e;a]]; - atom[car[e]] -> [eq[car[e]; QUOTE] -> cadr[e]; - eq[car[e]; COND] -> evcon[cdr[e]; a]; - T -> apply[car[e]; evlis[cdr[el; a]; a]]; - T -> apply[car[e]; evlis [cdr[e]; a]; a]] + atom[car[e]] -> [eq[car[e]; QUOTE] -> cadr[e]; + eq[car[e]; COND] -> evcon[cdr[e]; a]; + T -> apply[car[e]; evlis[cdr[el; a]; a]]; + T -> apply[car[e]; evlis [cdr[e]; a]; a]] ``` `pairlis` and `assoc` have been previously defined. ```mexpr evcon[c; a] = [eval[caar[c]; a] -> eval[cadar[c]; a]; - T -> evcon[cdr [c];a]] + T -> evcon[cdr [c];a]] ``` and ```mexpr evlis[m; a] = [null[m] -> NIL; - T -> cons [eval[car [m];a];evlis[cdr [m];a]]] + T -> cons [eval[car [m];a];evlis[cdr [m];a]]] ``` page 14 @@ -780,11 +780,11 @@ The following example is a LISP program that defines three functions `union`, `i ``` member[a; x] = [null[x] -> F; eq[a; car[x]] -> T; - T -> member[a; cdr[x]]] + T -> member[a; cdr[x]]] union[x; y] = [null[x] -> y; - member[car[x];y] -> union[cdr[x]; y]; - T -> cons[car[x]; union[cdr[x]; y]]] + member[car[x];y] -> union[cdr[x]; y]; + T -> cons[car[x]; union[cdr[x]; y]]] intersection[x;y] = [null[x] -> NIL; member[car[x]; y] -> cons[car[x]; intersection[cdr[x]; y]]; @@ -2719,8 +2719,8 @@ erty list for FF. The function `prop` searches the list `x` for an item that is `eq` to `y`. If such an element is found, the value of `prop` is the rest of the list beginning immediately after the element. Otherwise the value is `u[]`, where u is a function of no arguments. ``` prop[x; y; u] = [null[x] -> u[ ]; - eq[car[x];y] -> cdr[x] - T -> prop[cdr[x]; y; u]] + eq[car[x];y] -> cdr[x] + T -> prop[cdr[x]; y; u]] ``` SUBR ``` @@ -3291,70 +3291,86 @@ NIL by performing EVAL (OBLIST NIL). +page 70 + ## APPENDIX B : THE LISP INTERPRETER -This appendix is written in mixed M-expressions and English. Its purpose is to -describe as closely as possible the actual working of the interpreter and PROG feature. -The functions evalquote, ---- apply, eval, evlis, evcon, and the PROG feature are defined -by using a language that follows the M-expression notation as closely as possible and -contains some insertions in English. +This appendix is written in mixed M-expressions and English. Its purpose is to describe as closely as possible the actual working of the interpreter and PROG feature. The functions `evalquote`, `apply`, `eval`, `evlis`, `evcon`, and the `PROG` feature are defined by using a language that follows the M-expression notation as closely as possible and contains some insertions in English. -###### evalquote[fn;args]=[get [fn; FEXPR] vget [fn; FSUBR] - +```mexpr +evalquote[fn; args]=[get[fn; FEXPR] v get[fn; FSUBR] -> eval[cons [ fn; args]; NIL]; + T -> apply[fn; args; NIL] +``` -eval[cons [ fn; args]; NIL] +This definition shows that `evalquote` is capable of handling special forms as a sort of exception. Apply cannot handle special forms and will give error `A2` if given one as its first argument. + +The following definition of `apply` is an enlargement of the one given in Section I. It shows how functional arguments bound by FUNARG are processed, and describes the way in which machine language subroutines are called. + +In this description, `spread` can be regarded as a pseudo-function of one argument. This argument is a list. `spread` puts the individual items of this list into the AC, MQ, +$ARG3,... the standard cells *[general purpose registers]* for transmitting arguments to functions. -This definition shows that evalquote is capable of handling special forms as a sort -of exception. Apply cannot handle special forms and will give error A2 if given one as -its first argument. -The following definition'of apply is an enlargement of the one given in Section I. It -shows how functional arguments bound by FUNARG are processed, and describes the -way in which machine language subroutines are called. -In this description, spread can be regarded as a pseudo-function of one argument. -This argument is a list. spread puts the individual items of this list into the AC, MQ, -$ARG3,... the standard cells for transmitting arguments to functions. These M-expressions should not be taken too literally. In many cases, the actual program is a store and transfer where a recursion is suggested by these definitions. -apply[fn;args;a]=[ -null [fn]-NIL; -at ~rn[fn]-[~et [fn; EXPR]-~~~~~ [expr^1 ; args ; a]; -spread[args]; -T-apply[cdr[sassoc [fn;a;~[[];error [~2]]]];ar~s ;a]; -eq[car[fn]; ~~~~~]-a~~l~[caddr[fn];ar~s;cons[cons[cadr[fn];caddr[fn]];a]]; -eq[car[fn]; ~~~~~~]-a~~1~[cadr [fn]; args; caddr [fn]]; -eq[car [fn]; LAMBDA]-eval[caddr[fn]; nconc [pair[cadr[fn]; args]; a]]; -~-a~~ly[eval[fn;a];ar~s ;a]] +```mexpr +apply[fn; args; a]=[ + null[fn] -> NIL; + atom[fn] -> [get[fn; EXPR] -> apply[expr ; args ; a]; + get[fn; subr] -> {spread[args]; + $ALIST := a; + TSX subr, 4}; + T -> apply[cdr[sassoc[fn; a; lambda[[];error [A2]]]]; args ;a]; + eq[car[fn]; LABEL] -> apply[caddr[fn];args; + cons[cons[cadr[fn]; + caddr[fn]];a]]; + eq[car[fn]; FUNARG] -> apply[cadr[fn]; args; caddr[fn]]; + eq[car [fn]; LAMBDA] -> eval[caddr[fn]; + nconc [pair[cadr[fn]; args]; a]]; + T -> apply[eval[fn; a]; args; a]] +``` +*NOTE THAT the formatting of this MEXPR is beyond the capabilities of Markdown to reproduce; this is a rational reconstruction, but to be perfectly certain of the interpretation consult the PDF* -1. The value of get is set aside. This is the meaning of the apparent free or unde- - fined variable - -* eval[f orm; a]= [ - null[f orm]-NIL; - numberp[f orm]-f orm; - atom[form]-[get[f O~~;APVAL]-car [apval^1 1; - ~~cdr[sassoc[form;a;~[[ ];error[A8]]]]]; - eq[car[f O~~];QUOTE]-cadr [form]; 2 - eq[car [form]; FUNCTION]-~~~~[FUNARG; cadr [form];a]; - eq[car [form]; COND]-evcon[cdr[form]; a]; - eq[car [form]; ~~OG]-~ro~[cdr [form]; a]; - atom[car[form]] -[get[car [form];~~~~]-~a~~l~[ex~r;^1 evlis[cdr lforrn];a];a]; - get[car[form];~~~~~]-apply[fexpr !list[cdr [form];a];a]; - spread[evlis [cdr [form]; a]]; - get[car[form];~~~~]- - TSX subr f 4 - AC: =cdr [ form]; - get[car[form];F~u~R]-, MQ: = $ALIST: =a; ; -# (TSx fsubr!4 } +----- + +1. The value of get is set aside. This is the meaning of the apparent free or undefined variable. + +page 71 + +```mexpr +eval[form; a]= [ + null[form] -> NIL; + numberp[form] -> form; + atom[form] -> [get[form; APVAL] -> car[apval]; + T -> cdr[sassoc[form; a; lambda[[ ]; error[A8]]]]]; + eq[car[form]; QUOTE] -> cadr[form]; + eq[car[form]; FUNCTION] -> list[FUNARG; cadr[form]; a]; + eq[car [form]; COND] -> evcon[cdr[form]; a]; + eq[car [form]; PROG] -> prog[cdr [form]; a]; + atom[car[form]] -> [get[car [form]; EXPR] -> + apply[expr; evlis[cdr[form]; a]; a]; + get[car[form]; FEXPR] -> + apply[fexpr; list[cdr[form]; a]; a]; + get[car[form]; SUBR] -> {spread[evlis[cdr[form]; a]]; + $ALIST := a; + TSX subr 4}; + get[car[form]; FSUBR] -> {AC := cdr[form]; + MQ := $ALIST := a; + TSX fsubr 4} + T -> eval[cons[cdr[sassoc[car[form]; a; + lambda[[];error[A9]]]]; + cdr[form]]; a]]; + T-apply [car [form];evlis [cdr [form]; a]; a]] + +evcon[c; a] = [null[c] -> error[A3]; + eval[caar[c]; a] -> eval[cadar[a]; a]; + T -> evcon[cdr[ c]; a]] + +evlis[m; a] = maplist[m; lambda[[j]; eval[car[j]; a]]] +``` +### The PROG Feature -~-e~al[~0ns[cdr[sas~0~[car[form];a;~[[];error[~9]]]]; -cdr [form]]; a]]; -T-apply [car [form];evlis [cdr [form]; a]; a]] -evcon[c; a]= [null[c]--error [A3]; -eval[caar[ c]; a]-eval[cadar [a];a]; -T-evcon[cdr [ c];a]] -evlis[ - m; a] =maplist [m; ~[[j]; eval[car[j]; a]]] -The PROG Feature The PROG feature is an FSUBR coded into the system. It can best be explained in English, although it is possible to define it by using M-expressions. @@ -3367,51 +3383,53 @@ paired with a pointer into the remainder of the program. 3. When a set or a setq - is encountered, the name of the variable is located on the a-list. The value of the variable (or cs of the pair) is actually replaced with the new value. + +----- 1. The value of get is set aside. This is the meaning of the apparent free or unde- fined variable- 2. In the actual system this is handled by an FSUBR rather than as the separate special case as shown here. +page 72 + If the variable is bound several times on the a-list, only the first or most recent occurrence is changed. If the current binding of the variable is at a higher level than the entrance to the prog, then the change will remain in effect throughout the scope of that binding, and the old value will be lost. -If the variable does not occur on the a-list, then error diagnostic A4 or A5 will -occur. -4. When a return is encountered at any point, its argument is evaluated and returned -as the value of the most recent prog that has been entered. +If the variable does not occur on the a-list, then error diagnostic `A4` or `A5` will occur. + +4. When a return is encountered at any point, its argument is evaluated and returned as the value of the most recent prog that has been entered. 5. The form go may be used only in two ways. -a. (GO X) may occur on the top level of the prog, x must be a location symbol -of this prog and not another one on a higher or lower level. -b. This form may also occur as one of the value parts of a conditional expres- -sion, if this conditional expression occurs on the top level of the prog. -If a go - is used incorrectly or refers to a nonexistent location, error diagnostic A6 -will occur. -6. When the form cond occurs on the top level of a prog, it differs from other -conds in the following ways. -a. It is the only instance in which a gocan occur inside a cond. -b. If the cond runs out of clauses, error diagnostic A3 will not occur. Instead, -the prog will c6ntinue with the next statement. + a. `(GO X)` may occur on the top level of the prog, `x` must be a location symbol of this `prog` and not another one on a higher or lower level. + b. This form may also occur as one of the value parts of a conditional expression, if this conditional expression occurs on the top level of the `prog`. + If a `go` is used incorrectly or refers to a nonexistent location, error diagnostic `A6` will occur. + +6. When the form cond occurs on the top level of a `prog`, it differs from other + `cond`s in the following ways. + a. It is the only instance in which a `go` can occur inside a `cond`. + b. If the `cond` runs out of clauses, error diagnostic `A3` will not occur. Instead, the `prog` will continue with the next statement. + 7. When a statement is executed, this has the following meaning, with the exception -of the special forms cond, go, return, setq and the pseudo-functionset, all of which -are peculiar to prog. -The statement 5 is executed by performing eval[s;a], where 2 is the current a-list, -and then ignoring the value. + of the special forms `cond`, `go`, `return`, `setq` and the pseudo-function `set`, all of which are peculiar to `prog`. + The statement `s` is executed by performing `eval[s;a]`, where `a` is the current a-list, and then ignoring the value. + 8. If a prog runs out of statements, its value is NIL. -When a prog - is compiled, it will have the same effect as when it is interpreted, -although the method of execution is much different; for example, a go is always corn- -piled as a transfer. The following points should be noted concerning declared variables.^1 -1. Program variables follow the same rules as h variables do. -a. If a variable is purely local, it need not be declared. -b. Special variables can be used as free variables in compiled functions. They -may be set at a lower level than that at which they are bound. -c. Common program variables maintain complete communication between com- -piled programs and the interpreter. -2. & as distinct from setq can only be used to set common variables. + When a prog - is compiled, it will have the same effect as when it is interpreted, although the method of execution is much different; for example, a go is always cornpiled as a transfer. The following points should be noted concerning declared variables.1 + 1. Program variables follow the same rules as h variables do. + a. If a variable is purely local, it need not be declared. + b. Special variables can be used as free variables in compiled functions. They may be set at a lower level than that at which they are bound. + c. Common program variables maintain complete communication between compiled programs and the interpreter. + + 2. & as distinct from setq can only be used to set common variables. + + +----- 1. See Appendix D for an explanation of variable declaration. -APPENDIX C : THE LISP ASSEMBLY PROGRAM (LAP) +page 73 + +## APPENDIX C : THE LISP ASSEMBLY PROGRAM (LAP) lap is a two-pass assembler. It was specifically designed for use by the new com- piler, but it can also be used for defining functions in machine language, and for making @@ -3455,6 +3473,9 @@ FSUBR. n is the number of arguments which the subroutine expects. Symbols Atomic symbols appearing on the listing (except NIL or the first item on the listing) + +page 74 + are treated as location symbols. The appearance of the symbol defines it as the location of the next instruction in the listing. During pass one, these symbols and their values are made into a pair list, and appended to the initial symbol table to form the final sym- @@ -3500,6 +3521,8 @@ literal will not be created if it is equal to one that already exists. 4. If the field is of the form (SPECIAL x), then the value is the address of the SPECIAL cell on the property list of x. If one does not already exist, it will be created. +page 75 + The SPECIAL cell itself (but not the entire atom) is protected against garbage collection. 5. In all other cases, the field is assumed to be a list of subfields, and their sum @@ -3566,8 +3589,10 @@ LAP ( (6217Q (TRA NIL) )NIL) LAP ( (NIL (CLA A) (TSX 6204Q) (TRA B) ) ( (A 6243Q) (B 6220Q) ) ) -APPENDIX D -THE LISP COMPILER +page 76 + +## APPENDIX D : THE LISP COMPILER + The LISP Compiler is a program written in LISP that translates S-expression defi- nitions of functions into machine language subroutines. It is an optional feature that makes programs run many times faster than they would if they were to be interpreted @@ -3580,12 +3605,13 @@ space. Thus an EXPR, or an FEXPR, has been changed to a SUBR or an FSUBR, respectively. Experience has shown that compiled programs run anywhere from 10 to 100 times as fast as interpreted programs, the time depending upon the nature of the program. -Compiled programs are also more economical with memory than their corresponding 1 -S-expressions, taking only from 50 per cent to 80 per cent as much space.' +Compiled programs are also more economical with memory than their corresponding +S-expressions, taking only from 50 per cent to 80 per cent as much space.1 The major part of the compiler is a translator or function from the S-expression function notation into the assembly language, LAP. The only reasons why the compiler is regarded as a pseudo-function are that it calls LAP, and it removes EXPRts and FEXPR1s when it has finished compiling. + The compiler has an interesting and perhaps unique history. It was developed in the following steps: @@ -3600,18 +3626,20 @@ tape is created, the entire compiler was punched out in assembly language by usi punc hlap. 4. When a system tape is to be made, the compiler in assembly language is read in by using readlap. -The compiler is called by using the pseudo-function compile. The argument of com- -- pile is a list of the names of functions to be compiled. Each atomic symbol on this list +The compiler is called by using the pseudo-function compile. The argument of compile is a list of the names of functions to be compiled. Each atomic symbol on this list should have either an EXPR or an FEXPR on its property list before being compiled. The processing of each function occurs in three steps. First, the S-expression for the function is translated into assembly language. If no S-expression is found, then the compiler will print this fact and proceed with the next function. Second, the assembly +----- 1. Since the compiled program is binary program space, which is normally not otherwise accessible, one gains as free storage the total space formerly occupied by the S-expression definition. +page 77 + language program is assembled by LAP. Finally, if no error has occurred, then the EXPR or FEXPR is removed from the property list. When certain errors caused by undeclared free variables occur, the compiler will print a diagnostic and continue. @@ -3658,6 +3686,8 @@ When a variable is used free, it must have been bound by a higher level function If a program is being run interpretively, and a free variable is used without having been bound on a higher level, error diagnostic *A^89 will occur. +page 78 + If the program is being run compiled, the diagnostic may not occur, and the variable may have value NIL. There are three types of variables in compiled functions: ordinary variables, @@ -3699,6 +3729,8 @@ Consider the following definition of a function dot by using an S-expression: (YDOT (LAMBDA (X Y) (MAPLIST X (FUNCTION (LAMBDA (J) (CONS (CAR J) Y)) )))) +page 79 + Following the word FUNCTION is a functional constant. If we consider it as a sep- arate function, it is evident that it contains a bound variable "Jtt, and a free variable "Yfl. This free variable must be declared SPECIAL or COMMON, even though it is @@ -3733,6 +3765,8 @@ form the instruction TSX and plant this on top of the STR. 2. Once a direct TSX link is made, this particular calling point will not be traced. (Link will not make a TSX as long as the called function is being traced. ) +page 80 + ## APPENDIX E : OVERLORD - THE MONITOR Overlord is the monitor of the LISP System. It controls the handling of tapes, the @@ -3773,6 +3807,8 @@ card columns to use are as follows. address data word +page 81 + Overlord cards have the Overlord direction beginning in column 8. If the card has no other field, then comments may begin in column 16. Otherwise, the other fields of the card begin in column 16 and are separated by commas. The comments may begin @@ -3833,6 +3869,8 @@ Causes the computer to halt. An end of file mark is written on SYSPOT. An end of file is written on SYSPPT only if it has been used. If the FIN card was read on-line, the computer halts after doing these things. If the FIN card came from SYSPIT, then +page 82 + SYSPIT is advanced past the next end of file mark before the halt occurs. Use of Sense Switches @@ -3856,6 +3894,8 @@ become a system tape containing the basic system plus any changes that have been onto it. It may be mounted on the SYSTAP drive for some future run to use definitions that have been set onto it. +page 83 + ## APPENDIX F : LISP INPUT AND OUTPUT This appendix describes the LISP read and write programs and the character- @@ -3897,6 +3937,8 @@ b. The first two characters must not be $ $. c. It must be delimited on either side by a character from class C. There is a provision for reading in atomic symbols containing arbitrary characters. +page 84 + This is done by punching the form $$dsd, where s is any string of up to 30 characters, and d is any character not contained in the string s. Only the string s is used in forming the print name of the atomic symbol; d and the dollar signs will not appear when @@ -3940,6 +3982,9 @@ to Z. Each letter is a legitimate atomic symbol, and therefore may be referred t a straightforward way, without ambiguity. The second group of legal characters consists of the digits from 0 to 9. These must be handled with some care because if a digit is considered as an ordinary integer + +page 85 + rather than a character a new nonunique object will be created corresponding to it, and this object will not be the same as the character object for the same digit, even though it has the same print name. Since the character-handling programs depend on the char- @@ -3992,6 +4037,8 @@ Examples EVAL (DOLLAR NIL) value is " $ EVAL ((PRINT PERIOD) NIL) value is ". and If. is also printed. +page 86 + The remaining characters are all illegal as far as the key punch is concerned. The two characters corresponding to 12 and 72 have been reserved for end-of-file and end- of-record, respectively, The end-of-file character has print name $EOF$ and the end- @@ -4036,6 +4083,8 @@ whose print name is in BOFFO. sented by the sequence of characters in BOFFO. (Positive decimal integers from 0 to 9 are converted so as to point to the corresponding character object. ) +page 87 + 5. unpack [x]: SUBR pseudo-function This function has as argument a pointer to a full word. unpack considers the full word to be a set of 6 BCD characters, and has as value a list of these @@ -4074,9 +4123,10 @@ an object). There is also an object CHARCOUNT whose value is an integer object g the column just read on the card, i. e., the column number of the character given by CURCHAR. There are three functions which affect the value of CURCHAR: -1. startread [ 1: : SUBR ps eudo-function - startread is a function of no arguments which causes a new card to be read. - The value of startread is the first character on that card, or more precisely, +#### 1. startread [ ] : SUBR ps eudo-function +startread is a function of no arguments which causes a new card to be read. The value of startread is the first character on that card, or more precisely, + +page 88 the object corresponding to the first character on the card. If an end-of-file condition exists, the value of startread is $EOF$. The value of CURCHAR @@ -4085,35 +4135,30 @@ becomes 1. Both CURCHAR and CHARCOUNT are undefined until a startread is performed. A startread may be performed before the current card has been completely read. -2. advance [ 1: SUBR pseudo -function - advance is a function of no arguments which causes the next character to be - read. The value of advance is that character. After the 72nd character on the - card has been read, the next advance will have value $EOR$. After reading - $EOR$, the next advance will act like a startread, i. e., will read the first char- - acter of the next card unless an end-of-file condition exists. The new value of - CURCHAR is the same as the output of advance; executing advance also increases - the value of CHARCOUNT by 1. However, CHARCOUNT is undefined when - CURCHAR is either $EOR $ or $EOF $. -3. endread [ 1: SUBR pseudo-function - endread is a function of no arguments which causes the remainder of the - card to be read and ignored. endread sets CURCHAR to $EOR$ and leaves - CHARCOUNT undefined; the value of endread is always $EOR $. An advance - following endread acts like a startread. If CURCHAR already has value $EOR $ - and endread is performed, CURCHAR will remain the same and endread will, - as usual, have value $EOR $. +#### 2. advance [ ] : SUBR pseudo -function -Diagnostic Function +advance is a function of no arguments which causes the next character to be read. The value of advance is that character. After the 72nd character on the card has been read, the next advance will have value $EOR$. After reading $EOR$, the next advance will act like a startread, i. e., will read the first char acter of the next card unless an end-of-file condition exists. The new value of CURCHAR is the same as the output of advance; executing advance also increases the value of CHARCOUNT by 1. However, CHARCOUNT is undefined when CURCHAR is either $EOR $ or $EOF $. + +#### 3. endread [ ] : SUBR pseudo-function -error 1 [ 1: SUBR pseudo-function -errorL is a function of no arguments and has value NIL. It should be executed +endread is a function of no arguments which causes the remainder of the card to be read and ignored. endread sets CURCHAR to $EOR$ and leaves CHARCOUNT undefined; the value of endread is always $EOR $. An advance following endread acts like a startread. If CURCHAR already has value $EOR $ and endread is performed, CURCHAR will remain the same and endread will, as usual, have value $EOR $. + +### Diagnostic Function + +#### error 1 [ ]: SUBR pseudo-function + +error1 is a function of no arguments and has value NIL. It should be executed only while reading characters from a card (or tape). Its effect is to mark the char- acter just read, i. e., CURCHAR, so that when the end of the card is reached, either by successive advances or by an endread, the entire card is printed out along with a visual pointer to the defective character. For a line consisting of ABCDEFG fol- lowed by blanks, a pointer to C would look like this: -v + +``` + v ABCDEFG -A + A +``` If error 1 is performed an even number of times on the same character, the A will not appear. If error1 is performed before the first startread or while CURCHAR has value $EOR $ or $EOF $, it will have no effect. Executing a startread before @@ -4122,28 +4167,27 @@ card is considered to have been completed when CURCHAR has been set to $EOR$. Successive endreads will cause the error l printout to be reprinted. Any number of characters in a given line may be marked by error1. +page 89 + ## APPENDIX G : MEMORY ALLOCATION AND THE GARBAGE COLLECTOR The following diagram shows the way in which space is allocated in the LISP System. -Loader -LAP - -Compiler - -Free Storage - -Full Words - -Push-Down List - -Binary Program Space - -Interpreter, I/O, Read -Print, Arithmetic, -Overlord, Garbage -Collector, and other -system coding +| Address (octal) | Assigned to | +| --------------- | ------------------------------------------------------------ | +| 77777 | ----- | +| | Loader | +| 77600 | ----- | +| | LAP | +| | Compiler | +| 70000 | ----- | +| | Free storage | +| | Full words | +| | Pushdown list | +| | Binary program space | +| 17000 | | +| | Interpreter, I/O, Read Print, Arithmetic, Overlord, Garbage Collector, and other system coding | +| 00000 | | The addresses in this chart are only approximate. The available space is divided among binary program space, push-down list, full-word space, and free-storage space @@ -4159,6 +4203,9 @@ FEXPR1s, evalquote doublets waiting to be executed, APVALts, and partial results the computation that is in progress. Full-word space is filled with the BCD characters of PNAMEts, the actual numbers + +page 90 + of numerical atomic structures, and the TXL words of SUBRtsB FSUBRts, and SYMts. All available words in the free-storage area that are not in use are strung together in one long list called the free-storage list. Every time a word is needed (for example, @@ -4193,6 +4240,8 @@ can be recognized by the stationary pattern of the MQ lights. Any trap that prev completion of a garbage collection will create a panic condition in memory from which there is no recovery. +page 91 + ## APPENDIX H : RECURSION AND THE PUSH-DOWN LIST One of the most powerful resources of the LISP language is its ability to accept @@ -4239,6 +4288,9 @@ ter 1, to place the arguments on the push-down list, and to set up the parameter the push-down block. Because pointers to list structures are normally stored on the push-down list, the + +page 92 + garbage collector must mark the currently active portion of the push-down list during a garbage collection. Sometimes quantities are placed on the push- down list which should not be marked. In this case, the sign bit must be negative. Cells on the active portion @@ -4250,6 +4302,8 @@ list has the name of the function to which it belongs, it is possible to form a these names. This is called the backtrace, and is normally printed out after error diagnostics. +page 93 + ## APPENDIX I : LISP FOR SHARE DISTRIBUTION The Artificial Intelligence Project at Stanford University has produced a version of @@ -4294,6 +4348,8 @@ the time spent in the packet being finished. This time printout, to be meaningfu requires the computer to have a millisecond clock in cell 5 (RPQ F 89349, with mil- lisecond feature). +page 94 + It is also possible to determine how much time is required to execute a given func- tion. llTIMEl()lt initializes two time cells to zero and prints out, in the same format that is used for the evalquote time printout, two times, and these are both zero. @@ -4338,6 +4394,9 @@ after x number of function entrances. Furthermore, when the tracecount mecha- nism has been activated, by execution of ltTRACECOUNT(x)ll, some of the blank space in the garbage collector printout will be used to output the number of function entrances which have taken place up to the time of the garbage collection; each time + +page 95 + the arguments or value of a traced function are printed the number of function en- trances will be printed; and if an error occurs, the number of function entrances ac- complished before the error will be printed. @@ -4384,6 +4443,9 @@ of LISP to communicate between different systems. The functions tape, -- rewind, ttTAPE(s)tt, where s is a list, allows the user to specify up to ten scratch tapes; if more than ten are specified, only the first ten are used. The value of tape is its argument. The initial tape settings are, from one to ten, A4, A5, A6, A7, A8, B2, + +page 96 + B3, B4, B5, B6. The tapes must be specified by the octal number that occurs in the address portion of a machine-language instruction to rewind that tape; that is, a four- digit octal number is required - the first (high-order) digit is a 1 if channel A is de- @@ -4429,6 +4491,8 @@ Evalquote is available to the programmer as a LISP function - thus, one may now write I1(EVALQUOTE APPEND ((A)(B C D)))I1, rather than "(EVAL (QUOTE (APPEND (A)(B C D))) NIL)", should one desire to do so. +page 97 + ### Backtrace This function was copied (not quite literally) from M. I. T.'s LISP system on the @@ -4477,6 +4541,9 @@ defined results. For the convenience of those who find it difficult to get along with the tlCONDn form of the conditional statement, the following "IF" forms are provided in the new system. + +page 98 + "IF (a THEN b ELSE c)I1 and "IF (a b c)I1 are equivalent to nCOND ((a b)(T c))". "IF (a THEN b)n and "IF (a b)" are equivalent to "COND ((a b))". @@ -4518,6 +4585,8 @@ Characteristics of the System The set-up deck supplied with the SHARE LISP system produces a system tape with the following properties: +page 99 + Size (in words) - Binary Program Space 14000 octal Push-Down List 5000 octal @@ -4539,7 +4608,9 @@ SW5 on to suppress SYSPOT output SW6 on to return to overlord after accumulator printout resulting from error *I? 5*. SW6 off for error printout. -## Index +page 100 + +## Index to function descriptions | Function | Call type | Implementation | Pages | |--------------|------------|------------------|------------------------------| diff --git a/docs/index.html b/docs/index.html new file mode 120000 index 0000000..2eb3014 --- /dev/null +++ b/docs/index.html @@ -0,0 +1 @@ +codox/intro.html \ No newline at end of file diff --git a/resources/lisp1.5.lsp b/resources/lisp1.5.lsp index ddf36b2..d95abb7 100644 --- a/resources/lisp1.5.lsp +++ b/resources/lisp1.5.lsp @@ -12,7 +12,7 @@ 32767 EXPR (LAMBDA - (X Y) (COND ((NULL X) Y) ((QUOTE T) (CONS (CAR X) (APPEND (CDR X) Y)))))) + (X Y) (COND ((NULL X) Y) (T (CONS (CAR X) (APPEND (CDR X) Y)))))) (APPLY 32767 SUBR (BEOWULF BOOTSTRAP APPLY)) (ASSOC 32767 @@ -20,9 +20,9 @@ (LAMBDA (X L) (COND - ((NULL L) (QUOTE NIL)) + ((NULL L) NIL) ((AND (CONSP (CAR L)) (EQ (CAAR L) X)) (CAR L)) - ((QUOTE T) (ASSOC X (CDR L))))) + (T (ASSOC X (CDR L))))) SUBR (BEOWULF HOST ASSOC)) (ATOM 32767 SUBR (BEOWULF HOST ATOM)) (CAR 32767 SUBR (BEOWULF HOST CAR)) @@ -63,14 +63,14 @@ (LAMBDA (X) (COND - ((NULL X) (QUOTE NIL)) - ((ATOM X) X) ((QUOTE T) (CONS (COPY (CAR X)) (COPY (CDR X))))))) + ((NULL X) NIL) + ((ATOM X) X) (T (CONS (COPY (CAR X)) (COPY (CDR X))))))) (DEFINE 32767 SUBR (BEOWULF HOST DEFINE)) (DIFFERENCE 32767 SUBR (BEOWULF HOST DIFFERENCE)) (DIVIDE 32767 EXPR - (LAMBDA (X Y) (CONS (QUOTIENT X Y) (CONS (REMAINDER X Y) (QUOTE NIL))))) + (LAMBDA (X Y) (CONS (QUOTIENT X Y) (CONS (REMAINDER X Y) NIL)))) (DOC 32767 SUBR (BEOWULF HOST DOC)) (EFFACE 32767 @@ -78,8 +78,8 @@ (LAMBDA (X L) (COND - ((NULL L) (QUOTE NIL)) - ((EQUAL X (CAR L)) (CDR L)) ((QUOTE T) (RPLACD L (EFFACE X (CDR L))))))) + ((NULL L) NIL) + ((EQUAL X (CAR L)) (CDR L)) (T (RPLACD L (EFFACE X (CDR L))))))) (ERROR 32767 SUBR (BEOWULF HOST ERROR)) (EQ 32767 SUBR (BEOWULF HOST EQ)) (EQUAL 32767 SUBR (BEOWULF HOST EQUAL)) @@ -95,8 +95,8 @@ (LAMBDA (X Y) (COND - ((NULL X) (QUOTE NIL)) - ((EQ (CAR X) Y) (CAR (CDR X))) ((QUOTE T) (GET (CDR X) Y)))) + ((NULL X) NIL) + ((EQ (CAR X) Y) (CAR (CDR X))) (T (GET (CDR X) Y)))) SUBR (BEOWULF HOST GET)) (GREATERP 32767 SUBR (BEOWULF HOST GREATERP)) (INTEROP 32767 SUBR (BEOWULF INTEROP INTEROP)) @@ -106,9 +106,9 @@ (LAMBDA (X Y) (COND - ((NULL X) (QUOTE NIL)) + ((NULL X) NIL) ((MEMBER (CAR X) Y) (CONS (CAR X) (INTERSECTION (CDR X) Y))) - ((QUOTE T) (INTERSECTION (CDR X) Y))))) + (T (INTERSECTION (CDR X) Y))))) (LENGTH 32767 EXPR @@ -122,7 +122,7 @@ (LAMBDA (L F) (COND - ((NULL L) NIL) ((QUOTE T) (CONS (F (CAR L)) (MAPLIST (CDR L) F)))))) + ((NULL L) NIL) (T (CONS (F (CAR L)) (MAPLIST (CDR L) F)))))) (MEMBER 32767 EXPR @@ -130,11 +130,11 @@ (A X) (COND ((NULL X) (QUOTE F)) - ((EQ A (CAR X)) (QUOTE T)) ((QUOTE T) (MEMBER A (CDR X)))))) + ((EQ A (CAR X)) T) (T (MEMBER A (CDR X)))))) (MINUSP 32767 EXPR (LAMBDA (X) (LESSP X 0))) - (NOT 32767 EXPR (LAMBDA (X) (COND (X (QUOTE NIL)) ((QUOTE T) (QUOTE T))))) + (NOT 32767 EXPR (LAMBDA (X) (COND (X NIL) (T T)))) (NULL - 32767 EXPR (LAMBDA (X) (COND ((EQUAL X NIL) (QUOTE T)) (T (QUOTE F))))) + 32767 EXPR (LAMBDA (X) (COND ((EQUAL X NIL) T) (T (QUOTE F))))) (NUMBERP 32767 SUBR (BEOWULF HOST NUMBERP)) (OBLIST 32767 SUBR (BEOWULF HOST OBLIST)) (ONEP 32767 EXPR (LAMBDA (X) (EQ X 1))) @@ -155,7 +155,7 @@ (X Y A) (COND ((NULL X) A) - ((QUOTE T) (CONS (CONS (CAR X) (CAR Y)) (PAIRLIS (CDR X) (CDR Y) A))))) + (T (CONS (CONS (CAR X) (CAR Y)) (PAIRLIS (CDR X) (CDR Y) A))))) SUBR (BEOWULF HOST PAIRLIS)) (PLUS 32767 SUBR (BEOWULF HOST PLUS)) (PRETTY 32767) @@ -167,7 +167,7 @@ (X Y U) (COND ((NULL X) (U)) - ((EQ (CAR X) Y) (CDR X)) ((QUOTE T) (PROP (CDR X) Y U))))) + ((EQ (CAR X) Y) (CDR X)) (T (PROP (CDR X) Y U))))) (QUOTE 32767 EXPR (LAMBDA (X) X)) (QUOTIENT 32767 SUBR (BEOWULF HOST QUOTIENT)) (RANGE @@ -176,7 +176,7 @@ (LAMBDA (N M) (COND - ((LESSP M N) (QUOTE NIL)) ((QUOTE T) (CONS N (RANGE (ADD1 N) M)))))) + ((LESSP M N) NIL) (T (CONS N (RANGE (ADD1 N) M)))))) (READ 32767 SUBR (BEOWULF READ READ)) (REMAINDER 32767 SUBR (BEOWULF HOST REMAINDER)) (REPEAT @@ -193,9 +193,9 @@ (LAMBDA (A Z) (COND - ((NULL A) Z) ((EQ (CAAR A) Z) (CDAR A)) ((QUOTE T) (SUB2 (CDAR A) Z))))) + ((NULL A) Z) ((EQ (CAAR A) Z) (CDAR A)) (T (SUB2 (CDAR A) Z))))) (SUBLIS - 32767 EXPR (LAMBDA (A Y) (COND ((ATOM Y) (SUB2 A Y)) ((QUOTE T) (CONS))))) + 32767 EXPR (LAMBDA (A Y) (COND ((ATOM Y) (SUB2 A Y)) (T (CONS))))) (SUBST 32767 EXPR @@ -204,7 +204,7 @@ (COND ((EQUAL Y Z) X) ((ATOM Z) Z) - ((QUOTE T) (CONS (SUBST X Y (CAR Z)) (SUBST X Y (CDR Z))))))) + (T (CONS (SUBST X Y (CAR Z)) (SUBST X Y (CDR Z))))))) (SYSIN 32767 SUBR (BEOWULF IO SYSIN)) (SYSOUT 32767 SUBR (BEOWULF IO SYSOUT)) (TERPRI 32767) diff --git a/src/beowulf/bootstrap.clj b/src/beowulf/bootstrap.clj index 9637a69..09b4ee2 100644 --- a/src/beowulf/bootstrap.clj +++ b/src/beowulf/bootstrap.clj @@ -201,6 +201,8 @@ ([expr env depth] (trace-call 'EVAL (list expr env depth) depth) (let [result (cond + (= NIL expr) NIL ;; it was probably a mistake to make Lisp + ;; NIL distinct from Clojure nil (= (NUMBERP expr) T) expr (symbol? expr) (eval-symbolic expr env depth) (string? expr) (if (:strict *options*) diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj index 82821ce..d49296a 100644 --- a/src/beowulf/host.clj +++ b/src/beowulf/host.clj @@ -565,4 +565,4 @@ argument was, or was not, a cons cell." [o] (when (lax? 'CONSP) - (if (instance? o ConsCell) 'T 'F))) \ No newline at end of file + (if (instance? ConsCell o) 'T 'F))) \ No newline at end of file