symbol_lang/table.rs
1//! The lexically-scoped symbol table.
2
3use alloc::collections::BTreeMap;
4use alloc::vec::Vec;
5
6use intern_lang::Symbol;
7
8/// A lexically-scoped map from interned names to bindings.
9///
10/// `SymbolTable<T>` is the scope chain a name resolver threads: a stack of scopes,
11/// each mapping a [`Symbol`] to a binding of the language's choosing — a node
12/// handle, a declaration record, a type, whatever the language resolves a name
13/// *to*. The table is generic over that binding `T` and keys every scope on the
14/// four-byte `Symbol`, so a lookup compares integers, not strings.
15///
16/// A resolver [`enter_scope`](SymbolTable::enter_scope) on the way into a block,
17/// [`define`](SymbolTable::define)s names in it, [`lookup`](SymbolTable::lookup)s
18/// names outward through the enclosing scopes, and
19/// [`exit_scope`](SymbolTable::exit_scope) on the way out — or wraps the whole
20/// block in [`scoped`](SymbolTable::scoped). A name resolves to the nearest
21/// enclosing binding, so an inner definition shadows an outer one while its scope
22/// is open.
23///
24/// There is always a root scope, so the table is usable the moment it is built and
25/// the root can never be exited.
26///
27/// # Examples
28///
29/// ```
30/// use symbol_lang::SymbolTable;
31/// use intern_lang::Interner;
32///
33/// let mut names = Interner::new();
34/// let x = names.intern("x");
35///
36/// let mut table: SymbolTable<i32> = SymbolTable::new();
37/// table.define(x, 1); // outer `x` = 1
38/// assert_eq!(table.lookup(x), Some(&1));
39///
40/// table.enter_scope();
41/// table.define(x, 2); // inner `x` shadows the outer one
42/// assert_eq!(table.lookup(x), Some(&2));
43/// table.exit_scope();
44///
45/// assert_eq!(table.lookup(x), Some(&1)); // outer `x` is visible again
46/// ```
47pub struct SymbolTable<T> {
48 /// The scope chain, outermost first. Never empty: `scopes[0]` is the root, and
49 /// [`exit_scope`](SymbolTable::exit_scope) refuses to pop it.
50 scopes: Vec<BTreeMap<Symbol, T>>,
51}
52
53impl<T> SymbolTable<T> {
54 /// Creates a table with a single, empty root scope.
55 ///
56 /// # Examples
57 ///
58 /// ```
59 /// use symbol_lang::SymbolTable;
60 ///
61 /// let table: SymbolTable<()> = SymbolTable::new();
62 /// assert_eq!(table.depth(), 1);
63 /// ```
64 #[must_use]
65 pub fn new() -> Self {
66 Self {
67 scopes: alloc::vec![BTreeMap::new()],
68 }
69 }
70
71 /// A mutable reference to the current (innermost) scope. The chain is never
72 /// empty, so the last element is always present.
73 fn current_mut(&mut self) -> &mut BTreeMap<Symbol, T> {
74 let last = self.scopes.len() - 1; // `len >= 1` by the type invariant
75 &mut self.scopes[last]
76 }
77
78 /// Pushes a new, empty innermost scope.
79 ///
80 /// # Examples
81 ///
82 /// ```
83 /// use symbol_lang::SymbolTable;
84 ///
85 /// let mut table: SymbolTable<()> = SymbolTable::new();
86 /// table.enter_scope();
87 /// assert_eq!(table.depth(), 2);
88 /// ```
89 pub fn enter_scope(&mut self) {
90 self.scopes.push(BTreeMap::new());
91 }
92
93 /// Pops the innermost scope, discarding its bindings, and returns `true`.
94 ///
95 /// Exiting the root scope is a no-op that returns `false`, so the chain always
96 /// keeps at least one scope.
97 ///
98 /// # Examples
99 ///
100 /// ```
101 /// use symbol_lang::SymbolTable;
102 ///
103 /// let mut table: SymbolTable<()> = SymbolTable::new();
104 /// table.enter_scope();
105 /// assert!(table.exit_scope()); // popped the inner scope
106 /// assert!(!table.exit_scope()); // root cannot be exited
107 /// assert_eq!(table.depth(), 1);
108 /// ```
109 pub fn exit_scope(&mut self) -> bool {
110 if self.scopes.len() > 1 {
111 let _ = self.scopes.pop();
112 true
113 } else {
114 false
115 }
116 }
117
118 /// Runs `f` inside a freshly entered scope, exiting it afterward, and returns
119 /// what `f` returns.
120 ///
121 /// This is the balanced way to scope a block: exactly one scope is entered and
122 /// exited, so the chain depth is the same before and after no matter what `f`
123 /// does.
124 ///
125 /// # Examples
126 ///
127 /// ```
128 /// use symbol_lang::SymbolTable;
129 /// use intern_lang::Interner;
130 ///
131 /// let mut names = Interner::new();
132 /// let y = names.intern("y");
133 /// let mut table: SymbolTable<i32> = SymbolTable::new();
134 ///
135 /// let seen = table.scoped(|table| {
136 /// table.define(y, 9);
137 /// table.lookup(y).copied()
138 /// });
139 /// assert_eq!(seen, Some(9));
140 /// assert_eq!(table.lookup(y), None); // the inner `y` is gone
141 /// assert_eq!(table.depth(), 1);
142 /// ```
143 pub fn scoped<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
144 self.enter_scope();
145 let result = f(self);
146 let _ = self.exit_scope();
147 result
148 }
149
150 /// Binds `name` to `value` in the current scope, returning any binding the same
151 /// name already had *in that scope* (which it replaces).
152 ///
153 /// A `Some` return means the name was already defined in the current scope — a
154 /// duplicate definition the caller can report. It never touches an enclosing
155 /// scope, so it cannot disturb a shadowed outer binding.
156 ///
157 /// # Examples
158 ///
159 /// ```
160 /// use symbol_lang::SymbolTable;
161 /// use intern_lang::Interner;
162 ///
163 /// let mut names = Interner::new();
164 /// let n = names.intern("n");
165 /// let mut table: SymbolTable<i32> = SymbolTable::new();
166 ///
167 /// assert_eq!(table.define(n, 1), None); // fresh
168 /// assert_eq!(table.define(n, 2), Some(1)); // redefined: the old binding comes back
169 /// ```
170 pub fn define(&mut self, name: Symbol, value: T) -> Option<T> {
171 self.current_mut().insert(name, value)
172 }
173
174 /// Looks `name` up through the scope chain, returning the binding from the
175 /// nearest scope that defines it, or `None` if no scope does.
176 ///
177 /// # Examples
178 ///
179 /// ```
180 /// use symbol_lang::SymbolTable;
181 /// use intern_lang::Interner;
182 ///
183 /// let mut names = Interner::new();
184 /// let g = names.intern("g");
185 /// let mut table: SymbolTable<&str> = SymbolTable::new();
186 /// table.define(g, "global");
187 /// table.enter_scope();
188 /// assert_eq!(table.lookup(g), Some(&"global")); // found in the outer scope
189 /// ```
190 #[must_use]
191 pub fn lookup(&self, name: Symbol) -> Option<&T> {
192 self.scopes.iter().rev().find_map(|scope| scope.get(&name))
193 }
194
195 /// Looks `name` up in the current scope only, ignoring enclosing scopes.
196 ///
197 /// Useful for a duplicate-in-this-scope check before defining, when shadowing an
198 /// outer binding is allowed but redefining in the same scope is not.
199 ///
200 /// # Examples
201 ///
202 /// ```
203 /// use symbol_lang::SymbolTable;
204 /// use intern_lang::Interner;
205 ///
206 /// let mut names = Interner::new();
207 /// let v = names.intern("v");
208 /// let mut table: SymbolTable<i32> = SymbolTable::new();
209 /// table.define(v, 1);
210 /// table.enter_scope();
211 /// assert_eq!(table.lookup_local(v), None); // not in *this* scope
212 /// assert_eq!(table.lookup(v), Some(&1)); // but visible through the chain
213 /// ```
214 #[must_use]
215 pub fn lookup_local(&self, name: Symbol) -> Option<&T> {
216 self.scopes.last().and_then(|scope| scope.get(&name))
217 }
218
219 /// Returns `true` if `name` is bound anywhere in the scope chain.
220 ///
221 /// # Examples
222 ///
223 /// ```
224 /// use symbol_lang::SymbolTable;
225 /// use intern_lang::Interner;
226 ///
227 /// let mut names = Interner::new();
228 /// let a = names.intern("a");
229 /// let mut table: SymbolTable<()> = SymbolTable::new();
230 /// assert!(!table.is_defined(a));
231 /// table.define(a, ());
232 /// assert!(table.is_defined(a));
233 /// ```
234 #[must_use]
235 pub fn is_defined(&self, name: Symbol) -> bool {
236 self.lookup(name).is_some()
237 }
238
239 /// Returns the number of active scopes, including the root — always at least 1.
240 ///
241 /// # Examples
242 ///
243 /// ```
244 /// use symbol_lang::SymbolTable;
245 ///
246 /// let mut table: SymbolTable<()> = SymbolTable::new();
247 /// assert_eq!(table.depth(), 1);
248 /// table.enter_scope();
249 /// assert_eq!(table.depth(), 2);
250 /// ```
251 #[must_use]
252 pub fn depth(&self) -> usize {
253 self.scopes.len()
254 }
255}
256
257impl<T> Default for SymbolTable<T> {
258 #[inline]
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use intern_lang::Interner;
267
268 use super::*;
269
270 /// Interns three distinct names for tests.
271 fn names() -> (Interner, Symbol, Symbol, Symbol) {
272 let mut interner = Interner::new();
273 let a = interner.intern("a");
274 let b = interner.intern("b");
275 let c = interner.intern("c");
276 (interner, a, b, c)
277 }
278
279 #[test]
280 fn test_new_has_one_root_scope() {
281 let table: SymbolTable<()> = SymbolTable::new();
282 assert_eq!(table.depth(), 1);
283 }
284
285 #[test]
286 fn test_define_then_lookup() {
287 let (_i, a, _b, _c) = names();
288 let mut table: SymbolTable<i32> = SymbolTable::new();
289 assert_eq!(table.define(a, 7), None);
290 assert_eq!(table.lookup(a), Some(&7));
291 }
292
293 #[test]
294 fn test_redefine_returns_previous() {
295 let (_i, a, _b, _c) = names();
296 let mut table: SymbolTable<i32> = SymbolTable::new();
297 table.define(a, 1);
298 assert_eq!(table.define(a, 2), Some(1));
299 assert_eq!(table.lookup(a), Some(&2));
300 }
301
302 #[test]
303 fn test_inner_scope_shadows_then_restores() {
304 let (_i, a, _b, _c) = names();
305 let mut table: SymbolTable<i32> = SymbolTable::new();
306 table.define(a, 1);
307 table.enter_scope();
308 table.define(a, 2);
309 assert_eq!(table.lookup(a), Some(&2));
310 assert_eq!(table.lookup_local(a), Some(&2));
311 table.exit_scope();
312 assert_eq!(table.lookup(a), Some(&1));
313 }
314
315 #[test]
316 fn test_lookup_local_ignores_outer() {
317 let (_i, a, _b, _c) = names();
318 let mut table: SymbolTable<i32> = SymbolTable::new();
319 table.define(a, 1);
320 table.enter_scope();
321 assert_eq!(table.lookup_local(a), None);
322 assert_eq!(table.lookup(a), Some(&1));
323 }
324
325 #[test]
326 fn test_exit_root_is_a_noop() {
327 let mut table: SymbolTable<()> = SymbolTable::new();
328 assert!(!table.exit_scope());
329 assert_eq!(table.depth(), 1);
330 }
331
332 #[test]
333 fn test_define_in_inner_does_not_touch_outer() {
334 let (_i, a, _b, _c) = names();
335 let mut table: SymbolTable<i32> = SymbolTable::new();
336 table.define(a, 1);
337 table.scoped(|table| {
338 table.define(a, 99);
339 });
340 // The inner definition vanished with its scope; the outer stands.
341 assert_eq!(table.lookup(a), Some(&1));
342 }
343
344 #[test]
345 fn test_unbound_lookup_is_none() {
346 let (_i, a, b, _c) = names();
347 let mut table: SymbolTable<i32> = SymbolTable::new();
348 table.define(a, 1);
349 assert_eq!(table.lookup(b), None);
350 assert!(!table.is_defined(b));
351 }
352}