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 pub fn push_scope(&mut self) {
30 self.scopes.push(Frame::new());
31 }
32
33 pub fn pop_scope(&mut self) {
35 if self.scopes.len() > 1 {
36 self.scopes.pop();
37 }
38 }
39
40 pub fn define(&mut self, name: impl Into<String>, value: Value) {
42 if let Some(frame) = self.scopes.last_mut() {
43 frame.insert(name.into(), value);
44 }
45 }
46
47 #[must_use]
49 pub fn get(&self, name: &str) -> Option<&Value> {
50 for frame in self.scopes.iter().rev() {
51 if let Some(v) = frame.get(name) {
52 return Some(v);
53 }
54 }
55 None
56 }
57
58 #[must_use]
60 pub fn all_bindings(&self) -> Vec<(String, Value)> {
61 let mut seen = std::collections::HashSet::new();
62 let mut result = Vec::new();
63 for frame in self.scopes.iter().rev() {
64 for (name, value) in frame {
65 if seen.insert(name.clone()) {
66 result.push((name.clone(), value.clone()));
67 }
68 }
69 }
70 result.sort_by(|a, b| a.0.cmp(&b.0));
71 result
72 }
73
74 pub fn assign(&mut self, name: &str, value: Value) -> bool {
77 for frame in self.scopes.iter_mut().rev() {
78 if frame.contains_key(name) {
79 frame.insert(name.to_string(), value);
80 return true;
81 }
82 }
83 false
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Hash)]
89pub struct EffectOpKey {
90 pub effect: String,
92 pub operation: String,
94}
95
96type HandlerFrame = HashMap<String, Value>;
100
101#[derive(Debug, Clone, Default)]
108pub struct EffectStack {
109 local: Vec<HandlerFrame>,
112 module: HashMap<String, Value>,
114 project: HashMap<String, Value>,
116}
117
118impl EffectStack {
119 #[must_use]
121 pub fn new() -> Self {
122 Self {
123 local: Vec::new(),
124 module: HashMap::new(),
125 project: HashMap::new(),
126 }
127 }
128
129 #[must_use]
131 pub fn is_empty(&self) -> bool {
132 self.local.is_empty() && self.module.is_empty() && self.project.is_empty()
133 }
134
135 pub fn push_handlers(&mut self, handlers: HashMap<String, Value>) {
137 self.local.push(handlers);
138 }
139
140 pub fn pop_handlers(&mut self) {
142 self.local.pop();
143 }
144
145 pub fn set_module_handler(&mut self, effect_name: impl Into<String>, handler: Value) {
147 self.module.insert(effect_name.into(), handler);
148 }
149
150 pub fn set_project_handler(&mut self, effect_name: impl Into<String>, handler: Value) {
152 self.project.insert(effect_name.into(), handler);
153 }
154
155 #[must_use]
159 pub fn resolve(&self, effect_name: &str) -> Option<&Value> {
160 for frame in self.local.iter().rev() {
162 if let Some(handler) = frame.get(effect_name) {
163 return Some(handler);
164 }
165 }
166 if let Some(handler) = self.module.get(effect_name) {
168 return Some(handler);
169 }
170 self.project.get(effect_name)
172 }
173}
174
175#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn define_and_get() {
183 let mut env = Environment::new();
184 env.define("x", Value::Int(42));
185 assert_eq!(env.get("x"), Some(&Value::Int(42)));
186 }
187
188 #[test]
189 fn inner_scope_shadows_outer() {
190 let mut env = Environment::new();
191 env.define("x", Value::Int(1));
192 env.push_scope();
193 env.define("x", Value::Int(2));
194 assert_eq!(env.get("x"), Some(&Value::Int(2)));
195 env.pop_scope();
196 assert_eq!(env.get("x"), Some(&Value::Int(1)));
197 }
198
199 #[test]
200 fn lookup_outer_from_inner() {
201 let mut env = Environment::new();
202 env.define("y", Value::Bool(true));
203 env.push_scope();
204 assert_eq!(env.get("y"), Some(&Value::Bool(true)));
205 env.pop_scope();
206 }
207
208 #[test]
209 fn assign_updates_nearest_binding() {
210 let mut env = Environment::new();
211 env.define("z", Value::Int(0));
212 env.push_scope();
213 let updated = env.assign("z", Value::Int(99));
214 assert!(updated);
215 env.pop_scope();
216 assert_eq!(env.get("z"), Some(&Value::Int(99)));
217 }
218
219 #[test]
220 fn assign_returns_false_for_unknown() {
221 let mut env = Environment::new();
222 assert!(!env.assign("nope", Value::Void));
223 }
224
225 #[test]
226 fn pop_global_scope_is_noop() {
227 let mut env = Environment::new();
228 env.define("k", Value::Int(7));
229 env.pop_scope(); assert_eq!(env.get("k"), Some(&Value::Int(7)));
231 }
232
233 #[test]
236 fn effect_stack_resolve_returns_none_when_empty() {
237 let stack = EffectStack::new();
238 assert!(stack.resolve("Log").is_none());
239 }
240
241 #[test]
242 fn effect_stack_project_layer() {
243 let mut stack = EffectStack::new();
244 stack.set_project_handler("Log", Value::Int(1));
245 assert_eq!(stack.resolve("Log"), Some(&Value::Int(1)));
246 }
247
248 #[test]
249 fn effect_stack_module_overrides_project() {
250 let mut stack = EffectStack::new();
251 stack.set_project_handler("Log", Value::Int(1));
252 stack.set_module_handler("Log", Value::Int(2));
253 assert_eq!(stack.resolve("Log"), Some(&Value::Int(2)));
254 }
255
256 #[test]
257 fn effect_stack_local_overrides_module() {
258 let mut stack = EffectStack::new();
259 stack.set_module_handler("Log", Value::Int(1));
260 let mut frame = HashMap::new();
261 frame.insert("Log".to_string(), Value::Int(2));
262 stack.push_handlers(frame);
263 assert_eq!(stack.resolve("Log"), Some(&Value::Int(2)));
264 }
265
266 #[test]
267 fn effect_stack_innermost_local_wins() {
268 let mut stack = EffectStack::new();
269 let mut frame1 = HashMap::new();
270 frame1.insert("Log".to_string(), Value::Int(1));
271 stack.push_handlers(frame1);
272
273 let mut frame2 = HashMap::new();
274 frame2.insert("Log".to_string(), Value::Int(2));
275 stack.push_handlers(frame2);
276
277 assert_eq!(stack.resolve("Log"), Some(&Value::Int(2)));
278 }
279
280 #[test]
281 fn effect_stack_pop_restores_outer() {
282 let mut stack = EffectStack::new();
283 let mut frame1 = HashMap::new();
284 frame1.insert("Log".to_string(), Value::Int(1));
285 stack.push_handlers(frame1);
286
287 let mut frame2 = HashMap::new();
288 frame2.insert("Log".to_string(), Value::Int(2));
289 stack.push_handlers(frame2);
290
291 stack.pop_handlers();
292 assert_eq!(stack.resolve("Log"), Some(&Value::Int(1)));
293 }
294
295 #[test]
296 fn effect_stack_different_effects_in_same_frame() {
297 let mut stack = EffectStack::new();
298 let mut frame = HashMap::new();
299 frame.insert("Log".to_string(), Value::Int(1));
300 frame.insert("Clock".to_string(), Value::Int(2));
301 stack.push_handlers(frame);
302 assert_eq!(stack.resolve("Log"), Some(&Value::Int(1)));
303 assert_eq!(stack.resolve("Clock"), Some(&Value::Int(2)));
304 }
305
306 #[test]
307 fn effect_stack_local_falls_through_to_module() {
308 let mut stack = EffectStack::new();
309 stack.set_module_handler("Clock", Value::Int(10));
310 let mut frame = HashMap::new();
311 frame.insert("Log".to_string(), Value::Int(1));
312 stack.push_handlers(frame);
313 assert_eq!(stack.resolve("Log"), Some(&Value::Int(1)));
315 assert_eq!(stack.resolve("Clock"), Some(&Value::Int(10)));
317 }
318}