Former versions of ECL, as well as many other lisps, used linked lists to represent code. Executing code thus meant traversing these lists and performing code transformations, such as macro expansion, every time that a statement was to be executed. The result was a slow and memory hungry interpreter.
Beginning with version 0.3, ECL was shipped with a bytecodes compiler and interpreter which circumvent the limitations of linked lists. When you enter code at the lisp prompt, or when you load a source file, ECL begins a process known as minimal compilation. Barely this process consists on parsing each form, macroexpanding it and translating it into an intermediate language made of bytecodes.
The bytecodes compiler is implemented in
src/c/compiler.d. The main entry point is the lisp
si::make-lambda, which takes a name for the
function and the body of the lambda lists, and produces a lisp object that
can be invoked. For instance,
> (defvar fun (si::make-lambda 'f '((x) (1+ x)))) *FUN* > (funcall fun 2) 3
ECL can only execute bytecodes. When a list is passed to
EVAL it must be first compiled to bytecodes and, if the
process succeeds, the resulting bytecodes are passed to the
interpreter. Similarly, every time a function object is created, such as in
DEFMACRO, the compiler
processes the lambda form to produce a suitable bytecodes object.
The fact that ECL performs this eager compilation means that changes on a macro are not immediately seen in code which was already compiled. This has subtle implications. Take the following code:
> (defmacro f (a b) `(+ ,a ,b)) F > (defun g (x y) (f x y)) G > (g 1 2) 3 > (defmacro f (a b) `(- ,a ,b)) F > (g 1 2) 3
The last statement always outputs
3 while in former
implementations based on simple list traversal it would produce