Skip to main content

reifydb_engine/vm/
stack.rs

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