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}