From dba05187cf33faf7fc45af293d526f48f17595a2 Mon Sep 17 00:00:00 2001 From: Mariano Riefolo Date: Sat, 29 Jun 2024 11:44:00 +0200 Subject: [PATCH] Statements and state chapter --- src/lox/AstPrinter.java | 10 +++++ src/lox/Environment.java | 47 ++++++++++++++++++++++ src/lox/Expr.java | 28 +++++++++++++ src/lox/Interpreter.java | 74 ++++++++++++++++++++++++++++++++-- src/lox/Lox.java | 4 +- src/lox/Parser.java | 85 +++++++++++++++++++++++++++++++++++++-- src/lox/Stmt.java | 64 +++++++++++++++++++++++++++++ src/tool/GenerateAst.java | 11 ++++- 8 files changed, 312 insertions(+), 11 deletions(-) create mode 100644 src/lox/Environment.java create mode 100644 src/lox/Stmt.java diff --git a/src/lox/AstPrinter.java b/src/lox/AstPrinter.java index ecb07d3..61e7f7a 100644 --- a/src/lox/AstPrinter.java +++ b/src/lox/AstPrinter.java @@ -5,6 +5,11 @@ public class AstPrinter implements Expr.Visitor { return expr.accept(this); } + @Override + public String visitAssignExpr(Expr.Assign expr) { + return parethesize(expr.name.lexeme, expr.value); + } + @Override public String visitBinaryExpr(Expr.Binary expr) { return parethesize(expr.operator.lexeme, @@ -27,6 +32,11 @@ public class AstPrinter implements Expr.Visitor { return parethesize(expr.operator.lexeme, expr.right); } + @Override + public String visitVariableExpr(Expr.Variable expr) { + return parethesize(expr.name.lexeme); + } + private String parethesize(String name, Expr... exprs) { StringBuilder builder = new StringBuilder(); diff --git a/src/lox/Environment.java b/src/lox/Environment.java new file mode 100644 index 0000000..e1f5a1b --- /dev/null +++ b/src/lox/Environment.java @@ -0,0 +1,47 @@ +package lox; + +import java.util.HashMap; +import java.util.Map; + +public 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/src/lox/Expr.java b/src/lox/Expr.java index e6edfcf..77fd402 100644 --- a/src/lox/Expr.java +++ b/src/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/src/lox/Interpreter.java b/src/lox/Interpreter.java index 97bb0af..820173e 100644 --- a/src/lox/Interpreter.java +++ b/src/lox/Interpreter.java @@ -1,15 +1,28 @@ package lox; -public class Interpreter implements Expr.Visitor { - void interpret(Expr expression) { +import java.util.List; + +public 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); } } + @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); @@ -80,6 +93,11 @@ public class Interpreter implements Expr.Visitor { }; } + @Override + public Object visitVariableExpr(Expr.Variable expr) { + return environment.get(expr.name); + } + private void checkNumberOperand(Token operator, Object operand) { if (operand instanceof Double) return; throw new RuntimeError(operator, "Operand must be a number."); @@ -122,4 +140,52 @@ public class Interpreter implements Expr.Visitor { 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(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; + } } diff --git a/src/lox/Lox.java b/src/lox/Lox.java index 235af2d..17bd62c 100644 --- a/src/lox/Lox.java +++ b/src/lox/Lox.java @@ -50,11 +50,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/src/lox/Parser.java b/src/lox/Parser.java index 9ebd54a..50832e6 100644 --- a/src/lox/Parser.java +++ b/src/lox/Parser.java @@ -1,5 +1,6 @@ package lox; +import java.util.ArrayList; import java.util.List; import static lox.TokenType.*; @@ -14,16 +15,88 @@ public 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(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() { @@ -93,6 +166,10 @@ public 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/src/lox/Stmt.java b/src/lox/Stmt.java new file mode 100644 index 0000000..8014fe4 --- /dev/null +++ b/src/lox/Stmt.java @@ -0,0 +1,64 @@ +package 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/src/tool/GenerateAst.java b/src/tool/GenerateAst.java index 4f67c43..79a01bd 100644 --- a/src/tool/GenerateAst.java +++ b/src/tool/GenerateAst.java @@ -14,10 +14,19 @@ 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" )); }