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, 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).- Comparison operators are binary inside
eval. Compiled top-level code supports variadic=,<,>,<=,>=and variadicmin/max/gcd/lcm(desugared into nested binary operations at compile time), but that desugaring does not reach forms interpreted at runtime byeval, where these operators take two arguments and extra arguments are ignored (so(eval '(= 1 1 2))evaluates(= 1 1)and returns true).+ - * / listare fully variadic everywhere. User functions with more than 7 parameters returnnil. - Edge cases that fail. A zero-argument
(+)/(-)/(*)/(/)fails at runtime (a trap in WASM, an exception in JVM). Unary(- x)and(/ x)negate/invert like the interpreter. - 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. - No big-integer promotion. Arithmetic inside the runtime
evalinterpreter uses fixed-width integers and wraps on overflow, even on the JVM where compiled code itself promotes to big integers. - 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. - Top-level global variables are shared write-through from compiled code. A top-level
setq/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). The mirror is one-way: ifevallater reassigns such a variable, compiled code keeps reading its own copy. 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. The:test/:keykeywords of the sequence and alist functions are a compile-time expansion, however: inside a runtimeevalform they are silently ignored and the comparison stayseql(only the interpreter backend applies them insideeval).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.- 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:list-functions,rontolisp:list-macros,rontolisp:list-special-forms,rontolisp:fetch,rontolisp:http-handler,rontolisp:await,rontolisp:then,rontolisp:promisep,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.