Skip to main content

reifydb_engine/vm/
stack.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{collections::HashMap, sync::Arc};
5
6use reifydb_core::{internal, value::column::columns::Columns};
7use reifydb_rql::instruction::{CompiledClosure, CompiledFunction, ScopeType};
8use reifydb_type::{error, value::Value};
9
10use crate::{Result, error::EngineError};
11
12/// The VM data stack for intermediate results
13#[derive(Debug, Clone)]
14pub struct Stack {
15	variables: Vec<Variable>,
16}
17
18impl Stack {
19	pub fn new() -> Self {
20		Self {
21			variables: Vec::new(),
22		}
23	}
24
25	pub fn push(&mut self, value: Variable) {
26		self.variables.push(value);
27	}
28
29	pub fn pop(&mut self) -> Result<Variable> {
30		self.variables.pop().ok_or_else(|| error!(internal!("VM data stack underflow")))
31	}
32
33	pub fn peek(&self) -> Option<&Variable> {
34		self.variables.last()
35	}
36
37	pub fn is_empty(&self) -> bool {
38		self.variables.is_empty()
39	}
40
41	pub fn len(&self) -> usize {
42		self.variables.len()
43	}
44}
45
46impl Default for Stack {
47	fn default() -> Self {
48		Self::new()
49	}
50}
51
52/// A closure paired with its captured environment (snapshotted at definition time)
53#[derive(Debug, Clone)]
54pub struct ClosureValue {
55	pub def: CompiledClosure,
56	pub captured: HashMap<String, Variable>,
57}
58
59/// A variable can be either a scalar value, columnar data, a FOR loop iterator, or a closure
60#[derive(Debug, Clone)]
61pub enum Variable {
62	/// A scalar value stored as a 1-column, 1-row Columns
63	Scalar(Columns),
64	/// Columnar data that requires explicit conversion to scalar
65	Columns(Columns),
66	/// A FOR loop iterator tracking position in a result set
67	ForIterator {
68		columns: Columns,
69		index: usize,
70	},
71	/// A closure (anonymous function with captured environment)
72	Closure(ClosureValue),
73}
74
75impl Variable {
76	/// Create a scalar variable from a single Value.
77	pub fn scalar(value: Value) -> Self {
78		Variable::Scalar(Columns::scalar(value))
79	}
80
81	/// Create a columns variable
82	pub fn columns(columns: Columns) -> Self {
83		Variable::Columns(columns)
84	}
85}
86
87/// Context for storing and managing variables during query execution with scope support
88#[derive(Debug, Clone)]
89pub struct SymbolTable {
90	inner: Arc<SymbolTableInner>,
91}
92
93#[derive(Debug, Clone)]
94struct SymbolTableInner {
95	scopes: Vec<Scope>,
96	/// User-defined functions (pre-compiled)
97	functions: HashMap<String, CompiledFunction>,
98}
99
100/// Represents a single scope containing variables
101#[derive(Debug, Clone)]
102struct Scope {
103	variables: HashMap<String, VariableBinding>,
104	scope_type: ScopeType,
105}
106
107/// Control flow signal for loop and function constructs
108#[derive(Debug, Clone)]
109pub enum ControlFlow {
110	Normal,
111	Break,
112	Continue,
113	Return(Option<Columns>),
114}
115
116impl ControlFlow {
117	pub fn is_normal(&self) -> bool {
118		matches!(self, ControlFlow::Normal)
119	}
120}
121
122/// Represents a variable binding with its value and mutability
123#[derive(Debug, Clone)]
124struct VariableBinding {
125	variable: Variable,
126	mutable: bool,
127}
128
129impl SymbolTable {
130	/// Create a new variable context with a global scope
131	pub fn new() -> Self {
132		let global_scope = Scope {
133			variables: HashMap::new(),
134			scope_type: ScopeType::Global,
135		};
136
137		Self {
138			inner: Arc::new(SymbolTableInner {
139				scopes: vec![global_scope],
140				functions: HashMap::new(),
141			}),
142		}
143	}
144
145	/// Enter a new scope (push onto stack)
146	pub fn enter_scope(&mut self, scope_type: ScopeType) {
147		let new_scope = Scope {
148			variables: HashMap::new(),
149			scope_type,
150		};
151		Arc::make_mut(&mut self.inner).scopes.push(new_scope);
152	}
153
154	/// Exit the current scope (pop from stack)
155	/// Returns error if trying to exit the global scope
156	pub fn exit_scope(&mut self) -> Result<()> {
157		if self.inner.scopes.len() <= 1 {
158			return Err(error!(internal!("Cannot exit global scope")));
159		}
160		Arc::make_mut(&mut self.inner).scopes.pop();
161		Ok(())
162	}
163
164	/// Get the current scope depth (0 = global scope)
165	pub fn scope_depth(&self) -> usize {
166		self.inner.scopes.len() - 1
167	}
168
169	/// Get the type of the current scope
170	pub fn current_scope_type(&self) -> &ScopeType {
171		&self.inner.scopes.last().unwrap().scope_type
172	}
173
174	/// Set a variable in the current (innermost) scope (allows shadowing)
175	pub fn set(&mut self, name: String, variable: Variable, mutable: bool) -> Result<()> {
176		self.set_in_current_scope(name, variable, mutable)
177	}
178
179	/// Reassign an existing variable (checks mutability)
180	/// Searches from innermost to outermost scope to find the variable
181	pub fn reassign(&mut self, name: String, variable: Variable) -> Result<()> {
182		let inner = Arc::make_mut(&mut self.inner);
183		// Search from innermost scope to outermost scope
184		for scope in inner.scopes.iter_mut().rev() {
185			if let Some(existing) = scope.variables.get(&name) {
186				if !existing.mutable {
187					return Err(EngineError::VariableIsImmutable {
188						name: name.clone(),
189					}
190					.into());
191				}
192				let mutable = existing.mutable;
193				scope.variables.insert(
194					name,
195					VariableBinding {
196						variable,
197						mutable,
198					},
199				);
200				return Ok(());
201			}
202		}
203
204		Err(EngineError::VariableNotFound {
205			name: name.clone(),
206		}
207		.into())
208	}
209
210	/// Set a variable specifically in the current scope
211	/// Allows shadowing - new variable declarations can shadow existing ones
212	pub fn set_in_current_scope(&mut self, name: String, variable: Variable, mutable: bool) -> Result<()> {
213		let inner = Arc::make_mut(&mut self.inner);
214		let current_scope = inner.scopes.last_mut().unwrap();
215
216		// Allow shadowing - simply insert the new variable binding
217		current_scope.variables.insert(
218			name,
219			VariableBinding {
220				variable,
221				mutable,
222			},
223		);
224		Ok(())
225	}
226
227	/// Get a variable by searching from innermost to outermost scope
228	pub fn get(&self, name: &str) -> Option<&Variable> {
229		// Search from innermost scope (end of vector) to outermost scope (beginning)
230		for scope in self.inner.scopes.iter().rev() {
231			if let Some(binding) = scope.variables.get(name) {
232				return Some(&binding.variable);
233			}
234		}
235		None
236	}
237
238	/// Get a variable with its scope depth information
239	pub fn get_with_scope(&self, name: &str) -> Option<(&Variable, usize)> {
240		// Search from innermost scope to outermost scope
241		for (depth_from_end, scope) in self.inner.scopes.iter().rev().enumerate() {
242			if let Some(binding) = scope.variables.get(name) {
243				let scope_depth = self.inner.scopes.len() - 1 - depth_from_end;
244				return Some((&binding.variable, scope_depth));
245			}
246		}
247		None
248	}
249
250	/// Check if a variable exists in the current scope only
251	pub fn exists_in_current_scope(&self, name: &str) -> bool {
252		self.inner.scopes.last().unwrap().variables.contains_key(name)
253	}
254
255	/// Check if a variable exists in any scope (searches all scopes)
256	pub fn exists_in_any_scope(&self, name: &str) -> bool {
257		self.get(name).is_some()
258	}
259
260	/// Check if a variable is mutable (searches from innermost scope)
261	pub fn is_mutable(&self, name: &str) -> bool {
262		for scope in self.inner.scopes.iter().rev() {
263			if let Some(binding) = scope.variables.get(name) {
264				return binding.mutable;
265			}
266		}
267		false
268	}
269
270	/// Get all variable names from all scopes (for debugging)
271	pub fn all_variable_names(&self) -> Vec<String> {
272		let mut names = Vec::new();
273		for (scope_idx, scope) in self.inner.scopes.iter().enumerate() {
274			for name in scope.variables.keys() {
275				names.push(format!("{}@scope{}", name, scope_idx));
276			}
277		}
278		names
279	}
280
281	/// Get variable names visible in current scope (respects shadowing)
282	pub fn visible_variable_names(&self) -> Vec<String> {
283		let mut visible = HashMap::new();
284
285		// Process scopes from outermost to innermost so inner scopes override outer ones
286		for scope in &self.inner.scopes {
287			for name in scope.variables.keys() {
288				visible.insert(name.clone(), ());
289			}
290		}
291
292		visible.keys().cloned().collect()
293	}
294
295	/// Clear all variables in all scopes (reset to just global scope)
296	pub fn clear(&mut self) {
297		let inner = Arc::make_mut(&mut self.inner);
298		inner.scopes.clear();
299		inner.scopes.push(Scope {
300			variables: HashMap::new(),
301			scope_type: ScopeType::Global,
302		});
303		inner.functions.clear();
304	}
305
306	/// Define a user-defined function (pre-compiled)
307	pub fn define_function(&mut self, name: String, func: CompiledFunction) {
308		Arc::make_mut(&mut self.inner).functions.insert(name, func);
309	}
310
311	/// Get a user-defined function by name
312	pub fn get_function(&self, name: &str) -> Option<&CompiledFunction> {
313		self.inner.functions.get(name)
314	}
315
316	/// Check if a function exists
317	pub fn function_exists(&self, name: &str) -> bool {
318		self.inner.functions.contains_key(name)
319	}
320}
321
322impl Default for SymbolTable {
323	fn default() -> Self {
324		Self::new()
325	}
326}
327
328#[cfg(test)]
329pub mod tests {
330	use reifydb_core::value::column::{Column, data::ColumnData};
331	use reifydb_type::value::{Value, r#type::Type};
332
333	use super::*;
334
335	// Helper function to create test columns
336	fn create_test_columns(values: Vec<Value>) -> Columns {
337		if values.is_empty() {
338			let column_data = ColumnData::none_typed(Type::Boolean, 0);
339			let column = Column::new("test_col", column_data);
340			return Columns::new(vec![column]);
341		}
342
343		let mut column_data = ColumnData::none_typed(Type::Boolean, 0);
344		for value in values {
345			column_data.push_value(value);
346		}
347
348		let column = Column::new("test_col", column_data);
349		Columns::new(vec![column])
350	}
351
352	#[test]
353	fn test_basic_variable_operations() {
354		let mut ctx = SymbolTable::new();
355		let cols = create_test_columns(vec![Value::utf8("Alice".to_string())]);
356
357		// Set a variable
358		ctx.set("name".to_string(), Variable::columns(cols.clone()), false).unwrap();
359
360		// Get the variable
361		assert!(ctx.get("name").is_some());
362		assert!(!ctx.is_mutable("name"));
363		assert!(ctx.exists_in_any_scope("name"));
364		assert!(ctx.exists_in_current_scope("name"));
365	}
366
367	#[test]
368	fn test_mutable_variable() {
369		let mut ctx = SymbolTable::new();
370		let cols1 = create_test_columns(vec![Value::Int4(42)]);
371		let cols2 = create_test_columns(vec![Value::Int4(84)]);
372
373		// Set as mutable
374		ctx.set("counter".to_string(), Variable::columns(cols1.clone()), true).unwrap();
375		assert!(ctx.is_mutable("counter"));
376		assert!(ctx.get("counter").is_some());
377
378		// Update mutable variable
379		ctx.set("counter".to_string(), Variable::columns(cols2.clone()), true).unwrap();
380		assert!(ctx.get("counter").is_some());
381	}
382
383	#[test]
384	#[ignore]
385	fn test_immutable_variable_reassignment_fails() {
386		let mut ctx = SymbolTable::new();
387		let cols1 = create_test_columns(vec![Value::utf8("Alice".to_string())]);
388		let cols2 = create_test_columns(vec![Value::utf8("Bob".to_string())]);
389
390		// Set as immutable
391		ctx.set("name".to_string(), Variable::columns(cols1.clone()), false).unwrap();
392
393		// Try to reassign immutable variable - should fail
394		let result = ctx.set("name".to_string(), Variable::columns(cols2), false);
395		assert!(result.is_err());
396
397		// Original value should be preserved
398		assert!(ctx.get("name").is_some());
399	}
400
401	#[test]
402	fn test_scope_management() {
403		let mut ctx = SymbolTable::new();
404
405		// Initially in global scope
406		assert_eq!(ctx.scope_depth(), 0);
407		assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
408
409		// Enter a function scope
410		ctx.enter_scope(ScopeType::Function);
411		assert_eq!(ctx.scope_depth(), 1);
412		assert_eq!(ctx.current_scope_type(), &ScopeType::Function);
413
414		// Enter a block scope
415		ctx.enter_scope(ScopeType::Block);
416		assert_eq!(ctx.scope_depth(), 2);
417		assert_eq!(ctx.current_scope_type(), &ScopeType::Block);
418
419		// Exit block scope
420		ctx.exit_scope().unwrap();
421		assert_eq!(ctx.scope_depth(), 1);
422		assert_eq!(ctx.current_scope_type(), &ScopeType::Function);
423
424		// Exit function scope
425		ctx.exit_scope().unwrap();
426		assert_eq!(ctx.scope_depth(), 0);
427		assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
428
429		// Cannot exit global scope
430		assert!(ctx.exit_scope().is_err());
431	}
432
433	#[test]
434	fn test_variable_shadowing() {
435		let mut ctx = SymbolTable::new();
436		let outer_cols = create_test_columns(vec![Value::utf8("outer".to_string())]);
437		let inner_cols = create_test_columns(vec![Value::utf8("inner".to_string())]);
438
439		// Set variable in global scope
440		ctx.set("var".to_string(), Variable::columns(outer_cols.clone()), false).unwrap();
441		assert!(ctx.get("var").is_some());
442
443		// Enter new scope and shadow the variable
444		ctx.enter_scope(ScopeType::Block);
445		ctx.set("var".to_string(), Variable::columns(inner_cols.clone()), false).unwrap();
446
447		// Should see the inner variable
448		assert!(ctx.get("var").is_some());
449		assert!(ctx.exists_in_current_scope("var"));
450
451		// Exit scope - should see outer variable again
452		ctx.exit_scope().unwrap();
453		assert!(ctx.get("var").is_some());
454	}
455
456	#[test]
457	fn test_parent_scope_access() {
458		let mut ctx = SymbolTable::new();
459		let outer_cols = create_test_columns(vec![Value::utf8("outer".to_string())]);
460
461		// Set variable in global scope
462		ctx.set("global_var".to_string(), Variable::columns(outer_cols.clone()), false).unwrap();
463
464		// Enter new scope
465		ctx.enter_scope(ScopeType::Function);
466
467		// Should still be able to access parent scope variable
468		assert!(ctx.get("global_var").is_some());
469		assert!(!ctx.exists_in_current_scope("global_var"));
470		assert!(ctx.exists_in_any_scope("global_var"));
471
472		// Get with scope information
473		let (_, scope_depth) = ctx.get_with_scope("global_var").unwrap();
474		assert_eq!(scope_depth, 0); // Found in global scope
475	}
476
477	#[test]
478	fn test_scope_specific_mutability() {
479		let mut ctx = SymbolTable::new();
480		let cols1 = create_test_columns(vec![Value::utf8("value1".to_string())]);
481		let cols2 = create_test_columns(vec![Value::utf8("value2".to_string())]);
482
483		// Set immutable variable in global scope
484		ctx.set("var".to_string(), Variable::columns(cols1.clone()), false).unwrap();
485
486		// Enter new scope and create new variable with same name (shadowing)
487		ctx.enter_scope(ScopeType::Block);
488		ctx.set("var".to_string(), Variable::columns(cols2.clone()), true).unwrap(); // This one is mutable
489
490		// Should be mutable in current scope
491		assert!(ctx.is_mutable("var"));
492
493		// Exit scope - should be immutable again (from global scope)
494		ctx.exit_scope().unwrap();
495		assert!(!ctx.is_mutable("var"));
496	}
497
498	#[test]
499	fn test_visible_variable_names() {
500		let mut ctx = SymbolTable::new();
501		let cols = create_test_columns(vec![Value::utf8("test".to_string())]);
502
503		// Set variables in global scope
504		ctx.set("global1".to_string(), Variable::columns(cols.clone()), false).unwrap();
505		ctx.set("global2".to_string(), Variable::columns(cols.clone()), false).unwrap();
506
507		let global_visible = ctx.visible_variable_names();
508		assert_eq!(global_visible.len(), 2);
509		assert!(global_visible.contains(&"global1".to_string()));
510		assert!(global_visible.contains(&"global2".to_string()));
511
512		// Enter new scope and add more variables
513		ctx.enter_scope(ScopeType::Function);
514		ctx.set("local1".to_string(), Variable::columns(cols.clone()), false).unwrap();
515		ctx.set("global1".to_string(), Variable::columns(cols.clone()), false).unwrap(); // Shadow global1
516
517		let function_visible = ctx.visible_variable_names();
518		assert_eq!(function_visible.len(), 3); // global1 (shadowed), global2, local1
519		assert!(function_visible.contains(&"global1".to_string()));
520		assert!(function_visible.contains(&"global2".to_string()));
521		assert!(function_visible.contains(&"local1".to_string()));
522	}
523
524	#[test]
525	fn test_clear_resets_to_global() {
526		let mut ctx = SymbolTable::new();
527		let cols = create_test_columns(vec![Value::utf8("test".to_string())]);
528
529		// Add variables and enter scopes
530		ctx.set("var1".to_string(), Variable::columns(cols.clone()), false).unwrap();
531		ctx.enter_scope(ScopeType::Function);
532		ctx.set("var2".to_string(), Variable::columns(cols.clone()), false).unwrap();
533		ctx.enter_scope(ScopeType::Block);
534		ctx.set("var3".to_string(), Variable::columns(cols.clone()), false).unwrap();
535
536		assert_eq!(ctx.scope_depth(), 2);
537		assert_eq!(ctx.visible_variable_names().len(), 3);
538
539		// Clear should reset to global scope with no variables
540		ctx.clear();
541		assert_eq!(ctx.scope_depth(), 0);
542		assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
543		assert_eq!(ctx.visible_variable_names().len(), 0);
544	}
545
546	#[test]
547	fn test_nonexistent_variable() {
548		let ctx = SymbolTable::new();
549
550		assert!(ctx.get("nonexistent").is_none());
551		assert!(!ctx.exists_in_any_scope("nonexistent"));
552		assert!(!ctx.exists_in_current_scope("nonexistent"));
553		assert!(!ctx.is_mutable("nonexistent"));
554		assert!(ctx.get_with_scope("nonexistent").is_none());
555	}
556}