Many people think of Scheme, Lisp, and ECMAScript as “interpreted”
languages, though many of these languages have compilers.
What these languages do have is eval
- that is a command
that at run-time takes a source program, and evaluates it.
They may also have an interactive read-eval-print interface.
For such uses a traditional interpreter is easiest and most responsive.
Therefore, high-end Lisp systems traditionally provide both a compiler
and an interpreter. Such duplication is expensive, in terms of size,
development effort, and testing. If one has load-and-go capabilities,
that is the abilility to efficiently load a compiled program into
a running application, then one can simply implement eval
as a compile followed by a load.
When we compile to Java bytecodes, we create one or more files in
the .class
format. The standard Java class
java.lang.ClassLoader
has a method
defineClass
that takes a byte array laid out in the format of a .class
,
and from it dynamically creates a new class in the existing Java run-time.
(This facility is used for “applets” downloaded accross
the Network.) Kawa uses this scheme to implement eval
,
and it works well. Because ClassLoader.defineClass
takes an array, rather than a file, we can compile and load entirely
inside the Kawa run-time, without having to go via the filesystem
for temporary files, as a traditional compiler batch does. The result
is near-instant response.
There is a tradeoff, though. Doing a compile+load is a very heavy-duty operation, compared to a simply interpreting an expression. It creates a lot of temporary objects. Worse, it also creates some temporary classes, and some Java implementations do not garbage collect unused classes.
Kawa uses a compromise strategy. If the Expression
is “simple”, it is interpreted directly, using
the Expression.eval
. Otherwise, it is compiled.
Simple expressions include literals, (global) variable access,
assignment, and function application.
Implementing eval
in those cases is trivial.
Expressions that define new
local bindings (such as lambda expressions and let
forms)
do not implement eval
. If the user types in such an
expression, it is wrapped inside a dummy function,
compiled to bytecodes, and immediately executed. This is to avoid dealing
with lexical binding in the evaluator.
A ModuleExp
represents a top-level form:
class ModuleExp extends LambdaExp
{ ...;
public Object eval_module
(Environment env) {
if (body_is_simple) // Optimization
return body.eval (env);
Object v = eval (env);
return ((ModuleBody) v).run (env);
}
}
ModuleExp
is a sub-class of LambdaExp
,
since it is actually a dummy function created by
wrapping the top-level forms in an implicit lambda
.
The eval_module
method evaluates the top-level forms.
If the body is not simple, it invokes the eval
in LambdaExp
(which invokes the compiler).
The result of eval
is a ModuleBody
,
which we can run
.