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