diff --git a/README.md b/README.md index c8781ef..0bfbecb 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,34 @@ You will require to have [Leiningen](https://leiningen.org/) installed. This will start a Lisp 1.5 read/eval/print loop (REPL). -To end a session, type `quit` at the command prompt. +Command line arguments are as follows: + +``` + -f FILEPATH, --file-path FILEPATH Set the path to the directory for reading and writing Lisp files. + -h, --help + -p PROMPT, --prompt PROMPT Sprecan:: Set the REPL prompt to PROMPT + -r INITFILE, --read INITFILE resources/lisp1.5.lsp Read Lisp system from file INITFILE + -s, --strict Strictly interpret the Lisp 1.5 language, without extensions. + -t, --trace Trace Lisp evaluation. +``` + +To end a session, type `STOP` at the command prompt. + +### Input/output + +Lisp 1.5 greatly predates modern computers. It had a facility to print to a line printer, or to punch cards on a punch-card machine, and it had a facility to read system images in from tape; but there's no file I/O as we would currently understand it, and, because there are no character strings and the valid characters within an atom are limited, it isn't easy to compose a sensible filename. + +I've provided two functions to work around this problem. + +#### SYSOUT + +`SYSOUT` dumps the global object list to disk as a single S Expression (specifically: an association list). This allows you to persist your session, with all your current work, to disk. The function takes one argument, expected to be a symbol, and, if that argument is provided, writes a file whose name is that symbol with `.lsp` appended. If no argument is provided, it will construct a filename comprising the token `Sysout`, followed by the current date, followed by `.lsp`. In either case the file will be written to the directory given in the FILEPATH argument at startup time, or by default the current directory. + +Obviously, `SYSOUT` may be called interactively (and this is the expected practice). + +#### SYSIN + +`SYSIN` reads a file from disk and overwrites the global object list with its contents. The expected practice is that this will be a file created by `SYSOUT`. A command line flag `--read` is provided so that you can specify ## Learning Lisp 1.5 diff --git a/resources/lisp1.5.lsp b/resources/lisp1.5.lsp index 96182da..4cefe88 100644 --- a/resources/lisp1.5.lsp +++ b/resources/lisp1.5.lsp @@ -5,7 +5,7 @@ ;; it to NIL (F . NIL) ;; Binding all system functions to NIL so that you can see on the OBLIST that -;; they exist +;; they exist. (ADD1 . NIL) (APPEND . NIL) (APPLY . NIL) @@ -25,6 +25,7 @@ (PLUS . NIL) (PRETTY . NIL) (QUOTIENT . NIL) +(READ . NIL) (REMAINDER) (RPLACA . NIL) (RPLACD . NIL) diff --git a/src/beowulf/bootstrap.clj b/src/beowulf/bootstrap.clj index 8443b15..df1f8ce 100644 --- a/src/beowulf/bootstrap.clj +++ b/src/beowulf/bootstrap.clj @@ -16,7 +16,8 @@ [beowulf.host :refer [ADD1 DIFFERENCE FIXP NUMBERP PLUS QUOTIENT REMAINDER RPLACA RPLACD SUB1 TIMES]] [beowulf.io :refer [SYSIN SYSOUT]] - [beowulf.oblist :refer [*options* oblist NIL]]) + [beowulf.oblist :refer [*options* oblist NIL]] + [beowulf.read :refer [READ]]) (:import [beowulf.cons_cell ConsCell] [clojure.lang Symbol])) @@ -411,6 +412,7 @@ PLUS (apply PLUS args) PRETTY (apply pretty-print args) QUOTIENT (apply QUOTIENT args) + READ (READ) REMAINDER (apply REMAINDER args) RPLACA (apply RPLACA args) RPLACD (apply RPLACD args) diff --git a/src/beowulf/reader/generate.clj b/src/beowulf/reader/generate.clj index 7dc755e..d2e763b 100644 --- a/src/beowulf/reader/generate.clj +++ b/src/beowulf/reader/generate.clj @@ -174,7 +174,6 @@ (if (coll? p) (case (first p) - ">" 'GREATERP :λ "LAMBDA" :λexpr (make-cons-cell (generate (nth p 1)) @@ -184,6 +183,7 @@ :atom (symbol (second p)) :bindings (generate (second p)) :body (make-beowulf-list (map generate (rest p))) + (:coefficient :exponent) (generate (second p)) :cond (gen-cond p) :cond-clause (gen-cond-clause p) (:decimal :integer) (read-string (strip-leading-zeros (second p))) @@ -191,7 +191,6 @@ :dotted-pair (make-cons-cell (generate (nth p 1)) (generate (nth p 2))) - :exponent (generate (second p)) :fncall (gen-fn-call p) :iexpr (gen-iexpr p) :iop (case (second p) @@ -212,7 +211,7 @@ (list 'QUOTE (symbol (upper-case (second p))))) :mvar (symbol (upper-case (second p))) :octal (let [n (read-string (strip-leading-zeros (second p) "0")) - scale (generate (nth p 2))] + scale (generate (nth p 3))] (* n (expt 8 scale))) ;; the quote read macro (which probably didn't exist in Lisp 1.5, but...) @@ -221,7 +220,7 @@ (empty? (second p)) 0 (read-string (strip-leading-zeros (second p)))) :scientific (let [n (generate (second p)) - exponent (generate (nth p 2))] + exponent (generate (nth p 3))] (* n (expt 10 exponent))) ;; default diff --git a/src/beowulf/reader/parser.clj b/src/beowulf/reader/parser.clj index a08c8cb..30c2ae0 100644 --- a/src/beowulf/reader/parser.clj +++ b/src/beowulf/reader/parser.clj @@ -78,7 +78,7 @@ integer := #'-?[1-9][0-9]*'; decimal := #'-?[1-9][0-9]*\\.?[0-9]*' | #'0.[0-9]*'; scientific := coefficient e exponent; - coefficient := decimal; + coefficient := integer | decimal; exponent := integer; e := 'E'; octal := #'[+-]?[0-7]+{1,12}' q scale-factor; diff --git a/src/beowulf/reader/simplify.clj b/src/beowulf/reader/simplify.clj index 50b3833..7e75082 100644 --- a/src/beowulf/reader/simplify.clj +++ b/src/beowulf/reader/simplify.clj @@ -57,7 +57,7 @@ #(when (coll? %) (empty? %)) (case (first p) (:λexpr - :args :bindings :body :cond :cond-clause :defn :dot-terminal + :args :bindings :body :cond :cond-clause :defn :dot-terminal :fncall :lhs :quoted-expr :rhs ) (map #(simplify % context) p) (:arg :expr :coefficient :fn-name :number) (simplify (second p) context) (:arrow :dot :e :lpar :lsqb :opt-comment :opt-space :q :quote :rpar :rsqb diff --git a/test/beowulf/mexpr_test.clj b/test/beowulf/mexpr_test.clj index ea6528a..888e6e7 100644 --- a/test/beowulf/mexpr_test.clj +++ b/test/beowulf/mexpr_test.clj @@ -42,11 +42,16 @@ ;; Wrapping in a function call puts us into mexpr contest; ;; "T" would be interpreted as a sexpr, which would not be ;; quoted. - (let [expected "(ATOM A)" + (let [expected "(ATOM (QUOTE A))" actual (print-str (gsp "atom[A]"))] (is (= actual expected))) + (let [expected "(ATOM A)" + actual (print-str (gsp "atom[a]"))] + (is (= actual expected))) + ;; I'm not clear how `car[(A B C)]` should be translated, but ;; I suspect as (CAR (LIST A B C)). + (let [expected "(CAR (LIST A B C))" actual (print-str (gsp "car[(A B C)]"))] (is (= actual expected))) @@ -63,10 +68,10 @@ (deftest conditional-tests (testing "Conditional expressions" - (let [expected "(COND ((ATOM X) X) (T (FF (CAR X))))" + (let [expected "(COND ((ATOM X) X) ((QUOTE T) (FF (CAR X))))" actual (print-str (gsp "[atom[x]->x; T->ff[car[x]]]"))] (is (= actual expected))) - (let [expected "(LABEL FF (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR X))))))" + (let [expected "(LABEL FF (LAMBDA (X) (COND ((ATOM X) X) ((QUOTE T) (FF (CAR X))))))" actual (print-str (generate (simplify @@ -83,6 +88,6 @@ (deftest assignment-tests (testing "Function assignment" - (let [expected "(SET (QUOTE FF) (QUOTE (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR X)))))))" + (let [expected "(SET (QUOTE FF) (QUOTE (LAMBDA (X) (COND ((ATOM X) X) ((QUOTE T) (FF (CAR X)))))))" actual (print-str (gsp "ff[x]=[atom[x] -> x; T -> ff[car[x]]]"))] (is (= actual expected)))))