use crate::interpreter::value::{Scope, Value};
use anyhow::Result;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct StackFrame {
name: String,
parent_scope_index: Option<usize>,
variables: HashMap<String, Value>,
}
impl StackFrame {
pub fn new(name: String, parent_scope_index: Option<usize>) -> Self {
Self {
name,
parent_scope_index,
variables: HashMap::new(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn parent_scope_index(&self) -> Option<usize> {
self.parent_scope_index
}
pub fn variables(&self) -> &HashMap<String, Value> {
&self.variables
}
pub fn variables_mut(&mut self) -> &mut HashMap<String, Value> {
&mut self.variables
}
pub fn has_variable(&self, name: &str) -> bool {
self.variables.contains_key(name)
}
pub fn get_variable(&self, name: &str) -> Option<&Value> {
self.variables.get(name)
}
pub fn set_variable(&mut self, name: String, value: Value) -> Option<Value> {
self.variables.insert(name, value)
}
pub fn remove_variable(&mut self, name: &str) -> Option<Value> {
self.variables.remove(name)
}
pub fn variable_names(&self) -> Vec<String> {
self.variables.keys().cloned().collect()
}
}
pub struct ScopeManager {
scopes: Vec<Scope>,
call_stack: Vec<StackFrame>,
debug_mode: bool,
}
impl ScopeManager {
pub fn new(debug_mode: bool) -> Self {
let mut global = Scope::new();
global.insert("maxint".to_string(), Value::Integer(i64::MAX));
global.insert("true".to_string(), Value::Boolean(true));
global.insert("false".to_string(), Value::Boolean(false));
Self {
scopes: vec![global],
call_stack: Vec::new(),
debug_mode,
}
}
pub fn current_scope(&self) -> &Scope {
self.scopes
.last()
.expect("Scope stack should never be empty")
}
pub fn current_scope_mut(&mut self) -> &mut Scope {
self.scopes
.last_mut()
.expect("Scope stack should never be empty")
}
pub fn enter_scope(&mut self) {
self.scopes.push(Scope::new());
if self.debug_mode {
eprintln!("[scope] Entered new scope (total scopes: {})", self.scopes.len());
}
}
pub fn exit_scope(&mut self) {
if self.scopes.len() > 1 {
let scope_count = self.scopes.len();
self.scopes.pop();
if self.debug_mode {
eprintln!("[scope] Exited scope (total scopes: {})", scope_count - 1);
}
}
}
pub fn get_variable(&self, name: &str) -> Option<Value> {
let name_lower = name.to_lowercase();
for scope in self.scopes.iter().rev() {
if let Some(val) = scope.get(&name_lower) {
return Some(val.clone());
}
}
None
}
pub fn set_variable(&mut self, name: String, value: Value) -> Option<Value> {
let name_lower = name.to_lowercase();
let scope = self.scopes.last_mut()
.unwrap_or_else(|| panic!("No scope available for variable assignment"));
let old_value = scope.get(&name_lower).cloned();
scope.insert(name_lower, value);
old_value
}
pub fn declare_variables(&mut self, variables: &[String]) -> Result<()> {
for var_name in variables {
let name_lower = var_name.to_lowercase();
if !self.scopes.last()
.unwrap_or_else(|| panic!("No scope available for variable declaration"))
.contains_key(&name_lower)
{
self.scopes.last_mut()
.unwrap_or_else(|| panic!("No scope available for variable declaration"))
.insert(name_lower, Value::Nil);
} else {
return Err(anyhow::anyhow!("Variable '{}' already declared", var_name));
}
}
Ok(())
}
pub fn push_frame(&mut self, frame: StackFrame) {
if self.debug_mode {
eprintln!("[call] Entered frame: {}", frame.name);
}
self.call_stack.push(frame);
}
pub fn pop_frame(&mut self) -> Option<StackFrame> {
let frame = self.call_stack.pop();
if let Some(ref f) = frame {
if self.debug_mode {
eprintln!("[call] Exited frame: {}", f.name);
}
}
frame
}
pub fn current_frame(&self) -> Option<&StackFrame> {
self.call_stack.last()
}
pub fn current_frame_mut(&mut self) -> Option<&mut StackFrame> {
self.call_stack.last_mut()
}
pub fn call_stack(&self) -> &[StackFrame] {
&self.call_stack
}
pub fn in_function(&self, function_name: &str) -> bool {
self.call_stack.iter().any(|frame| frame.name() == function_name)
}
pub fn stack_depth(&self) -> usize {
self.call_stack.len()
}
pub fn set_debug_mode(&mut self, debug_mode: bool) {
self.debug_mode = debug_mode;
}
pub fn debug_mode(&self) -> bool {
self.debug_mode
}
}
pub struct ScopeUtils;
impl ScopeUtils {
pub fn create_variable_list(names: &[&str]) -> Vec<String> {
names.iter().map(|&s| s.to_string()).collect()
}
pub fn is_valid_variable_name(name: &str) -> bool {
let name_lower = name.to_lowercase();
!name_lower.is_empty()
&& name_lower.chars().all(|c| c.is_alphanumeric() || c == '_')
&& !name_lower.chars().next().unwrap_or('0').is_numeric()
&& name_lower != "true"
&& name_lower != "false"
&& name_lower != "maxint"
}
pub fn resolve_scope_hierarchy(scope_manager: &ScopeManager) -> Vec<String> {
let mut hierarchy = Vec::new();
for (i, scope) in scope_manager.scopes.iter().enumerate().rev() {
hierarchy.push(format!("scope_{}", i));
}
hierarchy
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stack_frame_creation() {
let frame = StackFrame::new("test_func".to_string(), None);
assert_eq!(frame.name(), "test_func");
assert!(!frame.has_variable("x"));
}
#[test]
fn test_scope_manager() {
let mut manager = ScopeManager::new(false);
assert_eq!(manager.scopes.len(), 1);
manager.enter_scope();
assert_eq!(manager.scopes.len(), 2);
manager.exit_scope();
assert_eq!(manager.scopes.len(), 1);
}
#[test]
fn test_variable_declaration() {
let mut manager = ScopeManager::new(false);
let vars = vec!["x".to_string(), "y".to_string()];
assert!(manager.declare_variables(&vars).is_ok());
assert!(manager.get_variable("x").is_some());
assert_eq!(manager.get_variable("x"), Some(Value::Nil));
}
#[test]
fn test_stack_frames() {
let mut manager = ScopeManager::new(false);
let frame = StackFrame::new("test".to_string(), None);
manager.push_frame(frame);
assert_eq!(manager.stack_depth(), 1);
assert!(manager.in_function("test"));
manager.pop_frame();
assert_eq!(manager.stack_depth(), 0);
}
#[test]
fn test_scope_utilities() {
let valid_names = ["x", "test_var", "abc123"];
let invalid_names = ["123var", "", "true", "false", "maxint"];
for name in valid_names {
assert!(ScopeUtils::is_valid_variable_name(name));
}
for name in invalid_names {
assert!(!ScopeUtils::is_valid_variable_name(name));
}
}
}