Skip to main content

SymbolTable

Struct SymbolTable 

Source
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 again

Implementations§

Source§

impl<T> SymbolTable<T>

Source

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);
Source

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);
Source

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);
Source

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);
Source

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 back
Source

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 scope
Source

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 chain
Source

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));
Source

pub fn depth(&self) -> usize

Returns the number of active scopes, including the root — always at least 1.

§Examples
use symbol_lang::SymbolTable;

let mut table: SymbolTable<()> = SymbolTable::new();
assert_eq!(table.depth(), 1);
table.enter_scope();
assert_eq!(table.depth(), 2);

Trait Implementations§

Source§

impl<T> Default for SymbolTable<T>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<T> Freeze for SymbolTable<T>

§

impl<T> RefUnwindSafe for SymbolTable<T>
where T: RefUnwindSafe,

§

impl<T> Send for SymbolTable<T>
where T: Send,

§

impl<T> Sync for SymbolTable<T>
where T: Sync,

§

impl<T> Unpin for SymbolTable<T>

§

impl<T> UnsafeUnpin for SymbolTable<T>

§

impl<T> UnwindSafe for SymbolTable<T>
where T: RefUnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.