pub struct SymbolTable<T> { /* private fields */ }Expand description
A lexically-scoped map from interned names to bindings.
SymbolTable<T> is the scope chain a name resolver threads: a stack of scopes,
each mapping a Symbol to a binding of the language’s choosing — a node
handle, a declaration record, a type, whatever the language resolves a name
to. The table is generic over that binding T and keys every scope on the
four-byte Symbol, so a lookup compares integers, not strings.
A resolver enter_scope on the way into a block,
defines names in it, lookups
names outward through the enclosing scopes, and
exit_scope on the way out — or wraps the whole
block in scoped. A name resolves to the nearest
enclosing binding, so an inner definition shadows an outer one while its scope
is open.
There is always a root scope, so the table is usable the moment it is built and the root can never be exited.
§Examples
use symbol_lang::SymbolTable;
use intern_lang::Interner;
let mut names = Interner::new();
let x = names.intern("x");
let mut table: SymbolTable<i32> = SymbolTable::new();
table.define(x, 1); // outer `x` = 1
assert_eq!(table.lookup(x), Some(&1));
table.enter_scope();
table.define(x, 2); // inner `x` shadows the outer one
assert_eq!(table.lookup(x), Some(&2));
table.exit_scope();
assert_eq!(table.lookup(x), Some(&1)); // outer `x` is visible againImplementations§
Source§impl<T> SymbolTable<T>
impl<T> SymbolTable<T>
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates a table with a single, empty root scope.
§Examples
use symbol_lang::SymbolTable;
let table: SymbolTable<()> = SymbolTable::new();
assert_eq!(table.depth(), 1);Sourcepub fn enter_scope(&mut self)
pub fn enter_scope(&mut self)
Pushes a new, empty innermost scope.
§Examples
use symbol_lang::SymbolTable;
let mut table: SymbolTable<()> = SymbolTable::new();
table.enter_scope();
assert_eq!(table.depth(), 2);Sourcepub fn exit_scope(&mut self) -> bool
pub fn exit_scope(&mut self) -> bool
Pops the innermost scope, discarding its bindings, and returns true.
Exiting the root scope is a no-op that returns false, so the chain always
keeps at least one scope.
§Examples
use symbol_lang::SymbolTable;
let mut table: SymbolTable<()> = SymbolTable::new();
table.enter_scope();
assert!(table.exit_scope()); // popped the inner scope
assert!(!table.exit_scope()); // root cannot be exited
assert_eq!(table.depth(), 1);Sourcepub fn scoped<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R
pub fn scoped<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R
Runs f inside a freshly entered scope, exiting it afterward, and returns
what f returns.
This is the balanced way to scope a block: exactly one scope is entered and
exited, so the chain depth is the same before and after no matter what f
does.
§Examples
use symbol_lang::SymbolTable;
use intern_lang::Interner;
let mut names = Interner::new();
let y = names.intern("y");
let mut table: SymbolTable<i32> = SymbolTable::new();
let seen = table.scoped(|table| {
table.define(y, 9);
table.lookup(y).copied()
});
assert_eq!(seen, Some(9));
assert_eq!(table.lookup(y), None); // the inner `y` is gone
assert_eq!(table.depth(), 1);Sourcepub fn define(&mut self, name: Symbol, value: T) -> Option<T>
pub fn define(&mut self, name: Symbol, value: T) -> Option<T>
Binds name to value in the current scope, returning any binding the same
name already had in that scope (which it replaces).
A Some return means the name was already defined in the current scope — a
duplicate definition the caller can report. It never touches an enclosing
scope, so it cannot disturb a shadowed outer binding.
§Examples
use symbol_lang::SymbolTable;
use intern_lang::Interner;
let mut names = Interner::new();
let n = names.intern("n");
let mut table: SymbolTable<i32> = SymbolTable::new();
assert_eq!(table.define(n, 1), None); // fresh
assert_eq!(table.define(n, 2), Some(1)); // redefined: the old binding comes backSourcepub fn lookup(&self, name: Symbol) -> Option<&T>
pub fn lookup(&self, name: Symbol) -> Option<&T>
Looks name up through the scope chain, returning the binding from the
nearest scope that defines it, or None if no scope does.
§Examples
use symbol_lang::SymbolTable;
use intern_lang::Interner;
let mut names = Interner::new();
let g = names.intern("g");
let mut table: SymbolTable<&str> = SymbolTable::new();
table.define(g, "global");
table.enter_scope();
assert_eq!(table.lookup(g), Some(&"global")); // found in the outer scopeSourcepub fn lookup_local(&self, name: Symbol) -> Option<&T>
pub fn lookup_local(&self, name: Symbol) -> Option<&T>
Looks name up in the current scope only, ignoring enclosing scopes.
Useful for a duplicate-in-this-scope check before defining, when shadowing an outer binding is allowed but redefining in the same scope is not.
§Examples
use symbol_lang::SymbolTable;
use intern_lang::Interner;
let mut names = Interner::new();
let v = names.intern("v");
let mut table: SymbolTable<i32> = SymbolTable::new();
table.define(v, 1);
table.enter_scope();
assert_eq!(table.lookup_local(v), None); // not in *this* scope
assert_eq!(table.lookup(v), Some(&1)); // but visible through the chainSourcepub fn is_defined(&self, name: Symbol) -> bool
pub fn is_defined(&self, name: Symbol) -> bool
Returns true if name is bound anywhere in the scope chain.
§Examples
use symbol_lang::SymbolTable;
use intern_lang::Interner;
let mut names = Interner::new();
let a = names.intern("a");
let mut table: SymbolTable<()> = SymbolTable::new();
assert!(!table.is_defined(a));
table.define(a, ());
assert!(table.is_defined(a));