devalang_wasm/language/scope/
variables.rs

1use crate::language::syntax::ast::Value;
2use std::collections::HashMap;
3
4/// Type of variable binding
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum BindingType {
7    /// let: Block-scoped, can be reassigned
8    Let,
9    /// var: Function/global-scoped, can be reassigned
10    Var,
11    /// const: Block-scoped, cannot be reassigned
12    Const,
13}
14
15/// Variable binding with metadata
16#[derive(Debug, Clone, PartialEq)]
17pub struct Binding {
18    pub value: Value,
19    pub binding_type: BindingType,
20    pub is_initialized: bool,
21}
22
23impl Binding {
24    pub fn new(value: Value, binding_type: BindingType) -> Self {
25        Self {
26            value,
27            binding_type,
28            is_initialized: true,
29        }
30    }
31
32    pub fn can_reassign(&self) -> bool {
33        matches!(self.binding_type, BindingType::Let | BindingType::Var)
34    }
35}
36
37/// Variable table with hierarchical scoping
38#[derive(Debug, Clone, PartialEq)]
39pub struct VariableTable {
40    /// Variables in current scope
41    bindings: HashMap<String, Binding>,
42    /// Parent scope (if any)
43    parent: Option<Box<VariableTable>>,
44}
45
46impl Default for VariableTable {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl VariableTable {
53    /// Create new empty variable table
54    pub fn new() -> Self {
55        Self {
56            bindings: HashMap::new(),
57            parent: None,
58        }
59    }
60
61    /// Create new variable table with parent scope
62    pub fn with_parent(parent: VariableTable) -> Self {
63        Self {
64            bindings: HashMap::new(),
65            parent: Some(Box::new(parent)),
66        }
67    }
68
69    /// Set variable with default binding type (Let)
70    pub fn set(&mut self, name: String, value: Value) {
71        self.set_with_type(name, value, BindingType::Let);
72    }
73
74    /// Set variable with specific binding type
75    pub fn set_with_type(&mut self, name: String, value: Value, binding_type: BindingType) {
76        // For 'var', check if it exists in parent scopes and update there
77        if binding_type == BindingType::Var {
78            if self.has_var_in_chain(&name) {
79                self.update_var_in_chain(name, value);
80                return;
81            }
82        }
83
84        self.bindings
85            .insert(name, Binding::new(value, binding_type));
86    }
87
88    /// Update existing variable (respecting const)
89    pub fn update(&mut self, name: &str, value: Value) -> Result<(), String> {
90        // Try to update in current scope
91        if let Some(binding) = self.bindings.get_mut(name) {
92            if !binding.can_reassign() {
93                return Err(format!("Cannot reassign const variable '{}'", name));
94            }
95            binding.value = value;
96            return Ok(());
97        }
98
99        // Try to update in parent scope
100        if let Some(parent) = &mut self.parent {
101            return parent.update(name, value);
102        }
103
104        Err(format!("Variable '{}' not found", name))
105    }
106
107    /// Get variable value (searches up the scope chain)
108    pub fn get(&self, name: &str) -> Option<&Value> {
109        if let Some(binding) = self.bindings.get(name) {
110            return Some(&binding.value);
111        }
112
113        // Search in parent scopes
114        let mut current = &self.parent;
115        while let Some(boxed) = current {
116            if let Some(binding) = boxed.bindings.get(name) {
117                return Some(&binding.value);
118            }
119            current = &boxed.parent;
120        }
121
122        None
123    }
124
125    /// Get binding (with metadata)
126    pub fn get_binding(&self, name: &str) -> Option<&Binding> {
127        if let Some(binding) = self.bindings.get(name) {
128            return Some(binding);
129        }
130
131        let mut current = &self.parent;
132        while let Some(boxed) = current {
133            if let Some(binding) = boxed.bindings.get(name) {
134                return Some(binding);
135            }
136            current = &boxed.parent;
137        }
138
139        None
140    }
141
142    /// Check if variable exists in current scope (not parent)
143    pub fn has_local(&self, name: &str) -> bool {
144        self.bindings.contains_key(name)
145    }
146
147    /// Check if variable exists in any scope
148    pub fn has(&self, name: &str) -> bool {
149        self.get(name).is_some()
150    }
151
152    /// Remove variable from current scope
153    pub fn remove(&mut self, name: &str) -> Option<Value> {
154        self.bindings.remove(name).map(|b| b.value)
155    }
156
157    /// Get all variables in current scope (for debugging)
158    pub fn local_variables(&self) -> Vec<String> {
159        self.bindings.keys().cloned().collect()
160    }
161
162    /// Check if 'var' exists in this or parent scopes
163    fn has_var_in_chain(&self, name: &str) -> bool {
164        if let Some(binding) = self.bindings.get(name) {
165            return binding.binding_type == BindingType::Var;
166        }
167
168        if let Some(parent) = &self.parent {
169            return parent.has_var_in_chain(name);
170        }
171
172        false
173    }
174
175    /// Update 'var' in the scope where it was defined
176    fn update_var_in_chain(&mut self, name: String, value: Value) {
177        if let Some(binding) = self.bindings.get_mut(&name) {
178            if binding.binding_type == BindingType::Var {
179                binding.value = value;
180                return;
181            }
182        }
183
184        if let Some(parent) = &mut self.parent {
185            parent.update_var_in_chain(name, value);
186        }
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_basic_let() {
196        let mut table = VariableTable::new();
197        table.set_with_type("x".to_string(), Value::Number(42.0), BindingType::Let);
198        assert_eq!(table.get("x"), Some(&Value::Number(42.0)));
199    }
200
201    #[test]
202    fn test_const_cannot_reassign() {
203        let mut table = VariableTable::new();
204        table.set_with_type("x".to_string(), Value::Number(42.0), BindingType::Const);
205
206        let result = table.update("x", Value::Number(100.0));
207        assert!(result.is_err());
208        assert_eq!(table.get("x"), Some(&Value::Number(42.0)));
209    }
210
211    #[test]
212    fn test_let_can_reassign() {
213        let mut table = VariableTable::new();
214        table.set_with_type("x".to_string(), Value::Number(42.0), BindingType::Let);
215
216        let result = table.update("x", Value::Number(100.0));
217        assert!(result.is_ok());
218        assert_eq!(table.get("x"), Some(&Value::Number(100.0)));
219    }
220
221    #[test]
222    fn test_scoped_access() {
223        let mut parent = VariableTable::new();
224        parent.set("global".to_string(), Value::Number(1.0));
225
226        let mut child = VariableTable::with_parent(parent);
227        child.set("local".to_string(), Value::Number(2.0));
228
229        assert_eq!(child.get("local"), Some(&Value::Number(2.0)));
230        assert_eq!(child.get("global"), Some(&Value::Number(1.0)));
231    }
232
233    #[test]
234    fn test_var_hoisting() {
235        let mut parent = VariableTable::new();
236        parent.set_with_type("x".to_string(), Value::Number(1.0), BindingType::Var);
237
238        let mut child = VariableTable::with_parent(parent);
239        child.set_with_type("x".to_string(), Value::Number(2.0), BindingType::Var);
240
241        // Verify child has access to the updated var
242        assert_eq!(child.get("x"), Some(&Value::Number(2.0)));
243
244        // Extract parent and verify it was updated
245        if let Some(parent_box) = child.parent {
246            assert_eq!(parent_box.get("x"), Some(&Value::Number(2.0)));
247        } else {
248            panic!("Test error: Parent should exist in this test scenario");
249        }
250    }
251
252    #[test]
253    fn test_shadowing() {
254        let mut parent = VariableTable::new();
255        parent.set("x".to_string(), Value::Number(1.0));
256
257        let mut child = VariableTable::with_parent(parent.clone());
258        child.set("x".to_string(), Value::Number(2.0));
259
260        assert_eq!(child.get("x"), Some(&Value::Number(2.0)));
261        assert_eq!(parent.get("x"), Some(&Value::Number(1.0)));
262    }
263}