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