1use std::collections::HashMap;
5
6use diagnostic::runtime::{variable_is_immutable, variable_not_found};
7use reifydb_core::{internal, value::column::columns::Columns};
8use reifydb_rql::instruction::{CompiledFunctionDef, ScopeType};
9use reifydb_type::{
10 error,
11 error::{Error, diagnostic},
12 value::Value,
13};
14
15#[derive(Debug, Clone)]
17pub struct Stack {
18 variables: Vec<Variable>,
19}
20
21impl Stack {
22 pub fn new() -> Self {
23 Self {
24 variables: Vec::new(),
25 }
26 }
27
28 pub fn push(&mut self, value: Variable) {
29 self.variables.push(value);
30 }
31
32 pub fn pop(&mut self) -> crate::Result<Variable> {
33 self.variables.pop().ok_or_else(|| error!(internal!("VM data stack underflow")))
34 }
35
36 pub fn peek(&self) -> Option<&Variable> {
37 self.variables.last()
38 }
39
40 pub fn is_empty(&self) -> bool {
41 self.variables.is_empty()
42 }
43
44 pub fn len(&self) -> usize {
45 self.variables.len()
46 }
47}
48
49impl Default for Stack {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55#[derive(Debug, Clone)]
57pub enum Variable {
58 Scalar(Columns),
60 Columns(Columns),
62 ForIterator {
64 columns: Columns,
65 index: usize,
66 },
67}
68
69impl Variable {
70 pub fn scalar(value: Value) -> Self {
72 Variable::Scalar(Columns::scalar(value))
73 }
74
75 pub fn columns(columns: Columns) -> Self {
77 Variable::Columns(columns)
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct SymbolTable {
84 scopes: Vec<Scope>,
85 functions: HashMap<String, CompiledFunctionDef>,
87}
88
89#[derive(Debug, Clone)]
91struct Scope {
92 variables: HashMap<String, VariableBinding>,
93 scope_type: ScopeType,
94}
95
96#[derive(Debug, Clone)]
98pub enum ControlFlow {
99 Normal,
100 Break,
101 Continue,
102 Return(Option<Columns>),
103}
104
105impl ControlFlow {
106 pub fn is_normal(&self) -> bool {
107 matches!(self, ControlFlow::Normal)
108 }
109}
110
111#[derive(Debug, Clone)]
113struct VariableBinding {
114 variable: Variable,
115 mutable: bool,
116}
117
118impl SymbolTable {
119 pub fn new() -> Self {
121 let global_scope = Scope {
122 variables: HashMap::new(),
123 scope_type: ScopeType::Global,
124 };
125
126 Self {
127 scopes: vec![global_scope],
128 functions: HashMap::new(),
129 }
130 }
131
132 pub fn enter_scope(&mut self, scope_type: ScopeType) {
134 let new_scope = Scope {
135 variables: HashMap::new(),
136 scope_type,
137 };
138 self.scopes.push(new_scope);
139 }
140
141 pub fn exit_scope(&mut self) -> crate::Result<()> {
144 if self.scopes.len() <= 1 {
145 return Err(error!(internal!("Cannot exit global scope")));
146 }
147 self.scopes.pop();
148 Ok(())
149 }
150
151 pub fn scope_depth(&self) -> usize {
153 self.scopes.len() - 1
154 }
155
156 pub fn current_scope_type(&self) -> &ScopeType {
158 &self.scopes.last().unwrap().scope_type
159 }
160
161 pub fn set(&mut self, name: String, variable: Variable, mutable: bool) -> crate::Result<()> {
163 self.set_in_current_scope(name, variable, mutable)
164 }
165
166 pub fn reassign(&mut self, name: String, variable: Variable) -> crate::Result<()> {
169 for scope in self.scopes.iter_mut().rev() {
171 if let Some(existing) = scope.variables.get(&name) {
172 if !existing.mutable {
173 return Err(Error(variable_is_immutable(&name)));
174 }
175 let mutable = existing.mutable;
176 scope.variables.insert(
177 name,
178 VariableBinding {
179 variable,
180 mutable,
181 },
182 );
183 return Ok(());
184 }
185 }
186
187 Err(Error(variable_not_found(&name)))
188 }
189
190 pub fn set_in_current_scope(&mut self, name: String, variable: Variable, mutable: bool) -> crate::Result<()> {
193 let current_scope = self.scopes.last_mut().unwrap();
194
195 current_scope.variables.insert(
197 name,
198 VariableBinding {
199 variable,
200 mutable,
201 },
202 );
203 Ok(())
204 }
205
206 pub fn get(&self, name: &str) -> Option<&Variable> {
208 for scope in self.scopes.iter().rev() {
210 if let Some(binding) = scope.variables.get(name) {
211 return Some(&binding.variable);
212 }
213 }
214 None
215 }
216
217 pub fn get_with_scope(&self, name: &str) -> Option<(&Variable, usize)> {
219 for (depth_from_end, scope) in self.scopes.iter().rev().enumerate() {
221 if let Some(binding) = scope.variables.get(name) {
222 let scope_depth = self.scopes.len() - 1 - depth_from_end;
223 return Some((&binding.variable, scope_depth));
224 }
225 }
226 None
227 }
228
229 pub fn exists_in_current_scope(&self, name: &str) -> bool {
231 self.scopes.last().unwrap().variables.contains_key(name)
232 }
233
234 pub fn exists_in_any_scope(&self, name: &str) -> bool {
236 self.get(name).is_some()
237 }
238
239 pub fn is_mutable(&self, name: &str) -> bool {
241 for scope in self.scopes.iter().rev() {
242 if let Some(binding) = scope.variables.get(name) {
243 return binding.mutable;
244 }
245 }
246 false
247 }
248
249 pub fn all_variable_names(&self) -> Vec<String> {
251 let mut names = Vec::new();
252 for (scope_idx, scope) in self.scopes.iter().enumerate() {
253 for name in scope.variables.keys() {
254 names.push(format!("{}@scope{}", name, scope_idx));
255 }
256 }
257 names
258 }
259
260 pub fn visible_variable_names(&self) -> Vec<String> {
262 let mut visible = HashMap::new();
263
264 for scope in &self.scopes {
266 for name in scope.variables.keys() {
267 visible.insert(name.clone(), ());
268 }
269 }
270
271 visible.keys().cloned().collect()
272 }
273
274 pub fn clear(&mut self) {
276 self.scopes.clear();
277 self.scopes.push(Scope {
278 variables: HashMap::new(),
279 scope_type: ScopeType::Global,
280 });
281 self.functions.clear();
282 }
283
284 pub fn define_function(&mut self, name: String, func: CompiledFunctionDef) {
286 self.functions.insert(name, func);
287 }
288
289 pub fn get_function(&self, name: &str) -> Option<&CompiledFunctionDef> {
291 self.functions.get(name)
292 }
293
294 pub fn function_exists(&self, name: &str) -> bool {
296 self.functions.contains_key(name)
297 }
298}
299
300impl Default for SymbolTable {
301 fn default() -> Self {
302 Self::new()
303 }
304}
305
306#[cfg(test)]
307pub mod tests {
308 use reifydb_core::value::column::{Column, data::ColumnData};
309 use reifydb_type::value::{Value, r#type::Type};
310
311 use super::*;
312
313 fn create_test_columns(values: Vec<Value>) -> Columns {
315 if values.is_empty() {
316 let column_data = ColumnData::none_typed(Type::Boolean, 0);
317 let column = Column::new("test_col", column_data);
318 return Columns::new(vec![column]);
319 }
320
321 let mut column_data = ColumnData::none_typed(Type::Boolean, 0);
322 for value in values {
323 column_data.push_value(value);
324 }
325
326 let column = Column::new("test_col", column_data);
327 Columns::new(vec![column])
328 }
329
330 #[test]
331 fn test_basic_variable_operations() {
332 let mut ctx = SymbolTable::new();
333 let cols = create_test_columns(vec![Value::utf8("Alice".to_string())]);
334
335 ctx.set("name".to_string(), Variable::columns(cols.clone()), false).unwrap();
337
338 assert!(ctx.get("name").is_some());
340 assert!(!ctx.is_mutable("name"));
341 assert!(ctx.exists_in_any_scope("name"));
342 assert!(ctx.exists_in_current_scope("name"));
343 }
344
345 #[test]
346 fn test_mutable_variable() {
347 let mut ctx = SymbolTable::new();
348 let cols1 = create_test_columns(vec![Value::Int4(42)]);
349 let cols2 = create_test_columns(vec![Value::Int4(84)]);
350
351 ctx.set("counter".to_string(), Variable::columns(cols1.clone()), true).unwrap();
353 assert!(ctx.is_mutable("counter"));
354 assert!(ctx.get("counter").is_some());
355
356 ctx.set("counter".to_string(), Variable::columns(cols2.clone()), true).unwrap();
358 assert!(ctx.get("counter").is_some());
359 }
360
361 #[test]
362 #[ignore]
363 fn test_immutable_variable_reassignment_fails() {
364 let mut ctx = SymbolTable::new();
365 let cols1 = create_test_columns(vec![Value::utf8("Alice".to_string())]);
366 let cols2 = create_test_columns(vec![Value::utf8("Bob".to_string())]);
367
368 ctx.set("name".to_string(), Variable::columns(cols1.clone()), false).unwrap();
370
371 let result = ctx.set("name".to_string(), Variable::columns(cols2), false);
373 assert!(result.is_err());
374
375 assert!(ctx.get("name").is_some());
377 }
378
379 #[test]
380 fn test_scope_management() {
381 let mut ctx = SymbolTable::new();
382
383 assert_eq!(ctx.scope_depth(), 0);
385 assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
386
387 ctx.enter_scope(ScopeType::Function);
389 assert_eq!(ctx.scope_depth(), 1);
390 assert_eq!(ctx.current_scope_type(), &ScopeType::Function);
391
392 ctx.enter_scope(ScopeType::Block);
394 assert_eq!(ctx.scope_depth(), 2);
395 assert_eq!(ctx.current_scope_type(), &ScopeType::Block);
396
397 ctx.exit_scope().unwrap();
399 assert_eq!(ctx.scope_depth(), 1);
400 assert_eq!(ctx.current_scope_type(), &ScopeType::Function);
401
402 ctx.exit_scope().unwrap();
404 assert_eq!(ctx.scope_depth(), 0);
405 assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
406
407 assert!(ctx.exit_scope().is_err());
409 }
410
411 #[test]
412 fn test_variable_shadowing() {
413 let mut ctx = SymbolTable::new();
414 let outer_cols = create_test_columns(vec![Value::utf8("outer".to_string())]);
415 let inner_cols = create_test_columns(vec![Value::utf8("inner".to_string())]);
416
417 ctx.set("var".to_string(), Variable::columns(outer_cols.clone()), false).unwrap();
419 assert!(ctx.get("var").is_some());
420
421 ctx.enter_scope(ScopeType::Block);
423 ctx.set("var".to_string(), Variable::columns(inner_cols.clone()), false).unwrap();
424
425 assert!(ctx.get("var").is_some());
427 assert!(ctx.exists_in_current_scope("var"));
428
429 ctx.exit_scope().unwrap();
431 assert!(ctx.get("var").is_some());
432 }
433
434 #[test]
435 fn test_parent_scope_access() {
436 let mut ctx = SymbolTable::new();
437 let outer_cols = create_test_columns(vec![Value::utf8("outer".to_string())]);
438
439 ctx.set("global_var".to_string(), Variable::columns(outer_cols.clone()), false).unwrap();
441
442 ctx.enter_scope(ScopeType::Function);
444
445 assert!(ctx.get("global_var").is_some());
447 assert!(!ctx.exists_in_current_scope("global_var"));
448 assert!(ctx.exists_in_any_scope("global_var"));
449
450 let (_, scope_depth) = ctx.get_with_scope("global_var").unwrap();
452 assert_eq!(scope_depth, 0); }
454
455 #[test]
456 fn test_scope_specific_mutability() {
457 let mut ctx = SymbolTable::new();
458 let cols1 = create_test_columns(vec![Value::utf8("value1".to_string())]);
459 let cols2 = create_test_columns(vec![Value::utf8("value2".to_string())]);
460
461 ctx.set("var".to_string(), Variable::columns(cols1.clone()), false).unwrap();
463
464 ctx.enter_scope(ScopeType::Block);
466 ctx.set("var".to_string(), Variable::columns(cols2.clone()), true).unwrap(); assert!(ctx.is_mutable("var"));
470
471 ctx.exit_scope().unwrap();
473 assert!(!ctx.is_mutable("var"));
474 }
475
476 #[test]
477 fn test_visible_variable_names() {
478 let mut ctx = SymbolTable::new();
479 let cols = create_test_columns(vec![Value::utf8("test".to_string())]);
480
481 ctx.set("global1".to_string(), Variable::columns(cols.clone()), false).unwrap();
483 ctx.set("global2".to_string(), Variable::columns(cols.clone()), false).unwrap();
484
485 let global_visible = ctx.visible_variable_names();
486 assert_eq!(global_visible.len(), 2);
487 assert!(global_visible.contains(&"global1".to_string()));
488 assert!(global_visible.contains(&"global2".to_string()));
489
490 ctx.enter_scope(ScopeType::Function);
492 ctx.set("local1".to_string(), Variable::columns(cols.clone()), false).unwrap();
493 ctx.set("global1".to_string(), Variable::columns(cols.clone()), false).unwrap(); let function_visible = ctx.visible_variable_names();
496 assert_eq!(function_visible.len(), 3); assert!(function_visible.contains(&"global1".to_string()));
498 assert!(function_visible.contains(&"global2".to_string()));
499 assert!(function_visible.contains(&"local1".to_string()));
500 }
501
502 #[test]
503 fn test_clear_resets_to_global() {
504 let mut ctx = SymbolTable::new();
505 let cols = create_test_columns(vec![Value::utf8("test".to_string())]);
506
507 ctx.set("var1".to_string(), Variable::columns(cols.clone()), false).unwrap();
509 ctx.enter_scope(ScopeType::Function);
510 ctx.set("var2".to_string(), Variable::columns(cols.clone()), false).unwrap();
511 ctx.enter_scope(ScopeType::Block);
512 ctx.set("var3".to_string(), Variable::columns(cols.clone()), false).unwrap();
513
514 assert_eq!(ctx.scope_depth(), 2);
515 assert_eq!(ctx.visible_variable_names().len(), 3);
516
517 ctx.clear();
519 assert_eq!(ctx.scope_depth(), 0);
520 assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
521 assert_eq!(ctx.visible_variable_names().len(), 0);
522 }
523
524 #[test]
525 fn test_nonexistent_variable() {
526 let ctx = SymbolTable::new();
527
528 assert!(ctx.get("nonexistent").is_none());
529 assert!(!ctx.exists_in_any_scope("nonexistent"));
530 assert!(!ctx.exists_in_current_scope("nonexistent"));
531 assert!(!ctx.is_mutable("nonexistent"));
532 assert!(ctx.get_with_scope("nonexistent").is_none());
533 }
534}