1use std::collections::HashMap;
4
5use crate::value::Value;
6
7type Frame = HashMap<String, Value>;
9
10#[derive(Debug, Clone, Default)]
15pub struct Environment {
16 scopes: Vec<Frame>,
17}
18
19impl Environment {
20 #[must_use]
22 pub fn new() -> Self {
23 Self {
24 scopes: vec![Frame::new()],
25 }
26 }
27
28 #[must_use]
36 pub fn with_globals(source: &Environment) -> Self {
37 let root = source.scopes.first().cloned().unwrap_or_default();
38 Self { scopes: vec![root] }
39 }
40
41 pub fn push_scope(&mut self) {
43 self.scopes.push(Frame::new());
44 }
45
46 pub fn pop_scope(&mut self) {
48 if self.scopes.len() > 1 {
49 self.scopes.pop();
50 }
51 }
52
53 pub fn define(&mut self, name: impl Into<String>, value: Value) {
55 if let Some(frame) = self.scopes.last_mut() {
56 frame.insert(name.into(), value);
57 }
58 }
59
60 #[must_use]
62 pub fn get(&self, name: &str) -> Option<&Value> {
63 for frame in self.scopes.iter().rev() {
64 if let Some(v) = frame.get(name) {
65 return Some(v);
66 }
67 }
68 None
69 }
70
71 #[must_use]
73 pub fn all_bindings(&self) -> Vec<(String, Value)> {
74 let mut seen = std::collections::HashSet::new();
75 let mut result = Vec::new();
76 for frame in self.scopes.iter().rev() {
77 for (name, value) in frame {
78 if seen.insert(name.clone()) {
79 result.push((name.clone(), value.clone()));
80 }
81 }
82 }
83 result.sort_by(|a, b| a.0.cmp(&b.0));
84 result
85 }
86
87 pub fn assign(&mut self, name: &str, value: Value) -> bool {
90 for frame in self.scopes.iter_mut().rev() {
91 if frame.contains_key(name) {
92 frame.insert(name.to_string(), value);
93 return true;
94 }
95 }
96 false
97 }
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
102pub struct EffectOpKey {
103 pub effect: String,
105 pub operation: String,
107}
108
109type HandlerFrame = HashMap<String, Value>;
113
114#[derive(Debug, Clone, Default)]
121pub struct EffectStack {
122 local: Vec<HandlerFrame>,
125 module: HashMap<String, Value>,
127 project: HashMap<String, Value>,
129}
130
131impl EffectStack {
132 #[must_use]
134 pub fn new() -> Self {
135 Self {
136 local: Vec::new(),
137 module: HashMap::new(),
138 project: HashMap::new(),
139 }
140 }
141
142 #[must_use]
144 pub fn is_empty(&self) -> bool {
145 self.local.is_empty() && self.module.is_empty() && self.project.is_empty()
146 }
147
148 pub fn push_handlers(&mut self, handlers: HashMap<String, Value>) {
150 self.local.push(handlers);
151 }
152
153 pub fn pop_handlers(&mut self) {
155 self.local.pop();
156 }
157
158 pub fn set_module_handler(&mut self, effect_name: impl Into<String>, handler: Value) {
160 self.module.insert(effect_name.into(), handler);
161 }
162
163 pub fn set_project_handler(&mut self, effect_name: impl Into<String>, handler: Value) {
165 self.project.insert(effect_name.into(), handler);
166 }
167
168 #[must_use]
172 pub fn resolve(&self, effect_name: &str) -> Option<&Value> {
173 for frame in self.local.iter().rev() {
175 if let Some(handler) = frame.get(effect_name) {
176 return Some(handler);
177 }
178 }
179 if let Some(handler) = self.module.get(effect_name) {
181 return Some(handler);
182 }
183 self.project.get(effect_name)
185 }
186}
187
188#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn define_and_get() {
196 let mut env = Environment::new();
197 env.define("x", Value::Int(42));
198 assert_eq!(env.get("x"), Some(&Value::Int(42)));
199 }
200
201 #[test]
202 fn inner_scope_shadows_outer() {
203 let mut env = Environment::new();
204 env.define("x", Value::Int(1));
205 env.push_scope();
206 env.define("x", Value::Int(2));
207 assert_eq!(env.get("x"), Some(&Value::Int(2)));
208 env.pop_scope();
209 assert_eq!(env.get("x"), Some(&Value::Int(1)));
210 }
211
212 #[test]
213 fn lookup_outer_from_inner() {
214 let mut env = Environment::new();
215 env.define("y", Value::Bool(true));
216 env.push_scope();
217 assert_eq!(env.get("y"), Some(&Value::Bool(true)));
218 env.pop_scope();
219 }
220
221 #[test]
222 fn assign_updates_nearest_binding() {
223 let mut env = Environment::new();
224 env.define("z", Value::Int(0));
225 env.push_scope();
226 let updated = env.assign("z", Value::Int(99));
227 assert!(updated);
228 env.pop_scope();
229 assert_eq!(env.get("z"), Some(&Value::Int(99)));
230 }
231
232 #[test]
233 fn assign_returns_false_for_unknown() {
234 let mut env = Environment::new();
235 assert!(!env.assign("nope", Value::Void));
236 }
237
238 #[test]
239 fn pop_global_scope_is_noop() {
240 let mut env = Environment::new();
241 env.define("k", Value::Int(7));
242 env.pop_scope(); assert_eq!(env.get("k"), Some(&Value::Int(7)));
244 }
245
246 #[test]
249 fn effect_stack_resolve_returns_none_when_empty() {
250 let stack = EffectStack::new();
251 assert!(stack.resolve("Log").is_none());
252 }
253
254 #[test]
255 fn effect_stack_project_layer() {
256 let mut stack = EffectStack::new();
257 stack.set_project_handler("Log", Value::Int(1));
258 assert_eq!(stack.resolve("Log"), Some(&Value::Int(1)));
259 }
260
261 #[test]
262 fn effect_stack_module_overrides_project() {
263 let mut stack = EffectStack::new();
264 stack.set_project_handler("Log", Value::Int(1));
265 stack.set_module_handler("Log", Value::Int(2));
266 assert_eq!(stack.resolve("Log"), Some(&Value::Int(2)));
267 }
268
269 #[test]
270 fn effect_stack_local_overrides_module() {
271 let mut stack = EffectStack::new();
272 stack.set_module_handler("Log", Value::Int(1));
273 let mut frame = HashMap::new();
274 frame.insert("Log".to_string(), Value::Int(2));
275 stack.push_handlers(frame);
276 assert_eq!(stack.resolve("Log"), Some(&Value::Int(2)));
277 }
278
279 #[test]
280 fn effect_stack_innermost_local_wins() {
281 let mut stack = EffectStack::new();
282 let mut frame1 = HashMap::new();
283 frame1.insert("Log".to_string(), Value::Int(1));
284 stack.push_handlers(frame1);
285
286 let mut frame2 = HashMap::new();
287 frame2.insert("Log".to_string(), Value::Int(2));
288 stack.push_handlers(frame2);
289
290 assert_eq!(stack.resolve("Log"), Some(&Value::Int(2)));
291 }
292
293 #[test]
294 fn effect_stack_pop_restores_outer() {
295 let mut stack = EffectStack::new();
296 let mut frame1 = HashMap::new();
297 frame1.insert("Log".to_string(), Value::Int(1));
298 stack.push_handlers(frame1);
299
300 let mut frame2 = HashMap::new();
301 frame2.insert("Log".to_string(), Value::Int(2));
302 stack.push_handlers(frame2);
303
304 stack.pop_handlers();
305 assert_eq!(stack.resolve("Log"), Some(&Value::Int(1)));
306 }
307
308 #[test]
309 fn effect_stack_different_effects_in_same_frame() {
310 let mut stack = EffectStack::new();
311 let mut frame = HashMap::new();
312 frame.insert("Log".to_string(), Value::Int(1));
313 frame.insert("Clock".to_string(), Value::Int(2));
314 stack.push_handlers(frame);
315 assert_eq!(stack.resolve("Log"), Some(&Value::Int(1)));
316 assert_eq!(stack.resolve("Clock"), Some(&Value::Int(2)));
317 }
318
319 #[test]
320 fn effect_stack_local_falls_through_to_module() {
321 let mut stack = EffectStack::new();
322 stack.set_module_handler("Clock", Value::Int(10));
323 let mut frame = HashMap::new();
324 frame.insert("Log".to_string(), Value::Int(1));
325 stack.push_handlers(frame);
326 assert_eq!(stack.resolve("Log"), Some(&Value::Int(1)));
328 assert_eq!(stack.resolve("Clock"), Some(&Value::Int(10)));
330 }
331}