diff --git a/CHANGELOG.md b/CHANGELOG.md index c487ddf..38c963d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Change Log All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). +## [0.3.0] - 2023-04-10 + +### Changed + +- Added property lists in the exact format used by Lisp 1.5; +- Added `ASSOC`, `EFFACE`, `MAPLIST`, `PROG`, and other functions; +- Where there are both interpreted (`EXPR`) and Clojure (`SUBR`) implementations of the same function, the `EXPR` is preferred (it is planned to make this configurable if/when there is a working compiler); +- More error messages/diagnostics are now printed in Old English (it is planned to implement internationalisation so that you can switch to messages you actually understand); +- Documentation improvements. + ## [0.2.1] - 2023-03-30 ### Changed diff --git a/README.md b/README.md index 73253be..b248e34 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,34 @@ LISP 1.5 is to all Lisp dialects as Beowulf is to English literature. -![Beowulf logo](img/beowulf_logo.png) +![Beowulf logo](https://simon-brooke.github.io/beowulf/docs/img/beowulf_logo_med.png) + +## Contents + * [What this is](#what-this-is) + + [Status](#status) + + [BUT WHY?!!?!](#but-why-----) + + [Project Target](#project-target) + + [Invoking](#invoking) + + [Building and Invoking](#building-and-invoking) + + [Reader macros](#reader-macros) + + [Functions and symbols implemented](#functions-and-symbols-implemented) + + [Architectural plan](#architectural-plan) + - [resources/lisp1.5.lsp](#resources-lisp15lsp) + - [beowulf/boostrap.clj](#beowulf-boostrapclj) + - [beowulf/host.clj](#beowulf-hostclj) + - [beowulf/read.clj](#beowulf-readclj) + + [Commentary](#commentary) + * [Installation](#installation) + + [Input/output](#input-output) + - [SYSOUT](#sysout) + - [SYSIN](#sysin) + * [Learning Lisp 1.5](#learning-lisp-15) + * [Other Lisp 1.5 resources](#other-lisp-15-resources) + + [Other implmentations](#other-implementations) + + [History resources](#history-resources) + * [License](#license) + +Table of contents generated with markdown-toc ## What this is @@ -13,6 +40,19 @@ 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. +### 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. + ### Status Working Lisp interpreter, but some key features not yet implemented. @@ -20,11 +60,20 @@ Working Lisp interpreter, but some key features not yet implemented. * [Project website](https://simon-brooke.github.io/beowulf/). * [Source code documentation](https://simon-brooke.github.io/beowulf/docs/codox/index.html). -### Building and Invoking +### Project Target -Build with +The project target is to be able to run the [Wang algorithm for the propositional calculus](https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf#page=52) given in chapter 8 of the *Lisp 1.5 Programmer's Manual*. When that runs, the project is as far as I am concerned feature complete. I may keep tinkering with it after that and I'll certainly accept pull requests which are in the spirit of the project (i.e. making Beowulf more usable, and/or implementing parts of Lisp 1.5 which I have not implemented), but this isn't intended to be a new language for doing real work; it's an +educational and archaeological project, not serious engineering. - lein uberjar +Some `readline`-like functionality would be really useful, but my attempt to +integrate [JLine](https://github.com/jline/jline3) has not (yet) been successful. + +An in-core structure editor would be an extremely nice thing, and I may well +implement one. + +You are of course welcome to fork the project and do whatever you like with it! + +### Invoking Invoke with @@ -37,107 +86,128 @@ 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. + -r INITFILE, --read SYSOUTFILE Read Lisp sysout from the file SYSOUTFILE + (defaults to `resources/lisp1.5.lsp`) + -s, --strict Strictly interpret the Lisp 1.5 language, + without extensions. ``` To end a session, type `STOP` at the command prompt. +### Building and Invoking + +Build with + + lein uberjar + + ### Reader macros -Currently I don't have +Currently `SETQ` and `DEFUN` are implemented as reader macros, sort of. It would +now be possible to reimplement them as `FEXPRs` and so the reader macro functionality will probably go away. ### Functions and symbols implemented -The following functions and symbols are implemented: - | Function | Type | Signature | Implementation | Documentation | |--------------|----------------|------------------|----------------|----------------------| -| NIL | Lisp variable | | | ? | -| T | Lisp variable | | | ? | -| F | Lisp variable | | | ? | -| ADD1 | Host function | (ADD1 X) | | ? | -| AND | Host function | (AND & ARGS) | PREDICATE | `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 | Lisp function | (APPEND X Y) | LAMBDA-fn | see manual pages 11, 61 | -| APPLY | Host function | (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. | -| ATOM | Host function | (ATOM X) | PREDICATE | 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 | Host function | (CAR X) | | Return the item indicated by the first pointer of a pair. NIL is treated specially: the CAR of NIL is NIL. | -| CAAAAR | Lisp function | (CAAAAR X) | LAMBDA-fn | ? | -| CAAADR | Lisp function | (CAAADR X) | LAMBDA-fn | ? | -| CAAAR | Lisp function | (CAAAR X) | LAMBDA-fn | ? | -| CAADAR | Lisp function | (CAADAR X) | LAMBDA-fn | ? | -| CAADDR | Lisp function | (CAADDR X) | LAMBDA-fn | ? | -| CAADR | Lisp function | (CAADR X) | LAMBDA-fn | ? | -| CAAR | Lisp function | (CAAR X) | LAMBDA-fn | ? | -| CADAAR | Lisp function | (CADAAR X) | LAMBDA-fn | ? | -| CADADR | Lisp function | (CADADR X) | LAMBDA-fn | ? | -| CADAR | Lisp function | (CADAR X) | LAMBDA-fn | ? | -| CADDAR | Lisp function | (CADDAR X) | LAMBDA-fn | ? | -| CADDDR | Lisp function | (CADDDR X) | LAMBDA-fn | ? | -| CADDR | Lisp function | (CADDR X) | LAMBDA-fn | ? | -| CADR | Lisp function | (CADR X) | LAMBDA-fn | ? | -| CDAAAR | Lisp function | (CDAAAR X) | LAMBDA-fn | ? | -| CDAADR | Lisp function | (CDAADR X) | LAMBDA-fn | ? | -| CDAAR | Lisp function | (CDAAR X) | LAMBDA-fn | ? | -| CDADAR | Lisp function | (CDADAR X) | LAMBDA-fn | ? | -| CDADDR | Lisp function | (CDADDR X) | LAMBDA-fn | ? | -| CDADR | Lisp function | (CDADR X) | LAMBDA-fn | ? | -| CDAR | Lisp function | (CDAR X) | LAMBDA-fn | ? | -| CDDAAR | Lisp function | (CDDAAR X) | LAMBDA-fn | ? | -| CDDADR | Lisp function | (CDDADR X) | LAMBDA-fn | ? | -| CDDAR | Lisp function | (CDDAR X) | LAMBDA-fn | ? | -| CDDDAR | Lisp function | (CDDDAR X) | LAMBDA-fn | ? | -| CDDDDR | Lisp function | (CDDDDR X) | LAMBDA-fn | ? | -| CDDDR | Lisp function | (CDDDR X) | LAMBDA-fn | ? | -| CDDR | Lisp function | (CDDR X) | LAMBDA-fn | ? | -| CDR | Host function | (CDR X) | | Return the item indicated by the second pointer of a pair. NIL is treated specially: the CDR of NIL is NIL. | -| CONS | Host function | (CONS CAR CDR) | | Construct a new instance of cons cell with this `car` and `cdr`. | -| COPY | Lisp function | (COPY X) | LAMBDA-fn | see manual pages 62 | -| DEFINE | Host function | (DEFINE ARGS) | PSEUDO-FUNCTION | 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 | (DIFFERENCE X Y) | | ? | -| DIVIDE | Lisp function | (DIVIDE X Y) | LAMBDA-fn | see manual pages 26, 64 | -| ERROR | Host function | (ERROR & ARGS) | PSEUDO-FUNCTION | Throw an error | -| EQ | Host function | (EQ X Y) | PREDICATE | Returns `T` if and only if both `x` and `y` are bound to the same atom, else `NIL`. | -| EQUAL | Host function | (EQUAL X Y) | PREDICATE | 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 | (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. | -| FACTORIAL | Lisp function | (FACTORIAL N) | LAMBDA-fn | ? | -| FIXP | Host function | (FIXP X) | PREDICATE | ? | -| GENSYM | Host function | (GENSYM ) | | Generate a unique symbol. | -| GET | Lisp function | (GET X Y) | LAMBDA-fn | see manual pages 41, 59 | -| GREATERP | Host function | (GREATERP X Y) | PREDICATE | ? | -| INTEROP | Host function | (INTEROP FN-SYMBOL ARGS) | (INTEROP) | 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 | (INTERSECTION X Y) | LAMBDA-fn | ? | -| LENGTH | Lisp function | (LENGTH L) | LAMBDA-fn | see manual pages 62 | -| LESSP | Host function | (LESSP X Y) | PREDICATE | ? | -| MEMBER | Lisp function | (MEMBER A X) | LAMBDA-fn | see manual pages 11, 62 | -| MINUSP | Lisp function | (MINUSP X) | LAMBDA-fn | see manual pages 26, 64 | -| NOT | Lisp function | (NOT X) | LAMBDA-fn | see manual pages 21, 23, 58 | -| NULL | Lisp function | (NULL X) | LAMBDA-fn | see manual pages 11, 57 | -| NUMBERP | Host function | (NUMBERP X) | PREDICATE | ? | -| OBLIST | Host function | (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. | -| ONEP | Lisp function | (ONEP X) | LAMBDA-fn | see manual pages 26, 64 | -| PAIR | Lisp function | (PAIR X Y) | LAMBDA-fn | see manual pages 60 | -| PLUS | Host function | (PLUS & ARGS) | | ? | -| PRETTY | Lisp variable | | (PRETTY) | ? | -| PRINT | Lisp variable | | PSEUDO-FUNCTION | ? | -| PROP | Lisp function | (PROP X Y U) | LAMBDA-fn | see manual pages 59 | -| QUOTIENT | Host function | (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. | -| RANGE | Lisp variable | ? | (RANGE (LAMBDA (N M) (COND ((LESSP M N) (QUOTE NIL)) ((QUOTE T) (CONS N (RANGE (ADD1 N) M)))))) | ? | -| READ | Host function | (READ ); (READ INPUT) | PSEUDO-FUNCTION | 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 | (REMAINDER X Y) | | ? | -| REPEAT | Lisp function | (REPEAT N X) | LAMBDA-fn | ? | -| RPLACA | Host function | (RPLACA CELL VALUE) | PSEUDO-FUNCTION | 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 | (RPLACD CELL VALUE) | PSEUDO-FUNCTION | 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 | (SET SYMBOL VAL) | PSEUDO-FUNCTION | 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 | (SUB1 N) | LAMBDA-fn | see manual pages 26, 64 | -| SYSIN | Host function | (SYSIN ); (SYSIN FILENAME) | (SYSIN) | 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 | (SYSOUT ); (SYSOUT FILEPATH) | (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. | -| TERPRI | Lisp variable | | PSEUDO-FUNCTION | ? | -| TIMES | Host function | (TIMES & ARGS) | | ? | -| TRACE | Host function | (TRACE S) | PSEUDO-FUNCTION | Add this symbol `s` to the set of symbols currently being traced. If `s` is not a symbol, does nothing. | -| UNTRACE | Host function | (UNTRACE S) | PSEUDO-FUNCTION | ? | -| ZEROP | Lisp function | (ZEROP N) | LAMBDA-fn | see manual pages 26, 64 | - +| NIL | Lisp variable | ? | | see manual pages 22, 69 | +| T | Lisp variable | ? | | see manual pages 22, 69 | +| F | Lisp variable | ? | | see manual pages 22, 69 | +| ADD1 | Host lambda function | ? | | ? | +| AND | Host lambda function | ? | PREDICATE | `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 | Lisp lambda function | ? | | see manual pages 11, 61 | +| APPLY | Host lambda function | ? | | 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 | Lisp lambda function, Host lambda function | ? | ? | 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. **NOTE THAT** this function is overridden by an implementation in Lisp, but is currently still present for bootstrapping. | +| ATOM | Host lambda function | ? | PREDICATE | 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 | Host lambda function | ? | | Return the item indicated by the first pointer of a pair. NIL is treated specially: the CAR of NIL is NIL. | +| CAAAAR | Lisp lambda function | ? | ? | ? | +| CAAADR | Lisp lambda function | ? | ? | ? | +| CAAAR | Lisp lambda function | ? | ? | ? | +| CAADAR | Lisp lambda function | ? | ? | ? | +| CAADDR | Lisp lambda function | ? | ? | ? | +| CAADR | Lisp lambda function | ? | ? | ? | +| CAAR | Lisp lambda function | ? | ? | ? | +| CADAAR | Lisp lambda function | ? | ? | ? | +| CADADR | Lisp lambda function | ? | ? | ? | +| CADAR | Lisp lambda function | ? | ? | ? | +| CADDAR | Lisp lambda function | ? | ? | ? | +| CADDDR | Lisp lambda function | ? | ? | ? | +| CADDR | Lisp lambda function | ? | ? | ? | +| CADR | Lisp lambda function | ? | ? | ? | +| CDAAAR | Lisp lambda function | ? | ? | ? | +| CDAADR | Lisp lambda function | ? | ? | ? | +| CDAAR | Lisp lambda function | ? | ? | ? | +| CDADAR | Lisp lambda function | ? | ? | ? | +| CDADDR | Lisp lambda function | ? | ? | ? | +| CDADR | Lisp lambda function | ? | ? | ? | +| CDAR | Lisp lambda function | ? | ? | ? | +| CDDAAR | Lisp lambda function | ? | ? | ? | +| CDDADR | Lisp lambda function | ? | ? | ? | +| CDDAR | Lisp lambda function | ? | ? | ? | +| CDDDAR | Lisp lambda function | ? | ? | ? | +| CDDDDR | Lisp lambda function | ? | ? | ? | +| CDDDR | Lisp lambda function | ? | ? | ? | +| CDDR | Lisp lambda function | ? | ? | ? | +| CDR | Host lambda function | ? | | Return the item indicated by the second pointer of a pair. NIL is treated specially: the CDR of NIL is NIL. | +| CONS | Host lambda function | ? | | Construct a new instance of cons cell with this `car` and `cdr`. | +| CONSP | Host lambda function | ? | ? | Return `T` if object `o` is a cons cell, else `F`. **NOTE THAT** this is an extension function, not available in strct mode. I believe that Lisp 1.5 did not have any mechanism for testing whether an argument was, or was not, a cons cell. | +| COPY | Lisp lambda function | ? | | see manual pages 62 | +| DEFINE | Host lambda function | ? | PSEUDO-FUNCTION | Bootstrap-only version of `DEFINE` which, post boostrap, can be overwritten in LISP. The single argument to `DEFINE` should be an association list of symbols to lambda functions. See page 58 of the manual. | +| DIFFERENCE | Host lambda function | ? | | ? | +| DIVIDE | Lisp lambda function | ? | | see manual pages 26, 64 | +| DOC | Host lambda function | ? | ? | Open the page for this `symbol` in the Lisp 1.5 manual, if known, in the default web browser. **NOTE THAT** this is an extension function, not available in strct mode. | +| EFFACE | Lisp lambda function | ? | PSEUDO-FUNCTION | see manual pages 63 | +| ERROR | Host lambda function | ? | PSEUDO-FUNCTION | Throw an error | +| EQ | Host lambda function | ? | PREDICATE | Returns `T` if and only if both `x` and `y` are bound to the same atom, else `NIL`. | +| EQUAL | Host lambda function | ? | PREDICATE | 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 lambda function | ? | | 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. However, if called with just a single arg, `expr`, I'll assume it's being called from the Clojure REPL and will coerce the `expr` to `ConsCell`. | +| FACTORIAL | Lisp lambda function | ? | ? | ? | +| FIXP | Host lambda function | ? | PREDICATE | ? | +| GENSYM | Host lambda function | ? | | Generate a unique symbol. | +| GET | Host lambda function | ? | | From the manual: '`get` is somewhat like `prop`; however its value is car of the rest of the list if the `indicator` is found, and NIL otherwise.' It's clear that `GET` is expected to be defined in terms of `PROP`, but we can't implement `PROP` here because we lack `EVAL`; and we can't have `EVAL` here because both it and `APPLY` depends on `GET`. OK, It's worse than that: the statement of the definition of `GET` (and of) `PROP` on page 59 says that the first argument to each must be a list; But the in the definition of `ASSOC` on page 70, when `GET` is called its first argument is always an atom. Since it's `ASSOC` and `EVAL` which I need to make work, I'm going to assume that page 59 is wrong. | +| GREATERP | Host lambda function | ? | PREDICATE | ? | +| INTEROP | Host lambda function | ? | ? | ? | +| INTERSECTION | Lisp lambda function | ? | ? | ? | +| LENGTH | Lisp lambda function | ? | | see manual pages 62 | +| LESSP | Host lambda function | ? | PREDICATE | ? | +| MAPLIST | Lisp lambda function | ? | FUNCTIONAL | see manual pages 20, 21, 63 | +| MEMBER | Lisp lambda function | ? | PREDICATE | see manual pages 11, 62 | +| MINUSP | Lisp lambda function | ? | PREDICATE | see manual pages 26, 64 | +| NOT | Lisp lambda function | ? | PREDICATE | see manual pages 21, 23, 58 | +| NULL | Lisp lambda function | ? | PREDICATE | see manual pages 11, 57 | +| NUMBERP | Host lambda function | ? | PREDICATE | ? | +| OBLIST | Host lambda 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 lambda function | ? | PREDICATE | see manual pages 26, 64 | +| OR | Host lambda function | ? | PREDICATE | `T` if and only if at least one of my `args` evaluates to something other than either `F` or `NIL`, else `F`. In `beowulf.host` principally because I don't yet feel confident to define varargs functions in Lisp. | +| PAIR | Lisp lambda function | ? | | see manual pages 60 | +| PAIRLIS | Lisp lambda function, Host lambda function | ? | ? | 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. **NOTE THAT** this function is overridden by an implementation in Lisp, but is currently still present for bootstrapping. | +| PLUS | Host lambda function | ? | | ? | +| PRETTY | | ? | ? | ? | +| PRINT | | ? | PSEUDO-FUNCTION | see manual pages 65, 84 | +| PROG | Host nlambda function | ? | | The accursed `PROG` feature. See page 71 of the manual. Lisp 1.5 introduced `PROG`, and most Lisps have been stuck with it ever since. It introduces imperative programming into what should be a pure functional language, and consequently it's going to be a pig to implement. Broadly, `PROG` is a variadic pseudo function called as a `FEXPR` (or possibly an `FSUBR`, although I'm not presently sure that would even work.) The arguments, which are unevaluated, are a list of forms, the first of which is expected to be a list of symbols which will be treated as names of variables within the program, and the rest of which (the 'program body') are either lists or symbols. Lists are treated as Lisp expressions which may be evaulated in turn. Symbols are treated as targets for the `GO` statement. **GO:** A `GO` statement takes the form of `(GO target)`, where `target` should be one of the symbols which occur at top level among that particular invocation of `PROG`s arguments. A `GO` statement may occur at top level in a PROG, or in a clause of a `COND` statement in a `PROG`, but not in a function called from the `PROG` statement. When a `GO` statement is evaluated, execution should transfer immediately to the expression which is the argument list immediately following the symbol which is its target. If the target is not found, an error with the code `A6` should be thrown. **RETURN:** A `RETURN` statement takes the form `(RETURN value)`, where `value` is any value. Following the evaluation of a `RETURN` statement, the `PROG` should immediately exit without executing any further expressions, returning the value. **SET and SETQ:** In addition to the above, if a `SET` or `SETQ` expression is encountered in any expression within the `PROG` body, it should affect not the global object list but instead only the local variables of the program. **COND:** In **strict** mode, when in normal execution, a `COND` statement none of whose clauses match should not return `NIL` but should throw an error with the code `A3`... *except* that inside a `PROG` body, it should not do so. *sigh*. **Flow of control:** Apart from the exceptions specified above, expressions in the program body are evaluated sequentially. If execution reaches the end of the program body, `NIL` is returned. Got all that? Good. | +| PROP | Lisp lambda function | ? | FUNCTIONAL | see manual pages 59 | +| QUOTE | Lisp lambda function | ? | | see manual pages 10, 22, 71 | +| QUOTIENT | Host lambda function | ? | | 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. | +| RANGE | Lisp lambda function | ? | ? | ? | +| READ | Host lambda function | ? | PSEUDO-FUNCTION | 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 lambda function | ? | | ? | +| REPEAT | Lisp lambda function | ? | ? | ? | +| RPLACA | Host lambda function | ? | PSEUDO-FUNCTION | 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 lambda function | ? | PSEUDO-FUNCTION | 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) | +| SEARCH | Lisp lambda function | ? | FUNCTIONAL | see manual pages 63 | +| SET | Host lambda function | ? | PSEUDO-FUNCTION | 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 lambda function, Host lambda function | ? | | ? | +| SUB2 | Lisp lambda function | ? | ? | ? | +| SUBLIS | Lisp lambda function | ? | | see manual pages 12, 61 | +| SUBST | Lisp lambda function | ? | | see manual pages 11, 61 | +| SYSIN | Host lambda function | ? | ? | 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. **NOTE THAT** this is an extension function, not available in strct mode. | +| SYSOUT | Host lambda function | ? | ? | 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. **NOTE THAT** this is an extension function, not available in strct mode. | +| TERPRI | | ? | PSEUDO-FUNCTION | see manual pages 65, 84 | +| TIMES | Host lambda function | ? | | ? | +| TRACE | Host lambda function | ? | PSEUDO-FUNCTION | Add this `s` to the set of symbols currently being traced. If `s` is not a symbol or sequence of symbols, does nothing. | +| UNION | Lisp lambda function | ? | ? | ? | +| UNTRACE | Host lambda function | ? | PSEUDO-FUNCTION | Remove this `s` from the set of symbols currently being traced. If `s` is not a symbol or sequence of symbols, does nothing. | +| ZEROP | Lisp lambda function | ? | PREDICATE | see manual pages 26, 64 | Functions described as 'Lisp function' above are defined in the default sysout file, `resources/lisp1.5.lsp`, which will be loaded by default unless @@ -199,19 +269,6 @@ Intended deviations from the behaviour of the real Lisp reader are as follows: a comment, as most modern Lisps do; but I do not believe Lisp 1.5 had this feature. -### 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 @@ -229,11 +286,19 @@ but this is software which is almost sixty years old). ## Installation -At present, clone the source and build it using +Download the latest [release 'uberjar'](https://github.com/simon-brooke/beowulf/releases) and run it using: -`lein uberjar`. +```bash + java -jar +``` -You will require to have [Leiningen](https://leiningen.org/) installed. +Or clone the source and build it using: + +```bash + lein uberjar` +``` + +To build it you will require to have [Leiningen](https://leiningen.org/) installed. ### Input/output @@ -266,7 +331,14 @@ 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. +### Other implementations + +There's an online (browser native) Lisp 1.5 implementation [here](https://pages.zick.run/ichigo/) (source code [here](https://github.com/zick/IchigoLisp)). It +even has a working compiler! + +### History resources + +I'm compiling a [list of links to historical documents on Lisp 1.5](https://simon-brooke.github.io/beowulf/docs/further_reading.html). ## License diff --git a/docs/codox/beowulf.gendoc.html b/docs/codox/beowulf.gendoc.html index 70a5d94..b272376 100644 --- a/docs/codox/beowulf.gendoc.html +++ b/docs/codox/beowulf.gendoc.html @@ -1,4 +1,4 @@ beowulf.gendoc documentation

beowulf.gendoc

Generate table of documentation of Lisp symbols and functions.

-

NOTE: this is very hacky. You almost certainly do not want to use this!

find-documentation

(find-documentation entry)

Find appropriate documentation for this entry from the oblist.

gen-doc-table

(gen-doc-table)

TODO: write docs

gen-index

(gen-index)(gen-index url destination)

TODO: write docs

host-functions

Functions which we can infer are written in Clojure. We need to collect these at run-time, not compile time, hence memoised function, not variable.

infer-implementation

(infer-implementation entry)

TODO: write docs

infer-signature

(infer-signature entry)

Infer the signature of the function value of this oblist entry, if any.

infer-type

(infer-type entry)

Try to work out what this entry from the oblist actually represents.

open-doc

(open-doc symbol)

Open the documentation page for this symbol, if known, in the default web browser.

\ No newline at end of file +

NOTE: this is very hacky. You almost certainly do not want to use this!

find-documentation

(find-documentation entry)

Find appropriate documentation for this entry from the oblist.

gen-doc-table

(gen-doc-table)

TODO: write docs

gen-index

(gen-index)(gen-index url destination)

TODO: write docs

host-functions

Functions which we can infer are written in Clojure. We need to collect these at run-time, not compile time, hence memoised function, not variable.

infer-implementation

(infer-implementation entry)

TODO: write docs

infer-signature

(infer-signature entry)

Infer the signature of the function value of this oblist entry, if any.

infer-type

(infer-type entry)

Try to work out what this entry from the oblist actually represents.

open-doc

(open-doc symbol)

Open the documentation page for this symbol, if known, in the default web browser.

\ No newline at end of file diff --git a/docs/codox/beowulf.io.html b/docs/codox/beowulf.io.html index 418bc6c..687c3b5 100644 --- a/docs/codox/beowulf.io.html +++ b/docs/codox/beowulf.io.html @@ -5,9 +5,9 @@

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.

default-sysout

TODO: write docs

resolve-subr

(resolve-subr entry)

If this oblist entry references a subroutine, attempt to fix up that reference.

safely-wrap-subr

(safely-wrap-subr entry)

TODO: write docs

safely-wrap-subrs

(safely-wrap-subrs objects)

TODO: write docs

SYSIN

(SYSIN)(SYSIN filename)

Read the contents of the file at this filename into the object list.

+

Hence functions SYSOUT and SYSIN, which do just that.

default-sysout

TODO: write docs

resolve-subr

(resolve-subr entry)(resolve-subr entry prop)

If this oblist entry references a subroutine, attempt to fix up that reference.

safely-wrap-subr

(safely-wrap-subr entry)

TODO: write docs

safely-wrap-subrs

(safely-wrap-subrs objects)

TODO: write docs

SYSIN

(SYSIN)(SYSIN 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.

-

NOTE THAT this is an extension function, not available in strct mode.

SYSOUT

(SYSOUT)(SYSOUT 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.

+

NOTE THAT this is an extension function, not available in strct mode.

SYSOUT

(SYSOUT)(SYSOUT 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.

NOTE THAT this is an extension function, not available in strct mode.

\ No newline at end of file diff --git a/docs/codox/beowulf.reader.generate.html b/docs/codox/beowulf.reader.generate.html index a404a20..36c5dd7 100644 --- a/docs/codox/beowulf.reader.generate.html +++ b/docs/codox/beowulf.reader.generate.html @@ -21,4 +21,4 @@ T->ff[car[x]]]]] (COND ((ATOM X) X) ((QUOTE T)(FF (CAR X)))))) -

quote ends

gen-cond

(gen-cond p context)

Generate a cond statement from this simplified parse tree fragment p; returns nil if p does not represent a (MEXPR) cond statement.

gen-cond-clause

(gen-cond-clause p context)

Generate a cond clause from this simplified parse tree fragment p; returns nil if p does not represent a cond clause.

gen-dot-terminated-list

(gen-dot-terminated-list p)

Generate a list, which may be dot-terminated, from this partial parse tree ‘p’. Note that the function acts recursively and progressively decapitates its argument, so that the argument will not always be a valid parse tree.

gen-fn-call

(gen-fn-call p context)

Generate a function call from this simplified parse tree fragment p; returns nil if p does not represent a (MEXPR) function call.

gen-iexpr

(gen-iexpr tree)

TODO: write docs

generate

(generate p)(generate p context)

Generate lisp structure from this parse tree p. It is assumed that p has been simplified.

generate-assign

(generate-assign tree context)

Generate an assignment statement based on this tree. If the thing being assigned to is a function signature, then we have to do something different to if it’s an atom.

generate-defn

(generate-defn tree context)

TODO: write docs

generate-set

(generate-set tree context)

Actually not sure what the mexpr representation of set looks like

strip-leading-zeros

(strip-leading-zeros s)(strip-leading-zeros s prefix)

read-string interprets strings with leading zeros as octal; strip any from this string s. If what’s left is empty (i.e. there were only zeros, return "0".

\ No newline at end of file +

quote ends

gen-cond

(gen-cond p context)

Generate a cond statement from this simplified parse tree fragment p; returns nil if p does not represent a (MEXPR) cond statement.

gen-cond-clause

(gen-cond-clause p context)

Generate a cond clause from this simplified parse tree fragment p; returns nil if p does not represent a cond clause.

gen-dot-terminated-list

(gen-dot-terminated-list p)

Generate a list, which may be dot-terminated, from this partial parse tree ‘p’. Note that the function acts recursively and progressively decapitates its argument, so that the argument will not always be a valid parse tree.

gen-fn-call

(gen-fn-call p context)

Generate a function call from this simplified parse tree fragment p; returns nil if p does not represent a (MEXPR) function call.

gen-iexpr

(gen-iexpr tree context)

TODO: write docs

generate

(generate p)(generate p context)

Generate lisp structure from this parse tree p. It is assumed that p has been simplified.

generate-assign

(generate-assign tree context)

Generate an assignment statement based on this tree. If the thing being assigned to is a function signature, then we have to do something different to if it’s an atom.

generate-defn

(generate-defn tree context)

TODO: write docs

generate-set

(generate-set tree context)

Actually not sure what the mexpr representation of set looks like

strip-leading-zeros

(strip-leading-zeros s)(strip-leading-zeros s prefix)

read-string interprets strings with leading zeros as octal; strip any from this string s. If what’s left is empty (i.e. there were only zeros, return "0".

\ No newline at end of file diff --git a/docs/codox/css/default.css b/docs/codox/css/default.css index 3ca495f..a445e91 100644 --- a/docs/codox/css/default.css +++ b/docs/codox/css/default.css @@ -37,6 +37,10 @@ h2 { font-size: 25px; } +th, td { + vertical-align: top; +} + h5.license { margin: 9px 0 22px 0; color: lime; diff --git a/docs/codox/index.html b/docs/codox/index.html index 45d68ba..80e307d 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -Beowulf 0.3.0-SNAPSHOT

Beowulf 0.3.0-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.3.0-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..

Public variables and functions:

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

Generate table of documentation of Lisp symbols 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.

beowulf.manual

Experimental code for accessing the manual online.

Public variables and functions:

beowulf.oblist

A namespace mainly devoted to the object list and other top level global variables.

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.

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

    Beowulf 0.3.0-SNAPSHOT

    Released under the GPL-2.0-or-later

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

    Installation

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

    [beowulf "0.3.0-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..

    Public variables and functions:

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

    Generate table of documentation of Lisp symbols 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.

    beowulf.manual

    Experimental code for accessing the manual online.

    Public variables and functions:

    beowulf.oblist

    A namespace mainly devoted to the object list and other top level global variables.

    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.

      \ No newline at end of file diff --git a/docs/codox/intro.html b/docs/codox/intro.html index af84ffe..2cd54be 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -3,19 +3,62 @@ beowulf

      beowulf

      Þý liste cræfte spræc

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

      -

      Beowulf logo

      +

      Beowulf logo

      +

      Contents

      + +Table of contents generated with markdown-toc

      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.

      +

      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.

      Status

      Working Lisp interpreter, but some key features not yet implemented.

      -

      Building and Invoking

      -

      Build with

      -
      lein uberjar
      -
      +

      Project Target

      +

      The project target is to be able to run the Wang algorithm for the propositional calculus given in chapter 8 of the Lisp 1.5 Programmer’s Manual. When that runs, the project is as far as I am concerned feature complete. I may keep tinkering with it after that and I’ll certainly accept pull requests which are in the spirit of the project (i.e. making Beowulf more usable, and/or implementing parts of Lisp 1.5 which I have not implemented), but this isn’t intended to be a new language for doing real work; it’s an educational and archaeological project, not serious engineering.

      +

      Some readline-like functionality would be really useful, but my attempt to integrate JLine has not (yet) been successful.

      +

      An in-core structure editor would be an extremely nice thing, and I may well implement one.

      +

      You are of course welcome to fork the project and do whatever you like with it!

      +

      Invoking

      Invoke with

      java -jar target/uberjar/beowulf-0.3.0-SNAPSHOT-standalone.jar --help
       
      @@ -23,14 +66,19 @@

      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.
      +  -r INITFILE, --read SYSOUTFILE           Read Lisp sysout from the file SYSOUTFILE 
      +                                           (defaults to `resources/lisp1.5.lsp`)
      +  -s, --strict                             Strictly interpret the Lisp 1.5 language, 
      +                                           without extensions.
       

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

      +

      Building and Invoking

      +

      Build with

      +
      lein uberjar
      +

      Reader macros

      -

      Currently I don’t have

      +

      Currently SETQ and DEFUN are implemented as reader macros, sort of. It would now be possible to reimplement them as FEXPRs and so the reader macro functionality will probably go away.

      Functions and symbols implemented

      -

      The following functions and symbols are implemented:

      @@ -45,590 +93,688 @@ - - + + - - + + - - + + - - + + - - + + - - - - + + + + - - + + + + + + + + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - + + + + + + + + + - - - - + + + + - - + + - + - - + + - - - - + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - + - - - + + + - - + + - - + + - - - - + + + + - - + + - - - - + + + + - - - + + + - - - - + + + + - - + + + + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - + + - - + + - - - - + + + + + + + + + + + - - - - + + + + + + + + + + + - - + + - - + + - - + + + + + + + + + - - - - + + + + + + + + + + + - - + + - + + - - - + + - - + + - - - + + + - - + + - - + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - + - - - - + + + + + + + + + + + - - - - + + + +
      NIL Lisp variable ? see manual pages 22, 69
      T Lisp variable ? see manual pages 22, 69
      F Lisp variable ? see manual pages 22, 69
      ADD1 Host function (ADD1 X) Host lambda function ? ?
      AND Host function (AND & ARGS) Host lambda function ? PREDICATE 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 Lisp function (APPEND X Y) LAMBDA-fn see manual pages 11, 61 Lisp lambda function ? see manual pages 11, 61
      APPLY Host function (APPLY FUNCTION ARGS ENVIRONMENT DEPTH) Host lambda function ? 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 Lisp lambda function, Host lambda function ? ? 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. NOTE THAT this function is overridden by an implementation in Lisp, but is currently still present for bootstrapping.
      ATOM Host function (ATOM X) Host lambda function ? PREDICATE 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 Host function (CAR X) Host lambda function ? Return the item indicated by the first pointer of a pair. NIL is treated specially: the CAR of NIL is NIL.
      CAAAAR Lisp function (CAAAAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CAAADR Lisp function (CAAADR X) LAMBDA-fn Lisp lambda function ? ? ?
      CAAAR Lisp function (CAAAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CAADAR Lisp function (CAADAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CAADDR Lisp function (CAADDR X) LAMBDA-fn Lisp lambda function ? ? ?
      CAADR Lisp function (CAADR X) LAMBDA-fn Lisp lambda function ? ? ?
      CAAR Lisp function (CAAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CADAAR Lisp function (CADAAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CADADR Lisp function (CADADR X) LAMBDA-fn Lisp lambda function ? ? ?
      CADAR Lisp function (CADAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CADDAR Lisp function (CADDAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CADDDR Lisp function (CADDDR X) LAMBDA-fn Lisp lambda function ? ? ?
      CADDR Lisp function (CADDR X) LAMBDA-fn Lisp lambda function ? ? ?
      CADR Lisp function (CADR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDAAAR Lisp function (CDAAAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDAADR Lisp function (CDAADR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDAAR Lisp function (CDAAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDADAR Lisp function (CDADAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDADDR Lisp function (CDADDR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDADR Lisp function (CDADR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDAR Lisp function (CDAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDDAAR Lisp function (CDDAAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDDADR Lisp function (CDDADR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDDAR Lisp function (CDDAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDDDAR Lisp function (CDDDAR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDDDDR Lisp function (CDDDDR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDDDR Lisp function (CDDDR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDDR Lisp function (CDDR X) LAMBDA-fn Lisp lambda function ? ? ?
      CDR Host function (CDR X) Host lambda function ? Return the item indicated by the second pointer of a pair. NIL is treated specially: the CDR of NIL is NIL.
      CONS Host function (CONS CAR CDR) Host lambda function ? Construct a new instance of cons cell with this car and cdr.
      CONSP Host lambda function ? ? Return T if object o is a cons cell, else F. NOTE THAT this is an extension function, not available in strct mode. I believe that Lisp 1.5 did not have any mechanism for testing whether an argument was, or was not, a cons cell.
      COPY Lisp function (COPY X) LAMBDA-fn see manual pages 62 Lisp lambda function ? see manual pages 62
      DEFINE Host function (DEFINE ARGS) Host lambda function ? PSEUDO-FUNCTION 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)) Bootstrap-only version of DEFINE which, post boostrap, can be overwritten in LISP. The single argument to DEFINE should be an association list of symbols to lambda functions. See page 58 of the manual.
      DIFFERENCE Host function (DIFFERENCE X Y) Host lambda function ? ?
      DIVIDE Lisp function (DIVIDE X Y) LAMBDA-fn see manual pages 26, 64 Lisp lambda function ? see manual pages 26, 64
      DOC Host lambda function ? ? Open the page for this symbol in the Lisp 1.5 manual, if known, in the default web browser. NOTE THAT this is an extension function, not available in strct mode.
      EFFACE Lisp lambda function ? PSEUDO-FUNCTION see manual pages 63
      ERROR Host function (ERROR & ARGS) Host lambda function ? PSEUDO-FUNCTION Throw an error
      EQ Host function (EQ X Y) Host lambda function ? PREDICATE Returns T if and only if both x and y are bound to the same atom, else NIL.
      EQUAL Host function (EQUAL X Y) Host lambda function ? PREDICATE 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 (EVAL EXPR); (EVAL EXPR ENV DEPTH) Host lambda function ? 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. 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. However, if called with just a single arg, expr, I’ll assume it’s being called from the Clojure REPL and will coerce the expr to ConsCell.
      FACTORIAL Lisp function (FACTORIAL N) LAMBDA-fn Lisp lambda function ? ? ?
      FIXP Host function (FIXP X) Host lambda function ? PREDICATE ?
      GENSYM Host function (GENSYM ) Host lambda function ? Generate a unique symbol.
      GET Lisp function (GET X Y) LAMBDA-fn see manual pages 41, 59 Host lambda function ? From the manual: ‘get is somewhat like prop; however its value is car of the rest of the list if the indicator is found, and NIL otherwise.’ It’s clear that GET is expected to be defined in terms of PROP, but we can’t implement PROP here because we lack EVAL; and we can’t have EVAL here because both it and APPLY depends on GET. OK, It’s worse than that: the statement of the definition of GET (and of) PROP on page 59 says that the first argument to each must be a list; But the in the definition of ASSOC on page 70, when GET is called its first argument is always an atom. Since it’s ASSOC and EVAL which I need to make work, I’m going to assume that page 59 is wrong.
      GREATERP Host function (GREATERP X Y) Host lambda function ? PREDICATE ?
      INTEROP Host function (INTEROP FN-SYMBOL ARGS) (INTEROP) 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. Host lambda function ? ? ?
      INTERSECTION Lisp function (INTERSECTION X Y) LAMBDA-fn Lisp lambda function ? ? ?
      LENGTH Lisp function (LENGTH L) LAMBDA-fn see manual pages 62 Lisp lambda function ? see manual pages 62
      LESSP Host function (LESSP X Y) Host lambda function ? PREDICATE ?
      MAPLIST Lisp lambda function ? FUNCTIONAL see manual pages 20, 21, 63
      MEMBER Lisp function (MEMBER A X) LAMBDA-fn see manual pages 11, 62 Lisp lambda function ? PREDICATE see manual pages 11, 62
      MINUSP Lisp function (MINUSP X) LAMBDA-fn see manual pages 26, 64 Lisp lambda function ? PREDICATE see manual pages 26, 64
      NOT Lisp function (NOT X) LAMBDA-fn see manual pages 21, 23, 58 Lisp lambda function ? PREDICATE see manual pages 21, 23, 58
      NULL Lisp function (NULL X) LAMBDA-fn see manual pages 11, 57 Lisp lambda function ? PREDICATE see manual pages 11, 57
      NUMBERP Host function (NUMBERP X) Host lambda function ? PREDICATE ?
      OBLIST Host function (OBLIST ) Host lambda 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 (ONEP X) LAMBDA-fn see manual pages 26, 64 Lisp lambda function ? PREDICATE see manual pages 26, 64
      OR Host lambda function ? PREDICATE T if and only if at least one of my args evaluates to something other than either F or NIL, else F. In beowulf.host principally because I don’t yet feel confident to define varargs functions in Lisp.
      PAIR Lisp function (PAIR X Y) LAMBDA-fn see manual pages 60 Lisp lambda function ? see manual pages 60
      PAIRLIS Lisp lambda function, Host lambda function ? ? 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. NOTE THAT this function is overridden by an implementation in Lisp, but is currently still present for bootstrapping.
      PLUS Host function (PLUS & ARGS) Host lambda function ? ?
      PRETTY Lisp variable (PRETTY) ? ? ?
      PRINT Lisp variable PSEUDO-FUNCTION ? PSEUDO-FUNCTION see manual pages 65, 84
      PROG Host nlambda function ? The accursed PROG feature. See page 71 of the manual. Lisp 1.5 introduced PROG, and most Lisps have been stuck with it ever since. It introduces imperative programming into what should be a pure functional language, and consequently it’s going to be a pig to implement. Broadly, PROG is a variadic pseudo function called as a FEXPR (or possibly an FSUBR, although I’m not presently sure that would even work.) The arguments, which are unevaluated, are a list of forms, the first of which is expected to be a list of symbols which will be treated as names of variables within the program, and the rest of which (the ‘program body’) are either lists or symbols. Lists are treated as Lisp expressions which may be evaulated in turn. Symbols are treated as targets for the GO statement. GO: A GO statement takes the form of (GO target), where target should be one of the symbols which occur at top level among that particular invocation of PROGs arguments. A GO statement may occur at top level in a PROG, or in a clause of a COND statement in a PROG, but not in a function called from the PROG statement. When a GO statement is evaluated, execution should transfer immediately to the expression which is the argument list immediately following the symbol which is its target. If the target is not found, an error with the code A6 should be thrown. RETURN: A RETURN statement takes the form (RETURN value), where value is any value. Following the evaluation of a RETURN statement, the PROG should immediately exit without executing any further expressions, returning the value. SET and SETQ: In addition to the above, if a SET or SETQ expression is encountered in any expression within the PROG body, it should affect not the global object list but instead only the local variables of the program. COND: In strict mode, when in normal execution, a COND statement none of whose clauses match should not return NIL but should throw an error with the code A3except that inside a PROG body, it should not do so. sigh. Flow of control: Apart from the exceptions specified above, expressions in the program body are evaluated sequentially. If execution reaches the end of the program body, NIL is returned. Got all that? Good.
      PROP Lisp function (PROP X Y U) LAMBDA-fn see manual pages 59 Lisp lambda function ? FUNCTIONAL see manual pages 59
      QUOTE Lisp lambda function ? see manual pages 10, 22, 71
      QUOTIENT Host function (QUOTIENT X Y) Host lambda function ? 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.
      RANGE Lisp variable Lisp lambda function ? ? (RANGE (LAMBDA (N M) (COND ((LESSP M N) (QUOTE NIL)) ((QUOTE T) (CONS N (RANGE (ADD1 N) M)))))) ?
      READ Host function (READ ); (READ INPUT) Host lambda function ? PSEUDO-FUNCTION 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 (REMAINDER X Y) Host lambda function ? ?
      REPEAT Lisp function (REPEAT N X) LAMBDA-fn Lisp lambda function ? ? ?
      RPLACA Host function (RPLACA CELL VALUE) Host lambda function ? PSEUDO-FUNCTION 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 (RPLACD CELL VALUE) Host lambda function ? PSEUDO-FUNCTION 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)
      SEARCH Lisp lambda function ? FUNCTIONAL see manual pages 63
      SET Host function (SET SYMBOL VAL) Host lambda function ? PSEUDO-FUNCTION 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 (SUB1 N) LAMBDA-fn see manual pages 26, 64
      SYSIN Host function (SYSIN ); (SYSIN FILENAME) (SYSIN) 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 (SYSOUT ); (SYSOUT FILEPATH) (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.
      TERPRI Lisp variable Lisp lambda function, Host lambda function ? PSEUDO-FUNCTION ?
      SUB2 Lisp lambda function ? ? ?
      SUBLIS Lisp lambda function ? see manual pages 12, 61
      SUBST Lisp lambda function ? see manual pages 11, 61
      SYSIN Host lambda function ? ? 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. NOTE THAT this is an extension function, not available in strct mode.
      SYSOUT Host lambda function ? ? 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. NOTE THAT this is an extension function, not available in strct mode.
      TERPRI ? PSEUDO-FUNCTION see manual pages 65, 84
      TIMES Host function (TIMES & ARGS) Host lambda function ? ?
      TRACE Host function (TRACE S) Host lambda function ? PSEUDO-FUNCTION Add this symbol s to the set of symbols currently being traced. If s is not a symbol, does nothing. Add this s to the set of symbols currently being traced. If s is not a symbol or sequence of symbols, does nothing.
      UNTRACE Host function (UNTRACE S) PSEUDO-FUNCTION UNION Lisp lambda function ? ? ?
      UNTRACE Host lambda function ? PSEUDO-FUNCTION Remove this s from the set of symbols currently being traced. If s is not a symbol or sequence of symbols, does nothing.
      ZEROP Lisp function (ZEROP N) LAMBDA-fn see manual pages 26, 64 Lisp lambda function ? PREDICATE see manual pages 26, 64
      @@ -657,19 +803,18 @@
    • 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;
    • 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.
    • -

      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.

      +

      Download the latest release ‘uberjar’ and run it using:

      +
          java -jar <path name of uberjar>
      +
      +

      Or clone the source and build it using:

      +
          lein uberjar`
      +
      +

      To build it 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.

      @@ -682,6 +827,9 @@

      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.

      +

      Other implementations

      +

      There’s an online (browser native) Lisp 1.5 implementation here (source code here). It even has a working compiler!

      +

      History resources

      +

      I’m compiling a list of links to historical documents on Lisp 1.5.

      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/project.clj b/project.clj index 358230a..06ca4dc 100644 --- a/project.clj +++ b/project.clj @@ -11,7 +11,7 @@ :source-uri "https://github.com/simon-brooke/beowulf/blob/master/{filepath}#L{line}" ;; :themes [:journeyman] } - :description "An implementation of LISP 1.5 in Clojure" + :description "LISP 1.5 is to all Lisp dialects as Beowulf is to English literature." :license {:name "GPL-2.0-or-later" :url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"} :dependencies [[org.clojure/clojure "1.11.1"] diff --git a/resources/codox/themes/journeyman/css/default.css b/resources/codox/themes/journeyman/css/default.css index 3ca495f..a445e91 100644 --- a/resources/codox/themes/journeyman/css/default.css +++ b/resources/codox/themes/journeyman/css/default.css @@ -37,6 +37,10 @@ h2 { font-size: 25px; } +th, td { + vertical-align: top; +} + h5.license { margin: 9px 0 22px 0; color: lime; diff --git a/resources/lisp1.5.lsp b/resources/lisp1.5.lsp index 6f7bc9f..e56bc7d 100644 --- a/resources/lisp1.5.lsp +++ b/resources/lisp1.5.lsp @@ -161,6 +161,7 @@ (PLUS 32767 SUBR (BEOWULF HOST PLUS)) (PRETTY 32767) (PRINT 32767) + (PROG 32767 FSUBR (BEOWULF HOST PROG)) (PROP 32767 EXPR diff --git a/resources/mexpr/not.mexpr b/resources/mexpr/not.mexpr new file mode 100644 index 0000000..4aa5b5b --- /dev/null +++ b/resources/mexpr/not.mexpr @@ -0,0 +1 @@ +not[x] = [x = F -> T; x = NIL -> T; T -> F] \ No newline at end of file diff --git a/src/beowulf/core.clj b/src/beowulf/core.clj index 502c27d..d20339d 100644 --- a/src/beowulf/core.clj +++ b/src/beowulf/core.clj @@ -46,12 +46,12 @@ ["-h" "--help"] ["-p PROMPT" "--prompt PROMPT" "Set the REPL prompt to PROMPT" :default "Sprecan::"] - ["-r INITFILE" "--read INITFILE" "Read Lisp system from file INITFILE" + ["-r SYSOUTFILE" "--read SYSOUTFILE" "Read Lisp system from file SYSOUTFILE" :default default-sysout :validate [#(and (.exists (io/file %)) (.canRead (io/file %))) - "Could not find initfile"]] + "Could not find sysout file"]] ["-s" "--strict" "Strictly interpret the Lisp 1.5 language, without extensions."] ["-t" "--time" "Time evaluations."]]) diff --git a/src/beowulf/gendoc.clj b/src/beowulf/gendoc.clj index 994549e..8204ede 100644 --- a/src/beowulf/gendoc.clj +++ b/src/beowulf/gendoc.clj @@ -8,7 +8,7 @@ *manual-url* page-url]] [beowulf.oblist :refer [NIL oblist]] [clojure.java.browse :refer [browse-url]] - [clojure.string :as s ])) + [clojure.string :as s])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; @@ -60,7 +60,6 @@ (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))) @@ -69,13 +68,25 @@ "Try to work out what this `entry` from the oblist actually represents." [entry] - (cond - (= (second entry) 'LAMBDA) "Lisp function" - (= (second entry) 'LABEL) "Labeled form" - ((host-functions) (first entry)) (if (fn? (eval (symbol ((host-functions) (first entry))))) - "Host function" - "Host variable") - :else "Lisp variable")) + (let [interpretation {'APVAL "Lisp variable" + 'EXPR "Lisp lambda function" + 'FEXPR "Lisp nlambda function" + 'SUBR "Host lambda function" + 'FSUBR "Host nlambda function"}] + (s/join ", " + (remove nil? + (map + #(when (some #{%} entry) (interpretation %)) + (keys interpretation)))))) + ;; (cond + ;; (= (nth entry 2) 'EXPR) "Lisp function" + ;; (= (nth entry 2) 'FEXPR) "Labeled form" + ;; ((host-functions) (first entry)) (try (if (fn? (eval (symbol ((host-functions) (first entry))))) + ;; "Host function" + ;; "Host variable") + ;; (catch Exception _ + ;; "?Host macro?")) + ;; :else "Lisp variable")) (defn- format-clj-signature "Format the signature of the Clojure function represented by `symbol` for @@ -87,7 +98,7 @@ (map (fn [l] (s/join (concat (list "(" symbol " ") - (s/join " " (map #(s/upper-case (str %)) l)) (list ")")))) + (s/join " " (map #(s/upper-case (str %)) l)) (list ")")))) arglists)))) (defn infer-signature @@ -102,17 +113,18 @@ (defn infer-implementation [entry] - (case (second entry) - LAMBDA (format "%s-fn" (second entry)) - LABEL (format "%s-fn" (second entry)) - (or (:implementation (index (keyword (first entry)))) (str entry)))) + (or (:implementation (index (keyword (first entry)))) "?")) + ;; (case (second entry) + ;; LAMBDA (format "%s-fn" (second entry)) + ;; LABEL (format "%s-fn" (second entry)) + ;; (or (:implementation (index (keyword (first entry)))) (str entry)))) (defn find-documentation "Find appropriate documentation for this `entry` from the oblist." [entry] (let [k (keyword (first entry))] (cond - (= (count entry) 1) (if-let [doc (get-metadata-for-entry entry :doc)] + (some #{'SUBR 'FSUBR} entry) (if-let [doc (get-metadata-for-entry entry :doc)] (s/replace doc "\n" " ") "?") (k index) (str "see manual pages " (format-page-references k)) @@ -159,12 +171,12 @@ web browser." [symbol] (let [doc (get-metadata-for-function symbol :doc)] - (if-let [pages (:page-nos (index (keyword symbol)))] - (browse-url (page-url (first pages))) - (if doc - (println doc) - (throw (ex-info "No documentation found" - {:phase :host - :function 'DOC - :args (list symbol) - :type :beowulf})))))) \ No newline at end of file + (if-let [pages (:page-nos (index (keyword symbol)))] + (browse-url (page-url (first pages))) + (if doc + (println doc) + (throw (ex-info "No documentation found" + {:phase :host + :function 'DOC + :args (list symbol) + :type :beowulf})))))) \ No newline at end of file diff --git a/src/beowulf/io.clj b/src/beowulf/io.clj index 7eb9ce1..3ad7b57 100644 --- a/src/beowulf/io.clj +++ b/src/beowulf/io.clj @@ -108,9 +108,12 @@ (defn resolve-subr "If this oblist `entry` references a subroutine, attempt to fix up that reference." - [entry] - (cond (= entry NIL) NIL - (= (CAR entry) 'SUBR) (try + ([entry] + (or (resolve-subr entry 'SUBR) + (resolve-subr entry 'FSUBR))) + ([entry prop] + (cond (= entry NIL) NIL + (= (CAR entry) prop) (try (make-cons-cell (CAR entry) (make-cons-cell @@ -122,7 +125,7 @@ (CADR entry)) (CDDR entry))) :else (make-cons-cell - (CAR entry) (resolve-subr (CDR entry))))) + (CAR entry) (resolve-subr (CDR entry)))))) (defn- resolve-subroutines diff --git a/src/beowulf/reader/generate.clj b/src/beowulf/reader/generate.clj index 029bf0f..8d4edcc 100644 --- a/src/beowulf/reader/generate.clj +++ b/src/beowulf/reader/generate.clj @@ -148,24 +148,25 @@ (defn generate-defn [tree context] - (make-beowulf-list - (list 'PUT - (list 'QUOTE (generate (-> tree second second) context)) - (list 'QUOTE 'EXPR) - (list 'QUOTE - (cons 'LAMBDA - (cons (generate (nth (second tree) 2) context) - (map #(generate % context) - (-> tree rest rest rest)))))))) + (if (= :mexpr (first tree)) + (generate-defn (second tree) context) + (make-beowulf-list + (list 'PUT + (list 'QUOTE (generate (-> tree second second second) context)) + (list 'QUOTE 'EXPR) + (list 'QUOTE + (cons 'LAMBDA + (list (generate (nth (-> tree second second) 2) context) + (generate (nth tree 3) context)))))))) (defn gen-iexpr - [tree] - (let [bundle (reduce #(assoc %1 (first %2) %2) - {} + [tree context] + (let [bundle (reduce #(assoc %1 (first %2) %2) + {} (rest tree))] - (list (generate (:iop bundle)) - (generate (:lhs bundle)) - (generate (:rhs bundle))))) + (list (generate (:iop bundle) context) + (generate (:lhs bundle) context) + (generate (:rhs bundle) context)))) (defn generate-set "Actually not sure what the mexpr representation of set looks like" @@ -203,77 +204,73 @@ (generate p :expr)) ([p context] (try - (expand-macros - (if - (coll? p) - (case (first p) - :λ "LAMBDA" - :λexpr (make-cons-cell - (generate (nth p 1) context) - (make-cons-cell (generate (nth p 2) context) - (generate (nth p 3) context))) - :args (make-beowulf-list (map #(generate % context) (rest p))) - :atom (case context - :mexpr (if (some #(Character/isUpperCase %) (second p)) - (list 'QUOTE (symbol (second p))) - (symbol (second p))) - :cond-mexpr (case (second p) - (T F NIL) (symbol (second p)) - ;; else - (symbol (second p))) - ;; else - (symbol (second p))) - :bindings (generate (second p) context) - :body (make-beowulf-list (map #(generate % context) (rest p))) - (:coefficient :exponent) (generate (second p) context) - :cond (gen-cond p (if (= context :mexpr) :cond-mexpr context)) - :cond-clause (gen-cond-clause p context) - :decimal (read-string (apply str (map second (rest p)))) - :defn (generate-assign p context) - :dotted-pair (make-cons-cell - (generate (nth p 1) context) - (generate (nth p 2) context)) - :fncall (gen-fn-call p context) - :iexpr (gen-iexpr p) - :integer (read-string (strip-leading-zeros (second p))) - :iop (case (second p) - "/" 'DIFFERENCE - "=" 'EQUAL - ">" 'GREATERP - "<" 'LESSP - "+" 'PLUS - "*" 'TIMES + (expand-macros + (if + (coll? p) + (case (first p) + :λ "LAMBDA" + :λexpr (make-cons-cell + (generate (nth p 1) context) + (make-cons-cell (generate (nth p 2) context) + (generate (nth p 3) context))) + :args (make-beowulf-list (map #(generate % context) (rest p))) + :atom (symbol (second p)) + :bindings (generate (second p) context) + :body (make-beowulf-list (map #(generate % context) (rest p))) + (:coefficient :exponent) (generate (second p) context) + :cond (gen-cond p (if (= context :mexpr) :cond-mexpr context)) + :cond-clause (gen-cond-clause p context) + :decimal (read-string (apply str (map second (rest p)))) + :defn (generate-defn p context) + :dotted-pair (make-cons-cell + (generate (nth p 1) context) + (generate (nth p 2) context)) + :fncall (gen-fn-call p context) + :iexpr (gen-iexpr p context) + :integer (read-string (strip-leading-zeros (second p))) + :iop (case (second p) + "/" 'DIFFERENCE + "=" 'EQUAL + ">" 'GREATERP + "<" 'LESSP + "+" 'PLUS + "*" 'TIMES ;; else - (throw (ex-info "Unrecognised infix operator symbol" - {:phase :generate - :fragment p}))) - :list (gen-dot-terminated-list (rest p)) - (:lhs :rhs) (generate (second p) context) - :mexpr (generate (second p) :mexpr) - :mconst (make-beowulf-list - (list 'QUOTE (symbol (upper-case (second p))))) - :mvar (symbol (upper-case (second p))) - :number (generate (second p) context) - :octal (let [n (read-string (strip-leading-zeros (second p) "0")) - scale (generate (nth p 3) context)] - (* n (expt 8 scale))) + (throw (ex-info "Unrecognised infix operator symbol" + {:phase :generate + :fragment p}))) + :list (gen-dot-terminated-list (rest p)) + (:lhs :rhs) (generate (second p) context) + :mexpr (generate (second p) (if (= context :cond-mexpr) context :mexpr)) + :mconst (if (= context :cond-mexpr) + (case (second p) + ("T" "F" "NIL") (symbol (second p)) + ;; else + (list 'QUOTE (symbol (second p)))) + ;; else + (list 'QUOTE (symbol (second p)))) + :mvar (symbol (upper-case (second p))) + :number (generate (second p) context) + :octal (let [n (read-string (strip-leading-zeros (second p) "0")) + scale (generate (nth p 3) context)] + (* n (expt 8 scale))) ;; the quote read macro (which probably didn't exist in Lisp 1.5, but...) - :quoted-expr (make-beowulf-list (list 'QUOTE (generate (second p) context))) - :scale-factor (if - (empty? (second p)) 0 - (read-string (strip-leading-zeros (second p)))) - :scientific (let [n (generate (second p) context) - exponent (generate (nth p 3) context)] - (* n (expt 10 exponent))) - :sexpr (generate (second p) :sexpr) - :subr (symbol (second p)) + :quoted-expr (make-beowulf-list (list 'QUOTE (generate (second p) context))) + :scale-factor (if + (empty? (second p)) 0 + (read-string (strip-leading-zeros (second p)))) + :scientific (let [n (generate (second p) context) + exponent (generate (nth p 3) context)] + (* n (expt 10 exponent))) + :sexpr (generate (second p) :sexpr) + :subr (symbol (second p)) ;; default - (throw (ex-info (str "Unrecognised head: " (first p)) - {:generating p}))) - p)) - (catch Throwable any - (throw (ex-info "Could not generate" - {:generating p} - any)))))) + (throw (ex-info (str "Unrecognised head: " (first p)) + {:generating p}))) + p)) + (catch Throwable any + (throw (ex-info "Could not generate" + {:generating p} + any)))))) diff --git a/src/beowulf/reader/parser.clj b/src/beowulf/reader/parser.clj index b2a46fe..0fd7abe 100644 --- a/src/beowulf/reader/parser.clj +++ b/src/beowulf/reader/parser.clj @@ -65,7 +65,7 @@ cond-clause := mexpr opt-space arrow opt-space mexpr opt-space; arrow := '->'; args := arg | (opt-space arg semi-colon opt-space)* opt-space arg opt-space; - arg := mexpr | sexpr; + arg := mexpr; fn-name := mvar; mvar := #'[a-z][a-z0-9]*'; mconst := #'[A-Z][A-Z0-9]*'; diff --git a/test/beowulf/mexpr_test.clj b/test/beowulf/mexpr_test.clj index 412476f..2f74389 100644 --- a/test/beowulf/mexpr_test.clj +++ b/test/beowulf/mexpr_test.clj @@ -68,10 +68,10 @@ (deftest conditional-tests (testing "Conditional expressions" - (let [expected "(COND ((ATOM X) X) ((QUOTE T) (FF (CAR X))))" + (let [expected "(COND ((ATOM X) X) (T (FF (CAR X))))" actual (print-str (gsp "[atom[x]->x; T->ff[car[x]]]"))] (is (= actual expected))) - (let [expected "(LABEL FF (LAMBDA (X) (COND ((ATOM X) X) ((QUOTE T) (FF (CAR X))))))" + (let [expected "(LABEL FF (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR X))))))" actual (print-str (generate (simplify-tree @@ -88,6 +88,6 @@ (deftest assignment-tests (testing "Function assignment" - (let [expected "(PUT (QUOTE FF) (QUOTE EXPR) (QUOTE (LAMBDA (X) (COND ((ATOM X) X) ((QUOTE T) (FF (CAR X)))))))" + (let [expected "(PUT (QUOTE FF) (QUOTE EXPR) (QUOTE (LAMBDA (X) (COND ((ATOM X) X) (T (FF (CAR X)))))))" actual (print-str (gsp "ff[x]=[atom[x] -> x; T -> ff[car[x]]]"))] (is (= actual expected)))))