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..
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
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.
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
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
F
The canonical false value - different from NIL
, which is not canonically false in Lisp 1.5.
make-beowulf-list
(make-beowulf-list x)
Construct a linked list of cons cells with the same content as the sequence x
.
make-cons-cell
(make-cons-cell car cdr)
Construct a new instance of cons cell with this car
and cdr
.
MutableSequence
protocol
Like a sequence, but mutable.
members
getCar
(getCar this)
Return the first element of this sequence.
getCdr
(getCdr this)
like more
, q.v., but returns List NIL
not Clojure nil
when empty.
getUid
(getUid this)
Returns a unique identifier for this object
rplaca
(rplaca this value)
replace the first element of this sequence with this value
rplacd
(rplacd this value)
replace the rest (but-first; cdr) of this sequence with this value
pretty-print
(pretty-print cell)
(pretty-print cell width level)
This isn’t the world’s best pretty printer but it sort of works.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
F
The canonical false value - different from NIL
, which is not canonically false in Lisp 1.5.
make-beowulf-list
(make-beowulf-list x)
Construct a linked list of cons cells with the same content as the sequence x
.
make-cons-cell
(make-cons-cell car cdr)
Construct a new instance of cons cell with this car
and cdr
.
MutableSequence
protocol
Like a sequence, but mutable.
members
getCar
(getCar this)
Return the first element of this sequence.
getCdr
(getCdr this)
like more
, q.v., but returns List NIL
not Clojure nil
when empty.
getUid
(getUid this)
Returns a unique identifier for this object
rplaca
(rplaca this value)
replace the first element of this sequence with this value
rplacd
(rplacd this value)
replace the rest (but-first; cdr) of this sequence with this value
pretty-print
(pretty-print cell)
(pretty-print cell width level)
This isn’t the world’s best pretty printer but it sort of works.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
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.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
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.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
AND
(AND & args)
T
if and only if none of my args
evaluate to either F
or NIL
, else F
.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
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.
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
(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.
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.
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))
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.
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))
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
lax?
(lax? symbol)
Are we in lax mode? If so. return true; is not, throw an exception with this symbol
.
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.
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.
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.
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)
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 u
s are atomic, and y
is any S-expression. What SUBLIS
does, is to treat the u
s as variables when they occur in y
, and to SUBSTitute the corresponding v
s 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.
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.
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)
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 u
s are atomic, and y
is any S-expression. What SUBLIS
does, is to treat the u
s as variables when they occur in y
, and to SUBSTitute the corresponding v
s 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
.
TRACE
(TRACE s)
Add this symbol s
to the set of symbols currently being traced. If s
is not a symbol, does nothing.
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
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
.
TRACE
(TRACE s)
Add this symbol s
to the set of symbols currently being traced. If s
is not a symbol, does nothing.
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
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.io
Non-standard extensions to Lisp 1.5 to read and write to the filesystem.
+Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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
.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.manual
Experimental code for accessing the manual online.
format-page-references
(format-page-references fn-symbol)
Format page references from the manual index for the function whose name is fn-symbol
.
index
This is data extracted from the index pages of Lisp 1.5 Programmer's Manual
. It’s here in the hope that we can automatically link to an online PDF link to the manual when the user invokes a function probably called DOC
or HELP
.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.oblist
A namespace mainly devoted to the object list and other top level global variables.
+Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.oblist
A namespace mainly devoted to the object list and other top level global variables.
Yes, this makes little sense, but if you put them anywhere else you end up in cyclic dependency hell.
NIL
The canonical empty list symbol.
TODO: this doesn’t really work, because (from Clojure) (empty? NIL)
throws an exception. It might be better to subclass beowulf.cons_cell.ConsCell to create a new singleton class Nil which overrides the empty
method of IPersistentCollection?
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
+Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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:
- It reads the meta-expression language
MEXPR
in addition to the symbolic expression languageSEXPR
, 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.
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.
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.
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.
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.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.reader.char-reader
Provide sensible line editing, auto completion, and history recall.
+Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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)
-
diff --git a/docs/codox/beowulf.reader.generate.html b/docs/codox/beowulf.reader.generate.html
index f5a8a7c..8b6f86b 100644
--- a/docs/codox/beowulf.reader.generate.html
+++ b/docs/codox/beowulf.reader.generate.html
@@ -1,6 +1,6 @@
-
- ADD1
- AND
- ASSOC
- ATOM
- ATOM?
- CAAAAR
- CAAADR
- CAAAR
- CAADAR
- CAADDR
- CAADR
- CAAR
- CADAAR
- CADADR
- CADAR
- CADDAR
- CADDDR
- CADDR
- CADR
- CAR
- CDAAAR
- CDAADR
- CDAAR
- CDADAR
- CDADDR
- CDADR
- CDAR
- CDDAAR
- CDDADR
- CDDAR
- CDDDAR
- CDDDDR
- CDDDR
- CDDR
- CDR
- CONS
- DEFINE
- DIFFERENCE
- EQ
- EQUAL
- ERROR
- FIXP
- GENSYM
- GREATERP
- lax?
- LESSP
- LIST
- NILP
- NULL
- NUMBERP
- OBLIST
- PAIRLIS
- PLUS
- QUOTIENT
- REMAINDER
- RPLACA
- RPLACD
- SET
- SUB1
- SUBLIS
- SUBST
- TIMES
- TRACE
- traced-symbols
- traced?
- uaf
- UNTRACE
- ADD1
- AND
- ASSOC
- ATOM
- ATOM?
- CAAAAR
- CAAADR
- CAAAR
- CAADAR
- CAADDR
- CAADR
- CAAR
- CADAAR
- CADADR
- CADAR
- CADDAR
- CADDDR
- CADDR
- CADR
- CAR
- CDAAAR
- CDAADR
- CDAAR
- CDADAR
- CDADDR
- CDADR
- CDAR
- CDDAAR
- CDDADR
- CDDAR
- CDDDAR
- CDDDDR
- CDDDR
- CDDR
- CDR
- CONS
- DEFINE
- DIFFERENCE
- EQ
- EQUAL
- ERROR
- FIXP
- GENSYM
- GREATERP
- lax?
- LESSP
- LIST
- NILP
- NULL
- NUMBERP
- OBLIST
- PAIRLIS
- PLUS
- QUOTIENT
- REMAINDER
- RPLACA
- RPLACD
- SET
- SUB1
- SUBLIS
- SUBST
- TIMES
- TRACE
- traced-symbols
- traced?
- uaf
- UNTRACE
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.reader.generate
Generating S-Expressions from parse trees.
+Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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 8ebe04e..e4dd087 100644 --- a/docs/codox/beowulf.reader.macros.html +++ b/docs/codox/beowulf.reader.macros.html @@ -1,5 +1,5 @@ -Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.reader.macros
Can I implement reader macros? let’s see!
+Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.reader.macros
Can I implement reader macros? let’s see!
We don’t need (at least, in the Clojure reader) to rewrite forms like 'FOO
, because that’s handled by the parser. But we do need to rewrite things which don’t evaluate their arguments, like SETQ
, because (unless LABEL does it, which I’m not yet sure of) we’re not yet able to implement things which don’t evaluate arguments.
TODO: at this stage, the following should probably also be read macros: DEFINE
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.reader.simplify
Simplify parse trees. Be aware that this is very tightly coupled with the parser.
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
.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.reader.simplify
Simplify parse trees. Be aware that this is very tightly coupled with the parser.
simplify
(simplify p)
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. Calls remove-optional-space
before processing.
simplify-tree
(simplify-tree p)
(simplify-tree 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-tree
.
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf.scratch
This namespace is for temporary functions and is intentionally excluded from Git.
accessor-symbol
(accessor-symbol l)
Generate a symbol by prepending C
and appending A
to this list of string fragments l
.
Generated by Codox
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..
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.
Public variables and functions:
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.
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.
Public variables and functions:
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 and other top level global variables.
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.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:
Generated by Codox
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..
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.
Public variables and functions:
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.
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.
Public variables and functions:
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 and other top level global variables.
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.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.scratch
This namespace is for temporary functions and is intentionally excluded from Git.
Public variables and functions:
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
beowulf
+Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
@@ -30,330 +30,601 @@Symbol | +Function | Type | Signature | +Implementation | Documentation | ||
---|---|---|---|---|---|---|---|
NIL | -? | -null | +Lisp variable | ++ | ? | ||
T | -? | -null | +Lisp variable | ++ | ? | ||
F | -? | -null | +Lisp variable | ++ | ? | ||
ADD1 | Host function | -([x]) | +(ADD1 X) | +? | |||
AND | Host function | -([& args]) | +(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 | -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. |
+ Lisp function | +(APPEND X Y) | +LAMBDA-fn | +see manual pages 11, 61 |
APPLY | Host function | -([function args environment depth]) | +(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 | -([x]) | +(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 | ? | -null | +||
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 | -? | -null | -? | +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 | -? | -null | -? | +Host function | +(CONS CAR CDR) | ++ | Construct a new instance of cons cell with this car and cdr . |
COPY | Lisp function | -(X) | -? | +(COPY X) | +LAMBDA-fn | +see manual pages 62 | |
DEFINE | Host function | -([args]) | +(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 | -([x y]) | +(DIFFERENCE X Y) | +? | |||
DIVIDE | Lisp function | -(X Y) | -? | +(DIVIDE X Y) | +LAMBDA-fn | +see manual pages 26, 64 | |
ERROR | Host function | -([& args]) | +(ERROR & ARGS) | +PSEUDO-FUNCTION | Throw an error | ||
EQ | Host function | -([x y]) | +(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 | -([x y]) | +(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 | -([expr] [expr env depth]) | +(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 | -([x]) | +(FIXP X) | +PREDICATE | ? | ||
GENSYM | Host function | -([]) | +(GENSYM ) | +Generate a unique symbol. | |||
GET | Lisp function | -(X Y) | -? | +(GET X Y) | +LAMBDA-fn | +see manual pages 41, 59 | |
GREATERP | Host function | -([x y]) | +(GREATERP X Y) | +PREDICATE | ? | ||
INTEROP | Host function | -([fn-symbol args]) | +(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 | -(X Y) | +(INTERSECTION X Y) | +LAMBDA-fn | ? | ||
LENGTH | Lisp function | -(L) | -? | +(LENGTH L) | +LAMBDA-fn | +see manual pages 62 | |
LESSP | Host function | -([x y]) | +(LESSP X Y) | +PREDICATE | ? | ||
MEMBER | Lisp function | -(A X) | -? | +(MEMBER A X) | +LAMBDA-fn | +see manual pages 11, 62 | |
MINUSP | Lisp function | -(X) | -? | +(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 | -(X) | -? | +(NULL X) | +LAMBDA-fn | +see manual pages 11, 57 | |
NUMBERP | Host function | -([x]) | +(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 | -(X) | -? | +(ONEP X) | +LAMBDA-fn | +see manual pages 26, 64 | |
PAIR | Lisp function | -(X Y) | -? | +(PAIR X Y) | +LAMBDA-fn | +see manual pages 60 | |
PLUS | Host function | -([& args]) | +(PLUS & ARGS) | +? | |||
PRETTY | -? | -null | +Lisp variable | ++ | (PRETTY) | ? | |
? | -null | +Lisp variable | ++ | PSEUDO-FUNCTION | ? | ||
PROP | Lisp function | -(X Y U) | -? | +(PROP X Y U) | +LAMBDA-fn | +see manual pages 59 | |
QUOTIENT | Host function | -([x y]) | +(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 | -([] [input]) | +(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 | -([x y]) | +(REMAINDER X Y) | +? | |||
REPEAT | Lisp function | -(N X) | +(REPEAT N X) | +LAMBDA-fn | ? | ||
RPLACA | Host function | -([cell value]) | +(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 | -([cell value]) | +(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 | -([symbol val]) | +(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 | -(N) | -? | +(SUB1 N) | +LAMBDA-fn | +see manual pages 26, 64 | |
SYSIN | Host function | -([filename]) | +(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 | -([] [filepath]) | +(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 | -? | -null | +Lisp variable | ++ | PSEUDO-FUNCTION | ? | |
TIMES | Host function | -([& args]) | +(TIMES & ARGS) | +? | |||
TRACE | -? | -null | -? | +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 | -? | -null | +Host function | +(UNTRACE S) | +PSEUDO-FUNCTION | ? | |
ZEROP | Lisp function | -(N) | -? | +(ZEROP N) | +LAMBDA-fn | +see manual pages 26, 64 |
Generated by Codox
Beowulf 0.3.0-SNAPSHOT
M-Expressions
+Generated by Codox
Beowulf 0.3.0-SNAPSHOT
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.
diff --git a/project.clj b/project.clj index a626c21..0989300 100644 --- a/project.clj +++ b/project.clj @@ -9,9 +9,9 @@ :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"] + [org.clojure/math.combinatorics "0.2.0"] ;; not needed in production builds [org.clojure/math.numeric-tower "0.0.5"] [org.clojure/tools.cli "1.0.214"] - [org.clojure/tools.trace "0.7.11"] [clojure.java-time "1.2.0"] [environ "1.2.0"] [instaparse "1.4.12"] @@ -22,7 +22,9 @@ :plugins [[lein-cloverage "1.2.2"] [lein-codox "0.10.7"] [lein-environ "1.1.0"]] - :profiles {:uberjar {:aot :all}} + :profiles {:uberjar {:aot :all + :omit-source true + :uberjar-exclusions [#"beowulf\.scratch"]}} :release-tasks [["vcs" "assert-committed"] ["change" "version" "leiningen.release/bump-version" "release"] ["vcs" "commit"] @@ -34,5 +36,4 @@ ["vcs" "commit"]] :target-path "target/%s" - :url "https://github.com/simon-brooke/the-great-game" - ) + :url "https://github.com/simon-brooke/the-great-game") diff --git a/resources/lisp1.5.lsp b/resources/lisp1.5.lsp index 2d4966e..13b90aa 100644 --- a/resources/lisp1.5.lsp +++ b/resources/lisp1.5.lsp @@ -13,6 +13,34 @@ (APPLY) (ATOM) (CAR) + (CAAAAR LAMBDA (X) (CAR (CAR (CAR (CAR X))))) + (CAAADR LAMBDA (X) (CAR (CAR (CAR (CDR X))))) + (CAAAR LAMBDA (X) (CAR (CAR (CAR X)))) + (CAADAR LAMBDA (X) (CAR (CAR (CDR (CAR X))))) + (CAADDR LAMBDA (X) (CAR (CAR (CDR (CDR X))))) + (CAADR LAMBDA (X) (CAR (CAR (CDR X)))) + (CAAR LAMBDA (X) (CAR (CAR X))) + (CADAAR LAMBDA (X) (CAR (CDR (CAR (CAR X))))) + (CADADR LAMBDA (X) (CAR (CDR (CAR (CDR X))))) + (CADAR LAMBDA (X) (CAR (CDR (CAR X)))) + (CADDAR LAMBDA (X) (CAR (CDR (CDR (CAR X))))) + (CADDDR LAMBDA (X) (CAR (CDR (CDR (CDR X))))) + (CADDR LAMBDA (X) (CAR (CDR (CDR X)))) + (CADR LAMBDA (X) (CAR (CDR X))) + (CDAAAR LAMBDA (X) (CDR (CAR (CAR (CAR X))))) + (CDAADR LAMBDA (X) (CDR (CAR (CAR (CDR X))))) + (CDAAR LAMBDA (X) (CDR (CAR (CAR X)))) + (CDADAR LAMBDA (X) (CDR (CAR (CDR (CAR X))))) + (CDADDR LAMBDA (X) (CDR (CAR (CDR (CDR X))))) + (CDADR LAMBDA (X) (CDR (CAR (CDR X)))) + (CDAR LAMBDA (X) (CDR (CAR X))) + (CDDAAR LAMBDA (X) (CDR (CDR (CAR (CAR X))))) + (CDDADR LAMBDA (X) (CDR (CDR (CAR (CDR X))))) + (CDDAR LAMBDA (X) (CDR (CDR (CAR X)))) + (CDDDAR LAMBDA (X) (CDR (CDR (CDR (CAR X))))) + (CDDDDR LAMBDA (X) (CDR (CDR (CDR (CDR X))))) + (CDDDR LAMBDA (X) (CDR (CDR (CDR X)))) + (CDDR LAMBDA (X) (CDR (CDR X))) (CDR) (CONS) (COPY @@ -79,6 +107,7 @@ (COND ((NULL X) (U)) ((EQ (CAR X) Y) (CDR X)) ((QUOTE T) (PROP (CDR X) Y U)))) (QUOTIENT) + (RANGE LAMBDA (N M) (COND ((LESSP M N) (QUOTE NIL)) ((QUOTE T) (CONS N (RANGE (ADD1 N) M))))) (READ) (REMAINDER) (REPEAT diff --git a/resources/mexpr/range.mexpr.lsp b/resources/mexpr/range.mexpr.lsp new file mode 100644 index 0000000..2e84d4f --- /dev/null +++ b/resources/mexpr/range.mexpr.lsp @@ -0,0 +1,3 @@ +;; this isn't a standard Lisp 1.5 function + +range[n; m] = [lessp[m; n] -> NIL; T -> cons[n; range[add1[n]; m]]] \ No newline at end of file diff --git a/src/beowulf/bootstrap.clj b/src/beowulf/bootstrap.clj index 78729e7..08f4864 100644 --- a/src/beowulf/bootstrap.clj +++ b/src/beowulf/bootstrap.clj @@ -249,7 +249,7 @@ (case function-symbol ;; there must be a better way of doing this! ADD1 (safe-apply ADD1 args) AND (safe-apply AND args) - APPLY (safe-apply APPLY args) ;; TODO: need to pass the environment and depth + APPLY (APPLY (first args) (rest args) environment depth) ;; TODO: need to pass the environment and depth ATOM (ATOM? (CAR args)) CAR (safe-apply CAR args) CDR (safe-apply CDR args) diff --git a/src/beowulf/gendoc.clj b/src/beowulf/gendoc.clj index 2988327..def8b58 100644 --- a/src/beowulf/gendoc.clj +++ b/src/beowulf/gendoc.clj @@ -4,7 +4,10 @@ NOTE: this is *very* hacky. You almost certainly do not want to use this!" (:require [beowulf.io :refer [default-sysout SYSIN]] - [beowulf.oblist :refer [oblist]] + [beowulf.host :refer [ASSOC]] + [beowulf.manual :refer [format-page-references index *manual-url*]] + [beowulf.oblist :refer [NIL oblist]] + [clojure.java.browse :refer [browse-url]] [clojure.string :refer [join replace upper-case]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -95,34 +98,56 @@ (= (second entry) 'LAMBDA) (str (cons (first entry) (nth entry 2))) :else "?")) +(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)))) + (defn find-documentation "Find appropriate documentation for this `entry` from the oblist." [entry] - (cond - (= (count entry) 1) (if-let [doc (get-metadata-for-entry entry :doc)] - (replace doc "\n" " ") - "?") - :else "?")) + (let [k (keyword (first entry))] + (cond + (= (count entry) 1) (if-let [doc (get-metadata-for-entry entry :doc)] + (replace doc "\n" " ") + "?") + (k index) (str "see manual pages " (format-page-references k)) + :else "?"))) (defn gen-doc-table ([] (gen-doc-table default-sysout)) ([sysfile] - (try (SYSIN sysfile) - (catch Throwable any - (println (.getMessage any) " while reading " sysfile))) + (when (= NIL @oblist) + (try (SYSIN sysfile) + (catch Throwable any + (println (.getMessage any) " while reading " sysfile)))) (join "\n" (doall (concat - '("| Symbol | Type | Signature | Documentation |" - "|--------|------|-----------|---------------|") + '("| Function | Type | Signature | Implementation | Documentation |" + "|--------------|----------------|------------------|----------------|----------------------|") (map - #(format "| %s | %s | %s | %s |" + #(format "| %-12s | %-14s | %-16s | %-14s | %-20s |" (first %) (infer-type %) (infer-signature %) + (infer-implementation %) (find-documentation %)) @oblist)))))) -;; (println (gen-doc-table)) \ No newline at end of file +(defn gen-index + ([] (gen-index "" "resources/scratch/manual.md")) + ([url destination] + (binding [*manual-url* url] + (spit destination + (with-out-str + (doall + (map + println + (list "## Index" + "" + (gen-doc-table))))))))) \ No newline at end of file diff --git a/src/beowulf/host.clj b/src/beowulf/host.clj index 46ea8db..8600faa 100644 --- a/src/beowulf/host.clj +++ b/src/beowulf/host.clj @@ -389,11 +389,11 @@ (defn LESSP [x y] - (< x y)) + (if (< x y) T F)) (defn GREATERP [x y] - (> x y)) + (if (> x y) T F)) ;;;; Miscellaneous ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -405,8 +405,11 @@ (defn ERROR "Throw an error" [& args] - (throw (ex-info "LISP ERROR" {:cause (apply vector args) - :phase :eval}))) + (throw (ex-info "LISP ERROR" {:args args + :phase :eval + :function 'ERROR + :type :lisp + :code (or (first args) 'A1)}))) ;;;; Assignment and the object list ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/beowulf/manual.clj b/src/beowulf/manual.clj new file mode 100644 index 0000000..8a36fe5 --- /dev/null +++ b/src/beowulf/manual.clj @@ -0,0 +1,769 @@ +(ns beowulf.manual + "Experimental code for accessing the manual online." + (:require [clojure.string :refer [ends-with? join trim]])) + +(def ^:dynamic *manual-url* + "https://www.softwarepreservation.org/projects/LISP/book/LISP%201.5%20Programmers%20Manual.pdf") + +(def ^:constant index + "This is data extracted from the index pages of `Lisp 1.5 Programmer's Manual`. + It's here in the hope that we can automatically link to an online PDF link + to the manual when the user invokes a function probably called `DOC` or `HELP`." + {:RECIP + {:fn-name "RECIP", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :QUOTE + {:fn-name "QUOTE", + :call-type "FSUBR", + :implementation "", + :page-nos ["10" "22" "71"]}, + :RECLAIM + {:fn-name "RECLAIM", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["67"]}, + :NUMOB + {:fn-name "NUMOB", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["86"]}, + :EVLIS + {:fn-name "EVLIS", + :call-type "SUBR", + :implementation "", + :page-nos ["71"]}, + :DASH + {:fn-name "DASH", + :call-type "SUBR", + :implementation "PREDICATE APVAL", + :page-nos ["85" "87 "]}, + :EQUAL + {:fn-name "EQUAL", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["11" "26" "57"]}, + :PRIN1 + {:fn-name "PRIN1", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["65" "84"]}, + :REMFLAG + {:fn-name "REMFLAG", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["41" "60"]}, + :DEFINE + {:fn-name "DEFINE", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["15" "18" "58"]}, + :PUNCHLAP + {:fn-name "PUNCHLAP", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION LIBRARY", + :page-nos ["68" "76"]}, + :STARTREAD + {:fn-name "STARTREAD", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["87"]}, + :PERIOD + {:fn-name "PERIOD", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :CP1 + {:fn-name "CP1", + :call-type "SUBR", + :implementation "", + :page-nos ["66"]}, + :NCONC + {:fn-name "NCONC", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["62"]}, + :EQ + {:fn-name "EQ", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["3" "23" "57"]}, + :RPLACD + {:fn-name "RPLACD", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["41" "58"]}, + :PROG2 + {:fn-name "PROG2", + :call-type "SUBR", + :implementation "", + :page-nos ["42" "66"]}, + :UNCOUNT + {:fn-name "UNCOUNT", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["34" "66"]}, + :ERROR1 + {:fn-name "ERROR1", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["88"]}, + :EXPT + {:fn-name "EXPT", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :NOT + {:fn-name "NOT", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["21" "23" "58"]}, + :SLASH + {:fn-name "SLASH", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :RPLACA + {:fn-name "RPLACA", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["41" "58"]}, + :QUOTIENT + {:fn-name "QUOTIENT", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :UNPACK + {:fn-name "UNPACK", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["87"]}, + :CONC + {:fn-name "CONC", + :call-type "FEXPR", + :implementation "", + :page-nos ["61"]}, + :CAR + {:fn-name "CAR", + :call-type "SUBR", + :implementation "", + :page-nos ["2" "56"]}, + :GENSYM + {:fn-name "GENSYM", + :call-type "SUBR", + :implementation "", + :page-nos ["66"]}, + :PROP + {:fn-name "PROP", + :call-type "SUBR", + :implementation "FUNCTIONAL ", + :page-nos [" 59"]}, + :MEMBER + {:fn-name "MEMBER", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["11" "62"]}, + :UNTRACESET + {:fn-name "UNTRACESET", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["68"]}, + :UNTRACE + {:fn-name "UNTRACE", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["32" "66"]}, + :MINUSP + {:fn-name "MINUSP", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["26" "64"]}, + :F + {:fn-name "F", + :call-type "APVAL", + :implementation "", + :page-nos ["22" "69"]}, + :SPECIAL + {:fn-name "SPECIAL", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["64" "78"]}, + :LPAR + {:fn-name "LPAR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :GO + {:fn-name "GO", + :call-type "FSUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["30" "72"]}, + :MKNAM + {:fn-name "MKNAM", + :call-type "SUBR", + :implementation "", + :page-nos ["86"]}, + :COMMON + {:fn-name "COMMON", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["64" "78"]}, + :NUMBERP + {:fn-name "NUMBERP", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["26" "64"]}, + :CONS + {:fn-name "CONS", + :call-type "SUBR", + :implementation "", + :page-nos ["2" "56"]}, + :PLUS + {:fn-name "PLUS", + :call-type "FSUBR", + :implementation "", + :page-nos ["25" "63"]}, + :SET + {:fn-name "SET", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["30" "71"]}, + :DOLLAR + {:fn-name "DOLLAR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :SASSOC + {:fn-name "SASSOC", + :call-type "SUBR", + :implementation "FUNCTIONAL", + :page-nos ["60"]}, + :SELECT + {:fn-name "SELECT", + :call-type "FEXPR", + :implementation "", + :page-nos ["66"]}, + :OPDEFINE + {:fn-name "OPDEFINE", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["65" "75"]}, + :PAUSE + {:fn-name "PAUSE", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67"]}, + :AND + {:fn-name "AND", + :call-type "FSUBR", + :implementation "PREDICATE", + :page-nos ["21" "58"]}, + :COMMA + {:fn-name "COMMA", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :EFFACE + {:fn-name "EFFACE", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["63"]}, + :CSETQ + {:fn-name "CSETQ", + :call-type "FEXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["59"]}, + :OPCHAR + {:fn-name "OPCHAR", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos [" 87"]}, + :PRINTPROP + {:fn-name "PRINTPROP", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION LIBRARY ", + :page-nos ["68"]}, + :PLB + {:fn-name "PLB", + :call-type "SUBR", + :implementation "PSEUDO- FUNCTION", + :page-nos ["67"]}, + :DIGIT + {:fn-name "DIGIT", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["87"]}, + :PUNCHDEF + {:fn-name "PUNCHDEF", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION LIBRARY", + :page-nos ["68"]}, + :ARRAY + {:fn-name "ARRAY", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["27" "64"]}, + :MAX + {:fn-name "MAX", + :call-type "FSUBR", + :implementation "", + :page-nos ["26" "64"]}, + :INTERN + {:fn-name "INTERN", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67" "87"]}, + :NIL + {:fn-name "NIL", + :call-type "APVAL", + :implementation "", + :page-nos ["22" "69"]}, + :TIMES + {:fn-name "TIMES", + :call-type "FSUBR", + :implementation "", + :page-nos ["26" "64"]}, + :ERROR + {:fn-name "ERROR", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["32" "66"]}, + :PUNCH + {:fn-name "PUNCH", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["65" "84"]}, + :REMPROP + {:fn-name "REMPROP", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["41" "59"]}, + :DIVIDE + {:fn-name "DIVIDE", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :OR + {:fn-name "OR", + :call-type "FSUBR", + :implementation "PREDICATE ", + :page-nos ["21" "58"]}, + :SUBLIS + {:fn-name "SUBLIS", + :call-type "SUBR", + :implementation "", + :page-nos ["12" "61"]}, + :LAP + {:fn-name "LAP", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["65" "73"]}, + :PROG + {:fn-name "PROG", + :call-type "FSUBR", + :implementation "", + :page-nos ["29" "71"]}, + :T + {:fn-name "T", + :call-type "APVAL", + :implementation "", + :page-nos ["22" "69"]}, + :GREATERP + {:fn-name "GREATERP", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["26" "64"]}, + :CSET + {:fn-name "CSET", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["17" "59"]}, + :FUNCTION + {:fn-name "FUNCTION", + :call-type "FSUBR", + :implementation "", + :page-nos ["21" "71"]}, + :LENGTH + {:fn-name "LENGTH", + :call-type "SUBR", + :implementation "", + :page-nos ["62"]}, + :MINUS + {:fn-name "MINUS", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "63"]}, + :COND + {:fn-name "COND", + :call-type "FSUBR", + :implementation "", + :page-nos ["18"]}, + :APPEND + {:fn-name "APPEND", + :call-type "SUBR", + :implementation "", + :page-nos ["11" "61"]}, + :CDR + {:fn-name "CDR", + :call-type "SUBR", + :implementation "", + :page-nos ["3" "56"]}, + :OBLIST + {:fn-name "OBLIST", + :call-type "APVAL", + :implementation "", + :page-nos ["69"]}, + :READ + {:fn-name "READ", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["5" "84"]}, + :ERRORSET + {:fn-name "ERRORSET", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["35" "66"]}, + :UNCOMMON + {:fn-name "UNCOMMON", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["64" "78"]}, + :EVAL + {:fn-name "EVAL", + :call-type "SUBR", + :implementation "", + :page-nos ["71"]}, + :MIN + {:fn-name "MIN", + :call-type "FSUBR", + :implementation "", + :page-nos ["26" "64"]}, + :PAIR + {:fn-name "PAIR", + :call-type "SUBR", + :implementation "", + :page-nos ["60"]}, + :BLANK + {:fn-name "BLANK", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :SETQ + {:fn-name "SETQ", + :call-type "FSUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["30" "71"]}, + :GET + {:fn-name "GET", + :call-type "SUBR", + :implementation "", + :page-nos ["41" "59"]}, + :PRINT + {:fn-name "PRINT", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["65" "84"]}, + :ENDREAD + {:fn-name "ENDREAD", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["8 8"]}, + :RETURN + {:fn-name "RETURN", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["30" "72"]}, + :LITER + {:fn-name "LITER", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["87"]}, + :EOF + {:fn-name "EOF", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "88"]}, + :TRACE + {:fn-name "TRACE", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["32" "66" "79"]}, + :TRACESET + {:fn-name "TRACESET", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION LIBRARY", + :page-nos ["68"]}, + :PACK + {:fn-name "PACK", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["86"]}, + :NULL + {:fn-name "NULL", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["11" "57"]}, + :CLEARBUFF + {:fn-name "CLEARBUFF", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["86"]}, + :LESSP + {:fn-name "LESSP", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos ["26" "64"]}, + :TERPRI + {:fn-name "TERPRI", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["65" "84"]}, + :ONEP + {:fn-name "ONEP", + :call-type "SUBR", + :implementation "PREDICATE ", + :page-nos [" 26" "64"]}, + :EXCISE + {:fn-name "EXCISE", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67" "77"]}, + :REMOB + {:fn-name "REMOB", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["67"]}, + :MAP + {:fn-name "MAP", + :call-type "SUBR", + :implementation "FUNCTIONAL ", + :page-nos ["63"]}, + :COMPILE + {:fn-name "COMPILE", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["64" "76"]}, + :ADD1 + {:fn-name "ADD1", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :ADVANCE + {:fn-name "ADVANCE", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["88"]}, + :SEARCH + {:fn-name "SEARCH", + :call-type "SUBR", + :implementation "FUNCTIONAL", + :page-nos ["63"]}, + :APPLY + {:fn-name "APPLY", + :call-type "SUBR", + :implementation "", + :page-nos ["70"]}, + :READLAP + {:fn-name "READLAP", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION ", + :page-nos ["65" "76"]}, + :UNSPECIAL + {:fn-name "UNSPECIAL", + :call-type "SUBR", + :implementation "", + :page-nos ["64" "78"]}, + :SUBST + {:fn-name "SUBST", + :call-type "SUBR", + :implementation "", + :page-nos ["11" "61"]}, + :COPY + {:fn-name "COPY", + :call-type "SUBR", + :implementation "", + :page-nos ["62"]}, + :LOGOR + {:fn-name "LOGOR", + :call-type "FSUBR", + :implementation "", + :page-nos ["26" "64"]}, + :LABEL + {:fn-name "LABEL", + :call-type "FSUBR", + :implementation "", + :page-nos ["8" "18" "70"]}, + :FIXP + {:fn-name "FIXP", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["26" "64"]}, + :SUB1 + {:fn-name "SUB1", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :ATTRIB + {:fn-name "ATTRIB", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["59"]}, + :DIFFERENCE + {:fn-name "DIFFERENCE", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :REMAINDER + {:fn-name "REMAINDER", + :call-type "SUBR", + :implementation "", + :page-nos ["26" "64"]}, + :REVERSE + {:fn-name "REVERSE", + :call-type "SUBR", + :implementation "", + :page-nos ["6 2"]}, + :EOR + {:fn-name "EOR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "88"]}, + :PLUSS + {:fn-name "PLUSS", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :TEMPUS-FUGIT + {:fn-name "TEMPUS-FUGIT", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67"]}, + :LOAD + {:fn-name "LOAD", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67"]}, + :CHARCOUNT + {:fn-name "CHARCOUNT", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "87"]}, + :RPAR + {:fn-name "RPAR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :COUNT + {:fn-name "COUNT", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["34" "66"]}, + :SPEAK + {:fn-name "SPEAK", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["34" "66 "]}, + :LOGXOR + {:fn-name "LOGXOR", + :call-type "FSUBR", + :implementation "", + :page-nos ["27" "64"]}, + :FLOATP + {:fn-name "FLOATP", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["26" "64"]}, + :ATOM + {:fn-name "ATOM", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["3" "57"]}, + :EQSIGN + {:fn-name "EQSIGN", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :LIST + {:fn-name "LIST", + :call-type "FSUBR", + :implementation "", + :page-nos ["57"]}, + :MAPLIST + {:fn-name "MAPLIST", + :call-type "SUBR", + :implementation "FUNCTIONAL ", + :page-nos ["20" "21" "63"]}, + :LOGAND + {:fn-name "LOGAND", + :call-type "FSUBR", + :implementation "", + :page-nos ["27" "64"]}, + :FLAG + {:fn-name "FLAG", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["41" "60"]}, + :MAPCON + {:fn-name "MAPCON", + :call-type "SUBR", + :implementation "FUNCTIONAL PSEUDO- FUNCTION", + :page-nos ["63"]}, + :STAR + {:fn-name "STAR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "85"]}, + :CURCHAR + {:fn-name "CURCHAR", + :call-type "APVAL", + :implementation "", + :page-nos ["69" "87"]}, + :DUMP + {:fn-name "DUMP", + :call-type "SUBR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["67"]}, + :DEFLIST + {:fn-name "DEFLIST", + :call-type "EXPR", + :implementation "PSEUDO-FUNCTION", + :page-nos ["41" "58"]}, + :LEFTSHIFT + {:fn-name "LEFTSHIFT", + :call-type "SUBR", + :implementation "", + :page-nos ["27" "64"]}, + :ZEROP + {:fn-name "ZEROP", + :call-type "SUBR", + :implementation "PREDICATE", + :page-nos ["26" "64"]}}) + +(defn page-url + "Format the URL for the page in the manual with this `page-no`." + [page-no] + (let [n (read-string page-no) + n' (when (and (number? n) + (ends-with? *manual-url* ".pdf")) + ;; annoyingly, the manual has eight pages of front-matter + ;; before numbering starts. + (+ n 8))] + (format + (if (ends-with? *manual-url* ".pdf") "%s#page=%s" "%s#page%s") + *manual-url* + (or n' (trim (str page-no)))))) + +(defn format-page-references + "Format page references from the manual index for the function whose name + is `fn-symbol`." + [fn-symbol] + (let [k (if (keyword? fn-symbol) fn-symbol (keyword fn-symbol))] + (join ", " + (doall + (map + (fn [n] + (let [p (trim n)] + (format "%s" + (page-url p) p))) + (:page-nos (index k))))))) \ No newline at end of file diff --git a/src/beowulf/read.clj b/src/beowulf/read.clj index 4afbccf..39abf1d 100644 --- a/src/beowulf/read.clj +++ b/src/beowulf/read.clj @@ -16,7 +16,7 @@ (:require [beowulf.reader.char-reader :refer [read-chars]] [beowulf.reader.generate :refer [generate]] [beowulf.reader.parser :refer [parse]] - [beowulf.reader.simplify :refer [remove-optional-space simplify]] + [beowulf.reader.simplify :refer [simplify]] [clojure.string :refer [join split starts-with? trim]]) (:import [java.io InputStream] [instaparse.gll Failure])) @@ -80,15 +80,17 @@ (if (instance? Failure parse-tree) (doall (println (number-lines source parse-tree)) (throw (ex-info "Parse failed" (assoc parse-tree :source source)))) - (generate (simplify (remove-optional-space parse-tree)))))) + (generate (simplify parse-tree))))) (defn 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." [] (loop [r (read-line)] - (if (= (count (re-seq #"\(" r)) + (if (and (= (count (re-seq #"\(" r)) (count (re-seq #"\)" r))) + (= (count (re-seq #"\[" r)) + (count (re-seq #"\]" r)))) r (recur (str r "\n" (read-line)))))) diff --git a/src/beowulf/reader/macros.clj b/src/beowulf/reader/macros.clj index 051b1d1..0ef1907 100644 --- a/src/beowulf/reader/macros.clj +++ b/src/beowulf/reader/macros.clj @@ -47,8 +47,8 @@ (def ^:dynamic *readmacros* {:car {'DEFUN (fn [f] (LIST 'SET (LIST 'QUOTE (second f)) - (CONS 'LAMBDA (rest (rest f))))) - 'SETQ (fn [f] (LIST 'SET (LIST 'QUOTE (second f)) (nth f 2)))}}) + (LIST 'QUOTE (CONS 'LAMBDA (rest (rest f)))))) + 'SETQ (fn [f] (LIST 'SET (LIST 'QUOTE (second f)) (LIST 'QUOTE (nth f 2))))}}) (defn expand-macros [form] diff --git a/src/beowulf/reader/parser.clj b/src/beowulf/reader/parser.clj index 1441c2f..082ecde 100644 --- a/src/beowulf/reader/parser.clj +++ b/src/beowulf/reader/parser.clj @@ -60,8 +60,8 @@ arrow := '->'; args := mexpr | (opt-space mexpr semi-colon opt-space)* opt-space mexpr opt-space; fn-name := mvar; - mvar := #'[a-z]+'; - mconst := #'[A-Z]+'; + mvar := #'[a-z][a-z0-9]*'; + mconst := #'[A-Z][A-Z0-9]*'; semi-colon := ';';" ;; Infix operators appear in mexprs, e.g. on page 7. Ooops! diff --git a/src/beowulf/reader/simplify.clj b/src/beowulf/reader/simplify.clj index 52f1dc2..fdfa3c7 100644 --- a/src/beowulf/reader/simplify.clj +++ b/src/beowulf/reader/simplify.clj @@ -25,7 +25,7 @@ ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(declare simplify) +(declare simplify-tree) (defn remove-optional-space [tree] @@ -48,17 +48,17 @@ (loop [r tree'] (if (and r (vector? r) (keyword? (first r))) (if (= (first r) key) - (recur (simplify (second r) context)) + (recur (simplify-tree (second r) context)) r) r)) tree'))) -(defn simplify +(defn simplify-tree "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`." + parse tree **BEFORE** it is passed to `simplify-tree`." ([p] (if (instance? Failure p) @@ -67,7 +67,7 @@ {:cause :parse-failure :phase :simplify :failure p})) - (simplify p :expr))) + (simplify-tree p :expr))) ([p context] (cond (string? p) p @@ -78,8 +78,8 @@ (case (first p) (:λexpr :args :bindings :body :cond :cond-clause :defn :dot-terminal - :fncall :lhs :quoted-expr :rhs ) (map #(simplify % context) p) - (:arg :expr :coefficient :fn-name :number) (simplify (second p) context) + :fncall :lhs :quoted-expr :rhs ) (map #(simplify-tree % context) p) + (:arg :expr :coefficient :fn-name :number) (simplify-tree (second p) context) (:arrow :dot :e :lpar :lsqb :opt-comment :opt-space :q :quote :rpar :rsqb :semi-colon :sep :space) nil :atom (if @@ -97,28 +97,35 @@ [:fncall [:mvar "cons"] [:args - (simplify (nth p 1) context) - (simplify (nth p 2) context)]] - (map #(simplify % context) p)) - :iexp (simplify (second p) context) + (simplify-tree (nth p 1) context) + (simplify-tree (nth p 2) context)]] + (map #(simplify-tree % context) p)) + :iexp (simplify-tree (second p) context) :iexpr [:iexpr - [:lhs (simplify (second p) context)] - (simplify (nth p 2) context) ;; really should be the operator - [:rhs (simplify (nth p 3) context)]] + [:lhs (simplify-tree (second p) context)] + (simplify-tree (nth p 2) context) ;; really should be the operator + [:rhs (simplify-tree (nth p 3) context)]] :mexpr (if (:strict *options*) (throw (ex-info "Cannot parse meta expressions in strict mode" {:cause :strict})) - (simplify (second p) :mexpr)) + (simplify-tree (second p) :mexpr)) :list (if (= context :mexpr) [:fncall [:mvar "list"] - [:args (apply vector (map simplify (rest p)))]] - (map #(simplify % context) p)) - :raw (first (remove empty? (map simplify (rest p)))) - :sexpr (simplify (second p) :sexpr) + [:args (apply vector (map simplify-tree (rest p)))]] + (map #(simplify-tree % context) p)) + :raw (first (remove empty? (map simplify-tree (rest p)))) + :sexpr (simplify-tree (second p) :sexpr) ;;default p))) :else p))) + +(defn simplify + "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. Calls + `remove-optional-space` before processing." + [p] + (simplify-tree (remove-optional-space p))) \ No newline at end of file diff --git a/test/beowulf/mexpr_test.clj b/test/beowulf/mexpr_test.clj index 1c38145..719d9e1 100644 --- a/test/beowulf/mexpr_test.clj +++ b/test/beowulf/mexpr_test.clj @@ -6,7 +6,7 @@ [beowulf.read :refer [gsp]] [beowulf.reader.generate :refer [generate]] [beowulf.reader.parser :refer [parse]] - [beowulf.reader.simplify :refer [simplify]])) + [beowulf.reader.simplify :refer [simplify-tree]])) ;; These tests are taken generally from the examples on page 10 of ;; Lisp 1.5 Programmers Manual: @@ -74,7 +74,7 @@ (let [expected "(LABEL FF (LAMBDA (X) (COND ((ATOM X) X) ((QUOTE T) (FF (CAR X))))))" actual (print-str (generate - (simplify + (simplify-tree (parse "label[ff;λ[[x];[atom[x]->x; T->ff[car[x]]]]]"))))] (is (= actual expected))))) diff --git a/test/beowulf/reader_macro_test.clj b/test/beowulf/reader_macro_test.clj index 228a6a9..0f94111 100644 --- a/test/beowulf/reader_macro_test.clj +++ b/test/beowulf/reader_macro_test.clj @@ -5,7 +5,7 @@ (deftest macro-expansion (testing "Expanding DEFUN" - (let [expected "(SET (QUOTE FACT) (LAMBDA (X) (COND ((ZEROP X) 1) (T (TIMES X (FACT (SUB1 X)))))))" + (let [expected "(SET (QUOTE FACT) (QUOTE (LAMBDA (X) (COND ((ZEROP X) 1) (T (TIMES X (FACT (SUB1 X))))))))" source "(DEFUN FACT (X) (COND ((ZEROP X) 1) (T (TIMES X (FACT (SUB1 X))))))" actual (print-str (gsp source))] (is (= actual expected))))) \ No newline at end of file