diff --git a/.gitignore b/.gitignore index d18f225..5903fe9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ pom.xml.asc /.nrepl-port .hgignore .hg/ +.idea/ +*~ diff --git a/README.md b/README.md index e95c3a4..56ed168 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # beowulf -LISP 1.5 is to all Lisp dialects as Beowulf is to Emglish literature. +LISP 1.5 is to all Lisp dialects as Beowulf is to English literature. ## What this is diff --git a/beowulf.iml b/beowulf.iml new file mode 100644 index 0000000..62bb49e --- /dev/null +++ b/beowulf.iml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/project.clj b/project.clj index 1e3cecb..2ea58de 100644 --- a/project.clj +++ b/project.clj @@ -13,7 +13,9 @@ [org.clojure/tools.trace "0.7.10"] [environ "1.1.0"] [instaparse "1.4.10"]] + :java-source-paths ["src/java"] :main ^:skip-aot beowulf.core + :min-lein-version "2.0.0" :plugins [[lein-cloverage "1.1.1"] [lein-codox "0.10.7"] [lein-environ "1.1.0"]] @@ -28,7 +30,7 @@ ["uberjar"] ["change" "version" "leiningen.release/bump-version"] ["vcs" "commit"]] - + :source-paths ["src/clojure"] :target-path "target/%s" - :url "https://github.com/simon-brooke/the-great-game" + :url "https://github.com/simon-brooke/beowulf" ) diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj deleted file mode 100644 index 042dc8f..0000000 --- a/src/beowulf/host.clj +++ /dev/null @@ -1,38 +0,0 @@ -(ns beowulf.host - "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.") - -;; these are CANDIDATES to be host-implemented. only a subset of them MUST be. -;; those which can be implemented in Lisp should be, since that aids -;; portability. - -;; RPLACA - -;; RPLACD - -;; PLUS - -;; MINUS - -;; DIFFERENCE - -;; QUOTIENT - -;; REMAINDER - -;; ADD1 - -;; SUB1 - -;; MAX - -;; MIN - -;; RECIP - -;; FIXP - -;; NUMBERP - -;; diff --git a/src/beowulf/bootstrap.clj b/src/clojure/beowulf/bootstrap.clj similarity index 100% rename from src/beowulf/bootstrap.clj rename to src/clojure/beowulf/bootstrap.clj diff --git a/src/beowulf/cons_cell.clj b/src/clojure/beowulf/cons_cell.clj similarity index 81% rename from src/beowulf/cons_cell.clj rename to src/clojure/beowulf/cons_cell.clj index 3fd104b..88bb948 100644 --- a/src/beowulf/cons_cell.clj +++ b/src/clojure/beowulf/cons_cell.clj @@ -3,20 +3,43 @@ Lisp 1.5 lists do not necessarily have a sequence as their CDR, so cannot be implemented on top of Clojure lists.") -(def NIL - "The canonical empty list symbol." - (symbol "NIL")) +;; (def NIL +;; "The canonical empty list symbol." +;; 'NIL) -(def T - "The canonical true value." - (symbol "T")) ;; true. +;; (def T +;; "The canonical true value." +;; 'T) ;; true. -(def F - "The canonical false value - different from `NIL`, which is not canonically - false in Lisp 1.5." - (symbol "F")) ;; false as distinct from nil +;; (def F +;; "The canonical false value - different from `NIL`, which is not canonically +;; false in Lisp 1.5." +;; 'F) ;; false as distinct from nil + +(deftype ConsCell [^:unsynchronized-mutable car ^:unsynchronized-mutable cdr] + ;; Note that, because the CAR and CDR fields are unsynchronised mutable - i.e. + ;; plain old Java instance variables which can be written as well as read - + ;; ConsCells are NOT thread safe. This does not matter, since Lisp 1.5 is + ;; single threaded. + + (CAR [this] (.car this)) + (CDR [this] (.cdr this)) + (RPLACA + [this value] + (if + (or + (instance? beowulf.cons_cell.ConsCell value) + (number? value) + (symbol? value) + (= value NIL)) + (do + (set! (. cell CAR) value) + cell) + (throw (ex-info + (str "Invalid value in RPLACA: `" value "` (" (type value) ")") + {:cause :bad-value + :detail :rplaca})))) -(deftype ConsCell [CAR CDR] clojure.lang.ISeq (cons [this x] (ConsCell. x this)) (first [this] (.CAR this)) diff --git a/src/beowulf/core.clj b/src/clojure/beowulf/core.clj similarity index 100% rename from src/beowulf/core.clj rename to src/clojure/beowulf/core.clj diff --git a/src/clojure/beowulf/host.clj b/src/clojure/beowulf/host.clj new file mode 100644 index 0000000..68728b8 --- /dev/null +++ b/src/clojure/beowulf/host.clj @@ -0,0 +1,61 @@ +(ns beowulf.host + "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 [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell NIL T F]])) + +;; these are CANDIDATES to be host-implemented. only a subset of them MUST be. +;; those which can be implemented in Lisp should be, since that aids +;; portability. + +;; RPLACA + +(defn RPLACA + [^beowulf.cons_cell.ConsCell cell value] + (if + (instance? beowulf.cons_cell.ConsCell cell) + (if + (or + (instance? beowulf.cons_cell.ConsCell value) + (number? value) + (symbol? value) + (= value NIL)) + (do + (set! (. cell CAR) value) + cell) + (throw (ex-info + (str "Invalid value in RPLACA: `" value "` (" (type value) ")") + {:cause :bad-value + :detail :rplaca}))) + (throw (ex-info + (str "Invalid cell in RPLACA: `" cell "` (" (type cell) ")") + {:cause :bad-value + :detail :rplaca})))) + +;; RPLACD + +;; PLUS + +;; MINUS + +;; DIFFERENCE + +;; QUOTIENT + +;; REMAINDER + +;; ADD1 + +;; SUB1 + +;; MAX + +;; MIN + +;; RECIP + +;; FIXP + +;; NUMBERP + +;; diff --git a/src/beowulf/read.clj b/src/clojure/beowulf/read.clj similarity index 100% rename from src/beowulf/read.clj rename to src/clojure/beowulf/read.clj diff --git a/src/java/beowulf/substrate/ConsCell.java b/src/java/beowulf/substrate/ConsCell.java new file mode 100644 index 0000000..6634215 --- /dev/null +++ b/src/java/beowulf/substrate/ConsCell.java @@ -0,0 +1,246 @@ +package beowulf.substrate; + +import clojure.lang.*; + +import java.lang.Number; +import beowulf.cons_cell.NIL; + +/** + *

+ * A cons cell - a tuple of two pointers - is the fundamental unit of Lisp store. + *

+ *

+ * Implementing mutable data in Clojure if hard - deliberately so. + * But Lisp 1.5 cons cells need to be mutable. This class is part of thrashing + * around trying to find a solution. + *

+ */ +public class ConsCell + implements clojure.lang.IPersistentCollection, + clojure.lang.ISeq, + clojure.lang.Seqable, + clojure.lang.Sequential { + + /** + * The car of a cons cell can't be just any object; it needs to be + * a number, a symbol or a cons cell. But as there is no common superclass + * or interface for those things, we use Object here and specify the + * types of objects which can be stored in the constructors and setter + * methods. + */ + private Object car; + + /** + * The car of a cons cell can't be just any object; it needs to be + * a number, a symbol or a cons cell. But as there is no common superclass + * or interface for those things, we use Object here and specify the + * types of objects which can be stored in the constructors and setter + * methods. + */ + private Object cdr; + + public ConsCell(ConsCell car, ConsCell cdr) { + this.car = car; + this.cdr = cdr; + } + + public ConsCell(ConsCell car, Symbol cdr) { + this.car = car; + this.cdr = cdr; + } + + public ConsCell(ConsCell car, Number cdr) { + this.car = car; + this.cdr = cdr; + } + + public ConsCell(Symbol car, ConsCell cdr) { + this.car = car; + this.cdr = cdr; + } + + public ConsCell(Symbol car, Symbol cdr) { + this.car = car; + this.cdr = cdr; + } + + public ConsCell(Symbol car, Number cdr) { + this.car = car; + this.cdr = cdr; + } + + public ConsCell(Number car, ConsCell cdr) { + this.car = car; + this.cdr = cdr; + } + + public ConsCell(Number car, Symbol cdr) { + this.car = car; + this.cdr = cdr; + } + + public ConsCell(Number car, Number cdr) { + this.car = car; + this.cdr = cdr; + } + + public Object getCar() { + return this.car; + } + + public Object getCdr() { + return this.cdr; + } + + public ConsCell setCar(ConsCell c) { + this.car = c; + return this; + } + + public ConsCell setCdr(ConsCell c) { + this.cdr = c; + return this; + } + + public ConsCell setCar(java.lang.Number n) { + this.car = n; + return this; + } + + public ConsCell setCdr(java.lang.Number n) { + this.cdr = n; + return this; + } + + public ConsCell setCar(clojure.lang.Symbol s) { + this.car = s; + return this; + } + + public ConsCell setCdr(clojure.lang.Symbol s) { + this.cdr = s; + return this; + } + + @Override + public boolean equals(Object other) { + boolean result; + + if (other instanceof IPersistentCollection) { + ISeq s = ((IPersistentCollection) other).seq(); + + result = this.car.equals(s.first()) && + this.cdr instanceof ConsCell && + ((ISeq) this.cdr).equiv(s.more()); + } else { + result = false; + } + + return result; + } + + @Override + public String toString() { + StringBuilder bob = new StringBuilder("("); + + for (Object d = this; d instanceof ConsCell; d = ((ConsCell)d).cdr) { + ConsCell cell = (ConsCell)d; + bob.append(cell.car.toString()) + + if ( cell.cdr instanceof ConsCell) { + bob.append(" "); + } else if ( cell.cdr.toString().equals("NIL")) { + /* That's an ugly hack to work around the fact I can't currently + * get a handle on the NIL symbol itself. In theory, nothing else + * in Lisp 1.5 should have the print-name `NIL`.*/ + bob.append(")"); + } else { + bob.append(" . ").append(cell.cdr.toString()).append(")"); + } + } + + return bob.toString(); + } + + /* IPersistentCollection interface implementation */ + + public int count() { + return this.cdr instanceof ConsCell ? + 1 + ((ConsCell) this.cdr).count() : + 1; + } + + /** + * `empty` is completely undocumented, I'll return `null` until something breaks. + */ + public IPersistentCollection empty() { + return null; + } + + /** + * God alone knows what `equiv` is intended to do; it's completely + * undocumented. But in PersistentList it's simply a synonym for 'equals', + * and that's what I'll implement. + */ + public boolean equiv(Object o) { + return this.equals(o); + } + + /* ISeq interface implementation */ + + public Object first() { + return this.car; + } + + public ISeq next() { + ISeq result; + + if (this.cdr instanceof ConsCell) { + result = (ISeq) this.cdr; + } else { + result = null; + } + + return result; + } + + public ISeq more() { + ISeq result; + + if (this.cdr instanceof ConsCell) { + result = (ISeq) this.cdr; + } else { + result = null; + } + + return result; + } + + /** + * Return a new cons cell comprising the object `o` as car, + * and myself as cdr. Hopefully by declaring the return value + * `ConsCell` I'll satisfy both the IPersistentCollection and the + * ISeq interfaces. + */ + public ConsCell cons(Object o) { + if (o instanceof ConsCell) { + return new ConsCell((ConsCell) o, this); + } else if (o instanceof Number) { + return new ConsCell((Number) o, this); + } else if (o instanceof Symbol) { + return new ConsCell((Symbol) o, this); + } else { + throw new IllegalArgumentException("Unrepresentable argument passed to CONS"); + } + } + + /* Seqable interface */ + public ISeq seq() { + return this; + } + + /* Sequential interface is just a marker and does not require us to + * implement anything */ + + +} diff --git a/test/beowulf/host_test.clj b/test/beowulf/host_test.clj new file mode 100644 index 0000000..ebe8aa6 --- /dev/null +++ b/test/beowulf/host_test.clj @@ -0,0 +1,18 @@ +(ns beowulf.host-test + (: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.host :refer :all] + [beowulf.read :refer [gsp]])) + +(deftest destructive-change-test + (testing "RPLACA" + (let + [l (make-beowulf-list '(A B C D E)) + target (.CDR l) + expected "(A F C D E)" + actual (print-str (RPLACA target 'F))] + (is (= actual expected))) + + )) +