207 lines
7.1 KiB
HTML
207 lines
7.1 KiB
HTML
|
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||
|
"http://www.w3.org/TR/html4/loose.dtd">
|
||
|
<html>
|
||
|
<head>
|
||
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
|
||
|
<title>femtoLisp</title>
|
||
|
</head>
|
||
|
<body>
|
||
|
<h1>femtoLisp</h1>
|
||
|
<hr>
|
||
|
femtoLisp is an elegant Lisp implementation. Its goal is to be a
|
||
|
reasonably efficient and capable interpreter with the shortest, simplest
|
||
|
code possible. As its name implies, it is small (10<sup>-15</sup>).
|
||
|
Right now it is just 1000 lines of C (give or take). It would make a great
|
||
|
teaching example, or a useful system anywhere a very small Lisp is wanted.
|
||
|
It is also a useful basis for developing other interpreters or related
|
||
|
languages.
|
||
|
|
||
|
|
||
|
<h2>The language implemented</h2>
|
||
|
|
||
|
femtoLisp tries to be a generic, simple Lisp dialect, influenced by McCarthy's
|
||
|
original.
|
||
|
|
||
|
<ul>
|
||
|
<li>Types: cons, symbol, 30-bit integer, builtin function
|
||
|
<li>Self-evaluating lambda, macro, and label forms
|
||
|
<li>Full Common Lisp-style macros
|
||
|
<li>Case-sensitive symbol names
|
||
|
<li>Scheme-style evaluation rule where any expression may appear in head
|
||
|
position as long as it evaluates to a callable
|
||
|
<li>Scheme-style formal argument lists (dotted lists for varargs)
|
||
|
<li>Transparent closure representation <tt>(lambda args body . env)</tt>
|
||
|
<li>A lambda body may contain only one form. Use explicit <tt>progn</tt> for
|
||
|
multiple forms. Included macros, however, allow <tt>defun</tt>,
|
||
|
<tt>let</tt>, etc. to accept multiple body forms.
|
||
|
<li>Builtin function names are constants and cannot be redefined.
|
||
|
<li>Symbols have one binding, as in Scheme.
|
||
|
</ul>
|
||
|
<b>Builtin special forms:</b><br>
|
||
|
<tt>quote, cond, if, and, or, lambda, macro, label, while, progn, prog1</tt>
|
||
|
<p>
|
||
|
<b>Builtin functions:</b><br>
|
||
|
<tt>eq, atom, not, symbolp, numberp, boundp, cons, car, cdr,
|
||
|
read, eval, print, load, set,
|
||
|
+, -, *, /, <, apply, rplaca, rplacd</tt>
|
||
|
<p>
|
||
|
<b>Included library functions and macros:</b><br>
|
||
|
<tt>
|
||
|
setq, setf, defmacro, defun, define, let, let*, labels, dotimes,
|
||
|
macroexpand-1, macroexpand, backquote,
|
||
|
|
||
|
null, consp, builtinp, self-evaluating-p, listp, eql, equal, every, any,
|
||
|
when, unless,
|
||
|
|
||
|
=, !=, >, <=, >=, compare, mod, abs, identity,
|
||
|
|
||
|
list, list*, length, last, nthcdr, lastcdr, list-ref, reverse, nreverse,
|
||
|
assoc, member, append, nconc, copy-list, copy-tree, revappend, nreconc,
|
||
|
|
||
|
mapcar, filter, reduce, map-int,
|
||
|
|
||
|
symbol-plist, set-symbol-plist, put, get
|
||
|
</tt>
|
||
|
<p>
|
||
|
<a href="system.lsp">system.lsp</a>
|
||
|
|
||
|
|
||
|
<h2>The implementation</h2>
|
||
|
|
||
|
<ul>
|
||
|
<li>Compacting copying garbage collector (<tt>O(1)</tt> in number of dead
|
||
|
objects)
|
||
|
<li>Tagged pointers for efficient type checking and fast integers
|
||
|
<li>Tail-recursive evaluator (tail calls use no stack space)
|
||
|
<li>Minimally-consing <tt>apply</tt>
|
||
|
<li>Interactive and script execution modes
|
||
|
</ul>
|
||
|
<p>
|
||
|
<a href="lisp.c">lisp.c</a>
|
||
|
|
||
|
|
||
|
<h2>femtoLisp2</h2>
|
||
|
|
||
|
This version includes robust reading and printing capabilities for
|
||
|
circular structures and escaped symbol names. It adds read and print support
|
||
|
for the Common Lisp read-macros <tt>#., #n#,</tt> and <tt>#n=</tt>.
|
||
|
This allows builtins to be printed in a readable fashion as e.g.
|
||
|
"<tt>#.eq</tt>".
|
||
|
<p>
|
||
|
The net result is that the interpreter achieves a highly satisfying property
|
||
|
of closure under I/O. In other words, every representable Lisp value can be
|
||
|
read and printed.
|
||
|
<p>
|
||
|
The traditional builtin <tt>label</tt> provides a purely-functional,
|
||
|
non-circular way
|
||
|
to write an anonymous recursive function. In femtoLisp2 you can
|
||
|
achieve the same effect "manually" using nothing more than the reader:
|
||
|
<br>
|
||
|
<tt>#0=(lambda (x) (if (<= x 0) 1 (* x (#0# (- x 1)))))</tt>
|
||
|
<p>
|
||
|
femtoLisp2 has the following extra features and optimizations:
|
||
|
<ul>
|
||
|
<li> builtin functions <tt>error, exit,</tt> and <tt>princ</tt>
|
||
|
<li> read support for backquote expressions
|
||
|
<li> delayed environment consing
|
||
|
<li> collective allocation of cons chains
|
||
|
</ul>
|
||
|
Those two optimizations are a Big Deal.
|
||
|
<p>
|
||
|
<a href="lisp2.c">lisp2.c</a> (uses <a href="flutils.c">flutils.c</a>)
|
||
|
|
||
|
|
||
|
<h2>Performance</h2>
|
||
|
|
||
|
femtoLisp's performance is surprising. It is faster than most
|
||
|
interpreters, and it is usually within a factor of 2-5 of compiled CLISP.
|
||
|
|
||
|
<table border=1>
|
||
|
<tr>
|
||
|
<td colspan=3><center><b>solve 5 queens problem 100x</b></center></td>
|
||
|
<tr>
|
||
|
<td> <td>interpreted<td>compiled
|
||
|
<tr>
|
||
|
<td>CLISP <td>4.02 sec <td>0.68 sec
|
||
|
<tr>
|
||
|
<td>femtoLisp2<td>2.62 sec <td>2.03 sec**
|
||
|
<tr>
|
||
|
<td>femtoLisp <td>6.02 sec <td>5.64 sec**
|
||
|
<tr>
|
||
|
|
||
|
<td colspan=3><center><b>recursive fib(34)</b></center></td>
|
||
|
<tr>
|
||
|
<td> <td>interpreted<td>compiled
|
||
|
<tr>
|
||
|
<td>CLISP <td>23.12 sec <td>4.04 sec
|
||
|
<tr>
|
||
|
<td>femtoLisp2<td>4.71 sec <td>n/a
|
||
|
<tr>
|
||
|
<td>femtoLisp <td>7.25 sec <td>n/a
|
||
|
<tr>
|
||
|
|
||
|
</table>
|
||
|
** femtoLisp is not a compiler; in this context "compiled" means macros
|
||
|
were pre-expanded.
|
||
|
|
||
|
|
||
|
<h2>"Installation"</h2>
|
||
|
|
||
|
Here is a <a href="Makefile">Makefile</a>. Type <tt>make</tt> to build
|
||
|
femtoLisp, <tt>make NAME=lisp2</tt> to build femtoLisp2.
|
||
|
|
||
|
|
||
|
<h2>Tail recursion</h2>
|
||
|
The femtoLisp evaluator is tail-recursive, following the idea in
|
||
|
<a href="http://library.readscheme.org/servlets/cite.ss?pattern=Ste-76b">
|
||
|
Lambda: The Ultimate Declarative</a> (should be required reading
|
||
|
for all schoolchildren).
|
||
|
<p>
|
||
|
The femtoLisp source provides a simple concrete example showing why a function
|
||
|
call is best viewed as a "renaming plus goto" rather than as a set of stack
|
||
|
operations.
|
||
|
<p>
|
||
|
Here is the non-tail-recursive evaluator code to evaluate the body of a
|
||
|
lambda (function), from <a href="lisp-nontail.c">lisp-nontail.c</a>:
|
||
|
<pre>
|
||
|
PUSH(*lenv); // preserve environment on stack
|
||
|
lenv = &Stack[SP-1];
|
||
|
v = eval(*body, lenv);
|
||
|
POP();
|
||
|
return v;
|
||
|
</pre>
|
||
|
(Note that because of the copying garbage collector, values are referenced
|
||
|
through relocatable handles.)
|
||
|
<p>
|
||
|
Superficially, the call to <tt>eval</tt> is not a tail call, because work
|
||
|
remains after it returns—namely, popping the environment off the stack.
|
||
|
In other words, the control stack must be saved and restored to allow us to
|
||
|
eventually restore the environment stack. However, restoring the environment
|
||
|
stack is the <i>only</i> work to be done. Yet after this point the old
|
||
|
environment is not used! So restoring the environment stack isn't
|
||
|
necessary, therefore restoring the control stack isn't either.
|
||
|
<p>
|
||
|
This perspective makes proper tail recursion seem like more than an
|
||
|
alternate design or optimization. It seems more correct.
|
||
|
<p>
|
||
|
Here is the corrected, tail-recursive version of the code:
|
||
|
<pre>
|
||
|
SP = saveSP; // restore stack completely
|
||
|
e = *body; // reassign arguments
|
||
|
*penv = *lenv;
|
||
|
goto eval_top;
|
||
|
</pre>
|
||
|
<tt>penv</tt> is a pointer to the old environment, which we overwrite.
|
||
|
(Notice that the variable <tt>penv</tt> does not even appear in the first code
|
||
|
example.)
|
||
|
So where is the environment saved and restored, if not here? The answer
|
||
|
is that the burden is shifted to the caller; a caller to <tt>eval</tt> must
|
||
|
expect that its environment might be overwritten, and take steps to save it
|
||
|
if it will be needed further after the call. In practice, this means
|
||
|
the environment is saved and restored around the evaluation of
|
||
|
arguments, rather than around function applications. Hence <tt>(f x)</tt>
|
||
|
might be a tail call to <tt>f</tt>, but <tt>(+ y (f x))</tt> is not.
|
||
|
|
||
|
</body>
|
||
|
</html>
|