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 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/// 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) -> 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/// A variable can be either a scalar value, columnar data, or a FOR loop iterator
56#[derive(Debug, Clone)]
57pub enum Variable {
58	/// A scalar value stored as a 1-column, 1-row Columns
59	Scalar(Columns),
60	/// Columnar data that requires explicit conversion to scalar
61	Columns(Columns),
62	/// A FOR loop iterator tracking position in a result set
63	ForIterator {
64		columns: Columns,
65		index: usize,
66	},
67}
68
69impl Variable {
70	/// Create a scalar variable from a single Value.
71	pub fn scalar(value: Value) -> Self {
72		Variable::Scalar(Columns::scalar(value))
73	}
74
75	/// Create a columns variable
76	pub fn columns(columns: Columns) -> Self {
77		Variable::Columns(columns)
78	}
79}
80
81/// Context for storing and managing variables during query execution with scope support
82#[derive(Debug, Clone)]
83pub struct SymbolTable {
84	scopes: Vec<Scope>,
85	/// User-defined functions (pre-compiled)
86	functions: HashMap<String, CompiledFunctionDef>,
87}
88
89/// Represents a single scope containing variables
90#[derive(Debug, Clone)]
91struct Scope {
92	variables: HashMap<String, VariableBinding>,
93	scope_type: ScopeType,
94}
95
96/// Control flow signal for loop and function constructs
97#[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/// Represents a variable binding with its value and mutability
112#[derive(Debug, Clone)]
113struct VariableBinding {
114	variable: Variable,
115	mutable: bool,
116}
117
118impl SymbolTable {
119	/// Create a new variable context with a global scope
120	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	/// Enter a new scope (push onto stack)
133	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	/// Exit the current scope (pop from stack)
142	/// Returns error if trying to exit the global scope
143	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	/// Get the current scope depth (0 = global scope)
152	pub fn scope_depth(&self) -> usize {
153		self.scopes.len() - 1
154	}
155
156	/// Get the type of the current scope
157	pub fn current_scope_type(&self) -> &ScopeType {
158		&self.scopes.last().unwrap().scope_type
159	}
160
161	/// Set a variable in the current (innermost) scope (allows shadowing)
162	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	/// Reassign an existing variable (checks mutability)
167	/// Searches from innermost to outermost scope to find the variable
168	pub fn reassign(&mut self, name: String, variable: Variable) -> crate::Result<()> {
169		// Search from innermost scope to outermost scope
170		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	/// Set a variable specifically in the current scope
191	/// Allows shadowing - new variable declarations can shadow existing ones
192	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		// Allow shadowing - simply insert the new variable binding
196		current_scope.variables.insert(
197			name,
198			VariableBinding {
199				variable,
200				mutable,
201			},
202		);
203		Ok(())
204	}
205
206	/// Get a variable by searching from innermost to outermost scope
207	pub fn get(&self, name: &str) -> Option<&Variable> {
208		// Search from innermost scope (end of vector) to outermost scope (beginning)
209		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	/// Get a variable with its scope depth information
218	pub fn get_with_scope(&self, name: &str) -> Option<(&Variable, usize)> {
219		// Search from innermost scope to outermost scope
220		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	/// Check if a variable exists in the current scope only
230	pub fn exists_in_current_scope(&self, name: &str) -> bool {
231		self.scopes.last().unwrap().variables.contains_key(name)
232	}
233
234	/// Check if a variable exists in any scope (searches all scopes)
235	pub fn exists_in_any_scope(&self, name: &str) -> bool {
236		self.get(name).is_some()
237	}
238
239	/// Check if a variable is mutable (searches from innermost scope)
240	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	/// Get all variable names from all scopes (for debugging)
250	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	/// Get variable names visible in current scope (respects shadowing)
261	pub fn visible_variable_names(&self) -> Vec<String> {
262		let mut visible = HashMap::new();
263
264		// Process scopes from outermost to innermost so inner scopes override outer ones
265		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	/// Clear all variables in all scopes (reset to just global scope)
275	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	/// Define a user-defined function (pre-compiled)
285	pub fn define_function(&mut self, name: String, func: CompiledFunctionDef) {
286		self.functions.insert(name, func);
287	}
288
289	/// Get a user-defined function by name
290	pub fn get_function(&self, name: &str) -> Option<&CompiledFunctionDef> {
291		self.functions.get(name)
292	}
293
294	/// Check if a function exists
295	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	// Helper function to create test columns
314	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		// Set a variable
336		ctx.set("name".to_string(), Variable::columns(cols.clone()), false).unwrap();
337
338		// Get the variable
339		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		// Set as mutable
352		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		// Update mutable variable
357		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		// Set as immutable
369		ctx.set("name".to_string(), Variable::columns(cols1.clone()), false).unwrap();
370
371		// Try to reassign immutable variable - should fail
372		let result = ctx.set("name".to_string(), Variable::columns(cols2), false);
373		assert!(result.is_err());
374
375		// Original value should be preserved
376		assert!(ctx.get("name").is_some());
377	}
378
379	#[test]
380	fn test_scope_management() {
381		let mut ctx = SymbolTable::new();
382
383		// Initially in global scope
384		assert_eq!(ctx.scope_depth(), 0);
385		assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
386
387		// Enter a function scope
388		ctx.enter_scope(ScopeType::Function);
389		assert_eq!(ctx.scope_depth(), 1);
390		assert_eq!(ctx.current_scope_type(), &ScopeType::Function);
391
392		// Enter a block scope
393		ctx.enter_scope(ScopeType::Block);
394		assert_eq!(ctx.scope_depth(), 2);
395		assert_eq!(ctx.current_scope_type(), &ScopeType::Block);
396
397		// Exit block scope
398		ctx.exit_scope().unwrap();
399		assert_eq!(ctx.scope_depth(), 1);
400		assert_eq!(ctx.current_scope_type(), &ScopeType::Function);
401
402		// Exit function scope
403		ctx.exit_scope().unwrap();
404		assert_eq!(ctx.scope_depth(), 0);
405		assert_eq!(ctx.current_scope_type(), &ScopeType::Global);
406
407		// Cannot exit global scope
408		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		// Set variable in global scope
418		ctx.set("var".to_string(), Variable::columns(outer_cols.clone()), false).unwrap();
419		assert!(ctx.get("var").is_some());
420
421		// Enter new scope and shadow the variable
422		ctx.enter_scope(ScopeType::Block);
423		ctx.set("var".to_string(), Variable::columns(inner_cols.clone()), false).unwrap();
424
425		// Should see the inner variable
426		assert!(ctx.get("var").is_some());
427		assert!(ctx.exists_in_current_scope("var"));
428
429		// Exit scope - should see outer variable again
430		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		// Set variable in global scope
440		ctx.set("global_var".to_string(), Variable::columns(outer_cols.clone()), false).unwrap();
441
442		// Enter new scope
443		ctx.enter_scope(ScopeType::Function);
444
445		// Should still be able to access parent scope variable
446		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		// Get with scope information
451		let (_, scope_depth) = ctx.get_with_scope("global_var").unwrap();
452		assert_eq!(scope_depth, 0); // Found in global scope
453	}
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		// Set immutable variable in global scope
462		ctx.set("var".to_string(), Variable::columns(cols1.clone()), false).unwrap();
463
464		// Enter new scope and create new variable with same name (shadowing)
465		ctx.enter_scope(ScopeType::Block);
466		ctx.set("var".to_string(), Variable::columns(cols2.clone()), true).unwrap(); // This one is mutable
467
468		// Should be mutable in current scope
469		assert!(ctx.is_mutable("var"));
470
471		// Exit scope - should be immutable again (from global scope)
472		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		// Set variables in global scope
482		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		// Enter new scope and add more variables
491		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(); // Shadow global1
494
495		let function_visible = ctx.visible_variable_names();
496		assert_eq!(function_visible.len(), 3); // global1 (shadowed), global2, local1
497		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		// Add variables and enter scopes
508		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		// Clear should reset to global scope with no variables
518		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}