Skip to content
Resolving & Binding Variables
step 1/3

Reading — step 1 of 3

Static Variable Resolution

~2 min readClosures & Classes

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 sees a is declared but not yet defined
  • Returning from top-level code
  • Using this outside 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…