Skip to main content

pepl_eval/
env.rs

1//! Scoped variable environment for the PEPL evaluator.
2
3use pepl_stdlib::Value;
4use std::collections::BTreeMap;
5
6/// A single scope level.
7#[derive(Debug, Clone)]
8struct Scope {
9    bindings: BTreeMap<String, Value>,
10}
11
12impl Scope {
13    fn new() -> Self {
14        Self {
15            bindings: BTreeMap::new(),
16        }
17    }
18}
19
20/// Scoped variable environment with push/pop semantics.
21///
22/// Variables are looked up from innermost scope outward.
23/// `define` always creates in the current (innermost) scope.
24/// `set` updates the first scope where the variable exists.
25#[derive(Debug, Clone)]
26pub struct Environment {
27    scopes: Vec<Scope>,
28}
29
30impl Environment {
31    /// Create a new environment with one global scope.
32    pub fn new() -> Self {
33        Self {
34            scopes: vec![Scope::new()],
35        }
36    }
37
38    /// Push a new scope (for action bodies, let blocks, etc.).
39    pub fn push_scope(&mut self) {
40        self.scopes.push(Scope::new());
41    }
42
43    /// Pop the innermost scope.
44    pub fn pop_scope(&mut self) {
45        if self.scopes.len() > 1 {
46            self.scopes.pop();
47        }
48    }
49
50    /// Define a variable in the current (innermost) scope.
51    pub fn define(&mut self, name: &str, value: Value) {
52        if let Some(scope) = self.scopes.last_mut() {
53            scope.bindings.insert(name.to_string(), value);
54        }
55    }
56
57    /// Look up a variable, searching from innermost to outermost scope.
58    pub fn get(&self, name: &str) -> Option<&Value> {
59        for scope in self.scopes.iter().rev() {
60            if let Some(v) = scope.bindings.get(name) {
61                return Some(v);
62            }
63        }
64        None
65    }
66
67    /// Update a variable in the first scope where it exists.
68    /// Returns `true` if found and updated, `false` if not found.
69    pub fn set(&mut self, name: &str, value: Value) -> bool {
70        for scope in self.scopes.iter_mut().rev() {
71            if scope.bindings.contains_key(name) {
72                scope.bindings.insert(name.to_string(), value);
73                return true;
74            }
75        }
76        false
77    }
78
79    /// Get all bindings in the global (outermost) scope.
80    /// Used for capturing state.
81    pub fn global_bindings(&self) -> &BTreeMap<String, Value> {
82        &self.scopes[0].bindings
83    }
84
85    /// Replace all bindings in the global scope (for rollback).
86    pub fn restore_global(&mut self, bindings: BTreeMap<String, Value>) {
87        self.scopes[0].bindings = bindings;
88    }
89}
90
91impl Default for Environment {
92    fn default() -> Self {
93        Self::new()
94    }
95}