1use std::collections::HashMap;
5
6use reifydb_core::value::column::Columns;
7use reifydb_type::{Value, diagnostic, error};
8
9#[derive(Debug, Clone)]
11pub enum Variable {
12 Scalar(Value),
14 Frame(Columns<'static>),
16}
17
18impl Variable {
19 pub fn scalar(value: Value) -> Self {
21 Variable::Scalar(value)
22 }
23
24 pub fn frame(columns: Columns<'static>) -> Self {
26 Variable::Frame(columns)
27 }
28
29 pub fn as_scalar(&self) -> Option<&Value> {
31 match self {
32 Variable::Scalar(value) => Some(value),
33 Variable::Frame(_) => None,
34 }
35 }
36
37 pub fn as_frame(&self) -> Option<&Columns<'static>> {
39 match self {
40 Variable::Scalar(_) => None,
41 Variable::Frame(columns) => Some(columns),
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
48pub struct Stack {
49 scopes: Vec<Scope>,
50}
51
52#[derive(Debug, Clone)]
54struct Scope {
55 variables: HashMap<String, VariableBinding>,
56 scope_type: ScopeType,
57}
58
59#[derive(Debug, Clone, PartialEq)]
61pub enum ScopeType {
62 Global,
63 Function,
64 Block,
65 Conditional,
66}
67
68#[derive(Debug, Clone)]
70struct VariableBinding {
71 variable: Variable,
72 mutable: bool,
73}
74
75impl Stack {
76 pub fn new() -> Self {
78 let global_scope = Scope {
79 variables: HashMap::new(),
80 scope_type: ScopeType::Global,
81 };
82
83 Self {
84 scopes: vec![global_scope],
85 }
86 }
87
88 pub fn enter_scope(&mut self, scope_type: ScopeType) {
90 let new_scope = Scope {
91 variables: HashMap::new(),
92 scope_type,
93 };
94 self.scopes.push(new_scope);
95 }
96
97 pub fn exit_scope(&mut self) -> crate::Result<()> {
100 if self.scopes.len() <= 1 {
101 return Err(error!(diagnostic::internal::internal("Cannot exit global scope".to_string())));
102 }
103 self.scopes.pop();
104 Ok(())
105 }
106
107 pub fn scope_depth(&self) -> usize {
109 self.scopes.len() - 1
110 }
111
112 pub fn current_scope_type(&self) -> &ScopeType {
114 &self.scopes.last().unwrap().scope_type
115 }
116
117 pub fn set(&mut self, name: String, variable: Variable, mutable: bool) -> crate::Result<()> {
119 self.set_in_current_scope(name, variable, mutable)
120 }
121
122 pub fn reassign(&mut self, name: String, variable: Variable) -> crate::Result<()> {
124 let current_scope = self.scopes.last_mut().unwrap();
125
126 if let Some(existing) = current_scope.variables.get(&name) {
128 if !existing.mutable {
129 return Err(reifydb_type::Error(
130 reifydb_type::diagnostic::runtime::variable_is_immutable(&name),
131 ));
132 }
133 current_scope.variables.insert(
135 name,
136 VariableBinding {
137 variable,
138 mutable: existing.mutable,
139 },
140 );
141 } else {
142 return Err(reifydb_type::Error(reifydb_type::diagnostic::runtime::variable_not_found(&name)));
143 }
144
145 Ok(())
146 }
147
148 pub fn set_in_current_scope(&mut self, name: String, variable: Variable, mutable: bool) -> crate::Result<()> {
151 let current_scope = self.scopes.last_mut().unwrap();
152
153 current_scope.variables.insert(
155 name,
156 VariableBinding {
157 variable,
158 mutable,
159 },
160 );
161 Ok(())
162 }
163
164 pub fn get(&self, name: &str) -> Option<&Variable> {
166 for scope in self.scopes.iter().rev() {
168 if let Some(binding) = scope.variables.get(name) {
169 return Some(&binding.variable);
170 }
171 }
172 None
173 }
174
175 pub fn get_with_scope(&self, name: &str) -> Option<(&Variable, usize)> {
177 for (depth_from_end, scope) in self.scopes.iter().rev().enumerate() {
179 if let Some(binding) = scope.variables.get(name) {
180 let scope_depth = self.scopes.len() - 1 - depth_from_end;
181 return Some((&binding.variable, scope_depth));
182 }
183 }
184 None
185 }
186
187 pub fn exists_in_current_scope(&self, name: &str) -> bool {
189 self.scopes.last().unwrap().variables.contains_key(name)
190 }
191
192 pub fn exists_in_any_scope(&self, name: &str) -> bool {
194 self.get(name).is_some()
195 }
196
197 pub fn is_mutable(&self, name: &str) -> bool {
199 for scope in self.scopes.iter().rev() {
200 if let Some(binding) = scope.variables.get(name) {
201 return binding.mutable;
202 }
203 }
204 false
205 }
206
207 pub fn all_variable_names(&self) -> Vec<String> {
209 let mut names = Vec::new();
210 for (scope_idx, scope) in self.scopes.iter().enumerate() {
211 for name in scope.variables.keys() {
212 names.push(format!("{}@scope{}", name, scope_idx));
213 }
214 }
215 names
216 }
217
218 pub fn visible_variable_names(&self) -> Vec<String> {
220 let mut visible = HashMap::new();
221
222 for scope in &self.scopes {
224 for name in scope.variables.keys() {
225 visible.insert(name.clone(), ());
226 }
227 }
228
229 visible.keys().cloned().collect()
230 }
231
232 pub fn clear(&mut self) {
234 self.scopes.clear();
235 self.scopes.push(Scope {
236 variables: HashMap::new(),
237 scope_type: ScopeType::Global,
238 });
239 }
240}
241
242impl Default for Stack {
243 fn default() -> Self {
244 Self::new()
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use reifydb_core::value::column::{Column, ColumnData};
251 use reifydb_type::Value;
252
253 use super::*;
254
255 fn create_test_columns(values: Vec<Value>) -> Columns<'static> {
257 if values.is_empty() {
258 let column_data = ColumnData::undefined(0);
259 let column = Column::new("test_col", column_data);
260 return Columns::new(vec![column]);
261 }
262
263 let mut column_data = ColumnData::undefined(0);
264 for value in values {
265 column_data.push_value(value);
266 }
267
268 let column = Column::new("test_col", column_data);
269 Columns::new(vec![column])
270 }
271
272 #[test]
273 fn test_basic_variable_operations() {
274 let mut ctx = Stack::new();
275 let cols = create_test_columns(vec![Value::utf8("Alice".to_string())]);
276
277 ctx.set("name".to_string(), Variable::frame(cols.clone()), false).unwrap();
279
280 assert!(ctx.get("name").is_some());
282 assert!(!ctx.is_mutable("name"));
283 assert!(ctx.exists_in_any_scope("name"));
284 assert!(ctx.exists_in_current_scope("name"));
285 }
286
287 #[test]
288 fn test_mutable_variable() {
289 let mut ctx = Stack::new();
290 let cols1 = create_test_columns(vec![Value::Int4(42)]);
291 let cols2 = create_test_columns(vec![Value::Int4(84)]);
292
293 ctx.set("counter".to_string(), Variable::frame(cols1.clone()), true).unwrap();
295 assert!(ctx.is_mutable("counter"));
296 assert!(ctx.get("counter").is_some());
297
298 ctx.set("counter".to_string(), Variable::frame(cols2.clone()), true).unwrap();
300 assert!(ctx.get("counter").is_some());
301 }
302
303 #[test]
304 #[ignore]
305 fn test_immutable_variable_reassignment_fails() {
306 let mut ctx = Stack::new();
307 let cols1 = create_test_columns(vec![Value::utf8("Alice".to_string())]);
308 let cols2 = create_test_columns(vec![Value::utf8("Bob".to_string())]);
309
310 ctx.set("name".to_string(), Variable::frame(cols1.clone()), false).unwrap();
312
313 let result = ctx.set("name".to_string(), Variable::frame(cols2), false);
315 assert!(result.is_err());
316
317 assert!(ctx.get("name").is_some());
319 }
320
321 #[test]
322 fn test_scope_management() {
323 let mut ctx = Stack::new();
324
325 assert_eq!(ctx.scope_depth(), 0);
327 assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
328
329 ctx.enter_scope(ScopeType::Function);
331 assert_eq!(ctx.scope_depth(), 1);
332 assert_eq!(ctx.current_scope_type(), &ScopeType::Function);
333
334 ctx.enter_scope(ScopeType::Block);
336 assert_eq!(ctx.scope_depth(), 2);
337 assert_eq!(ctx.current_scope_type(), &ScopeType::Block);
338
339 ctx.exit_scope().unwrap();
341 assert_eq!(ctx.scope_depth(), 1);
342 assert_eq!(ctx.current_scope_type(), &ScopeType::Function);
343
344 ctx.exit_scope().unwrap();
346 assert_eq!(ctx.scope_depth(), 0);
347 assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
348
349 assert!(ctx.exit_scope().is_err());
351 }
352
353 #[test]
354 fn test_variable_shadowing() {
355 let mut ctx = Stack::new();
356 let outer_cols = create_test_columns(vec![Value::utf8("outer".to_string())]);
357 let inner_cols = create_test_columns(vec![Value::utf8("inner".to_string())]);
358
359 ctx.set("var".to_string(), Variable::frame(outer_cols.clone()), false).unwrap();
361 assert!(ctx.get("var").is_some());
362
363 ctx.enter_scope(ScopeType::Block);
365 ctx.set("var".to_string(), Variable::frame(inner_cols.clone()), false).unwrap();
366
367 assert!(ctx.get("var").is_some());
369 assert!(ctx.exists_in_current_scope("var"));
370
371 ctx.exit_scope().unwrap();
373 assert!(ctx.get("var").is_some());
374 }
375
376 #[test]
377 fn test_parent_scope_access() {
378 let mut ctx = Stack::new();
379 let outer_cols = create_test_columns(vec![Value::utf8("outer".to_string())]);
380
381 ctx.set("global_var".to_string(), Variable::frame(outer_cols.clone()), false).unwrap();
383
384 ctx.enter_scope(ScopeType::Function);
386
387 assert!(ctx.get("global_var").is_some());
389 assert!(!ctx.exists_in_current_scope("global_var"));
390 assert!(ctx.exists_in_any_scope("global_var"));
391
392 let (_, scope_depth) = ctx.get_with_scope("global_var").unwrap();
394 assert_eq!(scope_depth, 0); }
396
397 #[test]
398 fn test_scope_specific_mutability() {
399 let mut ctx = Stack::new();
400 let cols1 = create_test_columns(vec![Value::utf8("value1".to_string())]);
401 let cols2 = create_test_columns(vec![Value::utf8("value2".to_string())]);
402
403 ctx.set("var".to_string(), Variable::frame(cols1.clone()), false).unwrap();
405
406 ctx.enter_scope(ScopeType::Block);
408 ctx.set("var".to_string(), Variable::frame(cols2.clone()), true).unwrap(); assert!(ctx.is_mutable("var"));
412
413 ctx.exit_scope().unwrap();
415 assert!(!ctx.is_mutable("var"));
416 }
417
418 #[test]
419 fn test_visible_variable_names() {
420 let mut ctx = Stack::new();
421 let cols = create_test_columns(vec![Value::utf8("test".to_string())]);
422
423 ctx.set("global1".to_string(), Variable::frame(cols.clone()), false).unwrap();
425 ctx.set("global2".to_string(), Variable::frame(cols.clone()), false).unwrap();
426
427 let global_visible = ctx.visible_variable_names();
428 assert_eq!(global_visible.len(), 2);
429 assert!(global_visible.contains(&"global1".to_string()));
430 assert!(global_visible.contains(&"global2".to_string()));
431
432 ctx.enter_scope(ScopeType::Function);
434 ctx.set("local1".to_string(), Variable::frame(cols.clone()), false).unwrap();
435 ctx.set("global1".to_string(), Variable::frame(cols.clone()), false).unwrap(); let function_visible = ctx.visible_variable_names();
438 assert_eq!(function_visible.len(), 3); assert!(function_visible.contains(&"global1".to_string()));
440 assert!(function_visible.contains(&"global2".to_string()));
441 assert!(function_visible.contains(&"local1".to_string()));
442 }
443
444 #[test]
445 fn test_clear_resets_to_global() {
446 let mut ctx = Stack::new();
447 let cols = create_test_columns(vec![Value::utf8("test".to_string())]);
448
449 ctx.set("var1".to_string(), Variable::frame(cols.clone()), false).unwrap();
451 ctx.enter_scope(ScopeType::Function);
452 ctx.set("var2".to_string(), Variable::frame(cols.clone()), false).unwrap();
453 ctx.enter_scope(ScopeType::Block);
454 ctx.set("var3".to_string(), Variable::frame(cols.clone()), false).unwrap();
455
456 assert_eq!(ctx.scope_depth(), 2);
457 assert_eq!(ctx.visible_variable_names().len(), 3);
458
459 ctx.clear();
461 assert_eq!(ctx.scope_depth(), 0);
462 assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
463 assert_eq!(ctx.visible_variable_names().len(), 0);
464 }
465
466 #[test]
467 fn test_nonexistent_variable() {
468 let ctx = Stack::new();
469
470 assert!(ctx.get("nonexistent").is_none());
471 assert!(!ctx.exists_in_any_scope("nonexistent"));
472 assert!(!ctx.exists_in_current_scope("nonexistent"));
473 assert!(!ctx.is_mutable("nonexistent"));
474 assert!(ctx.get_with_scope("nonexistent").is_none());
475 }
476}