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