Environment

The Environment manages variable bindings during evaluation. It uses a stack of scopes for lexical scoping.

Location

compiler/oric/src/eval/environment.rs (~408 lines)

Structure

pub struct Environment {
    /// Stack of scopes (innermost last)
    scopes: Vec<Scope>,
}

pub struct Scope {
    /// Variable bindings
    bindings: HashMap<Name, Value>,

    /// Scope kind for debugging
    kind: ScopeKind,
}

pub enum ScopeKind {
    Global,
    Function(Name),
    Block,
    Lambda,
    ForLoop,
    MatchArm,
}

Operations

Creating Environment

impl Environment {
    pub fn new() -> Self {
        Self {
            scopes: vec![Scope::global()],
        }
    }

    pub fn with_builtins() -> Self {
        let mut env = Self::new();

        // Bind built-in functions
        env.bind(name("print"), Value::Builtin(BuiltinFn::Print));
        env.bind(name("len"), Value::Builtin(BuiltinFn::Len));
        // ... more builtins

        env
    }
}

Scope Management

impl Environment {
    pub fn push_scope(&mut self) {
        self.scopes.push(Scope::block());
    }

    pub fn push_function_scope(&mut self, name: Name) {
        self.scopes.push(Scope::function(name));
    }

    pub fn push_scope_with(&mut self, captured: Scope) {
        // For closures - start with captured environment
        self.scopes.push(captured);
    }

    pub fn pop_scope(&mut self) -> Scope {
        self.scopes.pop().expect("cannot pop global scope")
    }
}

Variable Access

impl Environment {
    pub fn bind(&mut self, name: Name, value: Value) {
        let scope = self.scopes.last_mut().expect("no scope");
        scope.bindings.insert(name, value);
    }

    pub fn get(&self, name: Name) -> Option<&Value> {
        // Search from innermost to outermost
        for scope in self.scopes.iter().rev() {
            if let Some(value) = scope.bindings.get(&name) {
                return Some(value);
            }
        }
        None
    }

    pub fn set(&mut self, name: Name, value: Value) -> Result<(), EvalError> {
        // Find and update existing binding
        for scope in self.scopes.iter_mut().rev() {
            if scope.bindings.contains_key(&name) {
                scope.bindings.insert(name, value);
                return Ok(());
            }
        }
        Err(EvalError::UndefinedVariable(name))
    }
}

Scoping Rules

Lexical Scoping

Variables are looked up in the lexical scope:

let x = 1

@foo () -> int = x + 1  // x refers to outer x

let result = run(
    let x = 10,         // Shadows outer x
    x + 1,              // Uses inner x = 10
)
// result = 11, outer x unchanged

Shadowing

Inner scopes can shadow outer bindings:

fn eval_let(&mut self, name: Name, value: ExprId, body: ExprId) -> Result<Value, EvalError> {
    let value = self.eval_expr(value)?;

    self.env.push_scope();
    self.env.bind(name, value);  // May shadow outer binding

    let result = self.eval_expr(body);

    self.env.pop_scope();  // Shadowing ends
    result
}

Closures

Closures capture their environment:

let multiplier = 2
let double = x -> x * multiplier  // Captures multiplier
double(5)  // 10
fn eval_lambda(&mut self, params: &[Name], body: ExprId) -> Result<Value, EvalError> {
    // Capture current environment
    let captured = self.env.capture_scope();

    Ok(Value::Function(FunctionValue {
        params: params.to_vec(),
        body,
        captured_env: captured,
    }))
}

impl Environment {
    pub fn capture_scope(&self) -> Scope {
        // Clone all visible bindings
        let mut bindings = HashMap::new();
        for scope in &self.scopes {
            for (name, value) in &scope.bindings {
                bindings.insert(*name, value.clone());
            }
        }
        Scope { bindings, kind: ScopeKind::Lambda }
    }
}

Mutation

Mutable variables use let mut:

let mut x = 0
run(
    x = x + 1,  // Mutate x
    x = x + 1,
    x,
)
// x = 2
fn eval_assign(&mut self, name: Name, value: ExprId) -> Result<Value, EvalError> {
    let value = self.eval_expr(value)?;
    self.env.set(name, value)?;
    Ok(Value::Void)
}

Function Scopes

Functions get their own scope:

fn call_function(&mut self, func: &FunctionValue, args: Vec<Value>) -> Result<Value, EvalError> {
    // Start with captured environment (for closures)
    self.env.push_scope_with(func.captured_env.clone());

    // Bind parameters
    for (param, arg) in func.params.iter().zip(args) {
        self.env.bind(*param, arg);
    }

    let result = self.eval_expr(func.body);

    self.env.pop_scope();
    result
}

For Loop Scopes

For loops create a scope for the iteration variable:

fn eval_for(&mut self, var: Name, iter: ExprId, body: ExprId) -> Result<Value, EvalError> {
    let items = self.eval_expr(iter)?.as_list()?;

    for item in items.iter() {
        self.env.push_scope();
        self.env.bind(var, item.clone());
        self.eval_expr(body)?;
        self.env.pop_scope();
    }

    Ok(Value::Void)
}

Debugging

Print environment state:

impl Environment {
    pub fn debug_print(&self) {
        for (i, scope) in self.scopes.iter().enumerate() {
            eprintln!("Scope {} ({:?}):", i, scope.kind);
            for (name, value) in &scope.bindings {
                eprintln!("  {} = {:?}", name, value);
            }
        }
    }
}

Memory Considerations

  • Values are cloned when captured in closures
  • Arc-wrapped values (List, String, etc.) share memory
  • Scope cleanup happens immediately on pop