Skip to main content

rl_lang/resolver/
mod.rs

1//! Variable resolution pass - runs between parsing and evaluation.
2//!
3//! The resolver walks the AST and transforms unresolved name references
4//! into slot-indexed lookups (`depth`, `slot`), eliminating string-based
5//! name lookups at runtime.
6//!
7//! - `depth` - how many scopes up from the current scope the variable lives
8//! - `slot`  - the index of the variable within that scope's slot array
9//!
10//! Unresolved `Identifier` nodes become `ResolvedIdentifier { depth, slot }`.
11//! Unresolved `Assign` nodes become `ResolvedAssign { depth, slot, value }`.
12//! Function and lambda bodies are resolved in their own pushed scope.
13//! Import statements are read from disk, lexed, parsed, and resolved inline.
14
15use crate::ast::Ast;
16
17mod expressions;
18mod statements;
19
20/// Walks the AST and resolves all name references to `(depth, slot)` pairs.
21pub struct Resolver {
22    /// Stack of scopes, each scope being an ordered list of declared names.
23    /// Index in the list is the slot number; distance from the top is the depth.
24    scopes: Vec<Vec<String>>,
25    pub current_dir: std::path::PathBuf,
26    pub ast_arena: Ast,
27}
28
29impl Default for Resolver {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl Resolver {
36    /// Creates a new [`Resolver`] with a single empty global scope.
37    pub fn new() -> Self {
38        Self {
39            scopes: vec![vec![]],
40            current_dir: std::path::PathBuf::new(),
41            ast_arena: Ast::new(),
42        }
43    }
44
45    /// Pushes a new empty scope onto the scope stack.
46    pub fn push_scope(&mut self) {
47        self.scopes.push(vec![]);
48    }
49
50    /// Pops the innermost scope from the stack.
51    pub fn pop_scope(&mut self) {
52        self.scopes.pop();
53    }
54
55    /// Declares a name in the current scope and returns its slot index.
56    ///
57    /// The slot is the position of the name within the current scope frame,
58    /// used later by the evaluator for direct indexed access.
59    pub fn declare(&mut self, name: String) -> usize {
60        let frame = self.scopes.last_mut().unwrap();
61        let slot = frame.len();
62        frame.push(name);
63        slot
64    }
65
66    /// Searches for `name` by walking scopes from innermost to outermost.
67    ///
68    /// Returns `Some((depth, slot))` where `depth` is the number of scopes
69    /// above the current one (0 = current), and `slot` is the index within
70    /// that scope. Returns `None` if the name is not declared in any scope.
71    pub fn resolve_name(&self, name: &str) -> Option<(usize, usize)> {
72        for (depth, frame) in self.scopes.iter().rev().enumerate() {
73            if let Some(slot) = frame.iter().rposition(|n| n == name) {
74                return Some((depth, slot));
75            }
76        }
77        None
78    }
79}