Skip to main content

cljrs_env/
env.rs

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