Skip to main content

r/interpreter/
environment.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use fnv::FnvHashMap;
5use itertools::Itertools;
6use tracing::trace;
7
8use crate::interpreter::value::RValue;
9use crate::parser::ast::Expr;
10
11/// Source expressions for function arguments (promise expressions).
12///
13/// When a closure is called, the original unevaluated expressions for each
14/// argument are stored here so that `substitute()` can retrieve them.
15type PromiseExprs = FnvHashMap<String, Expr>;
16
17#[derive(Debug, Clone)]
18pub struct Environment {
19    inner: Rc<RefCell<EnvInner>>,
20}
21
22impl Environment {
23    /// Check if two environments are the same object (pointer equality).
24    pub fn ptr_eq(&self, other: &Environment) -> bool {
25        Rc::ptr_eq(&self.inner, &other.inner)
26    }
27}
28
29#[derive(Debug)]
30pub(crate) struct EnvInner {
31    bindings: FnvHashMap<String, RValue>,
32    parent: Option<Environment>,
33    #[allow(dead_code)]
34    name: Option<String>,
35    /// Expressions registered via on.exit() to run when this frame exits.
36    on_exit: Vec<Expr>,
37    /// Whether the environment is locked (cannot add new bindings).
38    locked: bool,
39    /// Set of binding names that are individually locked (cannot be modified).
40    locked_bindings: std::collections::HashSet<String>,
41    /// Active bindings: names mapped to zero-argument functions that are called on every access.
42    active_bindings: FnvHashMap<String, RValue>,
43    /// Promise expressions: original unevaluated expressions for function arguments.
44    /// Used by `substitute()` to retrieve the source expression for a parameter.
45    promise_exprs: PromiseExprs,
46}
47
48impl Environment {
49    pub fn new_global() -> Self {
50        Environment {
51            inner: Rc::new(RefCell::new(EnvInner {
52                bindings: FnvHashMap::default(),
53                parent: None,
54                name: Some("R_GlobalEnv".to_string()),
55                on_exit: Vec::new(),
56                locked: false,
57                locked_bindings: std::collections::HashSet::new(),
58                active_bindings: FnvHashMap::default(),
59                promise_exprs: FnvHashMap::default(),
60            })),
61        }
62    }
63
64    pub fn new_child(parent: &Environment) -> Self {
65        trace!(
66            "new child env (parent: {})",
67            parent.name().as_deref().unwrap_or("<anonymous>")
68        );
69        Environment {
70            inner: Rc::new(RefCell::new(EnvInner {
71                bindings: FnvHashMap::default(),
72                parent: Some(parent.clone()),
73                name: None,
74                on_exit: Vec::new(),
75                locked: false,
76                locked_bindings: std::collections::HashSet::new(),
77                active_bindings: FnvHashMap::default(),
78                promise_exprs: FnvHashMap::default(),
79            })),
80        }
81    }
82
83    pub fn get(&self, name: &str) -> Option<RValue> {
84        let inner = self.inner.borrow();
85        if let Some(val) = inner.bindings.get(name) {
86            Some(val.clone())
87        } else if let Some(ref parent) = inner.parent {
88            parent.get(name)
89        } else {
90            None
91        }
92    }
93
94    pub fn set(&self, name: String, value: RValue) {
95        self.inner.borrow_mut().bindings.insert(name, value);
96    }
97
98    /// Super-assignment: assign in parent environment (<<-)
99    ///
100    /// Walks up the environment chain looking for an existing binding.
101    /// If found, overwrites it in place. If not found, creates the binding
102    /// in the global environment (not base) — R treats global as the
103    /// creation boundary for `<<-`.
104    pub fn set_super(&self, name: String, value: RValue) {
105        // R semantics for <<-: search parent chain starting from the current env's
106        // parent. If found, update in place; if not found, create in global env.
107        //
108        // At global level specifically, the parent is base env, which should never
109        // receive user bindings. This short-circuit is correct because:
110        // - `source(file, local=TRUE)` creates a child env (not global), so the
111        //   parent chain search handles it normally.
112        // - `local({ x <<- 1 })` also creates a child env of global.
113        // - Only direct <<- at the R prompt / top-level script hits this path.
114        if self.is_global() {
115            self.set(name, value);
116            return;
117        }
118        let inner = self.inner.borrow();
119        if let Some(ref parent) = inner.parent {
120            if parent.has_local(&name) {
121                parent.set(name, value);
122            } else if parent.is_global() {
123                // Reached global without finding the binding — create it here
124                parent.set(name, value);
125            } else {
126                parent.set_super(name, value);
127            }
128        } else {
129            // No parent at all (we ARE base) — set locally
130            drop(inner);
131            self.set(name, value);
132        }
133    }
134
135    /// Returns true if this is the global environment.
136    fn is_global(&self) -> bool {
137        self.inner.borrow().name.as_deref() == Some("R_GlobalEnv")
138    }
139
140    pub fn has_local(&self, name: &str) -> bool {
141        let inner = self.inner.borrow();
142        inner.bindings.contains_key(name) || inner.active_bindings.contains_key(name)
143    }
144
145    pub fn remove(&self, name: &str) -> bool {
146        let mut inner = self.inner.borrow_mut();
147        let removed_binding = inner.bindings.remove(name).is_some();
148        let removed_active = inner.active_bindings.remove(name).is_some();
149        removed_binding || removed_active
150    }
151
152    /// Register an expression to run when this frame exits (on.exit).
153    /// If `add` is false (default), replaces existing on.exit expressions.
154    /// If `after` is true (default), appends after existing; if false, prepends before.
155    pub fn push_on_exit(&self, expr: Expr, add: bool, after: bool) {
156        let mut inner = self.inner.borrow_mut();
157        if add {
158            if after {
159                inner.on_exit.push(expr);
160            } else {
161                inner.on_exit.insert(0, expr);
162            }
163        } else {
164            inner.on_exit = vec![expr];
165        }
166    }
167
168    /// Take all on.exit expressions (empties the list).
169    pub fn take_on_exit(&self) -> Vec<Expr> {
170        std::mem::take(&mut self.inner.borrow_mut().on_exit)
171    }
172
173    /// Return the currently registered on.exit expressions without clearing them.
174    pub fn peek_on_exit(&self) -> Vec<Expr> {
175        self.inner.borrow().on_exit.clone()
176    }
177
178    pub fn new_empty() -> Self {
179        Environment {
180            inner: Rc::new(RefCell::new(EnvInner {
181                bindings: FnvHashMap::default(),
182                parent: None,
183                name: Some("R_EmptyEnv".to_string()),
184                on_exit: Vec::new(),
185                locked: false,
186                locked_bindings: std::collections::HashSet::new(),
187                active_bindings: FnvHashMap::default(),
188                promise_exprs: FnvHashMap::default(),
189            })),
190        }
191    }
192
193    pub fn ls(&self) -> Vec<String> {
194        let inner = self.inner.borrow();
195        inner
196            .bindings
197            .keys()
198            .chain(inner.active_bindings.keys())
199            .cloned()
200            .sorted()
201            .collect()
202    }
203
204    /// Return all local (non-active) bindings as name-value pairs, sorted by name.
205    pub fn local_bindings(&self) -> Vec<(String, RValue)> {
206        let inner = self.inner.borrow();
207        inner
208            .bindings
209            .iter()
210            .map(|(k, v)| (k.clone(), v.clone()))
211            .sorted_by(|(a, _), (b, _)| a.cmp(b))
212            .collect()
213    }
214
215    pub fn name(&self) -> Option<String> {
216        self.inner.borrow().name.clone()
217    }
218
219    pub fn set_name(&self, name: String) {
220        self.inner.borrow_mut().name = Some(name);
221    }
222
223    pub fn parent(&self) -> Option<Environment> {
224        self.inner.borrow().parent.clone()
225    }
226
227    /// Set the parent (enclosing) environment.
228    pub fn set_parent(&self, parent: Option<Environment>) {
229        self.inner.borrow_mut().parent = parent;
230    }
231
232    /// Look up a name, skipping non-function values (like R's findFun).
233    /// This implements R's behavior where `c(1,2)` still calls the `c` function
234    /// even if `c` has been assigned a non-function value in the current env.
235    pub fn get_function(&self, name: &str) -> Option<RValue> {
236        let inner = self.inner.borrow();
237        if let Some(val) = inner.bindings.get(name) {
238            if matches!(val, RValue::Function(_)) {
239                return Some(val.clone());
240            }
241        }
242        if let Some(ref parent) = inner.parent {
243            parent.get_function(name)
244        } else {
245            None
246        }
247    }
248
249    /// Lock the environment so no new bindings can be added.
250    /// If `bindings` is true, also lock all existing bindings.
251    pub fn lock(&self, bindings: bool) {
252        let mut inner = self.inner.borrow_mut();
253        inner.locked = true;
254        if bindings {
255            let names: Vec<String> = inner.bindings.keys().cloned().collect();
256            for name in names {
257                inner.locked_bindings.insert(name);
258            }
259        }
260    }
261
262    /// Return whether this environment is locked.
263    pub fn is_locked(&self) -> bool {
264        self.inner.borrow().locked
265    }
266
267    /// Lock a specific binding in this environment.
268    pub fn lock_binding(&self, name: &str) {
269        self.inner
270            .borrow_mut()
271            .locked_bindings
272            .insert(name.to_string());
273    }
274
275    /// Return whether a specific binding is locked.
276    pub fn binding_is_locked(&self, name: &str) -> bool {
277        self.inner.borrow().locked_bindings.contains(name)
278    }
279
280    // region: Active bindings
281
282    /// Register an active binding: a zero-argument function called on every access.
283    pub fn set_active_binding(&self, name: String, fun: RValue) {
284        let mut inner = self.inner.borrow_mut();
285        // Remove any regular binding with the same name
286        inner.bindings.remove(&name);
287        inner.active_bindings.insert(name, fun);
288    }
289
290    /// Get the function for an active binding in this environment (local only).
291    pub fn get_local_active_binding(&self, name: &str) -> Option<RValue> {
292        self.inner.borrow().active_bindings.get(name).cloned()
293    }
294
295    /// Walk the environment chain looking for an active binding.
296    /// Returns the function if found.
297    pub fn get_active_binding(&self, name: &str) -> Option<RValue> {
298        let inner = self.inner.borrow();
299        if let Some(fun) = inner.active_bindings.get(name) {
300            Some(fun.clone())
301        } else if let Some(ref parent) = inner.parent {
302            parent.get_active_binding(name)
303        } else {
304            None
305        }
306    }
307
308    /// Check if a name is an active binding (walks the chain).
309    pub fn is_active_binding(&self, name: &str) -> bool {
310        let inner = self.inner.borrow();
311        if inner.active_bindings.contains_key(name) {
312            true
313        } else if let Some(ref parent) = inner.parent {
314            parent.is_active_binding(name)
315        } else {
316            false
317        }
318    }
319
320    /// Check if a name is a local active binding (does not walk the chain).
321    pub fn is_local_active_binding(&self, name: &str) -> bool {
322        self.inner.borrow().active_bindings.contains_key(name)
323    }
324
325    // endregion
326
327    // region: Promise expressions
328
329    /// Store the original unevaluated expression for a function parameter.
330    /// Used by `substitute()` to recover the source expression.
331    pub fn set_promise_expr(&self, name: String, expr: Expr) {
332        self.inner.borrow_mut().promise_exprs.insert(name, expr);
333    }
334
335    /// Get the original unevaluated expression for a function parameter.
336    /// Checks this environment only (local), not parents.
337    pub fn get_promise_expr(&self, name: &str) -> Option<Expr> {
338        self.inner.borrow().promise_exprs.get(name).cloned()
339    }
340
341    // endregion
342}