diff --git a/README.md b/README.md index e335a1c..6712b2a 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,62 @@ LISP 1.5 is to all Lisp dialects as Beowulf is to Emglish literature. A work-in-progress towards an implementation of Lisp 1.5 in Clojure. The objective is to build a complete and accurate implementation of Lisp 1.5 as described in the manual, with, in so far as is possible, exactly the -same bahaviour; the only intended deviation is that the parser reads -'mexprs' (meta language expressions) as well as sexprs. +same bahaviour - except as documented below. -## BUT WHY?!!?! +### Status + +Boots to REPL, but few functions yet available. + +### Architectural plan + +Not everything documented in this section is yet built. It indicates the +direction of travel and intended destination, not the current state. + +#### resources/lisp1.5.lsp + +The objective is to have within `resources/lisp1.5.lsp`, all those functions +defined in the Lisp 1.5 Programmer's Manual which can be implemented in Lisp. + +This means that, while Beowulf is hosted on Clojure, all that would be +required to rehost Lisp 1.5 on a different platform would be to reimplement + +* bootstrap.clj +* host.clj +* read.clj + +#### beowulf/boostrap.clj + +This file is essentially Lisp as defined in Chapter 1 (pages 1-14) of the +Lisp 1.5 Programmer's Manual; that is to say, a very simple Lisp language, +which should, I believe, be sufficient in conjunction with the functions +provided by `beowulf.host`, be sufficient to bootstrap the full Lisp 1.5 +interpreter. + +In addition it contains the function `INTEROP`, which allows host language +functions to be called from Lisp. + +#### beowulf/host.clj + +This file 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. + +#### beowulf/read.clj + +This file provides the reader required for boostrapping. It's not a bad +reader - it provides feedback on errors found in the input - but it isn't +the real Lisp reader. + +Intended deviations from the behaviour of the real Lisp reader are as follows: + +1. It reads the meta-expression language `MEXPR` in addition to the + symbolic expression language `SEXPR`, which I do not believe the Lisp 1.5 + reader ever did; +2. It treats everything between a semi-colon and an end of line as a comment, + as most modern Lisps do; but I do not believe Lisp 1.5 had this feature. + + +### BUT WHY?!!?! Because. @@ -25,7 +77,11 @@ Because I'm barking mad, and this is therapy. ## Installation -Download from http://example.com/FIXME. +At present, clone the source and build it using + +`lein uberjar`. + +You will require to have [Leiningen](https://leiningen.org/) installed. ## Usage diff --git a/project.clj b/project.clj index 7738a79..d077b1d 100644 --- a/project.clj +++ b/project.clj @@ -1,10 +1,11 @@ -(defproject beowulf "0.1.0-SNAPSHOT" +(defproject beowulf "0.2.0-SNAPSHOT" :description "An implementation of LISP 1.5 in Clojure" :url "http://example.com/FIXME" :license {:name "GPL-2.0-or-later" :url "https://www.eclipse.org/legal/epl-2.0/"} - :dependencies [[org.clojure/clojure "1.10.0"] + :dependencies [[org.clojure/clojure "1.8.0"] [org.clojure/math.numeric-tower "0.0.4"] + [org.clojure/tools.cli "0.4.2"] [org.clojure/tools.trace "0.7.10"] [environ "1.1.0"] [instaparse "1.4.10"]] diff --git a/resources/lisp1.5.lsp b/resources/lisp1.5.lsp new file mode 100644 index 0000000..e69de29 diff --git a/src/beowulf/eval.clj b/src/beowulf/bootstrap.clj similarity index 87% rename from src/beowulf/eval.clj rename to src/beowulf/bootstrap.clj index 5d78087..79e6a23 100644 --- a/src/beowulf/eval.clj +++ b/src/beowulf/bootstrap.clj @@ -1,7 +1,17 @@ -(ns beowulf.eval +(ns beowulf.bootstrap (:require [clojure.tools.trace :refer :all] [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell NIL T F]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; This file is essentially Lisp as defined in Chapter 1 (pages 1-14) of the +;;; Lisp 1.5 Programmer's Manual; that is to say, a very simple Lisp language, +;;; which should, I believe, be sufficient in conjunction with the functions +;;; provided by `beowulf.host`, be sufficient to bootstrap the full Lisp 1.5 +;;; interpreter. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (declare EVAL) (def oblist @@ -9,6 +19,10 @@ be Lisp 1.5's EQuivalent of `SETQ`), possibly by other things." (atom NIL)) +(def ^:dynamic *trace?* + "Whether or not to trace `EVAL`." + false) + (defn NULL [x] (if (= x NIL) 'T 'F)) @@ -208,7 +222,7 @@ :else (make-cons-cell (SUBLIS a (CAR y)) (SUBLIS a (CDR y))))) -(deftrace APPLY +(defn APPLY "For bootstrapping, at least, a version of APPLY written in Clojure. All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects. See page 13 of the Lisp 1.5 Programmers Manual." @@ -217,6 +231,8 @@ (= (ATOM? function) 'T)(cond + ;; TODO: doesn't check whether `function` is bound in the environment; + ;; we'll need that before we can bootstrap. (= function 'CAR) (CAAR args) (= function 'CDR) (CDAR args) (= function 'CONS) (make-cons-cell (CAR args) (CADR args)) @@ -261,11 +277,8 @@ (EVAL (CAR args) env) (EVLIS (CDR args) env)))) - -(deftrace EVAL - "For bootstrapping, at least, a version of EVAL written in Clojure. - All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects. - See page 13 of the Lisp 1.5 Programmers Manual." +(deftrace traced-eval + "Essentially, identical to EVAL except traced." [expr env] (cond (= @@ -285,3 +298,29 @@ (EVLIS (CDR expr) env) env))) +(defn EVAL + "For bootstrapping, at least, a version of EVAL written in Clojure. + All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects. + See page 13 of the Lisp 1.5 Programmers Manual." + [expr env] + (cond + *trace?* (traced-eval expr env) + (= + (ATOM? expr) 'T) + (CDR (ASSOC expr env)) + (= + (ATOM? (CAR expr)) + 'T)(cond + (= (CAR expr) 'QUOTE) (CADR expr) + (= (CAR expr) 'COND) (EVCON (CDR expr) env) + :else (APPLY + (CAR expr) + (EVLIS (CDR expr) env) + env)) + :else (APPLY + (CAR expr) + (EVLIS (CDR expr) env) + env))) + + + diff --git a/src/beowulf/core.clj b/src/beowulf/core.clj index 8d8cebc..b57464a 100644 --- a/src/beowulf/core.clj +++ b/src/beowulf/core.clj @@ -1,21 +1,35 @@ (ns beowulf.core - (:require [beowulf.eval :refer [EVAL oblist]] + (:require [beowulf.bootstrap :refer [EVAL oblist *trace?*]] [beowulf.read :refer [READ]] + [clojure.java.io :as io] [clojure.pprint :refer [pprint]] + [clojure.tools.cli :refer [parse-opts]] [environ.core :refer [env]]) (:gen-class)) +(def cli-options + [["-h" "--help"] + ["-p PROMPT" "--prompt PROMPT" "Set the REPL prompt to PROMPT" + :default "Sprecan::"] + ["-r INITFILE" "--read INITFILE" "Read Lisp functions from the file INITFILE" + :validate [#(and + (.exists (io/file %)) + (.canRead (io/file %))) + "Could not find initfile"]] + ["-s" "--strict" "Strictly interpret the Lisp 1.5 language, without extensions."] + ["-t" "--trace" "Trace Lisp evaluation."]]) + (defn repl "Read/eval/print loop." - [] + [prompt] (loop [] - (print "Sprecan:: ") + (print prompt) (flush) (try (let [input (read-line)] (cond - (= input "quit") (throw (ex-info "Færwell!" {:cause :quit})) - input (println (str "> " (EVAL (READ input) @oblist))) + (= input "quit") (throw (ex-info "\nFærwell!" {:cause :quit})) + input (println (str "> " (print-str (EVAL (READ input) @oblist)))) :else (println))) (catch Exception @@ -32,22 +46,31 @@ (recur))) (defn -main - [& args] - (println - (str - "Hider wilcuman. Béowulf is mín nama\nSíðe " - (System/getProperty "beowulf.version") - "\n\n")) - (try - (repl) - (catch - Exception - e - (let [data (ex-data e)] + [& opts] + (let [args (parse-opts opts cli-options)] + (println + (str + "\nHider wilcuman. Béowulf is mín nama.\n" (if - data - (case (:cause data) - :quit nil - ;; default - (pprint data)) - (println e)))))) + (System/getProperty "beowulf.version") + (str "Síðe " (System/getProperty "beowulf.version") "\n")) + (if + (:help (:options args)) + (:summary args)) + (if (:errors args) + (apply str (interpose "; " (:errors args)))) + "\nSprecan 'quit' tó laéfan\n")) + (binding [*trace?* (true? (:trace (:options args)))] + (try + (repl (str (:prompt (:options args)) " ")) + (catch + Exception + e + (let [data (ex-data e)] + (if + data + (case (:cause data) + :quit nil + ;; default + (pprint data)) + (println e)))))))) diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj new file mode 100644 index 0000000..57d797d --- /dev/null +++ b/src/beowulf/host.clj @@ -0,0 +1,9 @@ +(ns beowulf.host) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; This file 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. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/beowulf/read.clj b/src/beowulf/read.clj index da35d65..e8a7848 100644 --- a/src/beowulf/read.clj +++ b/src/beowulf/read.clj @@ -2,9 +2,15 @@ (:require [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]]) -;; (:import [beowulf.cons-cell ConsCell]) - ) + [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell NIL]])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; This file provides the reader required for boostrapping. It's not a bad +;;; reader - it provides feedback on errors found in the input - but it isn't +;;; the real Lisp reader. +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (declare generate) diff --git a/test/beowulf/bootstrap_test.clj b/test/beowulf/bootstrap_test.clj index 57444eb..a7b879c 100644 --- a/test/beowulf/bootstrap_test.clj +++ b/test/beowulf/bootstrap_test.clj @@ -2,7 +2,7 @@ (:require [clojure.math.numeric-tower :refer [abs]] [clojure.test :refer :all] [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell NIL T F]] - [beowulf.eval :refer :all] + [beowulf.bootstrap :refer :all] [beowulf.read :refer [gsp]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;