From 64da4fd62808f4a079d99700541ed36d27df9ab4 Mon Sep 17 00:00:00 2001 From: Per Lundberg Date: Sun, 26 Jan 2020 00:14:47 +0200 Subject: [PATCH] Completed chapter 11 --- CSLox/Interpreter.cs | 30 ++- CSLox/Lox.cs | 12 +- CSLox/LoxEnvironment.cs | 21 ++ CSLox/Parser.cs | 2 +- CSLox/Resolver.cs | 254 ++++++++++++++++++ .../ch11_overwriting_local_variable.lox | 4 + CSLox/examples/ch11_return.lox | 1 + CSLox/examples/ch11_show_a.lox | 10 + 8 files changed, 329 insertions(+), 5 deletions(-) create mode 100644 CSLox/Resolver.cs create mode 100644 CSLox/examples/ch11_overwriting_local_variable.lox create mode 100644 CSLox/examples/ch11_return.lox create mode 100644 CSLox/examples/ch11_show_a.lox diff --git a/CSLox/Interpreter.cs b/CSLox/Interpreter.cs index e72061e..5a793c8 100644 --- a/CSLox/Interpreter.cs +++ b/CSLox/Interpreter.cs @@ -7,6 +7,7 @@ namespace CSLox internal class Interpreter : Expr.Visitor, Stmt.Visitor { private readonly LoxEnvironment globals = new LoxEnvironment(); + private readonly IDictionary locals = new Dictionary(); private LoxEnvironment loxEnvironment; @@ -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) @@ -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 statements, LoxEnvironment loxEnvironment) { LoxEnvironment previous = this.loxEnvironment; @@ -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; } diff --git a/CSLox/Lox.cs b/CSLox/Lox.cs index 040358e..8784683 100644 --- a/CSLox/Lox.cs +++ b/CSLox/Lox.cs @@ -69,11 +69,17 @@ private static void Run(string source) // For now, just print the tokens. var parser = new Parser(tokens); - IEnumerable 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 @@ -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) @@ -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) { diff --git a/CSLox/LoxEnvironment.cs b/CSLox/LoxEnvironment.cs index 86cc44f..707b478 100644 --- a/CSLox/LoxEnvironment.cs +++ b/CSLox/LoxEnvironment.cs @@ -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)) diff --git a/CSLox/Parser.cs b/CSLox/Parser.cs index c74bde9..6a6dc78 100644 --- a/CSLox/Parser.cs +++ b/CSLox/Parser.cs @@ -25,7 +25,7 @@ internal Parser(List tokens) this.tokens = tokens; } - internal IEnumerable ParseStatements() + internal IList ParseStatements() { var statements = new List(); diff --git a/CSLox/Resolver.cs b/CSLox/Resolver.cs new file mode 100644 index 0000000..5146c8e --- /dev/null +++ b/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, Stmt.Visitor + { + private readonly Stack> scopes = new Stack>(); + private FunctionType currentFunction = FunctionType.NONE; + + private readonly Interpreter interpreter; + + internal Resolver(Interpreter interpreter) + { + this.interpreter = interpreter; + } + + internal void Resolve(IEnumerable statements) + { + foreach (Stmt statement in statements) + { + Resolve(statement); + } + } + + private void BeginScope() + { + scopes.Push(new Dictionary()); + } + + 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 + } + } +} \ No newline at end of file diff --git a/CSLox/examples/ch11_overwriting_local_variable.lox b/CSLox/examples/ch11_overwriting_local_variable.lox new file mode 100644 index 0000000..d9948fd --- /dev/null +++ b/CSLox/examples/ch11_overwriting_local_variable.lox @@ -0,0 +1,4 @@ +fun bad() { + var a = "first"; + var a = "second"; +} diff --git a/CSLox/examples/ch11_return.lox b/CSLox/examples/ch11_return.lox new file mode 100644 index 0000000..9dbd1b6 --- /dev/null +++ b/CSLox/examples/ch11_return.lox @@ -0,0 +1 @@ +return "at top level"; diff --git a/CSLox/examples/ch11_show_a.lox b/CSLox/examples/ch11_show_a.lox new file mode 100644 index 0000000..f826c86 --- /dev/null +++ b/CSLox/examples/ch11_show_a.lox @@ -0,0 +1,10 @@ +var a = "global"; +{ + fun showA() { + print a; + } + + showA(); + var a = "block"; + showA(); +}