The environment module provides a lexically-scoped symbol table implementation for the R-Python language. It supports both variable and function bindings, with proper scope chain resolution.
The module implements two main structures:
Scope<A>: Represents a single scope with its variable and function bindingsEnvironment<A>: Manages a stack of scopes with a global scope at the bottom
The generic parameter A allows the environment to be used for different purposes:
- Type checking:
Environment<Type> - Interpretation:
Environment<Expression>
A single scope containing mappings for variables and functions.
pub struct Scope<A> {
pub variables: HashMap<Name, A>,
pub functions: HashMap<Name, Function>,
}new() -> Scope<A>: Creates a new empty scopemap_variable(var: Name, value: A): Binds a variable in the current scopemap_function(function: Function): Binds a function in the current scopelookup_var(var: &Name) -> Option<&A>: Looks up a variable in this scopelookup_function(name: &Name) -> Option<&Function>: Looks up a function in this scope
Manages a stack of scopes with lexical scoping rules.
pub struct Environment<A> {
pub globals: Scope<A>,
pub stack: LinkedList<Scope<A>>,
}new() -> Environment<A>: Creates a new environment with empty global scopemap_variable(var: Name, value: A): Maps a variable in the current scopemap_function(function: Function): Maps a function in the current scopelookup(var: &Name) -> Option<&A>: Looks up a variable through the scope chainlookup_function(name: &Name) -> Option<&Function>: Looks up a function through the scope chainpush(): Creates a new scope at the top of the stackpop(): Removes the topmost scopescoped_function() -> bool: Checks if we're in a function scope
-
Variable Resolution:
- First checks the local scopes from innermost to outermost
- Falls back to global scope if not found in any local scope
- Returns None if the variable is not found anywhere
-
Function Resolution:
- Follows the same rules as variable resolution
- Functions can be defined in any scope
- Inner functions can shadow outer functions
-
Scope Management:
- New scopes are pushed when entering a function or block
- Scopes are popped when exiting their block
- Global scope always remains at the bottom
let mut type_env: Environment<Type> = Environment::new();
// In global scope
type_env.map_variable("x".to_string(), Type::TInteger);
// In function scope
type_env.push();
type_env.map_variable("y".to_string(), Type::TReal);let mut runtime_env: Environment<Expression> = Environment::new();
// In global scope
runtime_env.map_variable("x".to_string(), Expression::CInt(42));
// In function scope
runtime_env.push();
runtime_env.map_variable("y".to_string(), Expression::CReal(3.14));let mut env: Environment<i32> = Environment::new();
// Global scope
env.map_variable("x".to_string(), 1);
// Outer function scope
env.push();
env.map_variable("y".to_string(), 2);
// Inner function scope
env.push();
env.map_variable("z".to_string(), 3);
// Variables from all scopes are accessible
assert!(env.lookup(&"x".to_string()).is_some()); // from global
assert!(env.lookup(&"y".to_string()).is_some()); // from outer
assert!(env.lookup(&"z".to_string()).is_some()); // from inner
// Pop scopes to clean up
env.pop(); // removes inner scope
env.pop(); // removes outer scope- The environment uses a
LinkedListfor the scope stack to efficiently push/pop scopes - All lookups traverse the entire scope chain for proper lexical scoping
- The generic parameter
Amust implementClonefor the environment to work - Functions are treated similarly to variables but stored in a separate map
- The global scope is always accessible, regardless of the current scope depth