Reading — step 1 of 3
Static Variable Resolution
Resolving & Binding Variables
A resolver is a static analysis pass that runs after parsing but before interpreting. It determines exactly which environment each variable reference points to, eliminating the need for runtime name lookup chains.
The Problem
Without a resolver, variable lookup walks the environment chain at runtime — O(d) per access where d is the scope depth. More critically, it can produce incorrect results with closures:
var a = "global";
{
fun showA() { print a; }
showA(); // "global" (correct)
var a = "block";
showA(); // should still be "global" but might not be!
}
The issue: when showA is called the second time, there is now a a in the block scope. Without resolution, the runtime lookup might find this new a instead of the global one that showA closed over.
How the Resolver Works
The resolver walks the AST and, for each variable reference, counts how many scopes away the variable was declared. It records this as a depth:
var a = "global"; // depth 0 (global)
{
fun showA() {
print a; // resolved: depth 2 (two scopes out)
}
}
The interpreter then uses this depth to access the variable directly: environment.getAt(depth, name) — no chain walking needed.
Scope Stack
The resolver maintains a stack of scope maps (name to "is-defined" boolean):
beginScope(): push new empty map
endScope(): pop top map
declare(name): add name with "not ready" to top map
define(name): mark name as "ready" in top map
resolve(name): walk stack from top, record depth
Detecting Errors
The resolver catches errors that the interpreter would miss:
- Using a variable in its own initializer:
var a = a;— the resolver seesais declared but not yet defined - Returning from top-level code
- Using
thisoutside a class
How Crafting Interpreters Does It
Nystrom's resolver stores the depth in a side table (a hash map from AST node to integer). This avoids modifying the AST. The interpreter checks this table during variable access.
Your Task
Implement a resolver pass that walks the AST and records the depth of each variable reference. Use these depths in the interpreter for O(1) variable access.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…