compiler/
symbol_table.rs

1use std::collections::HashMap;
2use std::rc::Rc;
3
4#[derive(Clone, Debug, Eq, PartialEq)]
5pub enum SymbolScope {
6    LOCAL,
7    Global,
8    Builtin,
9    Free,
10    Function,
11}
12
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct Symbol {
15    pub name: String,
16    pub scope: SymbolScope,
17    pub index: usize,
18}
19
20#[derive(Clone, Debug, Eq, PartialEq)]
21pub struct SymbolTable {
22    pub outer: Option<Rc<SymbolTable>>,
23    symbols: HashMap<String, Rc<Symbol>>,
24    pub free_symbols: Vec<Rc<Symbol>>,
25    pub num_definitions: usize,
26}
27
28impl SymbolTable {
29    pub fn new() -> SymbolTable {
30        SymbolTable { symbols: HashMap::new(), free_symbols: vec![], num_definitions: 0, outer: None }
31    }
32
33    pub fn new_enclosed_symbol_table(outer: SymbolTable) -> SymbolTable {
34        SymbolTable { symbols: HashMap::new(), free_symbols: vec![], num_definitions: 0, outer: Some(Rc::new(outer)) }
35    }
36
37    pub fn define(&mut self, name: String) -> Rc<Symbol> {
38        let mut scope = SymbolScope::LOCAL;
39        if self.outer.is_none() {
40            scope = SymbolScope::Global;
41        }
42
43        let symbol = Rc::new(Symbol { name: name.clone(), index: self.num_definitions, scope });
44
45        self.num_definitions += 1;
46        self.symbols.insert(name.clone(), Rc::clone(&symbol));
47        return symbol;
48    }
49
50    // Resolve a name in the current scope, capturing free variables from outers when needed.
51    pub fn resolve(&mut self, name: String) -> Option<Rc<Symbol>> {
52        if let Some(sym) = self.symbols.get(&name) {
53            return Some(sym.clone());
54        }
55
56        // If not found locally, try outer scopes.
57        if let Some(outer) = &self.outer {
58            // We can't mutate outer here, so use a read-only helper to locate the original symbol.
59            if let Some(original) = outer.resolve_readonly(&name) {
60                return match original.scope {
61                    // Globals and builtins are accessed directly.
62                    SymbolScope::Global | SymbolScope::Builtin => Some(original),
63                    // Locals (from outer scope) or already-free symbols should be captured as a new free symbol here.
64                    SymbolScope::LOCAL | SymbolScope::Free | SymbolScope::Function => {
65                        Some(self.define_free(original))
66                    }
67                };
68            }
69        }
70
71        None
72    }
73
74    // Read-only resolver used internally to search outer scopes without mutating them.
75    fn resolve_readonly(&self, name: &str) -> Option<Rc<Symbol>> {
76        if let Some(sym) = self.symbols.get(name) {
77            return Some(sym.clone());
78        }
79        if let Some(outer) = &self.outer {
80            return outer.resolve_readonly(name);
81        }
82        None
83    }
84
85    pub fn define_builtin(&mut self, index: usize, name: String) -> Rc<Symbol> {
86        let symbol = Rc::new(Symbol { name: name.clone(), index, scope: SymbolScope::Builtin });
87        self.symbols.insert(name.clone(), Rc::clone(&symbol));
88        return symbol;
89    }
90
91    pub fn define_function_name(&mut self, name: String) -> Rc<Symbol> {
92        let symbol = Rc::new(Symbol { name: name.clone(), index: 0, scope: SymbolScope::Function });
93        self.symbols.insert(name.clone(), Rc::clone(&symbol));
94        return symbol;
95    }
96
97    pub fn define_free(&mut self, original: Rc<Symbol>) -> Rc<Symbol> {
98        self.free_symbols.push(Rc::clone(&original));
99        let symbol = Rc::new(Symbol { name: original.name.clone(), index: self.free_symbols.len() - 1, scope: SymbolScope::Free });
100        self.symbols.insert(original.name.clone(), Rc::clone(&symbol));
101        return symbol;
102    }
103}