Doing something old with something new (2012 – 2017, 2020)
- Common Lisp 51.6%
- Go 46%
- Makefile 0.5%
- OMNeT++ MSG 0.5%
- NewLisp 0.5%
- Other 0.9%
| _morgue | ||
| call | ||
| dep | ||
| doc | ||
| eval | ||
| fun | ||
| hlp | ||
| l | ||
| lio | ||
| lob | ||
| msg | ||
| num | ||
| odep | ||
| olu | ||
| pre | ||
| regtests | ||
| scripts | ||
| sys | ||
| .gitignore | ||
| INSTALL | ||
| LICENSE | ||
| lin.go | ||
| lingo.png | ||
| Makefile | ||
| README | ||
| run-tests.lisp | ||
-*- text -*-
This is the source code for a Lisp in Go ("lingo"), intended to get
me some more practice with Go and for toying around with a simple
Lisp interpreter. It is still (and will likely ever be) work in
progress and mostly undocumented.
lingo is copyrighted code. See the file LICENSE for details. It uses
(but does not include) the separately, but similarly licensed input
line editor "Liner" by Peter Harris; see
<https://github.com/peterh/liner>. The code is pulled in using "go
get".
See the file INSTALL for installation instructions.
If you want to find your way around it, see pre/*.lisp (code
incorporated into the interpreter executable and loaded on startup)
and the regtests/ and l/ directories for some Lisp code that works
with lingo. See DOCSTRINGS.txt (generated by the build) for
information on the builtin functions; if necessary, look at the
source in fun/builtins-*.go. In the interpreter, (doc SYMBOL) shows
the documentation of SYMBOL's function, (describe SYMBOL) shows its
other properties (in the general sense). The function (sys:symbols)
returns a list of symbols, as does (apropos-list "").
The dialect is mostly traditional, leaning towards Maclisp, with
lexical bindings (yay closures!) and overall rather simple. Some
names come from Common Lisp. The feature set is quite incomplete for
now.
See the doc/ directory for a few things I needed to write down
myself because I tended to forget them.
A few points, in parts deviations from traditional behaviour, bear
mentioning:
Case-sensitive symbols
For aesthetic reasons, symbols are not changed to all uppercase.
Symbols whose names differ with respect to upper-/lowercase are
considered different. The standard symbols are all in lowercase.
Go-like format strings
Because I wasn't too keen on implementing Common Lisp's rather
complex feature set of the format function, the format string is
passed to Go's fmt.Sprintf() function, which stands in the
printf() tradition of C. While I have to admit this is mostly
due to lazyness, it may have the upside that some today, who are
not dyed-in-the-wool Lisp programmers, are more familiar with
it. (I certainly am.) By the way, the %v format works for all
Lisp objects.
No longer: Mandatory variable declarations
It used to be required that, before you assigned a value to a
variable, it either be bound as a function parameter (or similar
constructs like let), or be declared as a global variable using
defvar or defparameter, both of which can also be used to assign
an initial value. Setting a variable not predeclared this way
was an error.
I changed that because it had shown to be inconvenient for the
quick line of code in the REPL. Instead, a WARNING is issued,
similar to what SBCL does. The concept of warnings at all is
new; they can be turned into errors by setting the -W command
line option. (This is done by the regtests so we can catch and
peruse the warnings.) The (currently) only other case where a
warning is issued is when a builtin function is redefined.
Generalized callables
Like functions, a few other object types may be directly called
with arguments. This is the conventional function call form:
> (<function> &rest args) => value
Now, we also have these:
> (<vector> index) => value
> (<vector> index new-value) => new-value (also sets new value)
> (<table> key) => value
> (<table> key new-value) => new-value (also sets new value)
> (<regexp> string) => list-of-matches
This idea came from Arc, which also has tables callable like
functions. Pity they don't work with setf and friends as they
are not recognizable at the time a macro is expanded.
Function evaluation
If the object in function position is not a symbol with a
function definition, it will be evaluated to get a function
(i.e. actually a callable). This means that things like
((make-function) 3 4 5)
will work if (make-function) returns something that can be used
as a function, also a symbol with a function (or some other
callable) in the value cell instead of the function cell. This
feels a bit more natural for vectors or tables than it does for
functions. (I know this is, while convenient, dangerously close
to Lisp-1 territory.)
Limited numeric capabilities
Numbers are essentially all 64-bit floats. There is no
distiction between integers and floats, and there are no
bignums, rationals, or complex numbers. While all these things
are interesting and nice to have, I consider the implementation
effort as too big to be really fun. Besides, I don't have any
real knowledge of numerics and wouldn't use lingo (except as the
simple interactive RPN calculator in l/lic, which I use all the
time) for numerical applications anyway.
Lambda lists
In lambda lists, lingo supports ordinary positional parameters
as well as &optional, &rest, and &key parameters, but not &aux,
keyword-name, &allow-other-keys, or a supplied-p parameter. This
is due to ease and efficiency of the implementation (this is a
toy interpreter after all) as well as to me perceiving these
things not as particularly necessary and aesthetic. That said, I
do find &optional, &rest, and &key parameters useful enough to
support them, but a function that has all of these will still
seem ugly.
Destructuring bind
In a let, the left side of a bind clause can not only be a
symbol, but also a list of symbols -- a proper list, or one with
a non-nil end. In this case, the value (which must be a list) is
mapped to the symbol list and bound accordingly. If the value
list is longer than the symbol list and the symbol list is a
proper list, only so many from the value list are used as there
are symbols to bind them to. Example:
(let (((a b) '(3 4 5 6)))
(cons a b))
=> (3 . 4)
If the symbol list ends not with nil, but with a symbol, all
remaining values are bound to that symbol as a list. Example:
(let (((a b . c) '(3 4 5 6)))
(list a b c))
=> (3 4 (5 6))
If the value list is shorter than the symbol list, all symbols
for which there are no values are bound to nil. Example:
(let (((a b . c) '(3)))
(list a b c))
=> (3 nil nil)
It is an error in this case if the value is not a list.
Regarding the source code -- well, there is little documentation,
because writing documentation wouldn't have been as much fun as
writing the code. Good luck, and have fun!
For bug reports and feature ideas, see the issue tracker at
https://gitlab.com/jyrgenn/lingo/issues