From d6cbc63c180aa1f0e324c6c7301b8b7ba8051899 Mon Sep 17 00:00:00 2001 From: David Ashby Date: Sat, 18 Sep 2021 16:09:43 -0400 Subject: [PATCH] statements --- src/lox/Interpreter.java | 153 ++++++++++++++++++++++++++++++++++++++ src/lox/Lox.java | 14 +++- src/lox/Parser.java | 30 ++++++-- src/lox/RuntimeError.java | 10 +++ src/lox/Stmt.java | 37 +++++++++ src/tool/GenerateAST.java | 5 ++ 6 files changed, 242 insertions(+), 7 deletions(-) create mode 100644 src/lox/Interpreter.java create mode 100644 src/lox/RuntimeError.java create mode 100644 src/lox/Stmt.java diff --git a/src/lox/Interpreter.java b/src/lox/Interpreter.java new file mode 100644 index 0000000..b58a855 --- /dev/null +++ b/src/lox/Interpreter.java @@ -0,0 +1,153 @@ +package lox; + +import java.util.List; + +class Interpreter implements Expr.Visitor, Stmt.Visitor { + void interpret(List statements) { + try { + for (Stmt statement : statements) { + execute(statement); + } + } catch (RuntimeError error) { + Lox.runtimeError(error); + } + } + + @Override + public Object visitLiteralExpr(Expr.Literal expr) { + return expr.value; + } + + @Override + public Object visitGroupingExpr(Expr.Grouping expr) { + return evaluate(expr.expression); + } + + @Override + public Object visitUnaryExpr(Expr.Unary expr) { + Object right = evaluate(expr.right); + + switch (expr.operator.type) { + case BANG: + return !isTruthy(right); + case MINUS: + checkNumberOperand(expr.operator, right); + return -(double) right; + } + // actually unreachable + return null; + } + + @Override + public Object visitBinaryExpr(Expr.Binary expr) { + Object left = evaluate(expr.left); + Object right = evaluate(expr.right); + + switch (expr.operator.type) { + case MINUS: + checkNumberOperands(expr.operator, left, right); + return (double) left - (double) right; + case SLASH: + checkNumberOperands(expr.operator, left, right); + return (double) left / (double) right; + case STAR: + checkNumberOperands(expr.operator, left, right); + return (double) left * (double) right; + case PLUS: + if (left instanceof Double && right instanceof Double) { + return (double) left + (double) right; + } + if (left instanceof String && right instanceof String) { + return (String) left + (String) right; + } + throw new RuntimeError(expr.operator, "Operands must be two numbers or two strings"); + case GREATER: + checkNumberOperands(expr.operator, left, right); + return (double) left > (double) right; + case GREATER_EQUAL: + checkNumberOperands(expr.operator, left, right); + return (double) left >= (double) right; + case LESS: + checkNumberOperands(expr.operator, left, right); + return (double) left < (double) right; + case LESS_EQUAL: + checkNumberOperands(expr.operator, left, right); + return (double) left <= (double) right; + case BANG_EQUAL: + return !isEqual(left, right); + case EQUAL_EQUAL: + return isEqual(left, right); + } + // actually unreachable + 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; + } + + private void checkNumberOperand(Token operator, Object operand) { + if (operand instanceof Double) { + return; + } + throw new RuntimeError(operator, "Operator must be a number."); + } + + private void checkNumberOperands(Token operator, Object left, Object right) { + if (left instanceof Double && right instanceof Double) { + return; + } + throw new RuntimeError(operator, "Operands must be a numbers."); + } + + private boolean isTruthy(Object object) { + if (object == null) { + return false; // null is false + } + if (object instanceof Boolean) { + return (boolean) object; // booleans are their own truthiness + } + return true; // everything else is true + } + + private boolean isEqual(Object left, Object right) { + if (left == null && right == null) { + return true; // null is equal to another null + } + if (left == null) { + return false; // null is never equal to anything else + } + return left.equals(right); + } + + private String stringify(Object object) { + if (object == null) { + return "nil"; + } + if (object instanceof Double) { + String text = object.toString(); + if (text.endsWith(".0")) { + text = text.substring(0, text.length() - 2); + } + return text; + } + return object.toString(); + } + + private Object evaluate(Expr expr) { + return expr.accept(this); + } + + private void execute(Stmt stmt) { + stmt.accept(this); + } +} diff --git a/src/lox/Lox.java b/src/lox/Lox.java index 1f32b26..508aacc 100644 --- a/src/lox/Lox.java +++ b/src/lox/Lox.java @@ -11,7 +11,9 @@ import java.util.List; import static lox.TokenType.EOF; public class Lox { + private static final Interpreter interpreter = new Interpreter(); static boolean hadError = false; + static boolean hadRuntimeError = false; public static void main(String[] args) throws IOException { if (args.length > 1) { @@ -32,6 +34,9 @@ public class Lox { if (hadError) { System.exit(65); } + if (hadRuntimeError) { + System.exit(70); + } } private static void runPrompt() throws IOException { @@ -53,19 +58,24 @@ public class Lox { Scanner scanner = new Scanner(source); List tokens = scanner.scanTokens(); Parser parser = new Parser(tokens); - Expr expression = parser.parse(); + List statements = parser.parse(); if (hadError) { return; } - System.out.println(new ASTPrinter().print(expression)); + interpreter.interpret(statements); } static void error(int line, String message) { report(line, "", message); } + static void runtimeError(RuntimeError error) { + System.err.println(error.getMessage() + "\n[line " + error.token.line + "]"); + hadRuntimeError = true; + } + private static void report(int line, String where, String message) { System.err.println("[line " + line + "] Error" + where + ": " + message); hadError = true; diff --git a/src/lox/Parser.java b/src/lox/Parser.java index a569ebe..d298955 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,12 +15,31 @@ class Parser { this.tokens = tokens; } - Expr parse() { - try { - return expression(); - } catch (ParseError error) { - return null; + List parse() { + List statements = new ArrayList<>(); + while (!isAtEnd()) { + statements.add(statement()); } + return statements; + } + + private Stmt statement() { + if (match(PRINT)) { + return printStatement(); + } + return expressionStatement(); + } + + private Stmt printStatement() { + Expr value = expression(); + consume(SEMICOLON, "Expect ';' after value."); + return new Stmt.Print(value); + } + + private Stmt expressionStatement() { + Expr value = expression(); + consume(SEMICOLON, "Expect ';' after value."); + return new Stmt.Expression(value); } private Expr expression() { diff --git a/src/lox/RuntimeError.java b/src/lox/RuntimeError.java new file mode 100644 index 0000000..6366989 --- /dev/null +++ b/src/lox/RuntimeError.java @@ -0,0 +1,10 @@ +package lox; + +class RuntimeError extends RuntimeException { + final Token token; + + RuntimeError(Token token, String message) { + super(message); + this.token = token; + } +} diff --git a/src/lox/Stmt.java b/src/lox/Stmt.java new file mode 100644 index 0000000..2f5d407 --- /dev/null +++ b/src/lox/Stmt.java @@ -0,0 +1,37 @@ +package lox; + +abstract class Stmt { + interface Visitor { + R visitExpressionStmt(Expression stmt); + + R visitPrintStmt(Print stmt); + } + + 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; + } + + abstract R accept(Visitor visitor); +} diff --git a/src/tool/GenerateAST.java b/src/tool/GenerateAST.java index b887eb8..5a701cf 100644 --- a/src/tool/GenerateAST.java +++ b/src/tool/GenerateAST.java @@ -21,6 +21,11 @@ public class GenerateAST { "Grouping : Expr expression", "Literal : Object value", "Unary : Token operator, Expr right")); + + defineAST( + outputDir, + "Stmt", + Arrays.asList("Expression : Expr expression", "Print : Expr expression")); } private static void defineAST(String outputDir, String baseName, List types)