A generic nested lexical scope / symbol table: a stack of name->value
bindings, innermost scope first, supporting the "define here, look up
through enclosing scopes" shape almost every real language's semantic
analysis needs (variable resolution, nested function/block scoping,
...). No existing internal precedent to extract this from (unlike
Ichor.Toolkit.Fixpoint/Graph) -- a from-scratch design, but a
standard, well-understood one.
Deliberately minimal: define/3 never itself rejects a duplicate key
in the same scope. Whether redefining a name within one scope is
allowed is a real per-language policy choice (some languages reject
it, some don't) -- left to the caller, via lookup_local/2, rather
than baked in here. Nothing is grammar-specific; key can be any term
with a working ==/2, not just an atom.
Summary
Functions
Binds key to value in the current (innermost) scope, shadowing any same-named binding in an enclosing one.
Looks up key, searching from the innermost scope outward. :error if it's not bound anywhere.
Looks up key in the current (innermost) scope only, not any enclosing one -- what a caller wanting to reject same-scope redefinition checks before calling define/3.
A fresh scope with just the outermost (global) level.
Exits the current (innermost) scope, discarding its own bindings.
Raises (FunctionClauseError) if there's no nested scope left to pop
-- the outermost level, from new/0, is never popped, the same way
popping an empty stack is a caller bug, not a recoverable condition.
Enters a new, empty nested scope.
Types
Functions
Binds key to value in the current (innermost) scope, shadowing any same-named binding in an enclosing one.
Looks up key, searching from the innermost scope outward. :error if it's not bound anywhere.
Looks up key in the current (innermost) scope only, not any enclosing one -- what a caller wanting to reject same-scope redefinition checks before calling define/3.
@spec new() :: t()
A fresh scope with just the outermost (global) level.
Exits the current (innermost) scope, discarding its own bindings.
Raises (FunctionClauseError) if there's no nested scope left to pop
-- the outermost level, from new/0, is never popped, the same way
popping an empty stack is a caller bug, not a recoverable condition.
Enters a new, empty nested scope.