cljrs-eval
Tree-walking interpreter for clojurust. Evaluates Form AST nodes produced
by cljrs-reader within a namespace-aware lexical environment.
Phase: 5–8 + defrecord/reify/built-in protocols + source-path management/require + dynamic variables (binding) — implemented.
Purpose
Implements a complete Clojure-compatible tree-walking evaluator including all
special forms, macro expansion, tail-call optimization, sequential and
associative destructuring, syntax-quote with gensyms, lazy sequences, and a
full clojure.core bootstrap.
File layout
src/
lib.rs — module declarations, re-exports, standard_env(), standard_env_with_paths()
error.rs — EvalError enum, EvalResult<T> alias
env.rs — Frame, GlobalEnv (namespace registry + source paths + loaded set), Env (lexical scope), RequireSpec, RequireRefer
eval.rs — top-level eval dispatcher, form_to_value, inline tests
dynamics.rs — thread-local dynamic binding stack: BINDING_STACK, BindingGuard, push_frame/pop_frame, deref_var, set_thread_local, capture_current/install_frames, trace_current
callback.rs — thread-local eval context for Rust→Clojure callbacks; invoke(f, args) lets builtins call Clojure functions
apply.rs — eval_call, apply_value, call_cljrs_fn, ClosureThunk, handle_make_lazy_seq, handle_make_delay, handle_send, handle_vswap, handle_with_bindings, handle_alter_var_root, handle_vary_meta, type_tag_of, resolve_type_tag
special.rs — all special form handlers, SPECIAL_FORMS list; includes binding, extract_def_name, compile_meta_form
loader.rs — load_ns: resolves namespace names to files, evaluates them, applies alias/refer
macros.rs — macroexpand_1, macroexpand, value_to_form
destructure.rs — sequential + associative destructuring (bind_pattern, bind_sequential, bind_associative)
syntax_quote.rs — syntax-quote expander with gensym counter
builtins.rs — native built-in functions + BOOTSTRAP_SOURCE Clojure string
Public API
/// Evaluate a Form in env and return the resulting Value.
;
/// Create a GlobalEnv pre-populated with clojure.core builtins
/// (native functions + bootstrap HOFs) and a user namespace
/// that refers everything from clojure.core.
;
/// Like standard_env() but also configures the source search paths.
;
/// Load a namespace from source and wire alias/refer into current_ns.
;
pub type EvalResult<T = Value> = ;
Special forms
| Form | Notes |
|---|---|
def / defn / defmacro / defonce |
Intern values in current namespace |
fn / fn* |
Multi-arity; rest args; closure capture |
let / let* |
Sequential binding with destructuring |
letfn |
Mutually recursive local function bindings |
loop / loop* + recur |
Tail-call trampoline |
if / do / and / or |
Control flow |
quote |
Return unevaluated form |
var |
Return Value::Var for a namespace var |
set! |
Set thread-local binding if inside binding; else mutate var's root |
throw / try / catch / finally |
Exception handling |
ns |
Switch current namespace; processes :require clauses; auto-refers clojure.core |
require |
Load and wire namespaces; supports :as alias and :refer [...]/:refer :all |
load-file |
Evaluate a .cljrs/.cljc file by absolute path |
in-ns |
Switch to (or create) a namespace by quoted symbol |
alias |
Add a namespace alias to the current namespace |
. |
Stub — interop not yet implemented |
defprotocol |
Define a protocol with named method signatures |
extend-type |
Implement protocol methods for a type |
extend-protocol |
Protocol-first sugar for extend-type |
defmulti |
Define a multimethod with a dispatch function |
defmethod |
Add an implementation for one dispatch value |
future |
Evaluate body on a new thread; return Value::Future; conveys dynamic bindings |
defrecord |
Define a named record type; generates ->Name/map->Name constructors; supports inline protocol impls |
reify |
Create an anonymous protocol-implementing instance with a gensym'd type tag |
binding |
Establish thread-local dynamic var bindings for body (RAII BindingGuard) |
Built-in functions (clojure.core)
All built-ins have signature fn(&[Value]) -> ValueResult<Value>.
Arithmetic: + - * / mod rem quot inc dec max min abs
Comparison: = not= < > <= >= identical?
Predicates: nil? zero? pos? neg? not true? false? number?
integer? float? string? keyword? symbol? fn? seq? map?
vector? set? coll? boolean? char? var? atom? empty?
Collections: list vector hash-map hash-set conj assoc dissoc
get get-in count seq first rest next cons nth last
reverse concat keys vals contains? merge into empty vec set
Higher-order (bootstrap): map filter reduce keep remove mapcat
take drop take-while drop-while comp partial identity
constantly complement apply
Lazy sequences (bootstrap): lazy-seq (macro) range iterate repeat
repeatedly cycle
Atoms: atom deref reset! swap! compare-and-set!
Volatiles: volatile! vreset! vswap! volatile?
Delays: delay (macro) force realized?
Promises: promise deliver
Futures: future (special form) future-done? future-cancelled? future-cancel
Agents: agent send send-off await agent-error restart-agent
I/O: print println prn pr pr-str str read-string slurp spit
Misc: gensym type hash name namespace ex-info ex-data
ex-message ex-cause
Protocols: satisfies? extends?
Multimethods: prefer-method remove-method methods isa?
Records/reify: make-type-instance record? instance?
Dynamic vars: var-get var-set! bound? thread-bound? meta with-meta
alter-var-root vary-meta with-bindings* (intercepted in eval_call)
Lazy sequences
Lazy sequences use a Thunk trait (defined in cljrs-value) and two new
Value variants:
Value::LazySeq(GcPtr<LazySeq>)— a deferred sequence cell; forced at most once and cached.Value::Cons(GcPtr<CljxCons>)— a cons cell withhead: Valueandtail: Value, used whenconsis called with a lazy tail.
make-lazy-seq (special-cased in eval_call) wraps a zero-arg CljxFn in a
ClosureThunk and returns a Value::LazySeq. The lazy-seq macro expands to
(make-lazy-seq (fn [] ...)).
Destructuring
Both sequential and associative destructuring are supported in let, fn,
and loop binding positions.
Sequential ([a b & rest :as whole]): positional bindings, rest, :as.
Associative ({:keys [a b] :strs [c] :syms [d] :as m :or {b 99}}):
keyword, string, and symbol key extraction; entire-value alias; defaults.
Bootstrap HOFs
Higher-order functions that need to call back into the evaluator are defined in
BOOTSTRAP_SOURCE — a Clojure string eval'd at startup in clojure.core:
when when-not cond -> ->> reduce map filter remove keep
mapcat mapv filterv take drop take-while drop-while some
every? doseq dotimes for comp partial complement range
iterate repeat repeatedly cycle update-in map-keys map-vals etc.
Dependencies
| Crate | Role |
|---|---|
cljrs-types |
Span, CljxError |
cljrs-gc |
GcPtr<T> smart pointer |
cljrs-reader |
Form AST input |
cljrs-value |
Value, CljxFn, Namespace, LazySeq, CljxCons, all collections |
thiserror |
EvalError derive |
Deferred to later phases
deftype— blocked by.interop (field access via(.field obj)); Phase 9.interop,new— Phase 9ref/ STM (dosync,alter,commute,ensure) — deferredlockingmacro — deferredderive/ fullisa?hierarchy — deferred