diff --git a/src/lox/ASTPrinter.java b/src/lox/ASTPrinter.java index 6b23528..713d31b 100644 --- a/src/lox/ASTPrinter.java +++ b/src/lox/ASTPrinter.java @@ -33,6 +33,11 @@ class ASTPrinter implements Expr.Visitor { return expr.name.toString(); } + @Override + public String visitAssignExpr(Expr.Assign expr) { + return expr.name.toString(); + } + private String parenthesize(String name, Expr... exprs) { StringBuilder builder = new StringBuilder(); diff --git a/src/lox/Environment.java b/src/lox/Environment.java index 188efdd..984d364 100644 --- a/src/lox/Environment.java +++ b/src/lox/Environment.java @@ -4,16 +4,44 @@ import java.util.HashMap; import java.util.Map; class Environment { + final Environment enclosing; private final Map values = new HashMap<>(); + Environment() { + enclosing = null; + } + + Environment(Environment enclosing) { + this.enclosing = enclosing; + } + void define(String name, Object value) { values.put(name, value); } + void assign(Token name, Object value) { + if (values.containsKey(name.lexeme)) { + values.put(name.lexeme, value); + return; + } + + if (enclosing != null) { + enclosing.assign(name, value); + return; + } + + throw new RuntimeError(name, "Undefined variable '" + name.lexeme + "'."); + } + Object get(Token name) { if (values.containsKey(name.lexeme)) { return values.get(name.lexeme); } + + if (enclosing != null) { + return enclosing.get(name); + } + throw new RuntimeError(name, "Undefined variable '" + name.lexeme + "'."); } } diff --git a/src/lox/Expr.java b/src/lox/Expr.java index a1c9fb0..fe4dc2e 100644 --- a/src/lox/Expr.java +++ b/src/lox/Expr.java @@ -1,7 +1,10 @@ package lox; +import java.util.List; + abstract class Expr { interface Visitor { + R visitAssignExpr(Assign expr); R visitBinaryExpr(Binary expr); R visitGroupingExpr(Grouping expr); R visitLiteralExpr(Literal expr); @@ -9,6 +12,21 @@ abstract class Expr { R visitVariableExpr(Variable expr); } + static class Assign extends Expr { + Assign(Token name, Expr value) { + this.name = name; + this.value = value; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitAssignExpr(this); + } + + final Token name; + final Expr value; + } + static class Binary extends Expr { Binary(Expr left, Token operator, Expr right) { this.left = left; diff --git a/src/lox/Interpreter.java b/src/lox/Interpreter.java index ae0e43b..8cd9548 100644 --- a/src/lox/Interpreter.java +++ b/src/lox/Interpreter.java @@ -89,6 +89,13 @@ class Interpreter implements Expr.Visitor, Stmt.Visitor { return environment.get(expr.name); } + @Override + public Object visitAssignExpr(Expr.Assign expr) { + Object value = evaluate(expr.value); + environment.assign(expr.name, value); + return value; + } + @Override public Void visitExpressionStmt(Stmt.Expression stmt) { evaluate(stmt.expression); @@ -112,6 +119,12 @@ class Interpreter implements Expr.Visitor, Stmt.Visitor { return null; } + @Override + public Void visitBlockStmt(Stmt.Block stmt) { + executeBlock(stmt.statements, new Environment(environment)); + return null; + } + private void checkNumberOperand(Token operator, Object operand) { if (operand instanceof Double) { return; @@ -167,4 +180,18 @@ class Interpreter implements Expr.Visitor, Stmt.Visitor { private void execute(Stmt stmt) { stmt.accept(this); } + + void executeBlock(List statements, Environment environment) { + Environment previous = this.environment; + try { + this.environment = environment; + + for (Stmt statement : statements) { + // could also just redesign this to accept an environment field everywhere + execute(statement); + } + } finally { + this.environment = previous; + } + } } diff --git a/src/lox/Parser.java b/src/lox/Parser.java index bb081ec..e8cbefc 100644 --- a/src/lox/Parser.java +++ b/src/lox/Parser.java @@ -50,6 +50,9 @@ class Parser { if (match(PRINT)) { return printStatement(); } + if (match(LEFT_BRACE)) { + return new Stmt.Block(block()); + } return expressionStatement(); } @@ -65,8 +68,35 @@ class Parser { return new Stmt.Expression(value); } + private List block() { + List statements = new ArrayList<>(); + + while (!check(RIGHT_BRACE) && !isAtEnd()) { + statements.add(declaration()); + } + consume(RIGHT_BRACE, "Expect '}' after block."); + return statements; + } + + private Expr assignment() { + Expr expr = equality(); + + if (match(EQUAL)) { + Token equals = previous(); + Expr value = assignment(); + + if (expr instanceof Expr.Variable) { + Token name = ((Expr.Variable)expr).name; + return new Expr.Assign(name, value); + } + // does not throw because parsing _can_ continue + error(equals, "Invalid assignment target."); + } + return expr; + } + private Expr expression() { - return equality(); + return assignment(); } private Expr equality() { diff --git a/src/lox/Stmt.java b/src/lox/Stmt.java index 1be8253..2dc0f68 100644 --- a/src/lox/Stmt.java +++ b/src/lox/Stmt.java @@ -1,12 +1,28 @@ package lox; +import java.util.List; + abstract class Stmt { interface Visitor { + R visitBlockStmt(Block stmt); R visitExpressionStmt(Expression stmt); R visitVarStmt(Var stmt); R visitPrintStmt(Print stmt); } + static class Block extends Stmt { + Block(List statements) { + this.statements = statements; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitBlockStmt(this); + } + + final List statements; + } + static class Expression extends Stmt { Expression(Expr expression) { this.expression = expression; diff --git a/src/tool/GenerateAST.java b/src/tool/GenerateAST.java index 1dc6937..03ea38d 100644 --- a/src/tool/GenerateAST.java +++ b/src/tool/GenerateAST.java @@ -1,5 +1,6 @@ package tool; +import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.Charset; @@ -17,30 +18,32 @@ public class GenerateAST { outputDir, "Expr", Arrays.asList( - "Binary : Expr left, Token operator, Expr right", + "Assign : Token name, Expr value", + "Binary : Expr left, Token operator, Expr right", "Grouping : Expr expression", - "Literal : Object value", - "Unary : Token operator, Expr right", + "Literal : Object value", + "Unary : Token operator, Expr right", "Variable : Token name")); defineAST( outputDir, "Stmt", Arrays.asList( + "Block : List statements", "Expression : Expr expression", - "Var : Token name, Expr initializer", - "Print : Expr expression")); + "Var : Token name, Expr initializer", + "Print : Expr expression")); } private static void defineAST(String outputDir, String baseName, List types) throws IOException { - String path = outputDir + "/" + baseName + ".java"; + String path = outputDir + File.separator + baseName + ".java"; PrintWriter writer = new PrintWriter(path, Charset.defaultCharset()); writer.println("package lox;"); writer.println(); - // writer.println("import java.util.List;"); - // writer.println(); + writer.println("import java.util.List;"); + writer.println(); writer.println("abstract class " + baseName + " {"); defineVisitor(writer, baseName, types);