Skip to content

Commit

Permalink
Implement resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
iamsayantan committed Apr 19, 2022
1 parent 5f31a86 commit bfb4b81
Show file tree
Hide file tree
Showing 5 changed files with 369 additions and 12 deletions.
23 changes: 22 additions & 1 deletion environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (e *Environment) Define(name string, value interface{}) {
}

// Get looks up a variable in the environment. It starts by looking into the innermost
// environment and goes up till it reaches the global scope.
// environment and goes up till it reaches the global scope.
func (e *Environment) Get(name Token) (interface{}, error) {
val, ok := e.values[name.Lexeme]
if ok {
Expand Down Expand Up @@ -54,3 +54,24 @@ func (e *Environment) Assign(name Token, value interface{}) error {

return NewRuntimeError(name, "Undefined variable '"+name.Lexeme+"'.")
}

// GetAt will get the exact environment where the variable is defined in the environment chain and
// return the value.
func (e *Environment) GetAt(distance int, name string) interface{} {
return e.ancestor(distance).values[name]
}

// AssignAt walks fixed numbers of steps and stuffs the variable into that map.
func (e *Environment) AssignAt(distance int, name Token, value interface{}) {
e.ancestor(distance).values[name.Lexeme] = value
}

// ancestor walks a fixed number of hops up the parent chain and returns the environment there.
func (e *Environment) ancestor(distance int) *Environment {
env := e
for i := 0; i < distance; i++ {
env = env.enclosing
}

return env
}
7 changes: 7 additions & 0 deletions glox.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ func (r *Runtime) run(source string) {
return
}

resolver := NewResolver(interpreter, r)
resolver.resolveStatements(statements)

if r.hadError {
return
}

interpreter.Interpret(statements)
}

Expand Down
39 changes: 28 additions & 11 deletions interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ type Interpreter struct {
runtime *Runtime
globals *Environment
environment *Environment
locals map[Expr]int
}

func NewInterpreter(runtime *Runtime) *Interpreter {
global := NewEnvironment(nil)
global.Define("clock", Clock{})
return &Interpreter{runtime: runtime, environment: global, globals: global}
return &Interpreter{runtime: runtime, environment: global, globals: global, locals: make(map[Expr]int)}
}

type RuntimeError struct {
Expand Down Expand Up @@ -123,12 +124,7 @@ func (i *Interpreter) VisitWhileStmt(stmt *WhileStmt) error {
}

func (i *Interpreter) VisitVarExpr(expr *VarExpr) (interface{}, error) {
val, err := i.environment.Get(expr.Name)
if err != nil {
return nil, err
}

return val, nil
return i.lookupVariable(expr.Name, expr)
}

// VisitAssignExpr evaluates the right hand side expression to get the value and then stores it in the
Expand All @@ -143,9 +139,14 @@ func (i *Interpreter) VisitAssignExpr(expr *Assign) (interface{}, error) {
return nil, err
}

err = i.environment.Assign(expr.Name, val)
if err != nil {
return nil, err
distance, ok := i.locals[expr]
if ok {
i.environment.AssignAt(distance, expr.Name, val)
} else {
err = i.environment.Assign(expr.Name, val)
if err != nil {
return nil, err
}
}

return val, nil
Expand Down Expand Up @@ -361,7 +362,7 @@ func (i *Interpreter) VisitCallExpr(expr *Call) (interface{}, error) {

// VisitFunctionStmt interprets a function syntax node. We take FunctionStmt syntax node, which
// is a compile time representation of the function - and convert it to its runtime representation.
// Here that's LoxFunction that wraps the syntax node. Here we also bind the resulting object to
// Here that's LoxFunction that wraps the syntax node. Here we also bind the resulting object to
// a new variable. So after creating LoxFunction, we create a new binding in the current environment
// and store a reference to it there.
func (i *Interpreter) VisitFunctionStmt(stmt *FunctionStmt) error {
Expand Down Expand Up @@ -447,3 +448,19 @@ func (i *Interpreter) checkNumberOperandBoth(operator Token, left, right interfa

return NewRuntimeError(operator, "Both operands must be numbers")
}

func (i *Interpreter) resolve(expr Expr, depth int) {
i.locals[expr] = depth
}

// lookupVariable resolves a variable. First we look up the resolved distance in the local map. Remember
// we only resolved local variables, globals are treated differently and don't end up in the map. So, if
// we don't find it in the local map, then it must be in the global environment.
func (i *Interpreter) lookupVariable(name Token, expr Expr) (interface{}, error) {
distance, ok := i.locals[expr]
if ok {
return i.environment.GetAt(distance, name.Lexeme), nil
} else {
return i.globals.Get(name)
}
}
Loading

0 comments on commit bfb4b81

Please sign in to comment.