diff --git a/src/beowulf/bootstrap.clj b/src/beowulf/bootstrap.clj index 79e6a23..d7c3595 100644 --- a/src/beowulf/bootstrap.clj +++ b/src/beowulf/bootstrap.clj @@ -19,8 +19,8 @@ be Lisp 1.5's EQuivalent of `SETQ`), possibly by other things." (atom NIL)) -(def ^:dynamic *trace?* - "Whether or not to trace `EVAL`." +(def ^:dynamic *options* + "Command line options from invocation." false) (defn NULL @@ -304,7 +304,8 @@ See page 13 of the Lisp 1.5 Programmers Manual." [expr env] (cond - *trace?* (traced-eval expr env) + (true? (:trace *options*)) + (traced-eval expr env) (= (ATOM? expr) 'T) (CDR (ASSOC expr env)) diff --git a/src/beowulf/core.clj b/src/beowulf/core.clj index b57464a..6835742 100644 --- a/src/beowulf/core.clj +++ b/src/beowulf/core.clj @@ -1,5 +1,5 @@ (ns beowulf.core - (:require [beowulf.bootstrap :refer [EVAL oblist *trace?*]] + (:require [beowulf.bootstrap :refer [EVAL oblist *options*]] [beowulf.read :refer [READ]] [clojure.java.io :as io] [clojure.pprint :refer [pprint]] @@ -40,6 +40,7 @@ data (case (:cause data) :parse-failure (println (:failure data)) + :strict nil ;; the message, which has already been printed, is enough. :quit (throw e) ;; default (pprint data)))))) @@ -60,7 +61,7 @@ (if (:errors args) (apply str (interpose "; " (:errors args)))) "\nSprecan 'quit' tó laéfan\n")) - (binding [*trace?* (true? (:trace (:options args)))] + (binding [*options* (:options args)] (try (repl (str (:prompt (:options args)) " ")) (catch diff --git a/src/beowulf/read.clj b/src/beowulf/read.clj index e8a7848..66d22da 100644 --- a/src/beowulf/read.clj +++ b/src/beowulf/read.clj @@ -1,5 +1,6 @@ (ns beowulf.read - (:require [clojure.math.numeric-tower :refer [expt]] + (:require [beowulf.bootstrap :refer [*options*]] + [clojure.math.numeric-tower :refer [expt]] [clojure.string :refer [starts-with? upper-case]] [instaparse.core :as i] [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell NIL]])) @@ -24,7 +25,7 @@ ;; mexprs. I'm pretty clear that Lisp 1.5 could never read these, ;; but it's a convenience. - "mexpr := λexpr | fncall | defn | cond | mvar; + "mexpr := λexpr | fncall | defn | cond | mvar | mexpr comment; λexpr := λ lsqb bindings semi-colon body rsqb; λ := 'λ'; bindings := lsqb args rsqb; @@ -41,9 +42,12 @@ mvar := #'[a-z]+'; semi-colon := ';';" + ;; comments. I'm pretty confident Lisp 1.5 did NOT have these. + "comment := opt-space <';;'> #'[^\\n\\r]*';" + ;; sexprs. Note it's not clear to me whether Lisp 1.5 had the quote macro, ;; but I've included it on the basis that it can do little harm. - "sexpr := quoted-expr | atom | number | dotted-pair | list; + "sexpr := quoted-expr | atom | number | dotted-pair | list | sexpr comment; list := lpar sexpr rpar | lpar (sexpr sep)* rpar | lpar (sexpr sep)* dot-terminal; dotted-pair := lpar dot-terminal ; dot := '.'; @@ -95,6 +99,11 @@ (= context :mexpr) [:quoted-expr p] p) + :comment (if + (:strict *options*) + (throw + (ex-info "Cannot parse comments in strict mode" + {:cause :strict}))) :dotted-pair (if (= context :mexpr) [:fncall @@ -103,7 +112,12 @@ (simplify (nth p 1) context) (simplify (nth p 2) context)]] (map simplify p)) - :mexpr (simplify (second p) :mexpr) + :mexpr (if + (:strict *options*) + (throw + (ex-info "Cannot parse meta expressions in strict mode" + {:cause :strict})) + (simplify (second p) :mexpr)) :list (if (= context :mexpr) [:fncall @@ -115,7 +129,6 @@ p))) - ;; # From Lisp 1.5 Programmers Manual, page 10 ;; Note that I've retyped much of this, since copy/pasting out of PDF is less ;; than reliable. Any typos are mine. Quote starts [[ diff --git a/test/beowulf/mexpr_test.clj b/test/beowulf/mexpr_test.clj index 3500875..f7d9dbb 100644 --- a/test/beowulf/mexpr_test.clj +++ b/test/beowulf/mexpr_test.clj @@ -1,6 +1,7 @@ (ns beowulf.mexpr-test (:require [clojure.test :refer :all] - [beowulf.read :refer [parse simplify generate]])) + [beowulf.bootstrap :refer [*options*]] + [beowulf.read :refer [parse simplify generate gsp]])) ;; These tests are taken generally from the examples on page 10 of ;; Lisp 1.5 Programmers Manual: @@ -64,3 +65,10 @@ (parse "label[ff;λ[[x];[atom[x]->x; T->ff[car[x]]]]]"))))] (is (= actual expected))))) +(deftest strict-tests + (testing "Strict feature" + (binding [*options* {:strict true}] + (is (thrown-with-msg? + Exception + #"Cannot parse meta expressions in strict mode" + (gsp "label[ff;λ[[x];[atom[x]->x; T->ff[car[x]]]]]")))))) diff --git a/test/beowulf/sexpr_test.clj b/test/beowulf/sexpr_test.clj index 98d3358..fe665a1 100644 --- a/test/beowulf/sexpr_test.clj +++ b/test/beowulf/sexpr_test.clj @@ -2,7 +2,8 @@ (:require [clojure.math.numeric-tower :refer [abs]] [clojure.test :refer :all] [beowulf.cons-cell :refer :all] - [beowulf.read :refer [parse simplify generate]])) + [beowulf.bootstrap :refer [*options*]] + [beowulf.read :refer [parse simplify generate gsp]])) ;; broadly, sexprs should be homoiconic @@ -24,6 +25,32 @@ actual (generate (simplify (parse (str expected))))] (is (= actual expected))))) +(deftest comment-tests + (testing "Reading comments" + (let [expected 'A + actual (gsp "A ;; comment")] + (is (= actual expected))) + (let [expected 10 + actual (gsp "10 ;; comment")] + (is (= actual expected))) + (let [expected 2/5 + actual (gsp "4E-1 ;; comment")] + (is (= actual expected))) + (let [expected "(A B C)" + actual (print-str (gsp "(A ;; comment + B C)"))] + (is (= actual expected) + "Really important that comments work inside lists")) +;; ;; TODO: Currently failing and I'm not sure why +;; (binding [*options* {:strict true}] +;; (is (thrown-with-msg? +;; Exception +;; #"Cannot parse comments in strict mode" +;; (gsp "(A ;; comment +;; B C)")))) + )) + + (deftest number-tests (testing "Reading octal numbers" (let [expected 1