Skip to main content

cljrs_env/
env.rs

1//! Lexical environment: local frames, global namespace table, and current Env.
2
3use std::collections::HashMap;
4use std::sync::{Arc, Condvar, Mutex, RwLock};
5
6use crate::error::EvalResult;
7use cljrs_gc::{GcConfig, GcPtr};
8use cljrs_logging::feat_trace;
9use cljrs_reader::Form;
10use cljrs_value::{CljxFn, Namespace, Value, Var};
11// ── RequireSpec / RequireRefer ─────────────────────────────────────────────────
12
13/// How symbols should be referred into the requiring namespace.
14#[derive(Debug, Clone)]
15pub enum RequireRefer {
16    None,
17    All,
18    Named(Vec<Arc<str>>),
19}
20
21/// A parsed `require` specification.
22#[derive(Debug, Clone)]
23pub struct RequireSpec {
24    pub ns: Arc<str>,
25    pub alias: Option<Arc<str>>,
26    pub refer: RequireRefer,
27}
28
29// ── Frame ─────────────────────────────────────────────────────────────────────
30
31/// One stack frame of local bindings (a single `let*`, `fn`, or `loop*` scope).
32pub struct Frame {
33    pub bindings: Vec<(Arc<str>, Value)>,
34}
35
36impl Default for Frame {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42impl Frame {
43    pub fn new() -> Self {
44        Self {
45            bindings: Vec::new(),
46        }
47    }
48
49    pub fn bind(&mut self, name: Arc<str>, val: Value) {
50        // Shadow: push new binding; lookup searches from the end.
51        self.bindings.push((name, val));
52    }
53
54    pub fn lookup(&self, name: &str) -> Option<&Value> {
55        // Search in reverse order so later bindings shadow earlier ones.
56        feat_trace!("env", "lookup {}", name);
57        for (n, v) in self.bindings.iter().rev() {
58            if n.as_ref() == name {
59                return Some(v);
60            }
61        }
62        None
63    }
64}
65
66// ── GlobalEnv ─────────────────────────────────────────────────────────────────
67
68/// The global mutable store of all namespaces.
69pub struct GlobalEnv {
70    pub namespaces: RwLock<HashMap<Arc<str>, GcPtr<Namespace>>>,
71    /// Directories to search when resolving namespace names to files.
72    pub source_paths: RwLock<Vec<std::path::PathBuf>>,
73    /// Namespaces that have been fully loaded from a file (idempotent guard).
74    pub loaded: Mutex<std::collections::HashSet<Arc<str>>>,
75    /// Namespaces currently being loaded, mapped to the thread loading them.
76    /// Used to detect true circular requires (same thread) vs concurrent loads
77    /// (different thread — those wait on `loading_done` instead of erroring).
78    pub loading: Mutex<HashMap<Arc<str>, std::thread::ThreadId>>,
79    /// Signalled whenever a namespace finishes loading (or fails).
80    pub loading_done: Condvar,
81    /// Built-in namespace sources embedded in the binary.
82    /// Checked by `load_ns` before falling back to source-path search.
83    pub builtin_sources: RwLock<HashMap<Arc<str>, &'static str>>,
84    /// GC configuration for automatic collection based on memory pressure.
85    pub gc_config: RwLock<Option<Arc<GcConfig>>>,
86    /// True once the Clojure compiler namespaces have been loaded and IR
87    /// lowering is available.  Before this, all functions use tree-walking.
88    pub compiler_ready: std::sync::atomic::AtomicBool,
89    /// Evaluator function. Evaluates form given env, produces an EvalResult.
90    pub eval_fn: fn(&Form, &mut Env) -> EvalResult,
91    /// Call a cljrs function.
92    pub call_cljrs_fn: fn(&CljxFn, &[Value], &mut Env) -> EvalResult,
93    /// Hook for customization when a new fn* is defined.
94    on_fn_defined: Option<fn(&CljxFn, &mut Env)>,
95}
96
97impl std::fmt::Debug for GlobalEnv {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        write!(f, "GlobalEnv {{ ... }}")
100    }
101}
102
103impl GlobalEnv {
104    pub fn new(
105        eval_fn: fn(&Form, &mut Env) -> EvalResult,
106        call_cljrs_fn: fn(&CljxFn, &[Value], &mut Env) -> EvalResult,
107        on_fn_defined: Option<fn(&CljxFn, &mut Env)>,
108    ) -> Arc<Self> {
109        Arc::new(Self {
110            namespaces: RwLock::new(HashMap::new()),
111            source_paths: RwLock::new(Vec::new()),
112            loaded: Mutex::new(std::collections::HashSet::new()),
113            loading: Mutex::new(HashMap::new()),
114            loading_done: Condvar::new(),
115            builtin_sources: RwLock::new(HashMap::new()),
116            gc_config: RwLock::new(None),
117            compiler_ready: std::sync::atomic::AtomicBool::new(false),
118            eval_fn,
119            call_cljrs_fn,
120            on_fn_defined,
121        })
122    }
123
124    /// Replace the source path list.
125    pub fn set_source_paths(&self, paths: Vec<std::path::PathBuf>) {
126        *self.source_paths.write().unwrap() = paths;
127    }
128
129    /// Register an embedded namespace source (called by cljrs-stdlib at startup).
130    pub fn register_builtin_source(&self, ns: &str, src: &'static str) {
131        self.builtin_sources
132            .write()
133            .unwrap()
134            .insert(Arc::from(ns), src);
135    }
136
137    /// Look up an embedded source for a namespace, if one has been registered.
138    pub fn builtin_source(&self, ns: &str) -> Option<&'static str> {
139        self.builtin_sources.read().unwrap().get(ns).copied()
140    }
141
142    /// Mark a namespace as fully loaded from a file.
143    pub fn mark_loaded(&self, ns: &str) {
144        self.loaded.lock().unwrap().insert(Arc::from(ns));
145    }
146
147    /// True if the namespace has already been loaded from a file.
148    pub fn is_loaded(&self, ns: &str) -> bool {
149        self.loaded.lock().unwrap().contains(ns)
150    }
151
152    /// Set the GC configuration for automatic memory pressure management.
153    pub fn set_gc_config(&self, config: Arc<GcConfig>) {
154        *self.gc_config.write().unwrap() = Some(config);
155    }
156
157    /// Get the GC configuration, if one has been set.
158    pub fn gc_config(&self) -> Option<Arc<GcConfig>> {
159        self.gc_config.read().unwrap().clone()
160    }
161
162    /// Resolve a short alias to a full namespace name in `current_ns`.
163    pub fn resolve_alias(&self, current_ns: &str, alias: &str) -> Option<Arc<str>> {
164        let map = self.namespaces.read().unwrap();
165        let ns = map.get(current_ns)?;
166        let aliases = ns.get().aliases.lock().unwrap();
167        aliases.get(alias).cloned()
168    }
169
170    /// Return the namespace with this name, creating it if it doesn't exist.
171    pub fn get_or_create_ns(&self, name: &str) -> GcPtr<Namespace> {
172        // Fast path: already exists.
173        {
174            let map = self.namespaces.read().unwrap();
175            if let Some(ns) = map.get(name) {
176                return ns.clone();
177            }
178        }
179        // Slow path: insert.
180        let mut map = self.namespaces.write().unwrap();
181        // Re-check after acquiring write lock.
182        if let Some(ns) = map.get(name) {
183            return ns.clone();
184        }
185        let ns = GcPtr::new(Namespace::new(name));
186        map.insert(Arc::from(name), ns.clone());
187        ns
188    }
189
190    /// Intern `name` with `val` in the given namespace, returning the Var.
191    pub fn intern(&self, ns_name: &str, name: Arc<str>, val: Value) -> GcPtr<Var> {
192        let ns = self.get_or_create_ns(ns_name);
193        let mut interns = ns.get().interns.lock().unwrap();
194        if let Some(var) = interns.get(&name) {
195            // Update existing var.
196            var.get().bind(val);
197            return var.clone();
198        }
199        let var = GcPtr::new(Var::new(ns_name, name.as_ref()));
200        var.get().bind(val);
201        interns.insert(name, var.clone());
202        var
203    }
204
205    /// Look up a Var in the named namespace (interns only).
206    pub fn lookup_var(&self, ns_name: &str, sym_name: &str) -> Option<GcPtr<Var>> {
207        let map = self.namespaces.read().unwrap();
208        let ns = map.get(ns_name)?;
209        let interns = ns.get().interns.lock().unwrap();
210        interns.get(sym_name).cloned()
211    }
212
213    /// Look up a value in `ns_name`: checks interns then refers.
214    /// Routes through the dynamic binding stack so `binding` overrides work.
215    pub fn lookup_in_ns(&self, ns_name: &str, sym_name: &str) -> Option<Value> {
216        let map = self.namespaces.read().unwrap();
217        let ns = map.get(ns_name)?;
218        let ns_ref = ns.get();
219        // Check interns first.
220        {
221            let interns = ns_ref.interns.lock().unwrap();
222            if let Some(var) = interns.get(sym_name) {
223                return crate::dynamics::deref_var(var);
224            }
225        }
226        // Then refers.
227        {
228            let refers = ns_ref.refers.lock().unwrap();
229            if let Some(var) = refers.get(sym_name) {
230                return crate::dynamics::deref_var(var);
231            }
232        }
233        None
234    }
235
236    /// Look up the raw Var (not its value) in `ns_name`: interns then refers.
237    pub fn lookup_var_in_ns(&self, ns_name: &str, sym_name: &str) -> Option<GcPtr<Var>> {
238        let map = self.namespaces.read().unwrap();
239        let ns = map.get(ns_name)?;
240        let ns_ref = ns.get();
241        {
242            let interns = ns_ref.interns.lock().unwrap();
243            if let Some(var) = interns.get(sym_name) {
244                return Some(var.clone());
245            }
246        }
247        {
248            let refers = ns_ref.refers.lock().unwrap();
249            if let Some(var) = refers.get(sym_name) {
250                return Some(var.clone());
251            }
252        }
253        None
254    }
255
256    /// Copy all interns from `src_ns` into `dst_ns` as refers.
257    pub fn refer_all(&self, dst_ns: &str, src_ns: &str) {
258        let map = self.namespaces.read().unwrap();
259        let src = match map.get(src_ns) {
260            Some(ns) => ns.clone(),
261            None => return,
262        };
263        let dst = match map.get(dst_ns) {
264            Some(ns) => ns.clone(),
265            None => return,
266        };
267        let src_interns = src.get().interns.lock().unwrap();
268        let mut dst_refers = dst.get().refers.lock().unwrap();
269        for (name, var) in src_interns.iter() {
270            dst_refers.insert(name.clone(), var.clone());
271        }
272    }
273
274    /// Copy selected interns from `src_ns` into `dst_ns` as refers.
275    pub fn refer_named(&self, dst_ns: &str, src_ns: &str, names: &[Arc<str>]) {
276        let map = self.namespaces.read().unwrap();
277        let src = match map.get(src_ns) {
278            Some(ns) => ns.clone(),
279            None => return,
280        };
281        let dst = match map.get(dst_ns) {
282            Some(ns) => ns.clone(),
283            None => return,
284        };
285        let src_interns = src.get().interns.lock().unwrap();
286        let mut dst_refers = dst.get().refers.lock().unwrap();
287        for name in names {
288            if let Some(var) = src_interns.get(name) {
289                dst_refers
290                    .entry(name.clone())
291                    .or_insert_with(|| var.clone());
292            }
293        }
294    }
295
296    /// Register `alias` → `full_ns` in `current_ns`'s alias table.
297    pub fn add_alias(&self, current_ns: &str, alias: &str, full_ns: &str) {
298        let ns_ptr = self.get_or_create_ns(current_ns);
299        let mut aliases = ns_ptr.get().aliases.lock().unwrap();
300        aliases.insert(Arc::from(alias), Arc::from(full_ns));
301    }
302
303    /// Evaluate form given env.
304    #[inline(always)]
305    pub fn eval(&self, form: &Form, env: &mut Env) -> EvalResult {
306        (self.eval_fn)(form, env)
307    }
308
309    /// Call the given cljrs function.
310    #[inline(always)]
311    pub fn call_cljrs_fn(&self, func: &CljxFn, args: &[Value], env: &mut Env) -> EvalResult {
312        (self.call_cljrs_fn)(func, args, env)
313    }
314
315    /// Callback hook for new functions defined.
316    #[inline(always)]
317    pub fn on_fn_defined(&self, f: &CljxFn, env: &mut Env) {
318        if let Some(hook) = self.on_fn_defined {
319            hook(f, env);
320        }
321    }
322
323    /// Sets the on-new-function-defined hook.
324    pub fn set_on_fn_defined(&mut self, hook: fn(&CljxFn, &mut Env)) {
325        self.on_fn_defined = Some(hook);
326    }
327}
328
329// ── Env ───────────────────────────────────────────────────────────────────────
330
331/// The full execution environment: a stack of local frames plus the global env.
332pub struct Env {
333    pub frames: Vec<Frame>,
334    pub current_ns: Arc<str>,
335    pub globals: Arc<GlobalEnv>,
336}
337
338impl Env {
339    pub fn new(globals: Arc<GlobalEnv>, ns: &str) -> Self {
340        Self {
341            frames: Vec::new(),
342            current_ns: Arc::from(ns),
343            globals,
344        }
345    }
346
347    /// Create an Env pre-loaded with a function's closed-over bindings.
348    pub fn with_closure(globals: Arc<GlobalEnv>, ns: &str, f: &CljxFn) -> Self {
349        let mut env = Self::new(globals, ns);
350        if !f.closed_over_names.is_empty() {
351            env.push_frame();
352            for (name, val) in f.closed_over_names.iter().zip(f.closed_over_vals.iter()) {
353                env.bind(name.clone(), val.clone());
354            }
355        }
356        env
357    }
358
359    pub fn push_frame(&mut self) {
360        self.frames.push(Frame::new());
361    }
362
363    pub fn pop_frame(&mut self) {
364        self.frames.pop();
365    }
366
367    /// Bind `name` to `val` in the top frame.
368    pub fn bind(&mut self, name: Arc<str>, val: Value) {
369        if let Some(frame) = self.frames.last_mut() {
370            frame.bind(name, val);
371        }
372        // If there are no frames, the binding is silently dropped.
373        // Callers must push a frame first.
374    }
375
376    /// Look up `name`: local frames (innermost first), then the current namespace.
377    pub fn lookup(&self, name: &str) -> Option<Value> {
378        feat_trace!("env", "lookup {} in {} frames", name, self.frames.len());
379        for frame in self.frames.iter().rev() {
380            if let Some(v) = frame.lookup(name) {
381                return Some(v.clone());
382            }
383        }
384        self.globals.lookup_in_ns(&self.current_ns, name)
385    }
386
387    /// Look up the Var object for `name` in the current namespace.
388    pub fn lookup_var(&self, name: &str) -> Option<GcPtr<Var>> {
389        self.globals.lookup_var_in_ns(&self.current_ns, name)
390    }
391
392    /// Collect all current local bindings (all frames, innermost last).
393    /// Used for closure capture.
394    pub fn all_local_bindings(&self) -> (Vec<Arc<str>>, Vec<Value>) {
395        let mut names = Vec::new();
396        let mut vals = Vec::new();
397        // Outermost first so inner frames override on lookup.
398        for frame in &self.frames {
399            for (n, v) in &frame.bindings {
400                names.push(n.clone());
401                vals.push(v.clone());
402            }
403        }
404        (names, vals)
405    }
406
407    /// Create a child Env for closure capture (same globals, same ns, captures locals).
408    pub fn child(&self) -> Self {
409        let (names, vals) = self.all_local_bindings();
410        let mut child = Self::new(self.globals.clone(), &self.current_ns);
411        if !names.is_empty() {
412            child.push_frame();
413            for (n, v) in names.into_iter().zip(vals) {
414                child.bind(n, v);
415            }
416        }
417        child
418    }
419
420    #[inline(always)]
421    pub fn eval(&mut self, form: &Form) -> EvalResult {
422        let globals = self.globals.clone();
423        globals.eval(form, self)
424    }
425
426    #[inline(always)]
427    pub fn call_cljrs_fn(&mut self, func: &CljxFn, args: &[Value]) -> EvalResult {
428        let globals = self.globals.clone();
429        globals.call_cljrs_fn(func, args, self)
430    }
431
432    #[inline(always)]
433    pub fn on_fn_defined(&mut self, func: &CljxFn) {
434        let globals = self.globals.clone();
435        globals.on_fn_defined(func, self);
436    }
437}