devalang_wasm/language/scope/
variables.rs1use crate::language::syntax::ast::Value;
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum BindingType {
7 Let,
9 Var,
11 Const,
13}
14
15#[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#[derive(Debug, Clone, PartialEq)]
39pub struct VariableTable {
40 bindings: HashMap<String, Binding>,
42 parent: Option<Box<VariableTable>>,
44}
45
46impl Default for VariableTable {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl VariableTable {
53 pub fn new() -> Self {
55 Self {
56 bindings: HashMap::new(),
57 parent: None,
58 }
59 }
60
61 pub fn with_parent(parent: VariableTable) -> Self {
63 Self {
64 bindings: HashMap::new(),
65 parent: Some(Box::new(parent)),
66 }
67 }
68
69 pub fn set(&mut self, name: String, value: Value) {
71 self.set_with_type(name, value, BindingType::Let);
72 }
73
74 pub fn set_with_type(&mut self, name: String, value: Value, binding_type: BindingType) {
76 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 pub fn update(&mut self, name: &str, value: Value) -> Result<(), String> {
90 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 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 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 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 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 pub fn has_local(&self, name: &str) -> bool {
144 self.bindings.contains_key(name)
145 }
146
147 pub fn has(&self, name: &str) -> bool {
149 self.get(name).is_some()
150 }
151
152 pub fn remove(&mut self, name: &str) -> Option<Value> {
154 self.bindings.remove(name).map(|b| b.value)
155 }
156
157 pub fn local_variables(&self) -> Vec<String> {
159 self.bindings.keys().cloned().collect()
160 }
161
162 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 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 assert_eq!(child.get("x"), Some(&Value::Number(2.0)));
243
244 if let Some(parent_box) = child.parent {
246 assert_eq!(parent_box.get("x"), Some(&Value::Number(2.0)));
247 } else {
248 panic!("Parent should exist");
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}