diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/AstPrinter.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/AstPrinter.java deleted file mode 100644 index 0e30ff5..0000000 --- a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/AstPrinter.java +++ /dev/null @@ -1,43 +0,0 @@ -package ch.vanwa.lox_interpreter; - -class AstPrinter implements Expr.Visitor { - String print(Expr expr) { - return expr.accept(this); - } - - @Override - public String visitBinaryExpr(Expr.Binary expr) { - return parenthesize(expr.operator.lexeme(), - expr.left, expr.right); - } - - @Override - public String visitGroupingExpr(Expr.Grouping expr) { - return parenthesize("group", expr.expression); - } - - @Override - public String visitLiteralExpr(Expr.Literal expr) { - if (expr.value == null) - return "nil"; - return expr.value.toString(); - } - - @Override - public String visitUnaryExpr(Expr.Unary expr) { - return parenthesize(expr.operator.lexeme(), expr.right); - } - - private String parenthesize(String name, Expr... exprs) { - StringBuilder builder = new StringBuilder(); - - builder.append("(").append(name); - for (Expr expr : exprs) { - builder.append(" "); - builder.append(expr.accept(this)); - } - builder.append(")"); - - return builder.toString(); - } -} diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Environment.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Environment.java new file mode 100644 index 0000000..196575c --- /dev/null +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Environment.java @@ -0,0 +1,79 @@ +package ch.vanwa.lox_interpreter; + +import java.util.HashMap; +import java.util.Map; + +/** + * Environment mapping variable names to their values. + */ +class Environment { + /** + * Enclosing parent environment, can be null (only used for the outermost global + * environment). + */ + final Environment enclosing; + + /** + * Mapping of variable names to their values. + */ + private final Map values = new HashMap<>(); + + /** + * Initialise environment without an enclosing environment. + */ + Environment() { + enclosing = null; + } + + /** + * Initialise environment with an enclosing environment. + */ + Environment(Environment enclosing) { + this.enclosing = enclosing; + } + + /** + * Get the value of a variable. Throw an error if that variable is not known in + * this environment or in any of the enclosing ones. + * + * @param name Token with the variable lexeme used as variable name. + */ + 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() + "'."); + } + + /** + * Assign a value to a variable. Throw an error if that variable is not known in + * this environment or in any of the enclosing ones. + */ + 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() + "'."); + } + + /** + * Define a new variable in this environment. + */ + void define(String name, Object value) { + values.put(name, value); + } +} diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Expr.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Expr.java index eb73c65..c23d4a3 100644 --- a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Expr.java +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Expr.java @@ -4,6 +4,8 @@ import java.util.List; abstract class Expr { interface Visitor { + R visitAssignExpr(Assign expr); + R visitBinaryExpr(Binary expr); R visitGroupingExpr(Grouping expr); @@ -11,6 +13,23 @@ abstract class Expr { R visitLiteralExpr(Literal expr); R visitUnaryExpr(Unary 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 { @@ -71,5 +90,18 @@ abstract class Expr { final Expr right; } + static class Variable extends Expr { + Variable(Token name) { + this.name = name; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitVariableExpr(this); + } + + final Token name; + } + abstract R accept(Visitor visitor); } diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Interpreter.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Interpreter.java index 93d4a77..f27fade 100644 --- a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Interpreter.java +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Interpreter.java @@ -1,13 +1,19 @@ package ch.vanwa.lox_interpreter; +import java.util.List; + /** * Evaluate Lox expressions. */ -class Interpreter implements Expr.Visitor { - void interpret(Expr expression) { +class Interpreter implements Expr.Visitor, Stmt.Visitor { + + private Environment environment = new Environment(); + + void interpret(List statements) { try { - Object value = evaluate(expression); - System.out.println(stringify(value)); + for (Stmt statement : statements) { + execute(statement); + } } catch (RuntimeError error) { Lox.runtimeError(error); } @@ -27,6 +33,65 @@ class Interpreter implements Expr.Visitor { return expr.accept(this); } + private void execute(Stmt stmt) { + stmt.accept(this); + } + + /** + * Execute all statements within a block while using lexical scoping for + * variables. + */ + void executeBlock(List statements, + Environment environment) { + Environment previous = this.environment; + try { + this.environment = environment; + + for (Stmt statement : statements) { + execute(statement); + } + } finally { + this.environment = previous; + } + } + + @Override + public Void visitBlockStmt(Stmt.Block stmt) { + executeBlock(stmt.statements, new Environment(environment)); + return null; + } + + @Override + public Void visitExpressionStmt(Stmt.Expression stmt) { + evaluate(stmt.expression); + return null; + } + + @Override + public Void visitPrintStmt(Stmt.Print stmt) { + Object value = evaluate(stmt.expression); + System.out.println(stringify(value)); + return null; + } + + @Override + public Void visitVarStmt(Stmt.Var stmt) { + Object value = null; + if (stmt.initializer != null) { + value = evaluate(stmt.initializer); + } + + environment.define(stmt.name.lexeme(), value); + return null; + } + + @Override + public Object visitAssignExpr(Expr.Assign expr) { + Object value = evaluate(expr.value); + environment.assign(expr.name, value); + return value; + } + @Override public Object visitBinaryExpr(Expr.Binary expr) { Object left = evaluate(expr.left); @@ -90,6 +155,11 @@ class Interpreter implements Expr.Visitor { return null; } + @Override + public Object visitVariableExpr(Expr.Variable expr) { + return environment.get(expr.name); + } + /** * Throw a runtime error if the operand is not an instance of Double. */ diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Lox.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Lox.java index f5a9ed1..d8327b3 100644 --- a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Lox.java +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Lox.java @@ -69,12 +69,13 @@ public class Lox { var scanner = new Scanner(source); List tokens = scanner.scanTokens(); Parser parser = new Parser(tokens); - Expr expression = parser.parse(); + List statements = parser.parse(); + if (hadError) { return; } - interpreter.interpret(expression); + interpreter.interpret(statements); } /** diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Parser.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Parser.java index 3ca0fbc..7b56515 100644 --- a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Parser.java +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Parser.java @@ -1,5 +1,6 @@ package ch.vanwa.lox_interpreter; +import java.util.ArrayList; import java.util.List; import static ch.vanwa.lox_interpreter.TokenType.*; @@ -41,16 +42,94 @@ class Parser { * * @return Expression signifying the whole program or null, if parsing fails. */ - Expr parse() { + List parse() { + List statements = new ArrayList<>(); + while (!isAtEnd()) { + statements.add(declaration()); + } + + return statements; + } + + private Expr expression() { + return assignment(); + } + + private Stmt declaration() { try { - return expression(); + if (match(VAR)) { + return varDeclaration(); + } + + return statement(); } catch (ParseError error) { + synchronize(); return null; } } - private Expr expression() { - return equality(); + private Stmt statement() { + if (match(PRINT)) { + return printStatement(); + } + if (match(LEFT_BRACE)) { + return new Stmt.Block(block()); + } + + return expressionStatement(); + } + + private Stmt printStatement() { + Expr value = expression(); + consume(SEMICOLON, "Expect ';' after value."); + return new Stmt.Print(value); + } + + private Stmt varDeclaration() { + Token name = consume(IDENTIFIER, "Expect variable name."); + + Expr initializer = null; + if (match(EQUAL)) { + initializer = expression(); + } + + consume(SEMICOLON, "Expect ';' after variable declaration."); + return new Stmt.Var(name, initializer); + } + + private Stmt expressionStatement() { + Expr expr = expression(); + consume(SEMICOLON, "Expect ';' after expression."); + return new Stmt.Expression(expr); + } + + 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); + } + + error(equals, "Invalid assignment target."); + } + + return expr; } private Expr equality() { @@ -123,6 +202,10 @@ class Parser { return new Expr.Literal(previous().literal()); } + if (match(IDENTIFIER)) { + return new Expr.Variable(previous()); + } + if (match(LEFT_PAREN)) { Expr expr = expression(); consume(RIGHT_PAREN, "Expect ')' after expression."); diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Stmt.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Stmt.java new file mode 100644 index 0000000..a6546c7 --- /dev/null +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Stmt.java @@ -0,0 +1,71 @@ +package ch.vanwa.lox_interpreter; + +import java.util.List; + +abstract class Stmt { + interface Visitor { + R visitBlockStmt(Block stmt); + + R visitExpressionStmt(Expression stmt); + + R visitPrintStmt(Print stmt); + + R visitVarStmt(Var 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; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitExpressionStmt(this); + } + + final Expr expression; + } + + static class Print extends Stmt { + Print(Expr expression) { + this.expression = expression; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitPrintStmt(this); + } + + final Expr expression; + } + + static class Var extends Stmt { + Var(Token name, Expr initializer) { + this.name = name; + this.initializer = initializer; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitVarStmt(this); + } + + final Token name; + final Expr initializer; + } + + abstract R accept(Visitor visitor); +} diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/tool/GenerateAst.java b/java/lox-interpreter/src/main/java/ch/vanwa/tool/GenerateAst.java index 40daee3..9e3020d 100644 --- a/java/lox-interpreter/src/main/java/ch/vanwa/tool/GenerateAst.java +++ b/java/lox-interpreter/src/main/java/ch/vanwa/tool/GenerateAst.java @@ -14,10 +14,18 @@ public class GenerateAst { String outputDir = args[0]; defineAst(outputDir, "Expr", Arrays.asList( + "Assign : Token name, Expr value", "Binary : Expr left, Token operator, Expr right", "Grouping : Expr expression", "Literal : Object value", - "Unary : Token operator, Expr right")); + "Unary : Token operator, Expr right", + "Variable : Token name")); + + defineAst(outputDir, "Stmt", Arrays.asList( + "Block : List statements", + "Expression : Expr expression", + "Print : Expr expression", + "Var : Token name, Expr initializer")); } private static void defineAst(