diff --git a/src/com/craftinginterpreters/lox/CMakeLists.txt b/src/com/craftinginterpreters/lox/CMakeLists.txt index c326c88..ca2ee27 100644 --- a/src/com/craftinginterpreters/lox/CMakeLists.txt +++ b/src/com/craftinginterpreters/lox/CMakeLists.txt @@ -14,4 +14,6 @@ add_jar(Lox ${EXPR_JAVA_FILENAME} AstPrinter.java Parser.java + Interpreter.java + RuntimeError.java ENTRY_POINT com/craftinginterpreters/lox/Lox) diff --git a/src/com/craftinginterpreters/lox/Interpreter.java b/src/com/craftinginterpreters/lox/Interpreter.java new file mode 100644 index 0000000..24cf1c0 --- /dev/null +++ b/src/com/craftinginterpreters/lox/Interpreter.java @@ -0,0 +1,134 @@ +package com.craftinginterpreters.lox; + +class Interpreter implements Expr.Visitor { + @Override + public Object visitLiteralExpr(Expr.Literal expr) { + return expr.value; + } + + @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; + } + + // Unreachable. + return null; + } + + private void checkNumberOperand(Token operator, Object operand) { + if (operand instanceof Double) + return; + throw new RuntimeError(operator, "Operand 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 numbers."); + } + + private boolean isTruthy(Object object) { + if (object == null) + return false; + if (object instanceof Boolean) + return (boolean) object; + return true; + } + + private boolean isEqual(Object a, Object b) { + if (a == null && b == null) + return true; + if (a == null) + return false; + + return a.equals(b); + } + + 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(); + } + + @Override + public Object visitGroupingExpr(Expr.Grouping expr) { + return evaluate(expr.expression); + } + + private Object evaluate(Expr expr) { + return expr.accept(this); + } + + @Override + public Object visitBinaryExpr(Expr.Binary expr) { + Object left = evaluate(expr.left); + Object right = evaluate(expr.right); + + switch (expr.operator.type) { + 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); + case MINUS: + 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 SLASH: + checkNumberOperands(expr.operator, left, right); + return (double) left / (double) right; + case STAR: + checkNumberOperands(expr.operator, left, right); + return (double) left * (double) right; + } + + // Unreachable. + return null; + } + + public void interpret(Expr expression) { + try { + Object value = evaluate(expression); + System.out.println(stringify(value)); + } catch (RuntimeError error) { + Lox.runtimeError(error); + } + } +} diff --git a/src/com/craftinginterpreters/lox/Lox.java b/src/com/craftinginterpreters/lox/Lox.java index 586442d..a3334e3 100644 --- a/src/com/craftinginterpreters/lox/Lox.java +++ b/src/com/craftinginterpreters/lox/Lox.java @@ -9,7 +9,9 @@ import java.nio.file.Paths; import java.util.List; public class Lox { - private static boolean hadError = false; + private static final Interpreter interpreter = new Interpreter(); + public static boolean hadError = false; + public static boolean hadRuntimeError = false; public static void main(String[] args) throws IOException { if (args.length > 1) { @@ -29,6 +31,8 @@ public class Lox { // Indicate an error in the exit code. if (hadError) System.exit(65); + if (hadRuntimeError) + System.exit(70); } private static void runPrompt() throws IOException { @@ -52,9 +56,10 @@ public class Lox { Expr expression = parser.parse(); // Stop if there was a syntax error - if (hadError) return; + if (hadError) + return; - System.out.println(new AstPrinter().print(expression)); + interpreter.interpret(expression); } public static void error(int line, String message) { @@ -73,4 +78,9 @@ public class Lox { report(token.line, " at '" + token.lexeme + "'", message); } } + + public static void runtimeError(RuntimeError error) { + System.err.println(error.getMessage() + "\n[line " + error.token.line + "]"); + hadRuntimeError = true; + } } diff --git a/src/com/craftinginterpreters/lox/RuntimeError.java b/src/com/craftinginterpreters/lox/RuntimeError.java new file mode 100644 index 0000000..017865b --- /dev/null +++ b/src/com/craftinginterpreters/lox/RuntimeError.java @@ -0,0 +1,10 @@ +package com.craftinginterpreters.lox; + +class RuntimeError extends RuntimeException { + final Token token; + + RuntimeError(Token token, String message) { + super(message); + this.token = token; + } +}