~jlambda/craftinginterpreters

286500d81ba42a6adf6b15f6d289023fad1bb408 — jerry 4 months ago 4431880
Chapter 11 Resolving and Binding
M craftinginterpreters/notes.md => craftinginterpreters/notes.md +95 -0
@@ 711,6 711,101 @@ Partially because doing `functions` was one of my goals,
but also because I've been tired.  Only 2 more chapters to 
go so lets finish `jlox` and then move to `clox`

### Notes

This whole resolver class pretty much just runs it's own visitor 
pattern stuff, overriding both Expr and Stmt.

Pretty much what it does is it walks the tree (over everything in the tree without eval)
and whenever it encounters a variable assignment it creates a hashmap to
declare it, setting it to false, and then definining it, setting it to true.

Instead of saving the _value_ of variables it saves a pointer to 
which scope to use for the variable.  Then saves that in the 
interpreter as a "side-table" lookup.

This way when it encounters a function declaration it eventually 
"resolves" and saves where in the environment to look up the variable.

This works for the example provided:
```
var a = "global";
{
  fun showA() {
    print a;
  }

  showA();
  var a = "block";
  showA();
}
```

but only with respect to the enclosing block.
The variable itself is still dynamic in scope.
So it doesn't actually save the variable 
_at the time of function declaration_ it saves 
_which variable_ is pointed to.

It kind of seems like instead of resolving _where_ 
a variable is in scope, maybe we could just 
rewrite literals that point to that variable.
Maybe that's part of `clox` I don't know.

Otherwise that's a lot of code for what seems like
we could have done otherwise.  But it does show 
the usefulness of checking if in a function or not
and other aspects of semantic analysis.

### Terms

* semantic analysis
* static analysis
* lookup resolutions (e.g., how scheme handles environments)
* persistent data structures for environments
* variable resolution pass
  * no side effects
  * no control flow (both branches are visited)
  
```
* A block statement introduces a new scope for the statements it contains.
* A function declaration introduces a new scope for its body and binds its parameters in that scope.
* A variable declaration adds a new variable to the current scope.
* Variable and assignment expressions need to have their variables resolved.
```

### Challenges

Challenges

1. Why is it safe to eagerly define the variable bound to a function’s name when other variables must wait until after they are initialized before they can be used?

As the chapter said, recursion.  Probably also mutual recursion, and not having to declare before use.

2. How do other languages you know handle local variables that refer to the same name in their initializer, like:
```

    var a = "outer";
    {
      var a = a;
    }
```

Is it a runtime error? Compile error? Allowed? Do they treat global variables differently? Do you agree with their choices? Justify your answer.

Lexical Scope shadows.  It's probably not the most "correct" way to do it, but it's 
really useful anyway and I would miss programming without it.

3. Extend the resolver to report an error if a local variable is never used.

TODO: We can probably just add a table of used variables, and when the scope dies, 
if a variable is unused, throw an exception.  We can mark its use in `resolveLocal`

4. Our resolver calculates which environment the variable is found in, but it’s still looked up by name in that map. A more efficient environment representation would store local variables in an array and look them up by index.

Extend the resolver to associate a unique index for each local variable declared in a scope. When resolving a variable access, look up both the scope the variable is in and its index and store that. In the interpreter, use that to quickly access a variable by its index instead of using a map.

TODO


### Extras

M craftinginterpreters/src/main/java/craftinginterpreters/App.java => craftinginterpreters/src/main/java/craftinginterpreters/App.java +4 -0
@@ 63,6 63,10 @@ public class App

        // Expr expression = parser.parse();


        Resolver resolver = new Resolver(interpreter);
        resolver.resolve(statements);

        if (hadError) return;

        interpreter.interpret(statements, prompt);

M craftinginterpreters/src/main/java/craftinginterpreters/Environment.java => craftinginterpreters/src/main/java/craftinginterpreters/Environment.java +16 -0
@@ 36,6 36,22 @@ class Environment {
        values.put(name, value);
    }

    Environment ancestor(int distance) {
        Environment environment = this;
        for (int i = 0; i < distance; i++) {
            environment = environment.enclosing;
        }
        return environment;
    }

    Object getAt(int distance, String name) {
        return ancestor(distance).values.get(name);
    }

    void assignAt(int distance, Token name, Object value) {
        ancestor(distance).values.put(name.lexeme, value);
    }

    Environment() {
        enclosing = null;
    }

M craftinginterpreters/src/main/java/craftinginterpreters/Interpreter.java => craftinginterpreters/src/main/java/craftinginterpreters/Interpreter.java +33 -1
@@ 1,13 1,16 @@
package craftinginterpreters;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Interpreter implements Expr.Visitor<Object>,
                             Stmt.Visitor<Void> {

    final Environment globals = new Environment();
    private Environment environment = globals;
    private final Map<Expr, Integer> locals = new HashMap<>();
    private Token PROMPT = new Token(TokenType.DEBUG, "PROMPT", null, 0);
    private Token PROMPT_LAST = new Token(TokenType.DEBUG, "PROMPT_LAST", null, 0);



@@ 27,6 30,7 @@ class Interpreter implements Expr.Visitor<Object>,
            });
    }


    void interpret(List<Stmt> statements,
                   Boolean prompt) {



@@ 87,6 91,10 @@ class Interpreter implements Expr.Visitor<Object>,
        stmt.accept(this);
    }

    void resolve(Expr expr, int depth) {
        locals.put(expr, depth);
    }

    @Override
    public Void visitBlockStmt(Stmt.Block stmt){
        executeBlock(stmt.statements, new Environment(environment));


@@ 178,17 186,41 @@ class Interpreter implements Expr.Visitor<Object>,
    @Override
    public Object visitAssignExpr(Expr.Assign expr) {
        Object value = evaluate(expr.value);
        environment.assign(expr.name, value);

        Integer distance = locals.get(expr);
        if (distance != null) {
            environment.assignAt(distance, expr.name, value);
        } else {
            globals.assign(expr.name, value);
        }

        // replaced in resolving and binding
        //environment.assign(expr.name, value);

        return value;
    }

    @Override
    public Object visitVariableExpr(Expr.Variable expr) {
        /*
        // This existed but resolving and binding replaced the return
        // we can fix this later
        Object res = environment.get(expr.name);
        if (res == null) {
         Token err = new Token(TokenType.DEBUG, stringify(expr.name), null, 0);
         throw new RuntimeError(err, "Variable: " + expr.name.lexeme + " was not assigned");}
        return res;
        */
        return lookUpVariable(expr.name, expr);
    }

    private Object lookUpVariable(Token name, Expr expr) {
        Integer distance = locals.get(expr);
        if (distance != null) {
            return environment.getAt(distance, name.lexeme);
        } else {
            return globals.get(name);
        }
    }

    @Override

A craftinginterpreters/src/main/java/craftinginterpreters/Resolver.java => craftinginterpreters/src/main/java/craftinginterpreters/Resolver.java +211 -0
@@ 0,0 1,211 @@
package craftinginterpreters;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
    private final Interpreter interpreter;
    private final Stack<Map<String, Boolean>> scopes = new Stack<>();
    private FunctionType currentFunction = FunctionType.NONE;

    Resolver(Interpreter interpreter) {
        this.interpreter = interpreter;
    }

    void resolve(List<Stmt> statements) {
        for (Stmt statement : statements) {
            resolve(statement);
        }
    }

    private void resolve(Stmt stmt) {
        stmt.accept(this);
    }

    private void resolve(Expr expr){
        expr.accept(this);
    }

    private void resolveFunction(Stmt.Function function,
                                 FunctionType type) {
        FunctionType enclosingFunction = currentFunction;
        currentFunction = type;
        beginScope();
        for (Token param : function.params) {
            declare(param);
            define(param);
        }

        resolve(function.body);
        endScope();
        currentFunction = enclosingFunction;
    }

    private void beginScope() {
        scopes.push(new HashMap<String, Boolean>());
    }

    private void endScope() {
        scopes.pop();
    }

    private void declare(Token name) {
        if (scopes.isEmpty()) return;

        Map<String, Boolean> scope = scopes.peek();
        if (scope.containsKey(name.lexeme)) {
            App.error(name,
                      "Already a variable with this name in this scope.");
        }
        scope.put(name.lexeme, false);
    }

    private void define(Token name) {
        if (scopes.isEmpty()) return;
        scopes.peek().put(name.lexeme, true);
    }

    private void resolveLocal(Expr expr, Token name){
        for (int i = scopes.size() - 1; i >= 0; i--) {
            if (scopes.get(i).containsKey(name.lexeme)){
                interpreter.resolve(expr, scopes.size() - 1 - i);
                return;
            }
        }
    }

    @Override
    public Void visitBlockStmt(Stmt.Block stmt) {
        beginScope();
        resolve(stmt.statements);
        endScope();
        return null;
    }

    @Override
    public Void visitExpressionStmt(Stmt.Expression stmt) {
        resolve(stmt.expression);
        return null;
    }

    @Override
    public Void visitIfStmt(Stmt.If stmt) {
        resolve(stmt.condition);
        resolve(stmt.thenBranch);
        if (stmt.elseBranch != null) resolve(stmt.elseBranch);
        return null;
    }

    @Override
    public Void visitPrintStmt(Stmt.Print stmt) {
        resolve(stmt.expression);
        return null;
    }

    @Override
    public Void visitReturnStmt(Stmt.Return stmt) {

        if (currentFunction == FunctionType.NONE) {
            App.error(stmt.keyword, "Can't return from top-level code.");
        }

        if (stmt.value != null) {
            resolve(stmt.value);
        }
        return null;
    }

    @Override
    public Void visitWhileStmt(Stmt.While stmt) {
        resolve(stmt.condition);
        resolve(stmt.body);
        return null;
    }

    @Override
    public Void visitBinaryExpr(Expr.Binary expr) {
        resolve(expr.left);
        resolve(expr.right);
        return null;
    }

    @Override
    public Void visitCallExpr(Expr.Call expr) {
        resolve(expr.callee);

        for (Expr argument : expr.arguments) {
            resolve(argument);
        }

        return null;
    }

    @Override
    public Void visitGroupingExpr(Expr.Grouping expr) {
        resolve(expr.expression);
        return null;
    }

    @Override
    public Void visitLiteralExpr(Expr.Literal expr) {
        return null;
    }

    @Override
    public Void visitLogicalExpr(Expr.Logical expr) {
        resolve(expr.left);
        resolve(expr.right);
        return null;
    }

    @Override
    public Void visitUnaryExpr(Expr.Unary expr) {
        resolve(expr.right);
        return null;
    }

    @Override
    public Void visitFunctionStmt(Stmt.Function stmt) {
        declare(stmt.name);
        define(stmt.name);

        resolveFunction(stmt, FunctionType.FUNCTION);
        return null;
    }

    @Override
    public Void visitVarStmt(Stmt.Var stmt) {
        declare(stmt.name);
        if (stmt.initializer != null) {
            resolve(stmt.initializer);
        }
        define(stmt.name);
        return null;
    }

    @Override
    public Void visitAssignExpr(Expr.Assign expr) {
        resolve(expr.value);
        resolveLocal(expr, expr.name);
        return null;
    }

    @Override
    public Void visitVariableExpr(Expr.Variable expr) {
        if (!scopes.isEmpty() &&
            scopes.peek().get(expr.name.lexeme) == Boolean.FALSE) {
            App.error(expr.name,
                      "Can't read local variable in its own initializer.");
        }

        resolveLocal(expr, expr.name);
        return null;
    }

    private enum FunctionType {
        NONE, FUNCTION
    }

}