diff --git a/jlox/src/main/java/com/craftinginterpreters/lox/AstPrinter.java b/jlox/src/main/java/com/craftinginterpreters/lox/AstPrinter.java index e80091b..3ab3d7b 100644 --- a/jlox/src/main/java/com/craftinginterpreters/lox/AstPrinter.java +++ b/jlox/src/main/java/com/craftinginterpreters/lox/AstPrinter.java @@ -1,5 +1,7 @@ package com.craftinginterpreters.lox; +import com.craftinginterpreters.lox.Expr.Variable; + class AstPrinter implements Expr.Visitor { String print(Expr expr) { return expr.accept(this); @@ -52,4 +54,10 @@ class AstPrinter implements Expr.Visitor { System.out.println(new AstPrinter().print(expression)); } + @Override + public String visitVariableExpr(Variable expr) { + // TODO Auto-generated method stub + return null; + } + } diff --git a/jlox/src/main/java/com/craftinginterpreters/lox/Environment.java b/jlox/src/main/java/com/craftinginterpreters/lox/Environment.java new file mode 100644 index 0000000..96ba888 --- /dev/null +++ b/jlox/src/main/java/com/craftinginterpreters/lox/Environment.java @@ -0,0 +1,47 @@ +package com.craftinginterpreters.lox; + +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; + } + + 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 + "'."); + } + + 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 + "'."); + } + + void define(String name, Object value) { + values.put(name, value); + } +} diff --git a/jlox/src/main/java/com/craftinginterpreters/lox/Expr.java b/jlox/src/main/java/com/craftinginterpreters/lox/Expr.java index 54183de..a1da5a6 100644 --- a/jlox/src/main/java/com/craftinginterpreters/lox/Expr.java +++ b/jlox/src/main/java/com/craftinginterpreters/lox/Expr.java @@ -4,10 +4,26 @@ 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); 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 { Binary(Expr left, Token operator, Expr right) { @@ -63,6 +79,18 @@ abstract class Expr { final Token operator; 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/jlox/src/main/java/com/craftinginterpreters/lox/Interpreter.java b/jlox/src/main/java/com/craftinginterpreters/lox/Interpreter.java index cc53d71..c3e93d5 100644 --- a/jlox/src/main/java/com/craftinginterpreters/lox/Interpreter.java +++ b/jlox/src/main/java/com/craftinginterpreters/lox/Interpreter.java @@ -1,21 +1,95 @@ package com.craftinginterpreters.lox; +import java.util.List; + +import com.craftinginterpreters.lox.Expr.Assign; import com.craftinginterpreters.lox.Expr.Binary; import com.craftinginterpreters.lox.Expr.Grouping; import com.craftinginterpreters.lox.Expr.Literal; import com.craftinginterpreters.lox.Expr.Unary; +import com.craftinginterpreters.lox.Expr.Variable; +import com.craftinginterpreters.lox.Stmt.Block; +import com.craftinginterpreters.lox.Stmt.Expression; +import com.craftinginterpreters.lox.Stmt.Print; +import com.craftinginterpreters.lox.Stmt.Var; -class Interpreter implements Expr.Visitor { +class Interpreter implements Expr.Visitor, Stmt.Visitor { + private Environment environment = new Environment(); - void interpret(Expr expression) { + 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); } } + private Object evaluate(Expr expr) { + return expr.accept(this); + } + + 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) { + execute(statement); + } + } finally { + this.environment = previous; + } + } + + @Override + public Void visitBlockStmt(Block stmt) { + executeBlock(stmt.statements, new Environment(environment)); + return null; + } + + @Override + public Void visitExpressionStmt(Expression stmt) { + evaluate(stmt.expression); + return null; + } + + @Override + public Void visitPrintStmt(Print stmt) { + Object value = evaluate(stmt.expression); + System.out.println(stringify(value)); + return null; + } + + @Override + public Object visitVariableExpr(Variable expr) { + return environment.get(expr.name); + } + + @Override + public Void visitVarStmt(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(Assign expr) { + Object value = evaluate(expr.value); + environment.assign(expr.name, value); + return value; + } + @Override public Object visitBinaryExpr(Binary expr) { Object left = evaluate(expr.left); @@ -137,9 +211,4 @@ class Interpreter implements Expr.Visitor { return object.toString(); } - - private Object evaluate(Expr expr) { - return expr.accept(this); - } - } diff --git a/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java b/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java index a5760e7..265c804 100644 --- a/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java +++ b/jlox/src/main/java/com/craftinginterpreters/lox/Lox.java @@ -55,11 +55,11 @@ public class Lox { 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); } static void error(int line, String message) { diff --git a/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java b/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java index d32bdcf..a2a0b93 100644 --- a/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java +++ b/jlox/src/main/java/com/craftinginterpreters/lox/Parser.java @@ -1,5 +1,6 @@ package com.craftinginterpreters.lox; +import java.util.ArrayList; import java.util.List; class Parser { @@ -13,16 +14,96 @@ class Parser { this.tokens = tokens; } - 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(TokenType.VAR)) { + return varDeclaration(); + } + + return statement(); } catch (ParseError error) { + synchronize(); return null; } } - private Expr expression() { - return equality(); + private Stmt statement() { + if (match(TokenType.PRINT)) { + return printStatement(); + } + + if (match(TokenType.LEFT_BRACE)) { + return new Stmt.Block(block()); + } + + return expressionStatement(); + } + + private Stmt printStatement() { + Expr value = expression(); + consume(TokenType.SEMICOLON, "Expect ';' after value."); + return new Stmt.Print(value); + } + + private Stmt varDeclaration() { + Token name = consume(TokenType.IDENTIFIER, "Expect variable name."); + + Expr initializer = null; + if (match(TokenType.EQUAL)) { + initializer = expression(); + } + + consume(TokenType.SEMICOLON, "Expect ';' after variable declaration."); + return new Stmt.Var(name, initializer); + } + + private Stmt expressionStatement() { + Expr expr = expression(); + consume(TokenType.SEMICOLON, "Expect ';' after expression."); + return new Stmt.Expression(expr); + } + + private List block() { + List statements = new ArrayList<>(); + + while (!check(TokenType.RIGHT_BRACE) && !isAtEnd()) { + statements.add(declaration()); + } + + consume(TokenType.RIGHT_BRACE, "Expect '}' after block."); + return statements; + } + + private Expr assignment() { + Expr expr = equality(); + + if (match(TokenType.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 assigment target."); + } + + return expr; } private Expr equality() { @@ -95,6 +176,10 @@ class Parser { return new Expr.Literal(previous().literal); } + if (match(TokenType.IDENTIFIER)) { + return new Expr.Variable(previous()); + } + if (match(TokenType.LEFT_PAREN)) { Expr expr = expression(); consume(TokenType.RIGHT_PAREN, "Expect ')' after expression."); diff --git a/jlox/src/main/java/com/craftinginterpreters/lox/Stmt.java b/jlox/src/main/java/com/craftinginterpreters/lox/Stmt.java new file mode 100644 index 0000000..6177473 --- /dev/null +++ b/jlox/src/main/java/com/craftinginterpreters/lox/Stmt.java @@ -0,0 +1,64 @@ +package com.craftinginterpreters.lox; + +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/jlox/src/main/java/com/craftinginterpreters/tool/GenerateAst.java b/jlox/src/main/java/com/craftinginterpreters/tool/GenerateAst.java index 549de0a..9ba36ae 100644 --- a/jlox/src/main/java/com/craftinginterpreters/tool/GenerateAst.java +++ b/jlox/src/main/java/com/craftinginterpreters/tool/GenerateAst.java @@ -13,10 +13,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(String outputDir, String baseName, List types) throws IOException { diff --git a/tests/test1.lox b/tests/test1.lox new file mode 100644 index 0000000..d563807 --- /dev/null +++ b/tests/test1.lox @@ -0,0 +1,19 @@ +var a = "global a"; +var b = "global b"; +var c = "global c"; +{ + var a = "outer a"; + var b = "outer b"; + { + var a = "inner a"; + print a; + print b; + print c; + } + print a; + print b; + print c; +} +print a; +print b; +print c;