Compiled eval Limitations
eval works in all three backends. In the interpreter it is the full tree-walking evaluator. The WASM and JVM compilers each emit a small tree-walking interpreter into their output (_eval/_apply/_store plus the helpers _envLookup/_lookup) that runs the form at runtime, so no separate evaluator or parser is needed.
The compiled eval (WASM and JVM) implements a lexical environment plus a persistent global environment, and aims for parity with the interpreter: self-evaluating atoms, variable references, closures, the special forms and higher-order functions (let, lambda, cond, while, dotimes, setq, setf, push, pop, funcall, apply, mapcar, mapc, reduce, nested eval, ...), and application of any function or interpreted closure all behave as in the interpreter. Rather than enumerate everything, the differences are listed below.
Compiled eval limitations
The compiled eval (WASM and JVM) differs from the interpreter only in these cases:
letbinding lists must use the((name value) ...)form (a bare(let (x) ...)is not supported).- A
lambdabuilt at runtime does not parse lambda-list keywords. Compileddefun/lambdaforms support&optional/&rest/&key(desugared at compile time), and calling such a compiled variadic function fromevalworks. But a lambda that only exists inside an eval'd form, e.g.(eval '(funcall (lambda (&rest r) r) 1 2)), binds its parameters positionally —&restis treated as an ordinary parameter name — and its argument count is not checked. A runtime lambda without lambda-list keywords reports a wrong argument count as aprogram-error, as the interpreter does. - An unbound variable evaluates to the symbol itself. The interpreter signals
The variable x is unbound; the runtimeevalhas no error channel and returns the symbol instead. An undefined function in call position returnsnil. - Global variables are shared between compiled code and
eval. Asetq/defvar/defparameter/defconstantmirrors its value into the runtimeevalglobal environment, so an eval'd expression can read a global the compiled program defined (e.g.(setq add10 (make-adder 10))then(eval '(funcall add10 100))returns110). An eval'd assignment (setq,setf,push,pop) of such a variable writes the compiled variable as well, so compiled code sees it. A special variable (defvar/defparameter) is read and assigned through its current dynamic binding, as compiled code does: inside aletof it,evalsees and sets that binding. A(set ...)-- or(setf (symbol-value ...) ...)-- writes both as well. Only global variables are mirrored: a lexical of a top-level form -- alet/loop/dovariable -- is not, matching Common Lisp, whereevalevaluates in the null lexical environment ((let ((x 5)) (eval 'x))reads the globalx, or the symbol itself when there is none, on every backend). let*,do,do*,dolist,return,defvar,defparameter,defconstant,incf,decf,format,error,ecase,etypecase,ccase,concatenate,with-open-fileand the file-stream functions (open,close,write-line) are not supported. These forms are expanded or handled at compile time only; the runtimeevalinterpreter does not recognize them. The sequence functions (length,reverse,member,member-if,find,find-if,position,count,assoc,assoc-if,getf,last,butlast,remove,remove-if,remove-if-not,remove-duplicates,delete,delete-if,delete-if-not,substitute,nsubstitute,nconc,copy-list,nreverse,make-list,union,intersection,set-difference,adjoin,identity,mapcan,sort,every,some) andprinc-to-string/prin1-to-stringwork, since they resolve through the compiled function registry.defmacroand backquote are compile-time only. On the compilation path, user macros are fully expanded (and their definitions consumed) before the compilers run, and backquote templates are expanded by the reader; the runtimeeval/readof a compiled program recognizes neitherdefmacronor the backquote character. The same holds formacroexpand/macroexpand-1: a call with a literal quoted argument is folded to its expansion at compile time, and the runtimeevaldoes not know these functions (gensym, by contrast, works — it has a first-class wrapper).defstructis compile-time only. A top-leveldefstructis expanded into its generated functions before compilation, so calling a constructor/accessor/predicate fromevalworks, but an eval'd form can neither define a new structure nor use an accessor as asetfplace. A#S(...)literal is likewise resolved before compilation, so it is not recognized insideevaleither.- The CLOS subset is compile-time only. Like
defstruct, top-leveldefclass/defgeneric/defmethodforms are expanded before compilation — calling a generic function, a reader/accessor, or a constructor fromevalworks, but an eval'd form cannot define classes or methods, andmake-instance/slot-valueare not recognized insideeval(they resolve through the compile-time class registry). - The
rontolisppackage functions are not supported.rontolisp:version,rontolisp:fetch,rontolisp:http-handler,rontolisp:await,rontolisp:futurep,rontolisp:json-parseandrontolisp:json-stringifyare compiled directly (constants, inline calls or spliced-in library functions); the runtimeeval/loaddoes not recognize them.
These differences come from the design: the runtime eval resolves operators by name against a compile-time registry of the functions that were actually compiled into the output, and built-in functions are shared with the compiled code.