Skip to main content

tatara_eval/
env.rs

1//! Scoped environment — parent-pointer tree of bindings.
2//!
3//! Cheap to clone (Arc-backed). Extension is non-destructive: `extend` returns
4//! a new `Env` whose parent is `self`. That keeps closures immutable and
5//! removes a class of aliasing bugs that plague mutable interpreters.
6
7use std::collections::BTreeMap;
8use std::sync::Arc;
9
10use crate::value::Value;
11
12#[derive(Clone, Default)]
13pub struct Env {
14    inner: Arc<EnvFrame>,
15}
16
17struct EnvFrame {
18    bindings: BTreeMap<String, Value>,
19    parent: Option<Arc<EnvFrame>>,
20}
21
22impl Default for EnvFrame {
23    fn default() -> Self {
24        Self {
25            bindings: BTreeMap::new(),
26            parent: None,
27        }
28    }
29}
30
31impl Env {
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    pub fn lookup(&self, name: &str) -> Option<Value> {
37        let mut cursor = Some(&self.inner);
38        while let Some(frame) = cursor {
39            if let Some(v) = frame.bindings.get(name) {
40                return Some(v.clone());
41            }
42            cursor = frame.parent.as_ref();
43        }
44        None
45    }
46
47    /// Add one binding on top. Returns a new Env (immutable API).
48    pub fn extend(&self, name: impl Into<String>, value: Value) -> Self {
49        let mut bindings = BTreeMap::new();
50        bindings.insert(name.into(), value);
51        Self {
52            inner: Arc::new(EnvFrame {
53                bindings,
54                parent: Some(self.inner.clone()),
55            }),
56        }
57    }
58
59    /// Add many bindings on top in one frame.
60    pub fn extend_many<I, K, V>(&self, it: I) -> Self
61    where
62        I: IntoIterator<Item = (K, V)>,
63        K: Into<String>,
64        V: Into<Value>,
65    {
66        let mut bindings = BTreeMap::new();
67        for (k, v) in it {
68            bindings.insert(k.into(), v.into());
69        }
70        Self {
71            inner: Arc::new(EnvFrame {
72                bindings,
73                parent: Some(self.inner.clone()),
74            }),
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn lookup_walks_parent_chain() {
85        let e0 = Env::new().extend("x", Value::Int(1));
86        let e1 = e0.extend("y", Value::Int(2));
87        assert!(matches!(e1.lookup("x"), Some(Value::Int(1))));
88        assert!(matches!(e1.lookup("y"), Some(Value::Int(2))));
89        assert!(e1.lookup("z").is_none());
90    }
91
92    #[test]
93    fn extend_is_non_destructive() {
94        let e0 = Env::new().extend("x", Value::Int(1));
95        let _e1 = e0.extend("x", Value::Int(2));
96        // e0 still sees the original binding
97        assert!(matches!(e0.lookup("x"), Some(Value::Int(1))));
98    }
99}