Skip to content

Commit

Permalink
Completed chapter 11
Browse files Browse the repository at this point in the history
  • Loading branch information
perlun committed Jan 25, 2020
1 parent 4cbef59 commit 64da4fd
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 5 deletions.
30 changes: 28 additions & 2 deletions CSLox/Interpreter.cs
Expand Up @@ -7,6 +7,7 @@ namespace CSLox
internal class Interpreter : Expr.Visitor<object>, Stmt.Visitor<VoidObject>
{
private readonly LoxEnvironment globals = new LoxEnvironment();
private readonly IDictionary<Expr, int> locals = new Dictionary<Expr, int>();

private LoxEnvironment loxEnvironment;

Expand Down Expand Up @@ -95,7 +96,19 @@ public object VisitUnaryExpr(Expr.Unary expr)

public object VisitVariableExpr(Expr.Variable expr)
{
return loxEnvironment.Get(expr.name);
return LookUpVariable(expr.name, expr);
}

private object LookUpVariable(Token name, Expr expr)
{
if (locals.TryGetValue(expr, out int distance))
{
return loxEnvironment.GetAt(distance, name.lexeme);
}
else
{
return globals.Get(name);
}
}

private static void CheckNumberOperand(Token _operator, object operand)
Expand Down Expand Up @@ -174,6 +187,11 @@ private void Execute(Stmt stmt)
stmt.Accept(this);
}

internal void Resolve(Expr expr, int depth)
{
locals[expr] = depth;
}

internal void ExecuteBlock(IEnumerable<Stmt> statements, LoxEnvironment loxEnvironment)
{
LoxEnvironment previous = this.loxEnvironment;
Expand Down Expand Up @@ -267,7 +285,15 @@ public object VisitAssignExpr(Expr.Assign expr)
{
object value = Evaluate(expr.value);

loxEnvironment.Assign(expr.name, value);
if (locals.TryGetValue(expr, out int distance))
{
loxEnvironment.AssignAt(distance, expr.name, value);
}
else
{
globals.Assign(expr.name, value);
}

return value;
}

Expand Down
12 changes: 10 additions & 2 deletions CSLox/Lox.cs
Expand Up @@ -69,11 +69,17 @@ private static void Run(string source)

// For now, just print the tokens.
var parser = new Parser(tokens);
IEnumerable<Stmt> statements = parser.ParseStatements();
var statements = parser.ParseStatements();

// Stop if there was a syntax error.
if (!hadError)
{
var resolver = new Resolver(interpreter);
resolver.Resolve(statements);

// Stop if there was a resolution error.
if (hadError) return;

interpreter.Interpret(statements);
}
else
Expand All @@ -91,6 +97,8 @@ private static void Run(string source)
return;
}

// TODO: we don't run the resolver in this case, which essentially means that we will be unable to
// TODO: refer to local variables.
object result = interpreter.Evaluate(expression);

if (result != null)
Expand Down Expand Up @@ -118,7 +126,7 @@ private static void Report(int line, string where, string message)
hadError = true;
}

private static void Error(Token token, string message)
internal static void Error(Token token, string message)
{
if (token.type == TokenType.EOF)
{
Expand Down
21 changes: 21 additions & 0 deletions CSLox/LoxEnvironment.cs
Expand Up @@ -18,6 +18,27 @@ internal void Define(string name, object value)
values[name] = value;
}

internal object GetAt(int distance, string name)
{
return Ancestor(distance).values[name];
}

internal void AssignAt(int distance, Token name, object value)
{
Ancestor(distance).values[name.lexeme] = value;
}

private LoxEnvironment Ancestor(int distance)
{
LoxEnvironment environment = this;
for (int i = 0; i < distance; i++)
{
environment = environment.enclosing;
}

return environment;
}

internal object Get(Token name)
{
if (values.ContainsKey(name.lexeme))
Expand Down
2 changes: 1 addition & 1 deletion CSLox/Parser.cs
Expand Up @@ -25,7 +25,7 @@ internal Parser(List<Token> tokens)
this.tokens = tokens;
}

internal IEnumerable<Stmt> ParseStatements()
internal IList<Stmt> ParseStatements()
{
var statements = new List<Stmt>();

Expand Down
254 changes: 254 additions & 0 deletions CSLox/Resolver.cs
@@ -0,0 +1,254 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace CSLox
{
class Resolver : Expr.Visitor<VoidObject>, Stmt.Visitor<VoidObject>
{
private readonly Stack<IDictionary<string, bool>> scopes = new Stack<IDictionary<string, bool>>();
private FunctionType currentFunction = FunctionType.NONE;

private readonly Interpreter interpreter;

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

internal void Resolve(IEnumerable<Stmt> statements)
{
foreach (Stmt statement in statements)
{
Resolve(statement);
}
}

private void BeginScope()
{
scopes.Push(new Dictionary<string, bool>());
}

private void EndScope()
{
scopes.Pop();
}

private void Declare(Token name)
{
if (IsEmpty(scopes)) return;

// This adds the variable to the innermost scope so that it shadows any outer one and so that we know the
// variable exists. We mark it as “not ready yet” by binding its name to false in the scope map. Each value
// in the scope map means “is finished being initialized”.
var scope = scopes.Peek();

if (scope.ContainsKey(name.lexeme))
{
Lox.Error(name, "Variable with this name already declared in this scope.");
}

scope[name.lexeme] = false;
}

private static bool IsEmpty(ICollection stack)
{
return stack.Count == 0;
}

private void Define(Token name)
{
if (IsEmpty(scopes)) return;

// We set the variable’s value in the scope map to true to mark it as fully initialized and available for
// use. It’s alive!
scopes.Peek()[name.lexeme] = true;
}

private void ResolveLocal(Expr expr, Token name)
{
for (int i = scopes.Count - 1; i >= 0; i--)
{
// TODO: rewrite this for performance, since scopes.ElementAt() is much more inefficient on .NET
// TODO: than the Java counterpart.
if (scopes.ElementAt(i).ContainsKey(name.lexeme))
{
interpreter.Resolve(expr, scopes.Count - 1 - i);
return;
}
}

// Not found. Assume it is global.
}

public VoidObject VisitAssignExpr(Expr.Assign expr)
{
Resolve(expr.value);
ResolveLocal(expr, expr.name);
return null;
}

public VoidObject VisitBinaryExpr(Expr.Binary expr)
{
Resolve(expr.left);
Resolve(expr.right);
return null;
}

public VoidObject VisitCallExpr(Expr.Call expr)
{
Resolve(expr.callee);

foreach (Expr argument in expr.arguments)
{
Resolve(argument);
}

return null;
}

public VoidObject VisitGroupingExpr(Expr.Grouping expr)
{
Resolve(expr.expression);
return null;
}

public VoidObject VisitLiteralExpr(Expr.Literal expr)
{
return null;
}

public VoidObject VisitLogicalExpr(Expr.Logical expr)
{
Resolve(expr.left);
Resolve(expr.right);
return null;
}

public VoidObject VisitUnaryExpr(Expr.Unary expr)
{
Resolve(expr.right);
return null;
}

public VoidObject VisitVariableExpr(Expr.Variable expr)
{
if (!IsEmpty(scopes) &&
scopes.Peek()[expr.name.lexeme] == false)
{
Lox.Error(expr.name,
"Cannot read local variable in its own initializer.");
}

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

public VoidObject VisitBlockStmt(Stmt.Block stmt)
{
BeginScope();
Resolve(stmt.statements);
EndScope();
return null;
}

private void Resolve(Stmt stmt)
{
stmt.Accept(this);
}

private void Resolve(Expr expr)
{
expr.Accept(this);
}

public VoidObject VisitExpressionStmt(Stmt.Expression stmt)
{
Resolve(stmt.expression);
return null;
}

public VoidObject VisitFunctionStmt(Stmt.Function stmt)
{
Declare(stmt.name);
Define(stmt.name);

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

private void ResolveFunction(Stmt.Function function, FunctionType type)
{
FunctionType enclosingFunction = currentFunction;
currentFunction = type;

BeginScope();

foreach (Token param in function._params)
{
Declare(param);
Define(param);
}

Resolve(function.body);
EndScope();

currentFunction = enclosingFunction;
}

public VoidObject VisitIfStmt(Stmt.If stmt)
{
Resolve(stmt.condition);
Resolve(stmt.thenBranch);
if (stmt.elseBranch != null) Resolve(stmt.elseBranch);
return null;
}

public VoidObject VisitPrintStmt(Stmt.Print stmt)
{
Resolve(stmt.expression);
return null;
}

public VoidObject VisitReturnStmt(Stmt.Return stmt)
{
if (currentFunction == FunctionType.NONE)
{
Lox.Error(stmt.keyword, "Cannot return from top-level code.");
}

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

return null;
}

public VoidObject VisitVarStmt(Stmt.Var stmt)
{
Declare(stmt.name);
if (stmt.initializer != null)
{
Resolve(stmt.initializer);
}

Define(stmt.name);
return null;
}

public VoidObject VisitWhileStmt(Stmt.While stmt)
{
Resolve(stmt.condition);
Resolve(stmt.body);
return null;
}

private enum FunctionType
{
NONE,
FUNCTION
}
}
}
4 changes: 4 additions & 0 deletions CSLox/examples/ch11_overwriting_local_variable.lox
@@ -0,0 +1,4 @@
fun bad() {
var a = "first";
var a = "second";
}
1 change: 1 addition & 0 deletions CSLox/examples/ch11_return.lox
@@ -0,0 +1 @@
return "at top level";

0 comments on commit 64da4fd

Please sign in to comment.