diff --git a/src/clojure/beowulf/bootstrap.clj b/src/clojure/beowulf/bootstrap.clj index e082cc1..8ca3d3a 100644 --- a/src/clojure/beowulf/bootstrap.clj +++ b/src/clojure/beowulf/bootstrap.clj @@ -7,11 +7,12 @@ The convention is adopted that functions in this file with names in ALLUPPERCASE are Lisp 1.5 functions (although written in Clojure) and that - therefore all arguments must be numbers, symbols or `beowulf.cons_cell.ConsCell` + therefore all arguments must be numbers, symbols or `beowulf.substrate.ConsCell` objects." (:require [clojure.string :as s] [clojure.tools.trace :refer :all] - [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell NIL T F]])) + [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell NIL T F]]) + (:import (beowulf.substrate ConsCell))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; @@ -58,7 +59,7 @@ [x] (cond (= x NIL) NIL - (instance? beowulf.cons_cell.ConsCell x) (.CAR x) + (instance? ConsCell x) (.getCar x) :else (throw (Exception. @@ -70,7 +71,7 @@ [x] (cond (= x NIL) NIL - (instance? beowulf.cons_cell.ConsCell x) (.CDR x) + (instance? ConsCell x) (.getCdr x) :else (throw (Exception. @@ -297,7 +298,7 @@ :also-tried l-name}))) result (eval (cons f args))] (cond - (instance? beowulf.cons_cell.ConsCell result) result + (instance? ConsCell result) result (seq? result) (make-beowulf-list result) (symbol? result) result (string? result) (symbol result) diff --git a/src/clojure/beowulf/cons_cell.clj b/src/clojure/beowulf/cons_cell.clj index 88bb948..5c04188 100644 --- a/src/clojure/beowulf/cons_cell.clj +++ b/src/clojure/beowulf/cons_cell.clj @@ -1,133 +1,135 @@ (ns beowulf.cons-cell "The fundamental cons cell on which all Lisp structures are built. Lisp 1.5 lists do not necessarily have a sequence as their CDR, so - cannot be implemented on top of Clojure lists.") + cannot be implemented on top of Clojure lists." + (:import (beowulf.substrate ConsCell) + (java.io Writer))) -;; (def NIL -;; "The canonical empty list symbol." -;; 'NIL) +(def NIL + "The canonical empty list symbol." + 'NIL) -;; (def T -;; "The canonical true value." -;; '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." -;; '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. +;; (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})))) +;; (CAR [this] (.car this)) +;; (CDR [this] (.cdr this)) +;; (RPLACA +;; [this value] +;; (if +;; (or +;; (instance? beowulf.substrate.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})))) - clojure.lang.ISeq - (cons [this x] (ConsCell. x this)) - (first [this] (.CAR this)) - ;; next and more must return ISeq: - ;; https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/ISeq.java - (more [this] (if - (seq? (.CDR this)) - (.CDR this) - clojure.lang.PersistentList/EMPTY)) - (next [this] (if - (seq? (.CDR this)) - (.CDR this) - nil ;; next returns nil when empty - )) +;; clojure.lang.ISeq +;; (cons [this x] (ConsCell. x this)) +;; (first [this] (.CAR this)) +;; ;; next and more must return ISeq: +;; ;; https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/ISeq.java +;; (more [this] (if +;; (seq? (.CDR this)) +;; (.CDR this) +;; clojure.lang.PersistentList/EMPTY)) +;; (next [this] (if +;; (seq? (.CDR this)) +;; (.CDR this) +;; nil ;; next returns nil when empty +;; )) - clojure.lang.Seqable - (seq [this] this) +;; clojure.lang.Seqable +;; (seq [this] this) - ;; for some reason this marker protocol is needed otherwise compiler complains - ;; that `nth not supported on ConsCell` - clojure.lang.Sequential +;; ;; for some reason this marker protocol is needed otherwise compiler complains +;; ;; that `nth not supported on ConsCell` +;; clojure.lang.Sequential - clojure.lang.IPersistentCollection - (count [this] (if - (coll? (.CDR this)) - (inc (.count (.CDR this))) - 1)) - (empty [this] false) ;; a cons cell is by definition not empty. - (equiv [this other] (if - (seq? other) - (and - (if - (and - (seq? (first this)) - (seq? (first other))) - (.equiv (first this) (first other)) - (= (first this) (first other))) - (if - (and - (seq? (rest this)) - (seq? (rest other))) - (.equiv (rest this) (rest other)) - (= (rest this) (rest other)))) - false))) +;; clojure.lang.IPersistentCollection +;; (count [this] (if +;; (coll? (.CDR this)) +;; (inc (.count (.CDR this))) +;; 1)) +;; (empty [this] false) ;; a cons cell is by definition not empty. +;; (equiv [this other] (if +;; (seq? other) +;; (and +;; (if +;; (and +;; (seq? (first this)) +;; (seq? (first other))) +;; (.equiv (first this) (first other)) +;; (= (first this) (first other))) +;; (if +;; (and +;; (seq? (rest this)) +;; (seq? (rest other))) +;; (.equiv (rest this) (rest other)) +;; (= (rest this) (rest other)))) +;; false))) -(defn- to-string - "Printing ConsCells gave me a *lot* of trouble. This is an internal function - used by the print-method override (below) in order that the standard Clojure - `print` and `str` functions will print ConsCells correctly. The argument - `cell` must, obviously, be an instance of `ConsCell`." - [cell] - (loop [c cell - n 0 - s "("] - (if - (instance? beowulf.cons_cell.ConsCell c) - (let [car (.CAR c) - cdr (.CDR c) - cons? (instance? beowulf.cons_cell.ConsCell cdr) - ss (str - s - (to-string car) - (cond - cons? - " " - (or (nil? cdr) (= cdr 'NIL)) - ")" - :else - (str " . " (to-string cdr) ")")))] - (if - cons? - (recur cdr (inc n) ss) - ss)) - (str c)))) +;(defn- to-string +; "Printing ConsCells gave me a *lot* of trouble. This is an internal function +; used by the print-method override (below) in order that the standard Clojure +; `print` and `str` functions will print ConsCells correctly. The argument +; `cell` must, obviously, be an instance of `ConsCell`." +; [cell] +; (loop [c cell +; n 0 +; s "("] +; (if +; (instance? ConsCell c) +; (let [car (.getCar c) +; cdr (.getCdr c) +; cons? (instance? ConsCell cdr) +; ss (str +; s +; (to-string car) +; (cond +; cons? +; " " +; (or (nil? cdr) (= cdr 'NIL)) +; ")" +; :else +; (str " . " (to-string cdr) ")")))] +; (if +; cons? +; (recur cdr (inc n) ss) +; ss)) +; (str c)))) (defn pretty-print "This isn't the world's best pretty printer but it sort of works." - ([^beowulf.cons_cell.ConsCell cell] + ([^ConsCell cell] (println (pretty-print cell 80 0))) - ([^beowulf.cons_cell.ConsCell cell width level] + ([^ConsCell cell width level] (loop [c cell n (inc level) s "("] (if - (instance? beowulf.cons_cell.ConsCell c) - (let [car (.CAR c) - cdr (.CDR c) - cons? (instance? beowulf.cons_cell.ConsCell cdr) + (instance? ConsCell c) + (let [car (.getCar c) + cdr (.getCdr c) + cons? (instance? ConsCell cdr) print-width (count (print-str c)) indent (apply str (repeat n " ")) ss (str @@ -153,9 +155,9 @@ (defmethod clojure.core/print-method ;;; I have not worked out how to document defmethod without blowing up the world. - beowulf.cons_cell.ConsCell - [this writer] - (.write writer (to-string this))) + ConsCell + [this ^Writer writer] + (.write writer (.toString this))) (defmacro make-cons-cell @@ -171,7 +173,7 @@ (empty? x) NIL (coll? x) (ConsCell. (if - (seq? (first x)) + (coll? (first x)) (make-beowulf-list (first x)) (first x)) (make-beowulf-list (rest x))) diff --git a/src/clojure/beowulf/host.clj b/src/clojure/beowulf/host.clj index 68728b8..b716ec6 100644 --- a/src/clojure/beowulf/host.clj +++ b/src/clojure/beowulf/host.clj @@ -2,7 +2,8 @@ "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]])) + (:require [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell NIL T F]]) + (:import (beowulf.substrate ConsCell))) ;; 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 @@ -11,17 +12,20 @@ ;; RPLACA (defn RPLACA - [^beowulf.cons_cell.ConsCell cell value] + "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 + performance hacks in early Lisps)" + [^ConsCell cell value] (if - (instance? beowulf.cons_cell.ConsCell cell) + (instance? ConsCell cell) (if (or - (instance? beowulf.cons_cell.ConsCell value) + (instance? ConsCell value) (number? value) (symbol? value) (= value NIL)) (do - (set! (. cell CAR) value) + (.setCar cell value) cell) (throw (ex-info (str "Invalid value in RPLACA: `" value "` (" (type value) ")") @@ -34,8 +38,34 @@ ;; RPLACD +(defn RPLACD + "Replace the CDR pointer of this `cell` with this `value`. Dangerous, should + really not exist, but does in Lisp 1.5 (and was important for some + performance hacks in early Lisps)" + [^ConsCell cell value] + (if + (instance? ConsCell cell) + (if + (or + (instance? ConsCell value) + (number? value) + (symbol? value) + (= value NIL)) + (do + (.setCdr cell value) + cell) + (throw (ex-info + (str "Invalid value in RPLACD: `" value "` (" (type value) ")") + {:cause :bad-value + :detail :rplaca}))) + (throw (ex-info + (str "Invalid cell in RPLACD: `" cell "` (" (type cell) ")") + {:cause :bad-value + :detail :rplaca})))) + ;; PLUS + ;; MINUS ;; DIFFERENCE diff --git a/src/clojure/beowulf/read.clj b/src/clojure/beowulf/read.clj index 6ede7e8..196318a 100644 --- a/src/clojure/beowulf/read.clj +++ b/src/clojure/beowulf/read.clj @@ -267,7 +267,7 @@ (if (coll? p) (case (first p) - :λ "LAMBDA" + :λ 'LAMBDA :λexpr (make-cons-cell (generate (nth p 1)) (make-cons-cell (generate (nth p 2)) diff --git a/src/java/beowulf/substrate/ConsCell.java b/src/java/beowulf/substrate/ConsCell.java index 6634215..e6313e1 100644 --- a/src/java/beowulf/substrate/ConsCell.java +++ b/src/java/beowulf/substrate/ConsCell.java @@ -3,7 +3,7 @@ package beowulf.substrate; import clojure.lang.*; import java.lang.Number; -import beowulf.cons_cell.NIL; +//import beowulf.cons_cell.NIL; /** *

@@ -39,49 +39,31 @@ public class ConsCell */ 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; + /** + * Construct a new ConsCell object with this `car` and this `cdr`. + * + * @param car + * @param cdr + * @throws IllegalArgumentException if either `car` or `cdr` is not one + * of ConsCell, Symbol, Number + */ + public ConsCell(Object car, Object cdr) { + if (car instanceof ConsCell || car instanceof Number || car instanceof Symbol) { + this.car = car; + } else { + StringBuilder bob = new StringBuilder("Invalid CAR value (`") + .append(car.toString()).append("`; ") + .append(car.getClass().getName()).append(") passed to CONS"); + throw new IllegalArgumentException(bob.toString()); + } + if (cdr instanceof ConsCell || cdr instanceof Number || cdr instanceof Symbol) { + this.cdr = cdr; + } else { + StringBuilder bob = new StringBuilder("Invalid CDR value (`") + .append(cdr.toString()).append("`; ") + .append(cdr.getClass().getName()).append(") passed to CONS"); + throw new IllegalArgumentException(bob.toString()); + } } public Object getCar() { @@ -122,7 +104,7 @@ public class ConsCell return this; } - @Override + @Override public boolean equals(Object other) { boolean result; @@ -139,37 +121,45 @@ public class ConsCell return result; } - @Override - public String toString() { - StringBuilder bob = new StringBuilder("("); + @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()) + 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(")"); - } - } + 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; + return bob.toString(); } + /* IPersistentCollection interface implementation */ + + @Override + public int count() { + int result = 1; + ConsCell cell = this; + + while (cell.cdr instanceof ConsCell) { + result ++; + cell = (ConsCell)cell.cdr; + } + + return result; + } + + @Override /** * `empty` is completely undocumented, I'll return `null` until something breaks. */ @@ -182,16 +172,18 @@ public class ConsCell * undocumented. But in PersistentList it's simply a synonym for 'equals', * and that's what I'll implement. */ + @Override public boolean equiv(Object o) { return this.equals(o); } /* ISeq interface implementation */ - + @Override public Object first() { return this.car; } + @Override public ISeq next() { ISeq result; @@ -204,6 +196,7 @@ public class ConsCell return result; } + @Override public ISeq more() { ISeq result; @@ -222,6 +215,7 @@ public class ConsCell * `ConsCell` I'll satisfy both the IPersistentCollection and the * ISeq interfaces. */ + @Override public ConsCell cons(Object o) { if (o instanceof ConsCell) { return new ConsCell((ConsCell) o, this); @@ -235,6 +229,7 @@ public class ConsCell } /* Seqable interface */ + @Override public ISeq seq() { return this; } diff --git a/test/beowulf/bootstrap_test.clj b/test/beowulf/bootstrap_test.clj index 25ac23d..0a2d732 100644 --- a/test/beowulf/bootstrap_test.clj +++ b/test/beowulf/bootstrap_test.clj @@ -75,7 +75,7 @@ (is (= actual expected) "B is CDR of (A . B)")) (let [expected 'B actual (CDR (gsp "(A B C D)"))] - (is (instance? beowulf.cons_cell.ConsCell actual) + (is (instance? beowulf.substrate.ConsCell actual) "CDR of (A B C D) is a cons cell") (is (= (CAR actual) expected) "the CAR of that cons-cell is B")) (is (thrown-with-msg? diff --git a/test/beowulf/cons_cell_test.clj b/test/beowulf/cons_cell_test.clj index 7476db9..3a026a9 100644 --- a/test/beowulf/cons_cell_test.clj +++ b/test/beowulf/cons_cell_test.clj @@ -1,16 +1,16 @@ -(ns beowulf.core-test +(ns beowulf.cons-cell-test (:require [clojure.test :refer :all] [beowulf.cons-cell :refer :all])) (deftest cons-cell-tests (testing "make-cons-cell" (let [expected "(A . B)" - actual (print-str (beowulf.cons_cell.ConsCell. 'A 'B))] + actual (print-str (beowulf.substrate.ConsCell. 'A 'B))] (is (= actual expected) "Cons cells should print as cons cells, natch.")) (let [expected "(A . B)" actual (print-str (make-cons-cell 'A 'B))] (is (= actual expected) "Even if build with the macro.")) - (let [expected beowulf.cons_cell.ConsCell + (let [expected beowulf.substrate.ConsCell actual (print-str (make-cons-cell 'A 'B))] (is (= actual expected) "And they should be cons cells.")) ) @@ -19,37 +19,34 @@ actual (print-str (make-beowulf-list '(A (B C) (D E (F) G) H)))] (is (= actual expected) "Should work for clojure lists, recursively.")) (let [expected "(A (B C) (D E (F) G) H)" - actual (print-str (make-beowulf-list [A [B C] [D E [F] G] H]))] + actual (print-str (make-beowulf-list ['A ['B 'C] ['D 'E ['F] 'G] 'H]))] (is (= actual expected) "Should work for vectors, too.")) (let [expected "NIL" actual (print-str (make-beowulf-list []))] - (is (= actual expected) "An empty sequence is NIL.")) - (let [expected beowulf.cons_cell.ConsCell - actual (make-beowulf-list '(A (B C) (D E (F) G) H))] - (is (= actual expected) "A beowulf list is made of cons cells."))) + (is (= actual expected) "An empty sequence is NIL."))) (testing "pretty-print" (let [expected "(A\n (B C)\n (D E (F) G) H)" - actual (pretty-print (make-beowulf-list '(A (B C) (D E (F) G) H)) 20 0)] + actual (with-out-str (pretty-print (make-beowulf-list '(A (B C) (D E (F) G) H)) 20 0))] (is (= actual expected))) - (let [expected "(A (B C) (D E (F) G) H)" - actual (pretty-print (make-beowulf-list '(A (B C) (D E (F) G) H)))] - (is (= actual expected)))) - (testing "count" - (let [expected 4 - actual (count (make-beowulf-list '(A (B C) (D E (F) G) H)) 20 0)] - (is (= actual expected))) - (let [expected 1 - actual (count (make-beowulf-list '(A)))] - (is (= actual expected))) - (let [expected 1 - actual (count (make-cons-cell 'A 'B))] + (let [expected "(A (B C) (D E (F) G) H)\n" + actual (with-out-str (pretty-print (make-beowulf-list '(A (B C) (D E (F) G) H))))] (is (= actual expected)))) +;; (testing "count" +;; (let [expected 4 +;; actual (.count (make-beowulf-list '(A (B C) (D E (F) G) H)) 20 0)] +;; (is (= actual expected))) +;; (let [expected 1 +;; actual (.count (make-beowulf-list '(A)))] +;; (is (= actual expected))) +;; (let [expected 1 +;; actual (.count (make-cons-cell 'A 'B))] +;; (is (= actual expected)))) (testing "sequence functions" (let [expected "A" actual (print-str (first (make-beowulf-list '(A (B C) (D E (F) G) H))))] (is (= actual expected))) (let [expected "((B C) (D E (F) G) H)" - actual (print-str (more (make-beowulf-list '(A (B C) (D E (F) G) H))))] + actual (print-str (.more (make-beowulf-list '(A (B C) (D E (F) G) H))))] (is (= actual expected))) (let [expected "((B C) (D E (F) G) H)" actual (print-str (next (make-beowulf-list '(A (B C) (D E (F) G) H))))] diff --git a/test/beowulf/host_test.clj b/test/beowulf/host_test.clj index ebe8aa6..777bd36 100644 --- a/test/beowulf/host_test.clj +++ b/test/beowulf/host_test.clj @@ -2,6 +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.bootstrap :refer [CDR]] [beowulf.host :refer :all] [beowulf.read :refer [gsp]])) @@ -9,10 +10,18 @@ (testing "RPLACA" (let [l (make-beowulf-list '(A B C D E)) - target (.CDR l) + target (CDR l) expected "(A F C D E)" - actual (print-str (RPLACA target 'F))] + actual (do (RPLACA target 'F) (print-str l))] (is (= actual expected))) - - )) + ) + (testing "RPLACA" + (let + [l (make-beowulf-list '(A B C D E)) + target (CDR l) + expected "(A B . F)" + actual (do (RPLACD target 'F) (print-str l))] + (is (= actual expected))) + ) + )