diff --git a/TEST.lsp b/TEST.lsp new file mode 100644 index 0000000..8e474c2 --- /dev/null +++ b/TEST.lsp @@ -0,0 +1,35 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Beowulf Sysout file generated at 2023-03-29T12:34:39.278 +;; generated by simon +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +((NULL + LAMBDA (X) (COND ((EQUAL X (QUOTE NIL)) (QUOTE T)) ((QUOTE T) (QUOTE F)))) + (GCD + LAMBDA + (X Y) + (COND + ((GREATERP X Y) (GCD Y X)) + ((EQUAL (REMAINDER Y X) 0) X) ((QUOTE T) (GCD (REMAINDER Y X) X)))) + (NIL) + (T . T) + (F) + (ADD1) + (APPEND) + (APPLY) + (ATOM) + (CAR) + (CDR) + (CONS) + (DEFINE) + (DIFFERENCE) + (EQ) + (EQUAL) + (EVAL) + (FIXP) + (INTEROP) + (NUMBERP) + (OBLIST) + (PLUS) + (PRETTY) + (QUOTIENT) (REMAINDER) (RPLACA) (RPLACD) (SET) (SYSIN) (SYSOUT) (TIMES)) diff --git a/doc/mexpr.md b/doc/mexpr.md new file mode 100644 index 0000000..13445de --- /dev/null +++ b/doc/mexpr.md @@ -0,0 +1,62 @@ +# M-Expressions + +M-Expressions ('mexprs') are the grammar which John McCarthy origininally used to write Lisp, and the grammar in which many of the function definitions in the [Lisp 1.5 Programmer's Manual](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf) are stated. However, I have not seen anywhere a claim that Lisp 1.5 could *read* M-Expressions, and it is not clear to me whether it was even planned that it should do so. + +Rather, it seems to me probably that M-Expressions were only ever a grammar intended to be written on paper, like [Backus Naur Form](https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form), to describe and to reason about algorithms. + +I set out to make Beowulf read M-Expressions essentially out of curiousity, to see whether it could be done. I had this idea that if it could be done, I could implement most of Lisp 1.5 simply by copying in the M-Expression definitions out of the manual. + +Consequently, the Beowulf parser can parse the M-Expression grammar as stated in the manual, and generate S-Expressions from it according to the table specified on page 10 of the manual. + +There are two problems with this. + +## Problems with interpreting M-Expressions + +### Generating idiomatic Lisp + +In the M-Expression notation, a lower case character or sequence of characters represents a variable; an upper case character represents a constant. As the manual says, + +> 2 . The obvious translation of letting a constant translate into itself will not work. +Since the translation of `x` is `X`, the translation of `X` must be something else to avoid +ambiguity. The solution is to quote it. Thus `X` is translated into `(QUOTE X)`. + +Thus, necessarily, the translation of a constant must always be quoted. In practice, key constants in Lisp such as `T` are bound to themselves, so it is idiomatic in Lisp, certainly in the way we have learned to use it, to write, for example, + +``` +(SET (QUOTE NULL) + (QUOTE (LAMBDA (X) + (COND + ((EQUAL X NIL) T) (T F))))) +``` + +However, the literal translation of + +``` +null[x] = [x = NIL -> T; T -> F] +``` + +is + +``` +(SET (QUOTE NULL) + (QUOTE (LAMBDA (X) + (COND + ((EQUAL X (QUOTE NIL)) (QUOTE T)) + ((QUOTE T) (QUOTE F)))))) +``` + +This is certainly more prolix and more awkward, but it also risks being flat wrong. + +Is the value of `NIL` the atom `NIL`, or is it the empty list `()`? If the former, then the translation from the M-Expression above is correct. However, that means that recursive functions which recurse down a list seeking the end will fail. So the latter must be the case. + +`NULL` is described thus (Ibid, p11): + +> This is a predicate useful for deciding when a list is exhausted. It is true if and only if its argument is `NIL`. + +I think there is an ambiguity in referencing constants which are not bound to themselves in the M-Expression notation as given in the manual. This is particularly problematic with regards to `NIL`, but there may be others instances. + +### Curly braces + +The use of curly braces is not defined in the grammar as stated on page 10. They are not used in the initial definition of `APPLY` on page 13, but they are used in the more developed restatement on page 70. I believe they are to be read as indicating a `DO` statement -- a list of function calls to be made sequentially but without strict functional dependence on one another -- but I don't find the exposition here particularly clear and I'm not sure of this. + +Consequently, the M-Expression interpreter in Beowulf does not interpret curly braces. \ No newline at end of file diff --git a/resources/count.lsp b/resources/count.lsp deleted file mode 100644 index ca55508..0000000 --- a/resources/count.lsp +++ /dev/null @@ -1 +0,0 @@ -(DEFUN COUNT (L) (COND ((EQ '() L) 0) (T (PLUS 1 (COUNT (CDR L)))))) \ No newline at end of file diff --git a/resources/gcd.mexpr.lsp b/resources/gcd.mexpr.lsp index f5597b4..3190033 100644 --- a/resources/gcd.mexpr.lsp +++ b/resources/gcd.mexpr.lsp @@ -1,3 +1,5 @@ gcd[x;y] = [x>y -> gcd[y;x]; rem[y;x] = 0 -> x; - T -> gcd[rem[y;x];x]] \ No newline at end of file + T -> gcd[rem[y;x];x]] + +;; gcd[x;y] = [x>y -> gcd[y;x]; rem[y;x] = 0 -> x; T -> gcd[rem[y;x];x]] \ No newline at end of file diff --git a/resources/length.lsp b/resources/length.lsp new file mode 100644 index 0000000..b08df95 --- /dev/null +++ b/resources/length.lsp @@ -0,0 +1 @@ +(DEFUN LENGTH (L) (COND ((EQ NIL L) 0) (T (ADD1 (LENGTH (CDR L)))))) \ No newline at end of file diff --git a/resources/lisp1.5.lsp b/resources/lisp1.5.lsp index 4cefe88..0d2c983 100644 --- a/resources/lisp1.5.lsp +++ b/resources/lisp1.5.lsp @@ -7,6 +7,7 @@ ;; Binding all system functions to NIL so that you can see on the OBLIST that ;; they exist. (ADD1 . NIL) +(AND . NIL) (APPEND . NIL) (APPLY . NIL) (ATOM . NIL) diff --git a/src/beowulf/bootstrap.clj b/src/beowulf/bootstrap.clj index aab240b..330034b 100644 --- a/src/beowulf/bootstrap.clj +++ b/src/beowulf/bootstrap.clj @@ -13,7 +13,7 @@ [clojure.tools.trace :refer [deftrace]] [beowulf.cons-cell :refer [CAR CDR CONS LIST make-beowulf-list make-cons-cell pretty-print T F]] - [beowulf.host :refer [ADD1 DIFFERENCE FIXP NUMBERP PLUS QUOTIENT + [beowulf.host :refer [AND ADD1 DIFFERENCE FIXP NUMBERP PLUS QUOTIENT REMAINDER RPLACA RPLACD SUB1 TIMES]] [beowulf.io :refer [SYSIN SYSOUT]] [beowulf.oblist :refer [*options* oblist NIL]] @@ -404,6 +404,7 @@ (APPLY fn args environment) (case function-symbol ;; there must be a better way of doing this! ADD1 (apply ADD1 args) + AND (apply AND args) APPEND (apply APPEND args) APPLY (apply APPLY args) ATOM (ATOM? (CAR args)) @@ -417,6 +418,7 @@ ;; think about EVAL. Getting the environment right is subtle FIXP (apply FIXP args) INTEROP (when (lax? INTEROP) (apply INTEROP args)) + LIST (apply LIST args) NUMBERP (apply NUMBERP args) OBLIST (OBLIST) PLUS (apply PLUS args) diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj index b4220bc..f703100 100644 --- a/src/beowulf/host.clj +++ b/src/beowulf/host.clj @@ -13,6 +13,17 @@ ;; those which can be implemented in Lisp should be, since that aids ;; portability. +(defn AND + "True if and only if none of my `args` evaluate to either `F` or `NIL`, + else `F`. + + In `beowulf.host` principally because I don't yet feel confident to define + varargs functions in Lisp." + [& args] + (if (empty? (filter #(or (= 'F %) (empty? %)) args)) + 'T + 'F)) + (defn RPLACA "Replace the CAR pointer of this `cell` with this `value`. Dangerous, should really not exist, but does in Lisp 1.5 (and was important for some