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