blots_core/
environment.rs

1use crate::values::Value;
2use std::cell::RefCell;
3use std::collections::HashMap;
4use std::rc::Rc;
5
6#[derive(Debug)]
7enum LocalBindings {
8    Owned(RefCell<HashMap<String, Value>>),
9    Shared(Rc<HashMap<String, Value>>),
10}
11
12/// A scope chain environment for variable bindings.
13///
14/// Instead of cloning the entire HashMap on each function call,
15/// we create a linked chain of scopes. Lookups walk the chain,
16/// but creating a new scope is O(1).
17#[derive(Debug)]
18pub struct Environment {
19    /// Local bindings in this scope
20    local: LocalBindings,
21    /// Parent scope (if any)
22    parent: Option<Rc<Environment>>,
23}
24
25impl Environment {
26    /// Create a new root environment with no parent
27    pub fn new() -> Self {
28        Environment {
29            local: LocalBindings::Owned(RefCell::new(HashMap::new())),
30            parent: None,
31        }
32    }
33
34    /// Create a new root environment with initial bindings
35    pub fn with_bindings(bindings: HashMap<String, Value>) -> Self {
36        Environment {
37            local: LocalBindings::Owned(RefCell::new(bindings)),
38            parent: None,
39        }
40    }
41
42    /// Create a child environment that inherits from this one
43    pub fn extend(parent: Rc<Environment>) -> Self {
44        Environment {
45            local: LocalBindings::Owned(RefCell::new(HashMap::new())),
46            parent: Some(parent),
47        }
48    }
49
50    /// Create a child environment with initial local bindings
51    pub fn extend_with(parent: Rc<Environment>, local: HashMap<String, Value>) -> Self {
52        Environment {
53            local: LocalBindings::Owned(RefCell::new(local)),
54            parent: Some(parent),
55        }
56    }
57
58    /// Create a child environment with shared local bindings
59    pub fn extend_shared(parent: Rc<Environment>, local: Rc<HashMap<String, Value>>) -> Self {
60        Environment {
61            local: LocalBindings::Shared(local),
62            parent: Some(parent),
63        }
64    }
65
66    /// Look up a variable, walking the scope chain
67    pub fn get(&self, key: &str) -> Option<Value> {
68        // Check local scope first
69        let local_value = match &self.local {
70            LocalBindings::Owned(map) => map.borrow().get(key).copied(),
71            LocalBindings::Shared(map) => map.get(key).copied(),
72        };
73        if let Some(value) = local_value {
74            return Some(value);
75        }
76        // Then check parent scope
77        if let Some(parent) = &self.parent {
78            return parent.get(key);
79        }
80        None
81    }
82
83    /// Insert or update a binding in the local scope
84    pub fn insert(&self, key: String, value: Value) {
85        match &self.local {
86            LocalBindings::Owned(map) => {
87                map.borrow_mut().insert(key, value);
88            }
89            LocalBindings::Shared(_) => {
90                panic!("cannot insert into shared environment");
91            }
92        }
93    }
94
95    /// Check if a key exists in any scope
96    pub fn contains_key(&self, key: &str) -> bool {
97        let contains_local = match &self.local {
98            LocalBindings::Owned(map) => map.borrow().contains_key(key),
99            LocalBindings::Shared(map) => map.contains_key(key),
100        };
101        if contains_local {
102            return true;
103        }
104        if let Some(parent) = &self.parent {
105            return parent.contains_key(key);
106        }
107        false
108    }
109
110    /// Check if a key exists in the local scope only
111    pub fn contains_key_local(&self, key: &str) -> bool {
112        match &self.local {
113            LocalBindings::Owned(map) => map.borrow().contains_key(key),
114            LocalBindings::Shared(map) => map.contains_key(key),
115        }
116    }
117}
118
119impl Default for Environment {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125impl Clone for Environment {
126    fn clone(&self) -> Self {
127        // For closures that capture scope, we need to clone
128        // This flattens the scope chain into a single HashMap
129        Environment {
130            local: LocalBindings::Owned(RefCell::new(self.flatten())),
131            parent: None,
132        }
133    }
134}
135
136impl Environment {
137    /// Flatten the scope chain into a single HashMap
138    /// Used when capturing variables for closures or serialization
139    pub fn flatten(&self) -> HashMap<String, Value> {
140        let mut result = HashMap::new();
141        self.flatten_into(&mut result);
142        result
143    }
144
145    fn flatten_into(&self, result: &mut HashMap<String, Value>) {
146        // First add parent bindings (so local can override)
147        if let Some(parent) = &self.parent {
148            parent.flatten_into(result);
149        }
150        // Then add local bindings
151        match &self.local {
152            LocalBindings::Owned(map) => {
153                for (key, value) in map.borrow().iter() {
154                    result.insert(key.clone(), *value);
155                }
156            }
157            LocalBindings::Shared(map) => {
158                for (key, value) in map.iter() {
159                    result.insert(key.clone(), *value);
160                }
161            }
162        }
163    }
164
165    /// Iterate over all bindings (flattened)
166    /// Useful for serialization
167    pub fn iter(&self) -> impl Iterator<Item = (String, Value)> {
168        self.flatten().into_iter()
169    }
170
171    /// Get all keys (flattened)
172    /// Useful for completion
173    pub fn keys(&self) -> impl Iterator<Item = String> {
174        self.flatten().into_keys()
175    }
176}