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

- **`let` binding 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 variadic `min`/`max`/`gcd`/`lcm` (desugared into nested binary operations at compile time), but that desugaring does not reach forms interpreted at runtime by `eval`, where these operators take two arguments and extra arguments are ignored (so `(eval '(= 1 1 2))` evaluates `(= 1 1)` and returns true). `+ - * / list` are fully variadic everywhere. User functions with more than 10 parameters return `nil`.
- **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 `lambda` built at runtime does not parse lambda-list keywords.** Compiled `defun`/`lambda` forms support `&optional`/`&rest`/`&key` (desugared at compile time), and calling such a compiled variadic function from `eval` works. 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 — `&rest` is treated as an ordinary parameter name.
- **An unbound variable evaluates to the symbol itself.** The interpreter signals `The variable x is unbound`; the runtime `eval` has no error channel and returns the symbol instead. An undefined function in call position returns `nil`.
- **Top-level global variables are shared write-through from compiled code.** A top-level `setq`/`defvar`/`defparameter`/`defconstant` mirrors its value into the runtime `eval` global 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))` returns `110`). The mirror is one-way: if `eval` later 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-file` and the file-stream functions (`open`, `close`, `write-line`) are not supported.** These forms are expanded or handled at compile time only; the runtime `eval` interpreter 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`) and `princ-to-string`/`prin1-to-string` work, since they resolve through the compiled function registry. The `:test`/`:key` keywords of the sequence and alist functions are a compile-time expansion, however: inside a runtime `eval` form they are silently ignored and the comparison stays `eql` (only the interpreter backend applies them inside `eval`).
- **`defmacro` and 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 runtime `eval`/`read` of a compiled program recognizes neither `defmacro` nor the backquote character. The same holds for `macroexpand`/`macroexpand-1`: a call with a literal quoted argument is folded to its expansion at compile time, and the runtime `eval` does not know these functions (`gensym`, by contrast, works — it has a first-class wrapper).
- **`defstruct` is compile-time only.** A top-level `defstruct` is expanded into its generated functions before compilation, so calling a constructor/accessor/predicate from `eval` works, but an eval'd form can neither define a new structure nor use an accessor as a `setf` place. A `#S(...)` literal is likewise resolved before compilation, so it is not recognized inside `eval` either.
- **The CLOS subset is compile-time only.** Like `defstruct`, top-level `defclass`/`defgeneric`/`defmethod` forms are expanded before compilation — calling a generic function, a reader/accessor, or a constructor from `eval` works, but an eval'd form cannot define classes or methods, and `make-instance`/`slot-value` are not recognized inside `eval` (they resolve through the compile-time class registry).
- **The `rontolisp` package functions are not supported.** `rontolisp:version`, `rontolisp:list-functions`, `rontolisp:list-macros`, `rontolisp:list-special-forms`, `rontolisp:fetch`, `rontolisp:http-handler`, `rontolisp:await`, `rontolisp:futurep`, `rontolisp:json-parse` and `rontolisp:json-stringify` are compiled directly (constants, inline calls or spliced-in library functions); the runtime `eval`/`load` does 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.
