Skip to main content

virtual_rust/interpreter/
environment.rs

1//! Variable scoping and environment management.
2//!
3//! The [`Environment`] maintains a stack of scopes, where each scope is a
4//! `HashMap` mapping variable names to their [`Variable`] (value + mutability).
5//! New scopes are pushed on function calls and block entries, and popped on exit.
6
7use std::collections::HashMap;
8
9use crate::interpreter::error::RuntimeError;
10use crate::interpreter::value::Value;
11
12/// A stored variable with its current value and mutability flag.
13#[derive(Debug, Clone)]
14struct Variable {
15    value: Value,
16    mutable: bool,
17}
18
19/// Scoped variable environment using a stack of hash maps.
20///
21/// Variables are looked up from the innermost scope outward. Assignments
22/// respect mutability — attempting to assign to an immutable binding
23/// produces a [`RuntimeError`].
24#[derive(Debug, Clone)]
25pub struct Environment {
26    scopes: Vec<HashMap<String, Variable>>,
27}
28
29impl Default for Environment {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl Environment {
36    /// Creates a new environment with a single (global) scope.
37    pub fn new() -> Self {
38        Environment {
39            scopes: vec![HashMap::new()],
40        }
41    }
42
43    /// Pushes a new empty scope onto the stack.
44    pub fn push_scope(&mut self) {
45        self.scopes.push(HashMap::new());
46    }
47
48    /// Pops the innermost scope from the stack.
49    pub fn pop_scope(&mut self) {
50        self.scopes.pop();
51    }
52
53    /// Defines a new variable in the current (innermost) scope.
54    pub fn define(&mut self, name: String, value: Value, mutable: bool) {
55        if let Some(scope) = self.scopes.last_mut() {
56            scope.insert(name, Variable { value, mutable });
57        }
58    }
59
60    /// Looks up a variable by name, searching from innermost to outermost scope.
61    pub fn get(&self, name: &str) -> Option<&Value> {
62        self.scopes
63            .iter()
64            .rev()
65            .find_map(|scope| scope.get(name).map(|var| &var.value))
66    }
67
68    /// Sets an existing variable's value. Returns an error if the variable
69    /// is not found or is immutable.
70    pub fn set(&mut self, name: &str, value: Value) -> Result<(), RuntimeError> {
71        for scope in self.scopes.iter_mut().rev() {
72            if let Some(var) = scope.get_mut(name) {
73                if !var.mutable {
74                    return Err(RuntimeError::new(format!(
75                        "Cannot assign to immutable variable '{name}'"
76                    )));
77                }
78                var.value = value;
79                return Ok(());
80            }
81        }
82        Err(RuntimeError::new(format!(
83            "Undefined variable: '{name}'"
84        )))
85    }
86}