From 46f75a0c4f383b6e3fe437b045cc7f141bad7fdd Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sun, 26 Mar 2023 20:23:48 +0100 Subject: [PATCH] SYSOUT now sort-of working; SYSIN present but not tested Masses of stuff has had to be moved around because of cyclic dependency hell, and some of that may need to be revisited. --- docs/codox/beowulf.bootstrap.html | 2 +- docs/codox/beowulf.cons-cell.html | 2 +- docs/codox/beowulf.core.html | 2 +- docs/codox/beowulf.host.html | 2 +- docs/codox/beowulf.io.html | 8 +++ docs/codox/beowulf.read.html | 8 +-- docs/codox/beowulf.reader.generate.html | 2 +- docs/codox/beowulf.reader.macros.html | 2 +- docs/codox/beowulf.reader.parser.html | 2 +- docs/codox/beowulf.reader.simplify.html | 2 +- docs/codox/index.html | 2 +- docs/codox/intro.html | 2 +- project.clj | 1 + src/beowulf/bootstrap.clj | 50 +++------------- src/beowulf/cons_cell.clj | 42 ++++++++++++-- src/beowulf/core.clj | 46 ++++++++------- src/beowulf/host.clj | 8 +-- src/beowulf/io.clj | 77 +++++++++++++++++++++++++ src/beowulf/oblist.clj | 19 ++++++ src/beowulf/read.clj | 7 ++- src/beowulf/reader/generate.clj | 3 +- src/beowulf/reader/macros.clj | 10 ++-- src/beowulf/reader/simplify.clj | 3 +- test/beowulf/bootstrap_test.clj | 7 ++- test/beowulf/host_test.clj | 4 +- test/beowulf/interop_test.clj | 10 ++-- test/beowulf/mexpr_test.clj | 2 +- test/beowulf/sexpr_test.clj | 1 - 28 files changed, 215 insertions(+), 111 deletions(-) create mode 100644 docs/codox/beowulf.io.html create mode 100644 src/beowulf/io.clj create mode 100644 src/beowulf/oblist.clj diff --git a/docs/codox/beowulf.bootstrap.html b/docs/codox/beowulf.bootstrap.html index e2883df..3de9383 100644 --- a/docs/codox/beowulf.bootstrap.html +++ b/docs/codox/beowulf.bootstrap.html @@ -1,6 +1,6 @@ -beowulf.bootstrap documentation

beowulf.bootstrap

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..

+beowulf.bootstrap documentation

beowulf.bootstrap

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..

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 objects.

*options*

dynamic

Command line options from invocation.

APPEND

(APPEND x y)

Append the the elements of y to the elements of x.

All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 11 of the Lisp 1.5 Programmers Manual.

APPLY

(APPLY function args environment)

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.

ASSOC

(ASSOC x a)

If a is an association list such as the one formed by PAIRLIS in the above example, then assoc will produce the first pair whose first term is x. Thus it is a table searching function.

All args are assumed to be beowulf.cons-cell/ConsCell objects. See page 12 of the Lisp 1.5 Programmers Manual.

ATOM

macro

(ATOM x)

Returns T if and only if the argument x is bound to an atom; else F. It is not clear to me from the documentation whether (ATOM 7) should return T or F. I’m going to assume T.

ATOM?

macro

(ATOM? x)

The convention of returning F from predicates, rather than NIL, is going to tie me in knots. This is a variant of ATOM which returns NIL on failure.

CAAAAR

macro

(CAAAAR x)

TODO: write docs

CAAADR

macro

(CAAADR x)

TODO: write docs

CAAAR

macro

(CAAAR x)

TODO: write docs

CAADAR

macro

(CAADAR x)

TODO: write docs

CAADDR

macro

(CAADDR x)

TODO: write docs

CAADR

macro

(CAADR x)

TODO: write docs

CAAR

macro

(CAAR x)

TODO: write docs

CADAAR

macro

(CADAAR x)

TODO: write docs

CADADR

macro

(CADADR x)

TODO: write docs

CADAR

macro

(CADAR x)

TODO: write docs

CADDAR

macro

(CADDAR x)

TODO: write docs

CADDDR

macro

(CADDDR x)

TODO: write docs

CADDR

macro

(CADDR x)

TODO: write docs

CADR

macro

(CADR x)

TODO: write docs

CAR

(CAR x)

Return the item indicated by the first pointer of a pair. NIL is treated specially: the CAR of NIL is NIL.

CDAAAR

macro

(CDAAAR x)

TODO: write docs

CDAADR

macro

(CDAADR x)

TODO: write docs

CDAAR

macro

(CDAAR x)

TODO: write docs

CDADAR

macro

(CDADAR x)

TODO: write docs

CDADDR

macro

(CDADDR x)

TODO: write docs

CDADR

macro

(CDADR x)

TODO: write docs

CDAR

macro

(CDAR x)

TODO: write docs

CDDAAR

macro

(CDDAAR x)

TODO: write docs

CDDADR

macro

(CDDADR x)

TODO: write docs

CDDAR

macro

(CDDAR x)

TODO: write docs

CDDDAR

macro

(CDDDAR x)

TODO: write docs

CDDDDR

macro

(CDDDDR x)

TODO: write docs

CDDDR

macro

(CDDDR x)

TODO: write docs

CDDR

macro

(CDDR x)

TODO: write docs

CDR

(CDR x)

Return the item indicated by the second pointer of a pair. NIL is treated specially: the CDR of NIL is NIL.

CONS

(CONS car cdr)

Construct a new instance of cons cell with this car and cdr.

DEFINE

(DEFINE args)

Bootstrap-only version of DEFINE which, post boostrap, can be overwritten in LISP.

diff --git a/docs/codox/beowulf.cons-cell.html b/docs/codox/beowulf.cons-cell.html index df217e8..0c4d56b 100644 --- a/docs/codox/beowulf.cons-cell.html +++ b/docs/codox/beowulf.cons-cell.html @@ -1,3 +1,3 @@ -beowulf.cons-cell documentation

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, and must have both CAR and CDR mutable, so cannot be implemented on top of Clojure lists.

cons-cell?

(cons-cell? o)

Is this object o a beowulf cons-cell?

F

The canonical false value - different from NIL, which is not canonically false in Lisp 1.5.

make-beowulf-list

(make-beowulf-list x)

Construct a linked list of cons cells with the same content as the sequence x.

make-cons-cell

(make-cons-cell car cdr)

Construct a new instance of cons cell with this car and cdr.

MutableSequence

protocol

Like a sequence, but mutable.

members

getCar

(getCar this)

Return the first element of this sequence.

getCdr

(getCdr this)

like more, q.v., but returns List NIL not Clojure nil when empty.

getUid

(getUid this)

Returns a unique identifier for this object

rplaca

(rplaca this value)

replace the first element of this sequence with this value

rplacd

(rplacd this value)

replace the rest (but-first; cdr) of this sequence with this value

NIL

The canonical empty list symbol.

pretty-print

(pretty-print cell)(pretty-print cell width level)

This isn’t the world’s best pretty printer but it sort of works.

T

The canonical true value.

\ No newline at end of file +beowulf.cons-cell documentation

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, and must have both CAR and CDR mutable, so cannot be implemented on top of Clojure lists.

cons-cell?

(cons-cell? o)

Is this object o a beowulf cons-cell?

F

The canonical false value - different from NIL, which is not canonically false in Lisp 1.5.

make-beowulf-list

(make-beowulf-list x)

Construct a linked list of cons cells with the same content as the sequence x.

make-cons-cell

(make-cons-cell car cdr)

Construct a new instance of cons cell with this car and cdr.

MutableSequence

protocol

Like a sequence, but mutable.

members

getCar

(getCar this)

Return the first element of this sequence.

getCdr

(getCdr this)

like more, q.v., but returns List NIL not Clojure nil when empty.

getUid

(getUid this)

Returns a unique identifier for this object

rplaca

(rplaca this value)

replace the first element of this sequence with this value

rplacd

(rplacd this value)

replace the rest (but-first; cdr) of this sequence with this value

NIL

The canonical empty list symbol.

pretty-print

(pretty-print cell)(pretty-print cell width level)

This isn’t the world’s best pretty printer but it sort of works.

T

The canonical true value.

\ No newline at end of file diff --git a/docs/codox/beowulf.core.html b/docs/codox/beowulf.core.html index 26270d3..35a71e3 100644 --- a/docs/codox/beowulf.core.html +++ b/docs/codox/beowulf.core.html @@ -1,3 +1,3 @@ -beowulf.core documentation

beowulf.core

Essentially, the -main function and the bootstrap read-eval-print loop.

-main

(-main & opts)

Parse options, print the banner, read the init file if any, and enter the read/eval/print loop.

cli-options

TODO: write docs

repl

(repl prompt)

Read/eval/print loop.

stop-word

TODO: write docs

\ No newline at end of file +beowulf.core documentation

beowulf.core

Essentially, the -main function and the bootstrap read-eval-print loop.

-main

(-main & opts)

Parse options, print the banner, read the init file if any, and enter the read/eval/print loop.

cli-options

TODO: write docs

repl

(repl prompt)

Read/eval/print loop.

stop-word

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/beowulf.host.html b/docs/codox/beowulf.host.html index ee61b9b..e754d62 100644 --- a/docs/codox/beowulf.host.html +++ b/docs/codox/beowulf.host.html @@ -1,3 +1,3 @@ -beowulf.host documentation

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.

ADD1

(ADD1 x)

TODO: write docs

DIFFERENCE

(DIFFERENCE x y)

TODO: write docs

FIXP

(FIXP x)

TODO: write docs

LIST

(LIST & args)

TODO: write docs

NUMBERP

(NUMBERP x)

TODO: write docs

PLUS2

(PLUS2 x y)

Lisp 1.5 PLUS is varargs, and implementing varargs functions in Clojure is not an added complexity I want. So this is a two arg PLUS, on which a varargs PLUS can be built in the Lisp 1.5 layer using REDUCE.

QUOTIENT

(QUOTIENT x y)

I’m not certain from the documentation whether Lisp 1.5 QUOTIENT returned the integer part of the quotient, or a realnum representing the whole quotient. I am for now implementing the latter.

REMAINDER

(REMAINDER x y)

TODO: write docs

RPLACA

(RPLACA 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)

RPLACD

(RPLACD cell value)

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)

SUB1

(SUB1 x)

TODO: write docs

TIMES2

(TIMES2 x y)

TODO: write docs

\ No newline at end of file +beowulf.host documentation

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.

ADD1

(ADD1 x)

TODO: write docs

DIFFERENCE

(DIFFERENCE x y)

TODO: write docs

FIXP

(FIXP x)

TODO: write docs

LIST

(LIST & args)

TODO: write docs

NUMBERP

(NUMBERP x)

TODO: write docs

PLUS2

(PLUS2 x y)

Lisp 1.5 PLUS is varargs, and implementing varargs functions in Clojure is not an added complexity I want. So this is a two arg PLUS, on which a varargs PLUS can be built in the Lisp 1.5 layer using REDUCE.

QUOTIENT

(QUOTIENT x y)

I’m not certain from the documentation whether Lisp 1.5 QUOTIENT returned the integer part of the quotient, or a realnum representing the whole quotient. I am for now implementing the latter.

REMAINDER

(REMAINDER x y)

TODO: write docs

RPLACA

(RPLACA 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)

RPLACD

(RPLACD cell value)

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)

SUB1

(SUB1 x)

TODO: write docs

TIMES2

(TIMES2 x y)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/beowulf.io.html b/docs/codox/beowulf.io.html new file mode 100644 index 0000000..f37db22 --- /dev/null +++ b/docs/codox/beowulf.io.html @@ -0,0 +1,8 @@ + +beowulf.io documentation

beowulf.io

Non-standard extensions to Lisp 1.5 to read and write to the filesystem.

+

Lisp 1.5 had only READ, which read one S-Expression at a time, and various forms of PRIN* functions, which printed to the line printer. There was also PUNCH, which wrote to a card punch. It does not seem that there was any concept of an interactive terminal.

+

See Appendix E, OVERLORD - THE MONITOR, and Appendix F, LISP INPUT +AND OUTPUT.

+

For our purposes, to save the current state of the Lisp system it should be sufficient to print the current contents of the oblist to file; and to restore a previous state from file, to overwrite the contents of the oblist with data from that file.

+

Hence functions SYSOUT and SYSIN, which do just that.

SYSOUT

(SYSOUT)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/beowulf.read.html b/docs/codox/beowulf.read.html index e5d4d8b..b1dd297 100644 --- a/docs/codox/beowulf.read.html +++ b/docs/codox/beowulf.read.html @@ -1,9 +1,9 @@ -beowulf.read documentation

beowulf.read

This 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.

+beowulf.read documentation

beowulf.read

This 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 fLAMthe symbolic expression language SEXPR, which I do not believe the Lisp 1.5 reader ever did;
  2. -
  3. 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.
  4. +
  5. 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;
  6. +
  7. It treats everything between a double 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.
-

Both these extensions can be disabled by using the --strict command line switch.

gsp

(gsp s)

Shortcut macro - the internals of read; or, if you like, read-string. Argument s should be a string representation of a valid Lisp expression.

number-lines

(number-lines s)(number-lines s e)

TODO: write docs

READ

(READ)(READ input)

An implementation of a Lisp reader sufficient for bootstrapping; not necessarily the final Lisp reader. input should be either a string representation of a LISP expression, or else an input stream. A single form will be read.

read-from-console

(read-from-console)

Attempt to read a complete lisp expression from the console.

strip-line-comments

(strip-line-comments s)

Strip blank lines and comment lines from this string s, expected to be Lisp source.

\ No newline at end of file +

Both these extensions can be disabled by using the --strict command line switch.

gsp

(gsp s)

Shortcut macro - the internals of read; or, if you like, read-string. Argument s should be a string representation of a valid Lisp expression.

number-lines

(number-lines s)(number-lines s e)

TODO: write docs

READ

(READ)(READ input)

An implementation of a Lisp reader sufficient for bootstrapping; not necessarily the final Lisp reader. input should be either a string representation of a LISP expression, or else an input stream. A single form will be read.

read-from-console

(read-from-console)

Attempt to read a complete lisp expression from the console. NOTE that this will only really work for S-Expressions, not M-Expressions.

strip-line-comments

(strip-line-comments s)

Strip blank lines and comment lines from this string s, expected to be Lisp source.

\ No newline at end of file diff --git a/docs/codox/beowulf.reader.generate.html b/docs/codox/beowulf.reader.generate.html index 3241c93..054c622 100644 --- a/docs/codox/beowulf.reader.generate.html +++ b/docs/codox/beowulf.reader.generate.html @@ -1,6 +1,6 @@ -beowulf.reader.generate documentation

beowulf.reader.generate

Generating S-Expressions from parse trees.

+beowulf.reader.generate documentation

beowulf.reader.generate

Generating S-Expressions from parse trees.

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/docs/codox/beowulf.reader.macros.html b/docs/codox/beowulf.reader.macros.html index 5534744..3485d0e 100644 --- a/docs/codox/beowulf.reader.macros.html +++ b/docs/codox/beowulf.reader.macros.html @@ -1,3 +1,3 @@ -beowulf.reader.macros documentation

beowulf.reader.macros

Can I implement reader macros? let’s see!

*readmacros*

dynamic

TODO: write docs

expand-macros

(expand-macros form)

TODO: write docs

\ No newline at end of file +beowulf.reader.macros documentation

beowulf.reader.macros

Can I implement reader macros? let’s see!

*readmacros*

dynamic

TODO: write docs

expand-macros

(expand-macros form)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/beowulf.reader.parser.html b/docs/codox/beowulf.reader.parser.html index 43b0526..9de4af2 100644 --- a/docs/codox/beowulf.reader.parser.html +++ b/docs/codox/beowulf.reader.parser.html @@ -1,3 +1,3 @@ -beowulf.reader.parser documentation

beowulf.reader.parser

The actual parser, supporting both S-expression and M-expression syntax.

parse

Parse a string presented as argument into a parse tree which can then be operated upon further.

\ No newline at end of file +beowulf.reader.parser documentation

beowulf.reader.parser

The actual parser, supporting both S-expression and M-expression syntax.

parse

Parse a string presented as argument into a parse tree which can then be operated upon further.

\ No newline at end of file diff --git a/docs/codox/beowulf.reader.simplify.html b/docs/codox/beowulf.reader.simplify.html index 11b5d77..ca4c68d 100644 --- a/docs/codox/beowulf.reader.simplify.html +++ b/docs/codox/beowulf.reader.simplify.html @@ -1,3 +1,3 @@ -beowulf.reader.simplify documentation

beowulf.reader.simplify

Simplify parse trees. Be aware that this is very tightly coupled with the parser.

remove-nesting

(remove-nesting tree context)

TODO: write docs

remove-optional-space

(remove-optional-space tree)

TODO: write docs

simplify

(simplify p)(simplify p context)

Simplify this parse tree p. If p is an instaparse failure object, throw an ex-info, with p as the value of its :failure key.

\ No newline at end of file +beowulf.reader.simplify documentation

beowulf.reader.simplify

Simplify parse trees. Be aware that this is very tightly coupled with the parser.

remove-nesting

(remove-nesting tree context)

TODO: write docs

remove-optional-space

(remove-optional-space tree)

TODO: write docs

simplify

(simplify p)(simplify p context)

Simplify this parse tree p. If p is an instaparse failure object, throw an ex-info, with p as the value of its :failure key.

\ No newline at end of file diff --git a/docs/codox/index.html b/docs/codox/index.html index 1cfca84..8ad2e8d 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -Beowulf 0.2.1-SNAPSHOT

Beowulf 0.2.1-SNAPSHOT

Released under the GPL-2.0-or-later

An implementation of LISP 1.5 in Clojure.

Installation

To install, add the following dependency to your project or build file:

[beowulf "0.2.1-SNAPSHOT"]

Topics

Namespaces

beowulf.bootstrap

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..

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, and must have both CAR and CDR mutable, so cannot be implemented on top of Clojure lists.

beowulf.core

Essentially, the -main function and the bootstrap read-eval-print loop.

Public variables and functions:

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.

beowulf.read

This 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.

Public variables and functions:

beowulf.reader.macros

Can I implement reader macros? let’s see!

Public variables and functions:

beowulf.reader.parser

The actual parser, supporting both S-expression and M-expression syntax.

Public variables and functions:

beowulf.reader.simplify

Simplify parse trees. Be aware that this is very tightly coupled with the parser.

Public variables and functions:

\ No newline at end of file +Beowulf 0.2.1-SNAPSHOT

Beowulf 0.2.1-SNAPSHOT

Released under the GPL-2.0-or-later

An implementation of LISP 1.5 in Clojure.

Installation

To install, add the following dependency to your project or build file:

[beowulf "0.2.1-SNAPSHOT"]

Topics

Namespaces

beowulf.bootstrap

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..

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, and must have both CAR and CDR mutable, so cannot be implemented on top of Clojure lists.

beowulf.core

Essentially, the -main function and the bootstrap read-eval-print loop.

Public variables and functions:

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.

beowulf.io

Non-standard extensions to Lisp 1.5 to read and write to the filesystem.

Public variables and functions:

beowulf.read

This 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.

Public variables and functions:

beowulf.reader.macros

Can I implement reader macros? let’s see!

Public variables and functions:

beowulf.reader.parser

The actual parser, supporting both S-expression and M-expression syntax.

Public variables and functions:

beowulf.reader.simplify

Simplify parse trees. Be aware that this is very tightly coupled with the parser.

Public variables and functions:

\ No newline at end of file diff --git a/docs/codox/intro.html b/docs/codox/intro.html index 0598d94..77d790d 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -1,4 +1,4 @@ -Introduction to beowulf

Introduction to beowulf

+Introduction to beowulf

Introduction to beowulf

TODO: write great documentation

\ No newline at end of file diff --git a/project.clj b/project.clj index 77d2367..ceda0ea 100644 --- a/project.clj +++ b/project.clj @@ -11,6 +11,7 @@ [org.clojure/math.numeric-tower "0.0.5"] [org.clojure/tools.cli "1.0.214"] [org.clojure/tools.trace "0.7.11"] + [clojure.java-time "1.2.0"] [environ "1.2.0"] [instaparse "1.4.12"] [rhizome "0.2.9"] ;; not needed in production builds diff --git a/src/beowulf/bootstrap.clj b/src/beowulf/bootstrap.clj index 1b01322..8d9689a 100644 --- a/src/beowulf/bootstrap.clj +++ b/src/beowulf/bootstrap.clj @@ -11,10 +11,12 @@ objects." (:require [clojure.string :as s] [clojure.tools.trace :refer [deftrace]] - [beowulf.cons-cell :refer [cons-cell? make-beowulf-list make-cons-cell - NIL pretty-print T F]] + [beowulf.cons-cell :refer [CAR CDR CONS LIST make-beowulf-list make-cons-cell + pretty-print T F]] [beowulf.host :refer [ADD1 DIFFERENCE FIXP NUMBERP PLUS2 QUOTIENT - REMAINDER RPLACA RPLACD SUB1 TIMES2]]) + REMAINDER RPLACA RPLACD SUB1 TIMES2]] + [beowulf.io :refer [SYSIN SYSOUT]] + [beowulf.oblist :refer [*options* oblist NIL]]) (:import [beowulf.cons_cell ConsCell])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -29,13 +31,6 @@ (declare EVAL) -(def oblist - "The default environment." - (atom NIL)) - -(def ^:dynamic *options* - "Command line options from invocation." - {}) (defmacro NULL "Returns `T` if and only if the argument `x` is bound to `NIL`; else `F`." @@ -61,35 +56,6 @@ [x] `(if (or (symbol? ~x) (number? ~x)) T NIL)) -(defn CONS - "Construct a new instance of cons cell with this `car` and `cdr`." - [car cdr] - (beowulf.cons_cell.ConsCell. car cdr (gensym "c"))) - -(defn CAR - "Return the item indicated by the first pointer of a pair. NIL is treated - specially: the CAR of NIL is NIL." - [x] - (if - (= x NIL) NIL - (try - (or (.getCar x) NIL) - (catch Exception any - (throw (Exception. - (str "Cannot take CAR of `" x "` (" (.getName (.getClass x)) ")") any)))))) - -(defn CDR - "Return the item indicated by the second pointer of a pair. NIL is treated - specially: the CDR of NIL is NIL." - [x] - (if - (= x NIL) NIL - (try - (.getCdr x) - (catch Exception any - (throw (Exception. - (str "Cannot take CDR of `" x "` (" (.getName (.getClass x)) ")") any)))))) - (defn uaf "Universal access function; `l` is expected to be an arbitrary LISP list, `path` a (clojure) list of the characters `a` and `d`. Intended to make declaring @@ -267,7 +233,7 @@ :else (make-cons-cell (SUBLIS a (CAR y)) (SUBLIS a (CDR y))))) -(deftrace interop-interpret-q-name +(defn interop-interpret-q-name "For interoperation with Clojure, it will often be necessary to pass qualified names that are not representable in Lisp 1.5. This function takes a sequence in the form `(PART PART PART... NAME)` and returns @@ -308,7 +274,7 @@ :else (conj (to-clojure (CDR l)) (to-clojure (CAR l))))) -(deftrace INTEROP +(defn INTEROP "Clojure (or other host environment) interoperation API. `fn-symbol` is expected to be either @@ -437,6 +403,8 @@ (= function 'EQ) (apply EQ args) (= function 'INTEROP) (INTEROP (CAR args) (CDR args)) (= function 'SET) (SET (CAR args) (CADR args)) + (= function 'SYSIN) (SYSIN (CAR args)) + (= function 'SYSOUT) (SYSOUT (CAR args)) (EVAL function environment) (APPLY (EVAL function environment) args diff --git a/src/beowulf/cons_cell.clj b/src/beowulf/cons_cell.clj index 47f7e95..a4585d9 100644 --- a/src/beowulf/cons_cell.clj +++ b/src/beowulf/cons_cell.clj @@ -2,9 +2,10 @@ "The fundamental cons cell on which all Lisp structures are built. Lisp 1.5 lists do not necessarily have a sequence as their CDR, and must have both CAR and CDR mutable, so cannot be implemented on top - of Clojure lists.") + of Clojure lists." + (:require [beowulf.oblist :refer [NIL]])) -(declare cons-cell? NIL) +(declare cons-cell?) (def T "The canonical true value." @@ -222,10 +223,6 @@ (throw (ex-info "Cound not construct cons cell" {:car car :cdr cdr} any))))) -(def NIL - "The canonical empty list symbol." - 'NIL) - (defn cons-cell? "Is this object `o` a beowulf cons-cell?" [o] @@ -251,3 +248,36 @@ (throw (ex-info "Could not construct Beowulf list" {:content x} any))))) + +(defn CONS + "Construct a new instance of cons cell with this `car` and `cdr`." + [car cdr] + (beowulf.cons_cell.ConsCell. car cdr (gensym "c"))) + +(defn CAR + "Return the item indicated by the first pointer of a pair. NIL is treated + specially: the CAR of NIL is NIL." + [x] + (if + (= x NIL) NIL + (try + (or (.getCar x) NIL) + (catch Exception any + (throw (Exception. + (str "Cannot take CAR of `" x "` (" (.getName (.getClass x)) ")") any)))))) + +(defn CDR + "Return the item indicated by the second pointer of a pair. NIL is treated + specially: the CDR of NIL is NIL." + [x] + (if + (= x NIL) NIL + (try + (.getCdr x) + (catch Exception any + (throw (Exception. + (str "Cannot take CDR of `" x "` (" (.getName (.getClass x)) ")") any)))))) + +(defn LIST + [& args] + (make-beowulf-list args)) \ No newline at end of file diff --git a/src/beowulf/core.clj b/src/beowulf/core.clj index 66d8dff..99fab8f 100644 --- a/src/beowulf/core.clj +++ b/src/beowulf/core.clj @@ -1,7 +1,8 @@ (ns beowulf.core "Essentially, the `-main` function and the bootstrap read-eval-print loop." - (:require [beowulf.bootstrap :refer [EVAL oblist *options*]] + (:require [beowulf.bootstrap :refer [EVAL]] [beowulf.read :refer [READ read-from-console]] + [beowulf.oblist :refer [*options* oblist]] [clojure.java.io :as io] [clojure.pprint :refer [pprint]] [clojure.string :refer [trim]] @@ -11,13 +12,20 @@ (def stop-word "STOP") (def cli-options - [["-h" "--help"] + [["-f FILEPATH" "--file-path FILEPATH" + "Set the path to the directory for reading and writing Lisp files." + :validate [#(and (.exists (io/file %)) + (.isDirectory (io/file %)) + (.canRead (io/file %)) + (.canWrite (io/file %))) + "File path must exist and must be a directory."]] + ["-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 %))) + (.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."]]) @@ -29,8 +37,6 @@ (print prompt) (flush) (try - ;; TODO: does not currently allow the reading of forms covering multiple - ;; lines. (let [input (trim (read-from-console))] (cond (= input stop-word) (throw (ex-info "\nFærwell!" {:cause :quit})) @@ -57,26 +63,26 @@ [& opts] (let [args (parse-opts opts cli-options)] (println - (str - "\nHider wilcuman. Béowulf is mín nama.\n" - (when - (System/getProperty "beowulf.version") - (str "Síðe " (System/getProperty "beowulf.version") "\n")) - (when - (:help (:options args)) - (:summary args)) - (when (:errors args) - (apply str (interpose "; " (:errors args)))) - "\nSprecan '" stop-word "' tó laéfan\n")) + (str + "\nHider wilcuman. Béowulf is mín nama.\n" + (when + (System/getProperty "beowulf.version") + (str "Síðe " (System/getProperty "beowulf.version") "\n")) + (when + (:help (:options args)) + (:summary args)) + (when (:errors args) + (apply str (interpose "; " (:errors args)))) + "\nSprecan '" stop-word "' tó laéfan\n")) (binding [*options* (:options args)] (try (repl (str (:prompt (:options args)) " ")) (catch - Exception - e + Exception + e (let [data (ex-data e)] (if - data + data (case (:cause data) :quit nil ;; default diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj index 8992bb0..ff3b895 100644 --- a/src/beowulf/host.clj +++ b/src/beowulf/host.clj @@ -2,9 +2,9 @@ "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 [F make-beowulf-list NIL T]] + (:require [beowulf.cons-cell :refer [F make-beowulf-list T]] ;; note hyphen - this is Clojure... - ) + [beowulf.oblist :refer [NIL]]) (:import [beowulf.cons_cell ConsCell] ;; note underscore - same namespace, but Java. )) @@ -108,7 +108,3 @@ (defn NUMBERP [x] (if (number? x) T F)) - -(defn LIST - [& args] - (make-beowulf-list args)) \ No newline at end of file diff --git a/src/beowulf/io.clj b/src/beowulf/io.clj new file mode 100644 index 0000000..be6b5cb --- /dev/null +++ b/src/beowulf/io.clj @@ -0,0 +1,77 @@ +(ns beowulf.io + "Non-standard extensions to Lisp 1.5 to read and write to the filesystem. + + Lisp 1.5 had only `READ`, which read one S-Expression at a time, and + various forms of `PRIN*` functions, which printed to the line printer. + There was also `PUNCH`, which wrote to a card punch. It does not seem + that there was any concept of an interactive terminal. + + See Appendix E, `OVERLORD - THE MONITOR`, and Appendix F, `LISP INPUT + AND OUTPUT`. + + For our purposes, to save the current state of the Lisp system it should + be sufficient to print the current contents of the oblist to file; and to + restore a previous state from file, to overwrite the contents of the + oblist with data from that file. + + Hence functions SYSOUT and SYSIN, which do just that." + (:require [beowulf.cons-cell :refer [pretty-print]] + [beowulf.oblist :refer [*options* oblist]] + [beowulf.read :refer [READ]] + [clojure.string :refer [ends-with?]] + [java-time.api :refer [local-date local-date-time]])) + +(defn- full-path + [fp] + (str + (if (:filepath *options*) + (str (:filepath *options*) (java.io.File/separator)) + "") + (if (and (string? fp) + (> (count fp) 0)) + fp + (str "Sysout-" (local-date))) + (if (ends-with? fp ".lsp") + fp + (str fp ".lsp")))) + +(defn SYSOUT + "Dump the current content of the object list to file. If no `filepath` is + specified, a file name will be constructed of the symbol `Sysout` and + the current date. File paths will be considered relative to the filepath + set when starting Lisp." + ([] + (SYSOUT nil)) + ([filepath] + (spit (full-path (str filepath)) + (with-out-str + (println (apply str (repeat 79 ";"))) + (println (format ";; Beowulf %s Sysout file generated at %s" + (System/getProperty "beowulf.version") + (local-date-time))) + (when (System/getenv "USER") + (println (format ";; generated by %s" (System/getenv "USER")))) + (println (apply str (repeat 79 ";"))) + (println) + (pretty-print @oblist))))) + +(defn SYSIN + "Read the contents of the file at this `filepath` into the object list. + + If the file is not a valid Beowulf sysout file, this will probably + corrupt the system, you have been warned. File paths will be considered + relative to the filepath set when starting Lisp. + + **NOTE THAT** if the provided `filepath` does not end with `.lsp` (which, + if you're writing it from the Lisp REPL it won't), the extension `.lsp` + will be appended." + [filepath] + (let [fp (full-path (str filepath)) + content (try (READ (slurp fp)) + (catch Throwable any + (throw (ex-info "Could not read from sysout" + {:context "SYSIN" + :filepath fp} + any))))] + (swap! oblist #(when (or % (seq content)) content)))) + diff --git a/src/beowulf/oblist.clj b/src/beowulf/oblist.clj new file mode 100644 index 0000000..5c36256 --- /dev/null +++ b/src/beowulf/oblist.clj @@ -0,0 +1,19 @@ +(ns beowulf.oblist + "A namespace mainly devoted to the object list. + + Yes, this makes little sense, but if you put it anywhere else you end + up in cyclic dependency hell." + ) + +(def NIL + "The canonical empty list symbol." + 'NIL) + +(def oblist + "The default environment." + (atom NIL)) + +(def ^:dynamic *options* + "Command line options from invocation." + {}) + diff --git a/src/beowulf/read.clj b/src/beowulf/read.clj index 44cf163..31b0a65 100644 --- a/src/beowulf/read.clj +++ b/src/beowulf/read.clj @@ -5,10 +5,10 @@ Intended deviations from the behaviour of the real Lisp reader are as follows: - 1. It reads the meta-expression language `MEXPR` in addition to fLAMthe + 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, + 2. It treats everything between a double 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. Both these extensions can be disabled by using the `--strict` command line @@ -64,7 +64,8 @@ (generate (simplify (remove-optional-space parse-tree)))))) (defn read-from-console - "Attempt to read a complete lisp expression from the console." + "Attempt to read a complete lisp expression from the console. NOTE that this + will only really work for S-Expressions, not M-Expressions." [] (loop [r (read-line)] (if (= (count (re-seq #"\(" r)) diff --git a/src/beowulf/reader/generate.clj b/src/beowulf/reader/generate.clj index 53f5a56..cba61b5 100644 --- a/src/beowulf/reader/generate.clj +++ b/src/beowulf/reader/generate.clj @@ -55,8 +55,9 @@ *quote ends* " - (:require [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell NIL]] + (:require [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell]] [beowulf.reader.macros :refer [expand-macros]] + [beowulf.oblist :refer [NIL]] [clojure.math.numeric-tower :refer [expt]] [clojure.string :refer [upper-case]])) diff --git a/src/beowulf/reader/macros.clj b/src/beowulf/reader/macros.clj index 2fc4214..51b6ecd 100644 --- a/src/beowulf/reader/macros.clj +++ b/src/beowulf/reader/macros.clj @@ -1,8 +1,6 @@ (ns beowulf.reader.macros "Can I implement reader macros? let's see!" - (:require [beowulf.bootstrap :refer [CADR CADDR CDDR CONS]] - [beowulf.cons-cell :refer [make-beowulf-list]] - [beowulf.host :refer [LIST]] + (:require [beowulf.cons-cell :refer [CONS LIST make-beowulf-list]] [clojure.string :refer [join]]) (:import [beowulf.cons_cell ConsCell])) @@ -14,9 +12,9 @@ (def ^:dynamic *readmacros* {:car {'DEFUN (fn [f] - (LIST 'SET (LIST 'QUOTE (CADR f)) - (CONS 'LAMBDA (CDDR f)))) - 'SETQ (fn [f] (LIST 'SET (LIST 'QUOTE (CADR f)) (CADDR f)))}}) + (LIST 'SET (LIST 'QUOTE (second f)) + (CONS 'LAMBDA (rest (rest f))))) + 'SETQ (fn [f] (LIST 'SET (LIST 'QUOTE (second f)) (nth f 2)))}}) (defn expand-macros [form] diff --git a/src/beowulf/reader/simplify.clj b/src/beowulf/reader/simplify.clj index 1cf525a..f4520d3 100644 --- a/src/beowulf/reader/simplify.clj +++ b/src/beowulf/reader/simplify.clj @@ -1,8 +1,7 @@ (ns beowulf.reader.simplify "Simplify parse trees. Be aware that this is very tightly coupled with the parser." - (:require [beowulf.bootstrap :refer [*options*]] - [clojure.tools.trace :refer [deftrace]] + (:require [beowulf.oblist :refer [*options*]] [instaparse.failure :as f]) (:import [instaparse.gll Failure])) diff --git a/test/beowulf/bootstrap_test.clj b/test/beowulf/bootstrap_test.clj index 0934988..e7a4d56 100644 --- a/test/beowulf/bootstrap_test.clj +++ b/test/beowulf/bootstrap_test.clj @@ -1,9 +1,10 @@ (ns beowulf.bootstrap-test (:require [clojure.test :refer [deftest testing is]] - [beowulf.cons-cell :refer [make-cons-cell NIL T F]] - [beowulf.bootstrap :refer [APPEND ASSOC ATOM ATOM? CAR CAAAAR CADR - CADDR CADDDR CDR EQ EQUAL MEMBER + [beowulf.cons-cell :refer [CAR CDR make-cons-cell T F]] + [beowulf.bootstrap :refer [APPEND ASSOC ATOM ATOM? CAAAAR CADR + CADDR CADDDR EQ EQUAL MEMBER PAIRLIS SUBLIS SUBST]] + [beowulf.oblist :refer [NIL]] [beowulf.read :refer [gsp]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/test/beowulf/host_test.clj b/test/beowulf/host_test.clj index 867c5c0..10b86f4 100644 --- a/test/beowulf/host_test.clj +++ b/test/beowulf/host_test.clj @@ -1,8 +1,8 @@ (ns beowulf.host-test (:require [clojure.test :refer [deftest is testing]] - [beowulf.bootstrap :refer [CDR]] - [beowulf.cons-cell :refer [F make-beowulf-list NIL T]] + [beowulf.cons-cell :refer [CDR F make-beowulf-list T]] [beowulf.host :refer [DIFFERENCE NUMBERP PLUS2 RPLACA RPLACD TIMES2]] + [beowulf.oblist :refer [NIL]] [beowulf.read :refer [gsp]])) (deftest destructive-change-test diff --git a/test/beowulf/interop_test.clj b/test/beowulf/interop_test.clj index 10810fd..98290f2 100644 --- a/test/beowulf/interop_test.clj +++ b/test/beowulf/interop_test.clj @@ -1,6 +1,6 @@ (ns beowulf.interop-test (:require [clojure.test :refer [deftest is testing]] - [beowulf.bootstrap :refer [EVAL INTEROP QUOTE]] + [beowulf.bootstrap :refer [EVAL INTEROP]] [beowulf.read :refer [gsp]])) @@ -9,8 +9,8 @@ (let [expected (symbol "123") actual (INTEROP (gsp "(CLOJURE CORE STR)") (gsp "(1 2 3)"))] (is (= actual expected)))) - ;; (testing "INTEROP called from Lisp" - ;; (let [expected 'ABC - ;; actual (EVAL (gsp "(INTEROP '(CLOJURE CORE STR) '(A B C))") (gsp "((A . A)(B . B)(C . C))"))] - ;; (is (= actual expected)))) + ;; (testing "INTEROP called from Lisp" + ;; (let [expected 'ABC + ;; actual (EVAL (gsp "(INTEROP '(CLOJURE CORE STR) '(QUOTE (A B C)))") (gsp "((A . P)(B . Q)(C . R))"))] + ;; (is (= actual expected)))) ) diff --git a/test/beowulf/mexpr_test.clj b/test/beowulf/mexpr_test.clj index dc8e5b5..ea6528a 100644 --- a/test/beowulf/mexpr_test.clj +++ b/test/beowulf/mexpr_test.clj @@ -2,7 +2,7 @@ "These tests are taken generally from the examples on page 10 of Lisp 1.5 Programmers Manual" (:require [clojure.test :refer [deftest is testing]] - [beowulf.bootstrap :refer [*options*]] + [beowulf.oblist :refer [*options*]] [beowulf.read :refer [gsp]] [beowulf.reader.generate :refer [generate]] [beowulf.reader.parser :refer [parse]] diff --git a/test/beowulf/sexpr_test.clj b/test/beowulf/sexpr_test.clj index a175eb5..4bbcefc 100644 --- a/test/beowulf/sexpr_test.clj +++ b/test/beowulf/sexpr_test.clj @@ -1,6 +1,5 @@ (ns beowulf.sexpr-test (:require [clojure.test :refer [deftest is testing]] - [beowulf.bootstrap :refer [*options*]] [beowulf.cons-cell :refer []] [beowulf.read :refer [gsp]]))