Skip to main content

reifydb_evaluate/
stack.rs

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