diff --git a/README.md b/README.md index ecdf752..27de79f 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,73 @@ Command line arguments as follows: ``` -h, --help Print this message - -p PROMPT, --prompt PROMPT Sprecan:: Set the REPL prompt to PROMPT + -p PROMPT, --prompt PROMPT Set the REPL prompt to PROMPT -r INITFILE, --read INITFILE Read Lisp functions from the file INITFILE -s, --strict Strictly interpret the Lisp 1.5 language, without extensions. ``` +To end a session, type `STOP` at the command prompt. + +### Functions and symbols implemented + +The following functions and symbols are implemented: + +| Symbol | Type | Signature | Documentation | +|--------|------|-----------|---------------| +| NIL | ? | null | ? | +| T | ? | null | ? | +| F | ? | null | ? | +| ADD1 | Host function | ([x]) | ? | +| AND | Host function | ([& args]) | `T` if and only if none of my `args` evaluate to either `F` or `NIL`, else `F`. In `beowulf.host` principally because I don't yet feel confident to define varargs functions in Lisp. | +| APPEND | Host function | ([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 | Host function | ([function args environment depth]) | Apply this `function` to these `arguments` in this `environment` and return the result. 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. | +| ATOM | Host function | ([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`. | +| CAR | ? | null | ? | +| CDR | ? | null | ? | +| CONS | ? | null | ? | +| COPY | Lisp function | (X) | ? | +| DEFINE | Host function | ([args]) | Bootstrap-only version of `DEFINE` which, post boostrap, can be overwritten in LISP. The single argument to `DEFINE` should be an assoc list which should be nconc'ed onto the front of the oblist. Broadly, (SETQ OBLIST (NCONC ARG1 OBLIST)) | +| DIFFERENCE | Host function | ([x y]) | ? | +| DIVIDE | Lisp function | (X Y) | ? | +| ERROR | Host function | ([& args]) | Throw an error | +| EQ | Host function | ([x y]) | Returns `T` if and only if both `x` and `y` are bound to the same atom, else `NIL`. | +| EQUAL | Host function | ([x y]) | This is a predicate that is true if its two arguments are identical S-expressions, and false if they are different. (The elementary predicate `EQ` is defined only for atomic arguments.) The definition of `EQUAL` is an example of a conditional expression inside a conditional expression. NOTE: returns `F` on failure, not `NIL` | +| EVAL | Host function | ([expr] [expr env depth]) | Evaluate this `expr` and return the result. If `environment` is not passed, it defaults to the current value of the global object list. The `depth` argument is part of the tracing system and should not be set by user code. All args are assumed to be numbers, symbols or `beowulf.cons-cell/ConsCell` objects. | +| FIXP | Host function | ([x]) | ? | +| GENSYM | Host function | ([]) | Generate a unique symbol. | +| GET | Lisp function | (X Y) | ? | +| GREATERP | Host function | ([x y]) | ? | +| INTEROP | Host function | ([fn-symbol args]) | Clojure (or other host environment) interoperation API. `fn-symbol` is expected to be either 1. a symbol bound in the host environment to a function; or 2. a sequence (list) of symbols forming a qualified path name bound to a function. Lower case characters cannot normally be represented in Lisp 1.5, so both the upper case and lower case variants of `fn-symbol` will be tried. If the function you're looking for has a mixed case name, that is not currently accessible. `args` is expected to be a Lisp 1.5 list of arguments to be passed to that function. Return value must be something acceptable to Lisp 1.5, so either a symbol, a number, or a Lisp 1.5 list. If `fn-symbol` is not found (even when cast to lower case), or is not a function, or the value returned cannot be represented in Lisp 1.5, an exception is thrown with `:cause` bound to `:interop` and `:detail` set to a value representing the actual problem. | +| INTERSECTION | Lisp function | (X Y) | ? | +| LENGTH | Lisp function | (L) | ? | +| LESSP | Host function | ([x y]) | ? | +| MEMBER | Lisp function | (A X) | ? | +| MINUSP | Lisp function | (X) | ? | +| NULL | Lisp function | (X) | ? | +| NUMBERP | Host function | ([x]) | ? | +| OBLIST | Host function | ([]) | Return a list of the symbols currently bound on the object list. **NOTE THAT** in the Lisp 1.5 manual, footnote at the bottom of page 69, it implies that an argument can be passed but I'm not sure of the semantics of this. | +| ONEP | Lisp function | (X) | ? | +| PAIR | Lisp function | (X Y) | ? | +| PLUS | Host function | ([& args]) | ? | +| PRETTY | ? | null | ? | +| PRINT | ? | null | ? | +| PROP | Lisp function | (X Y U) | ? | +| QUOTIENT | Host function | ([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. | +| READ | Host function | ([] [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. | +| REMAINDER | Host function | ([x y]) | ? | +| REPEAT | Lisp function | (N X) | ? | +| RPLACA | Host function | ([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 | Host function | ([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) | +| SET | Host function | ([symbol val]) | Implementation of SET in Clojure. Add to the `oblist` a binding of the value of `var` to the value of `val`. NOTE WELL: this is not SETQ! | +| SUB1 | Lisp function | (N) | ? | +| SYSIN | Host function | ([filename]) | Read the contents of the file at this `filename` 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. It is intended that sysout files can be read both from resources within the jar file, and from the file system. If a named file exists in both the file system and the resources, the file system will be preferred. **NOTE THAT** if the provided `filename` does not end with `.lsp` (which, if you're writing it from the Lisp REPL, it won't), the extension `.lsp` will be appended. | +| SYSOUT | Host function | ([] [filepath]) | 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. | +| TERPRI | ? | null | ? | +| TIMES | Host function | ([& args]) | ? | +| TRACE | ? | null | ? | +| UNTRACE | ? | null | ? | +| ZEROP | Lisp function | (N) | ? | + ### Architectural plan Not everything documented in this section is yet built. It indicates the @@ -110,8 +172,13 @@ What's surprised me in working on this is how much more polished Lisp 1.5 is than legend had led me to believe. The language is remarkably close to [Portable Standard Lisp](http://www.softwarepreservation.org/projects/LISP/standard_lisp_family/#Portable_Standard_LISP_) which is in my opinion one of the best and most usable early Lisp -implementations. I'm convinced you could still use Lisp 1.5 for interesting -and useful software (which isn't to say that some modern Lisps aren't better, +implementations. + +What's even more surprising is how faithful a reimplementation of Lisp 1.5 +the first Lisp dialect I learned, [Acornsoft Lisp](https://en.wikipedia.org/wiki/Acornsoft_LISP), turns out to have been. + +I'm convinced you could still use Lisp 1.5 for interesting +and useful software (which isn't to say that modern Lisps aren't better, but this is software which is almost sixty years old). ## Installation @@ -122,25 +189,6 @@ At present, clone the source and build it using You will require to have [Leiningen](https://leiningen.org/) installed. -## Usage - -`java -jar beowulf-0.1.0-standalone.jar` - -This will start a Lisp 1.5 read/eval/print loop (REPL). - -Command line arguments are as follows: - -``` - -f FILEPATH, --file-path FILEPATH Set the path to the directory for reading and writing Lisp files. - -h, --help - -p PROMPT, --prompt PROMPT Sprecan:: Set the REPL prompt to PROMPT - -r INITFILE, --read INITFILE resources/lisp1.5.lsp Read Lisp system from file INITFILE - -s, --strict Strictly interpret the Lisp 1.5 language, without extensions. - -t, --trace Trace Lisp evaluation. -``` - -To end a session, type `STOP` at the command prompt. - ### Input/output Lisp 1.5 greatly predates modern computers. It had a facility to print to a line printer, or to punch cards on a punch-card machine, and it had a facility to read system images in from tape; but there's no file I/O as we would currently understand it, and, because there are no character strings and the valid characters within an atom are limited, it isn't easy to compose a sensible filename. diff --git a/doc/intro.md b/doc/intro.md deleted file mode 100644 index d608497..0000000 --- a/doc/intro.md +++ /dev/null @@ -1,3 +0,0 @@ -# Introduction to beowulf - -TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) diff --git a/doc/intro.md b/doc/intro.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/doc/intro.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/docs/codox/beowulf.bootstrap.html b/docs/codox/beowulf.bootstrap.html index 1e47574..4827d95 100644 --- a/docs/codox/beowulf.bootstrap.html +++ b/docs/codox/beowulf.bootstrap.html @@ -1,21 +1,23 @@ -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.

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)(APPLY function args environment)

Despatcher for APPLY, selects beteen traced-apply and apply-internal based on the value of :trace in *options*. Apply this function to these arguments and return the result. If environment is not passed, if defaults to the current value of the global object list.

apply-internal

(apply-internal function args environment)

Internal guts of both APPLY and traced-apply. Apply this function to these arguments in this environment and return the result.

-

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

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

DEFINE

(DEFINE args)

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

-

The single argument to DEFINE should be an assoc list which should be nconc’ed onto the front of the oblist. Broadly, (SETQ OBLIST (NCONC ARG1 OBLIST))

EQ

(EQ x y)

Returns T if and only if both x and y are bound to the same atom, else NIL.

EQUAL

(EQUAL x y)

This is a predicate that is true if its two arguments are identical S-expressions, and false if they are different. (The elementary predicate EQ is defined only for atomic arguments.) The definition of EQUAL is an example of a conditional expression inside a conditional expression.

-

NOTE: returns F on failure, not NIL

EVAL

(EVAL expr)(EVAL expr env)

Despatcher for EVAL, selects beteen traced-eval and eval-internal based on the value of :trace in *options*. Evaluate this expr and return the result. If environment is not passed, if defaults to the current value of the global object list. All args are assumed to be symbols or beowulf.cons-cell/ConsCell objects.

INTEROP

(INTEROP fn-symbol args)

Clojure (or other host environment) interoperation API. fn-symbol is expected to be either

+

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 depth)

Apply this function to these arguments in this environment and return the result.

+

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

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

DEFINE

(DEFINE args)

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

+

The single argument to DEFINE should be an assoc list which should be nconc’ed onto the front of the oblist. Broadly, (SETQ OBLIST (NCONC ARG1 OBLIST))

EQ

(EQ x y)

Returns T if and only if both x and y are bound to the same atom, else NIL.

EQUAL

(EQUAL x y)

This is a predicate that is true if its two arguments are identical S-expressions, and false if they are different. (The elementary predicate EQ is defined only for atomic arguments.) The definition of EQUAL is an example of a conditional expression inside a conditional expression.

+

NOTE: returns F on failure, not NIL

EVAL

(EVAL expr)(EVAL expr env depth)

Evaluate this expr and return the result. If environment is not passed, it defaults to the current value of the global object list. The depth argument is part of the tracing system and should not be set by user code.

+

All args are assumed to be numbers, symbols or beowulf.cons-cell/ConsCell objects.

INTEROP

(INTEROP fn-symbol args)

Clojure (or other host environment) interoperation API. fn-symbol is expected to be either

  1. a symbol bound in the host environment to a function; or
  2. a sequence (list) of symbols forming a qualified path name bound to a function.

Lower case characters cannot normally be represented in Lisp 1.5, so both the upper case and lower case variants of fn-symbol will be tried. If the function you’re looking for has a mixed case name, that is not currently accessible.

args is expected to be a Lisp 1.5 list of arguments to be passed to that function. Return value must be something acceptable to Lisp 1.5, so either a symbol, a number, or a Lisp 1.5 list.

-

If fn-symbol is not found (even when cast to lower case), or is not a function, or the value returned cannot be represented in Lisp 1.5, an exception is thrown with :cause bound to :interop and :detail set to a value representing the actual problem.

interop-interpret-q-name

(interop-interpret-q-name l)

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 a symbol in the form PART.PART.PART/NAME. This symbol will then be tried in both that form and lower-cased. Names with hyphens or underscores cannot be represented with this scheme.

MEMBER

(MEMBER x y)

This predicate is true if the S-expression x occurs among the elements of the list y.

-

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

NILP

macro

(NILP x)

Not part of LISP 1.5: T if o is NIL, else NIL.

NULL

macro

(NULL x)

Returns T if and only if the argument x is bound to NIL; else F.

OBLIST

(OBLIST)

Not certain whether or not this is part of LISP 1.5; adapted from PSL. return the current value of the object list. Note that in PSL this function returns a list of the symbols bound, not the whole association list.

PAIRLIS

(PAIRLIS x y a)

This function gives the list of pairs of corresponding elements of the lists x and y, and APPENDs this to the list a. The resultant list of pairs, which is like a table with two columns, is called an association list.

+

If fn-symbol is not found (even when cast to lower case), or is not a function, or the value returned cannot be represented in Lisp 1.5, an exception is thrown with :cause bound to :interop and :detail set to a value representing the actual problem.

interop-interpret-q-name

(interop-interpret-q-name l)

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 a symbol in the form PART.PART.PART/NAME. This symbol will then be tried in both that form and lower-cased. Names with hyphens or underscores cannot be represented with this scheme.

lax?

(lax? symbol)

Are we in lax mode? If so. return true; is not, throw an exception with this symbol.

MEMBER

(MEMBER x y)

This predicate is true if the S-expression x occurs among the elements of the list y.

+

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

NILP

macro

(NILP x)

Not part of LISP 1.5: T if o is NIL, else NIL.

NULL

macro

(NULL x)

Returns T if and only if the argument x is bound to NIL; else F.

OBLIST

(OBLIST)

Return a list of the symbols currently bound on the object list.

+

NOTE THAT in the Lisp 1.5 manual, footnote at the bottom of page 69, it implies that an argument can be passed but I’m not sure of the semantics of this.

PAIRLIS

(PAIRLIS x y a)

This function gives the list of pairs of corresponding elements of the lists x and y, and APPENDs this to the list a. The resultant list of pairs, which is like a table with two columns, is called an association list.

Eessentially, it builds the environment on the stack, implementing shallow binding.

-

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

QUOTE

macro

(QUOTE f)

Quote, but in upper case for LISP 1.5

SET

(SET symbol val)

Implementation of SET in Clojure. Add to the oblist a binding of the value of var to the value of val. NOTE WELL: this is not SETQ!

SUBLIS

(SUBLIS a y)

Here a is assumed to be an association list of the form ((ul . vl)...(un . vn)), where the us are atomic, and y is any S-expression. What SUBLIS does, is to treat the us as variables when they occur in y, and to SUBSTitute the corresponding vs from the pair list.

+

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

QUOTE

macro

(QUOTE f)

Quote, but in upper case for LISP 1.5

SET

(SET symbol val)

Implementation of SET in Clojure. Add to the oblist a binding of the value of var to the value of val. NOTE WELL: this is not SETQ!

SUBLIS

(SUBLIS a y)

Here a is assumed to be an association list of the form ((ul . vl)...(un . vn)), where the us are atomic, and y is any S-expression. What SUBLIS does, is to treat the us as variables when they occur in y, and to SUBSTitute the corresponding vs from the pair list.

My interpretation is that this is variable binding in the stack frame.

-

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

SUBST

(SUBST x y z)

This function gives the result of substituting the S-expression x for all occurrences of the atomic symbol y in the S-expression z.

to-beowulf

(to-beowulf o)

Return a beowulf-native representation of the Clojure object o. Numbers and symbols are unaffected. Collections have to be converted; strings must be converted to symbols.

to-clojure

(to-clojure l)

If l is a beowulf.cons_cell.ConsCell, return a Clojure list having the same members in the same order.

traced-apply

(traced-apply & args__3196__auto__)

Traced wrapper for internal-apply, q.v. Apply this function to these arguments in this environment and return the result.

traced-eval

(traced-eval & args__3196__auto__)

Essentially, identical to EVAL except traced.

uaf

(uaf l path)

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 all those fiddly #'c[ad]+r' functions a bit easier

\ No newline at end of file +

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

SUBST

(SUBST x y z)

This function gives the result of substituting the S-expression x for all occurrences of the atomic symbol y in the S-expression z.

to-beowulf

(to-beowulf o)

Return a beowulf-native representation of the Clojure object o. Numbers and symbols are unaffected. Collections have to be converted; strings must be converted to symbols.

to-clojure

(to-clojure l)

If l is a beowulf.cons_cell.ConsCell, return a Clojure list having the same members in the same order.

uaf

(uaf l path)

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 all those fiddly #'c[ad]+r' functions a bit easier

\ No newline at end of file diff --git a/docs/codox/beowulf.cons-cell.html b/docs/codox/beowulf.cons-cell.html index 5425a75..d58fe66 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.

CAR

(CAR x)

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

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.

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.

LIST

(LIST & args)

TODO: write docs

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

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.

CAR

(CAR x)

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

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.

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.

LIST

(LIST & args)

TODO: write docs

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

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 288c9c1..37468f3 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.gendoc.html b/docs/codox/beowulf.gendoc.html new file mode 100644 index 0000000..fc240b4 --- /dev/null +++ b/docs/codox/beowulf.gendoc.html @@ -0,0 +1,3 @@ + +beowulf.gendoc documentation

beowulf.gendoc

TODO: write docs

find-documentation

(find-documentation entry)

TODO: write docs

gen-doc-table

(gen-doc-table)

TODO: write docs

host-functions

Functions which we can infer are written in Clojure.

infer-signature

(infer-signature entry)

TODO: write docs

infer-type

(infer-type entry)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/beowulf.host.html b/docs/codox/beowulf.host.html index 2ab9866..11882b0 100644 --- a/docs/codox/beowulf.host.html +++ b/docs/codox/beowulf.host.html @@ -1,3 +1,4 @@ -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

NUMBERP

(NUMBERP x)

TODO: write docs

PLUS

(PLUS & args)

TODO: write docs

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

TIMES

(TIMES & args)

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

AND

(AND & args)

T if and only if none of my args evaluate to either F or NIL, else F.

+

In beowulf.host principally because I don’t yet feel confident to define varargs functions in Lisp.

DIFFERENCE

(DIFFERENCE x y)

TODO: write docs

ERROR

(ERROR & args)

Throw an error

FIXP

(FIXP x)

TODO: write docs

GENSYM

(GENSYM)

Generate a unique symbol.

GREATERP

(GREATERP x y)

TODO: write docs

LESSP

(LESSP x y)

TODO: write docs

NUMBERP

(NUMBERP x)

TODO: write docs

PLUS

(PLUS & args)

TODO: write docs

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

TIMES

(TIMES & args)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/beowulf.io.html b/docs/codox/beowulf.io.html index 168da81..1107af5 100644 --- a/docs/codox/beowulf.io.html +++ b/docs/codox/beowulf.io.html @@ -1,6 +1,6 @@ -beowulf.io documentation

beowulf.io

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

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

diff --git a/docs/codox/beowulf.oblist.html b/docs/codox/beowulf.oblist.html index 8cbcc93..4488f32 100644 --- a/docs/codox/beowulf.oblist.html +++ b/docs/codox/beowulf.oblist.html @@ -1,4 +1,4 @@ -beowulf.oblist documentation

beowulf.oblist

A namespace mainly devoted to the object list.

+beowulf.oblist documentation

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.

*options*

dynamic

Command line options from invocation.

NIL

The canonical empty list symbol.

oblist

The default environment.

\ No newline at end of file diff --git a/docs/codox/beowulf.read.html b/docs/codox/beowulf.read.html index 73bc882..e9893d6 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 the symbolic expression language SEXPR, which I do not believe the Lisp 1.5 reader ever did;
  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 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 +

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.char-reader.html b/docs/codox/beowulf.reader.char-reader.html new file mode 100644 index 0000000..72b9707 --- /dev/null +++ b/docs/codox/beowulf.reader.char-reader.html @@ -0,0 +1,14 @@ + +beowulf.reader.char-reader documentation

beowulf.reader.char-reader

Provide sensible line editing, auto completion, and history recall.

+

None of what’s needed here is really working yet, and a pull request with a working implementation would be greatly welcomed.

+

What’s needed (rough specification)

+
    +
  1. Carriage return does not cause input to be returned, unless a. the number of open brackets ( and closing brackets ) match; and b. the number of open square brackets [ and closing square brackets ] also match;
  2. +
  3. aborts editing and returns the string STOP;
  4. +
  5. and scroll back and forward through history, but ideally I’d like this to be the Lisp history (i.e. the history of S-Expressions actually read by READ, rather than the strings which were supplied to READ);
  6. +
  7. offers potential auto-completions taken from the value of (OBLIST), ideally the current value, not the value at the time the session started;
  8. +
  9. and offer movement and editing within the line.
  10. +

get-reader

Return a reader, first constructing it if necessary.

+

NOTE THAT this is not settled API. The existence and call signature of this function is not guaranteed in future versions.

read-chars

(read-chars)

A drop-in replacement for clojure.core/read-line, except that line editing and history should be enabled.

+

NOTE THAT this does not work yet, but it is in the API because I hope that it will work later!

\ No newline at end of file diff --git a/docs/codox/beowulf.reader.generate.html b/docs/codox/beowulf.reader.generate.html index d076543..bd7730a 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 5b34af5..7a5424f 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 ebd4ab9..c21ace6 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 42576fc..b38c76b 100644 --- a/docs/codox/beowulf.reader.simplify.html +++ b/docs/codox/beowulf.reader.simplify.html @@ -1,4 +1,4 @@ -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.

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

NOTE THAT it is assumed that remove-optional-space has been run on the parse tree BEFORE it is passed to simplify.

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

beowulf.trace

Tracing of function execution

TRACE

(TRACE s)

Add this symbol s to the set of symbols currently being traced. If s is not a symbol, does nothing.

traced-symbols

Symbols currently being traced.

traced?

(traced? s)

Return true iff s is a symbol currently being traced, else nil.

UNTRACE

(UNTRACE s)

TODO: write docs

\ No newline at end of file diff --git a/docs/codox/index.html b/docs/codox/index.html index 79a8f7d..2c78db7 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.io

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

Public variables and functions:

beowulf.oblist

A namespace mainly devoted to the object list.

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

A namespace mainly devoted to the object list.

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

Provide sensible line editing, auto completion, and history recall.

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:

beowulf.trace

Tracing of function execution

Public variables and functions:

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

Introduction to beowulf

-

TODO: write great documentation

\ No newline at end of file +beowulf

beowulf

+

LISP 1.5 is to all Lisp dialects as Beowulf is to Emglish literature.

+

What this is

+

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 - except as documented below.

+

Status

+

Boots to REPL, but few functions yet available.

+ +

Building and Invoking

+

Build with

+
lein uberjar
+
+

Invoke with

+
java -jar target/uberjar/beowulf-0.2.1-SNAPSHOT-standalone.jar --help
+
+

(Obviously, check your version number)

+

Command line arguments as follows:

+
  -h, --help                               Print this message
+  -p PROMPT, --prompt PROMPT               Set the REPL prompt to PROMPT
+  -r INITFILE, --read INITFILE             Read Lisp functions from the file INITFILE
+  -s, --strict                             Strictly interpret the Lisp 1.5 language, without extensions.
+
+

To end a session, type STOP at the command prompt.

+

Functions and symbols implemented

+

The following functions and symbols are implemented:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Symbol Type Signature Documentation
NIL ? null ?
T ? null ?
F ? null ?
ADD1 Host function ([x]) ?
AND Host function ([& args]) T if and only if none of my args evaluate to either F or NIL, else F. In beowulf.host principally because I don’t yet feel confident to define varargs functions in Lisp.
APPEND Host function ([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 Host function ([function args environment depth]) Apply this function to these arguments in this environment and return the result. 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.
ATOM Host function ([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.
CAR ? null ?
CDR ? null ?
CONS ? null ?
COPY Lisp function (X) ?
DEFINE Host function ([args]) Bootstrap-only version of DEFINE which, post boostrap, can be overwritten in LISP. The single argument to DEFINE should be an assoc list which should be nconc’ed onto the front of the oblist. Broadly, (SETQ OBLIST (NCONC ARG1 OBLIST))
DIFFERENCE Host function ([x y]) ?
DIVIDE Lisp function (X Y) ?
ERROR Host function ([& args]) Throw an error
EQ Host function ([x y]) Returns T if and only if both x and y are bound to the same atom, else NIL.
EQUAL Host function ([x y]) This is a predicate that is true if its two arguments are identical S-expressions, and false if they are different. (The elementary predicate EQ is defined only for atomic arguments.) The definition of EQUAL is an example of a conditional expression inside a conditional expression. NOTE: returns F on failure, not NIL
EVAL Host function ([expr] [expr env depth]) Evaluate this expr and return the result. If environment is not passed, it defaults to the current value of the global object list. The depth argument is part of the tracing system and should not be set by user code. All args are assumed to be numbers, symbols or beowulf.cons-cell/ConsCell objects.
FIXP Host function ([x]) ?
GENSYM Host function ([]) Generate a unique symbol.
GET Lisp function (X Y) ?
GREATERP Host function ([x y]) ?
INTEROP Host function ([fn-symbol args]) Clojure (or other host environment) interoperation API. fn-symbol is expected to be either 1. a symbol bound in the host environment to a function; or 2. a sequence (list) of symbols forming a qualified path name bound to a function. Lower case characters cannot normally be represented in Lisp 1.5, so both the upper case and lower case variants of fn-symbol will be tried. If the function you’re looking for has a mixed case name, that is not currently accessible. args is expected to be a Lisp 1.5 list of arguments to be passed to that function. Return value must be something acceptable to Lisp 1.5, so either a symbol, a number, or a Lisp 1.5 list. If fn-symbol is not found (even when cast to lower case), or is not a function, or the value returned cannot be represented in Lisp 1.5, an exception is thrown with :cause bound to :interop and :detail set to a value representing the actual problem.
INTERSECTION Lisp function (X Y) ?
LENGTH Lisp function (L) ?
LESSP Host function ([x y]) ?
MEMBER Lisp function (A X) ?
MINUSP Lisp function (X) ?
NULL Lisp function (X) ?
NUMBERP Host function ([x]) ?
OBLIST Host function ([]) Return a list of the symbols currently bound on the object list. NOTE THAT in the Lisp 1.5 manual, footnote at the bottom of page 69, it implies that an argument can be passed but I’m not sure of the semantics of this.
ONEP Lisp function (X) ?
PAIR Lisp function (X Y) ?
PLUS Host function ([& args]) ?
PRETTY ? null ?
PRINT ? null ?
PROP Lisp function (X Y U) ?
QUOTIENT Host function ([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.
READ Host function ([] [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.
REMAINDER Host function ([x y]) ?
REPEAT Lisp function (N X) ?
RPLACA Host function ([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 Host function ([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)
SET Host function ([symbol val]) Implementation of SET in Clojure. Add to the oblist a binding of the value of var to the value of val. NOTE WELL: this is not SETQ!
SUB1 Lisp function (N) ?
SYSIN Host function ([filename]) Read the contents of the file at this filename 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. It is intended that sysout files can be read both from resources within the jar file, and from the file system. If a named file exists in both the file system and the resources, the file system will be preferred. NOTE THAT if the provided filename does not end with .lsp (which, if you’re writing it from the Lisp REPL, it won’t), the extension .lsp will be appended.
SYSOUT Host function ([] [filepath]) 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.
TERPRI ? null ?
TIMES Host function ([& args]) ?
TRACE ? null ?
UNTRACE ? null ?
ZEROP Lisp function (N) ?
+

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
  • +
+

The objective this is to make it fairly easy to implement Lisp 1.5 on top of any of the many Make A Lisp implementations.

+

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, 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. +
  3. 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.
  4. +
+

BUT WHY?!!?!

+

Because.

+

Because Lisp is the only computer language worth learning, and if a thing is worth learning, it’s worth learning properly; which means going back to the beginning and trying to understand that.

+

Because there is, so far as I know, no working implementation of Lisp 1.5 for modern machines.

+

Because I’m barking mad, and this is therapy.

+

Commentary

+

What’s surprised me in working on this is how much more polished Lisp 1.5 is than legend had led me to believe. The language is remarkably close to Portable Standard Lisp which is in my opinion one of the best and most usable early Lisp implementations.

+

What’s even more surprising is how faithful a reimplementation of Lisp 1.5 the first Lisp dialect I learned, Acornsoft Lisp, turns out to have been.

+

I’m convinced you could still use Lisp 1.5 for interesting and useful software (which isn’t to say that modern Lisps aren’t better, but this is software which is almost sixty years old).

+

Installation

+

At present, clone the source and build it using

+

lein uberjar.

+

You will require to have Leiningen installed.

+

Input/output

+

Lisp 1.5 greatly predates modern computers. It had a facility to print to a line printer, or to punch cards on a punch-card machine, and it had a facility to read system images in from tape; but there’s no file I/O as we would currently understand it, and, because there are no character strings and the valid characters within an atom are limited, it isn’t easy to compose a sensible filename.

+

I’ve provided two functions to work around this problem.

+

SYSOUT

+

SYSOUT dumps the global object list to disk as a single S Expression (specifically: an association list). This allows you to persist your session, with all your current work, to disk. The function takes one argument, expected to be a symbol, and, if that argument is provided, writes a file whose name is that symbol with .lsp appended. If no argument is provided, it will construct a filename comprising the token Sysout, followed by the current date, followed by .lsp. In either case the file will be written to the directory given in the FILEPATH argument at startup time, or by default the current directory.

+

Obviously, SYSOUT may be called interactively (and this is the expected practice).

+

SYSIN

+

SYSIN reads a file from disk and overwrites the global object list with its contents. The expected practice is that this will be a file created by SYSOUT. A command line flag --read is provided so that you can specify

+

Learning Lisp 1.5

+

The Lisp 1.5 Programmer's Manual is still in print, ISBN 13 978-0-262-13011-0; but it’s also available online.

+

Other Lisp 1.5 resources

+

The main resource I’m aware of is the Software Preservation Society’s site, here. It has lots of fascinating stuff including full assembler listings for various obsolete processors, but I failed to find the Lisp source of Lisp functions as a text file, which is why resources/lisp1.5.lsp is largely copytyped and reconstructed from the manual.

+

I’m not at this time aware of any other working Lisp 1.5 implementations.

+

License

+

Copyright © 2019 Simon Brooke. Licensed under the GNU General Public License, version 2.0 or (at your option) any later version.

\ No newline at end of file diff --git a/docs/codox/mexpr.html b/docs/codox/mexpr.html new file mode 100644 index 0000000..299043c --- /dev/null +++ b/docs/codox/mexpr.html @@ -0,0 +1,41 @@ + +M-Expressions

M-Expressions

+

M-Expressions (‘mexprs’) are the grammar which John McCarthy origininally used to write Lisp, and the grammar in which many of the function definitions in the Lisp 1.5 Programmer’s Manual are stated. However, I have not seen anywhere a claim that Lisp 1.5 could read M-Expressions, and it is not clear to me whether it was even planned that it should do so.

+

Rather, it seems to me probably that M-Expressions were only ever a grammar intended to be written on paper, like Backus Naur Form, to describe and to reason about algorithms.

+

I set out to make Beowulf read M-Expressions essentially out of curiousity, to see whether it could be done. I had this idea that if it could be done, I could implement most of Lisp 1.5 simply by copying in the M-Expression definitions out of the manual.

+

Consequently, the Beowulf parser can parse the M-Expression grammar as stated in the manual, and generate S-Expressions from it according to the table specified on page 10 of the manual.

+

There are two problems with this.

+

Problems with interpreting M-Expressions

+

Generating idiomatic Lisp

+

In the M-Expression notation, a lower case character or sequence of characters represents a variable; an upper case character represents a constant. As the manual says,

+
+

2 . The obvious translation of letting a constant translate into itself will not work. Since the translation of x is X, the translation of X must be something else to avoid ambiguity. The solution is to quote it. Thus X is translated into (QUOTE X).

+
+

Thus, necessarily, the translation of a constant must always be quoted. In practice, key constants in Lisp such as T are bound to themselves, so it is idiomatic in Lisp, certainly in the way we have learned to use it, to write, for example,

+
(SET (QUOTE NULL) 
+    (QUOTE (LAMBDA (X) 
+        (COND 
+            ((EQUAL X NIL) T) (T F)))))
+
+

However, the literal translation of

+
null[x] = [x = NIL -> T; T -> F]
+
+

is

+
(SET (QUOTE NULL) 
+    (QUOTE (LAMBDA (X) 
+        (COND 
+            ((EQUAL X (QUOTE NIL)) (QUOTE T))
+            ((QUOTE T) (QUOTE F))))))
+
+

This is certainly more prolix and more awkward, but it also risks being flat wrong.

+

Is the value of NIL the atom NIL, or is it the empty list ()? If the former, then the translation from the M-Expression above is correct. However, that means that recursive functions which recurse down a list seeking the end will fail. So the latter must be the case.

+

NULL is described thus (Ibid, p11):

+
+

This is a predicate useful for deciding when a list is exhausted. It is true if and only if its argument is NIL.

+
+

NIL is used explicitly in an M-Expression for example in the definition of intersection (Ibid, p15).

+

I think there is an ambiguity in referencing constants which are not bound to themselves in the M-Expression notation as given in the manual. This is particularly problematic with regards to NIL and F, but there may be others instances.

+

Curly braces

+

The use of curly braces is not defined in the grammar as stated on page 10. They are not used in the initial definition of APPLY on page 13, but they are used in the more developed restatement on page 70. I believe they are to be read as indicating a DO statement – a list of function calls to be made sequentially but without strict functional dependence on one another – but I don’t find the exposition here particularly clear and I’m not sure of this.

+

Consequently, the M-Expression interpreter in Beowulf does not interpret curly braces.

\ No newline at end of file diff --git a/project.clj b/project.clj index ab4107f..fe0202b 100644 --- a/project.clj +++ b/project.clj @@ -1,5 +1,6 @@ (defproject beowulf "0.2.1-SNAPSHOT" - :cloverage {:output "docs/cloverage"} + :cloverage {:output "docs/cloverage" + :ns-exclude-regex [#"beowulf\.gendoc"]} :codox {:metadata {:doc "**TODO**: write docs" :doc/format :markdown} :output-path "docs/codox" @@ -18,7 +19,7 @@ [rhizome "0.2.9"] ;; not needed in production builds ] :main ^:skip-aot beowulf.core - :plugins [[lein-cloverage "1.1.1"] + :plugins [[lein-cloverage "1.2.2"] [lein-codox "0.10.7"] [lein-environ "1.1.0"]] :profiles {:uberjar {:aot :all}} diff --git a/src/beowulf/gendoc.clj b/src/beowulf/gendoc.clj new file mode 100644 index 0000000..dc8b5b4 --- /dev/null +++ b/src/beowulf/gendoc.clj @@ -0,0 +1,77 @@ +(ns beowulf.gendoc + (:require [beowulf.oblist :refer [oblist]] + [clojure.string :refer [join replace]])) + +(def host-functions + "Functions which we can infer are written in Clojure." + (reduce + merge + {} + (map + ns-publics + ['beowulf.bootstrap + 'beowulf.host + 'beowulf.io + 'beowulf.read]))) + +;; OK, this, improbably, works. There's probably a better way... +;; (:doc (meta (eval (read-string (str "#'" "beowulf.read" "/" "READ"))))) + +(defn- get-metadata-for-function + "Return the metadata associated with this compiled Clojure `function`. + + If `key` is passed, return only the value of `key` in that metadata. + The value of `key` should be a keyword; of `function`, a function." + ([function] + (try + (meta (eval (read-string (str function)))) + (catch Throwable _ "?"))) + ([function key] + (when (keyword? key) + (key (get-metadata-for-function function))))) + + +(defn- get-metadata-for-entry [entry key] + (let [fn (host-functions (symbol (first entry)))] + (get-metadata-for-function fn key))) + + +(defn infer-type + [entry] + (cond + (= (second entry) 'LAMBDA) "Lisp function" + (host-functions (first entry)) "Host function" + :else "?")) + +(defn infer-signature + [entry] + (cond + (= (count entry) 1) (get-metadata-for-entry entry :arglists) + (= (second entry) 'LAMBDA) (nth entry 2) + :else "?")) + +(defn find-documentation + [entry] + (cond + (= (count entry) 1) (if-let [doc (get-metadata-for-entry entry :doc)] + (replace doc "\n" " ") + "?") + :else "?")) + +(defn gen-doc-table + [] + (join + "\n" + (doall + (concat + '("| Symbol | Type | Signature | Documentation |" + "|--------|------|-----------|---------------|") + (map + #(format "| %s | %s | %s | %s |" + (first %) + (infer-type %) + (infer-signature %) + (find-documentation %)) + @oblist))))) + +;; (println (gen-doc-table)) \ No newline at end of file