diff --git a/src/com/lox/Interpreter.java b/src/com/lox/Interpreter.java new file mode 100644 index 0000000..09a93da --- /dev/null +++ b/src/com/lox/Interpreter.java @@ -0,0 +1,118 @@ +package com.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."); + } + + @Override + public Object visitGroupingExpr(Expr.Grouping expr) { + return evaluate(expr.expression); + } + @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 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; + case BANG_EQUAL: return !isEqual(left, right); + case EQUAL_EQUAL: return isEqual(left, right); + } + + // Unreachable. + return null; + } + private Object evaluate(Expr expr) { + return expr.accept(this); + } + + 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); + } + void interpret(Expr expression) { + try { + Object value = evaluate(expression); + System.out.println(stringify(value)); + } catch (RuntimeError error) { + Lox.runtimeError(error); + } + } + 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(); + } +} \ No newline at end of file diff --git a/src/com/lox/Lox.java b/src/com/lox/Lox.java index b3af507..65c6df8 100644 --- a/src/com/lox/Lox.java +++ b/src/com/lox/Lox.java @@ -9,7 +9,9 @@ import java.nio.file.Paths; import java.util.List; 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) { System.out.println("Usage: jlox [script]"); @@ -29,8 +31,7 @@ public class Lox { // Stop if there was a syntax error. if (hadError) return; - - System.out.println(new AstPrinter().print(expression)); + interpreter.interpret(expression); } static void error(int line, String message) { report(line, "", message); @@ -51,11 +52,17 @@ public class Lox { } } + static void runtimeError(RuntimeError error) { + System.err.println(error.getMessage() + + "\n[line " + error.token.line + "]"); + hadRuntimeError = true; + } private static void runFile(String path) throws IOException { byte[] bytes = Files.readAllBytes(Paths.get(path)); run(new String(bytes, Charset.defaultCharset())); if(hadError) System.exit(65); + if(hadRuntimeError) System.exit(70); } private static void runPrompt() throws IOException { InputStreamReader input = new InputStreamReader(System.in); diff --git a/src/com/lox/RuntimeError.java b/src/com/lox/RuntimeError.java new file mode 100644 index 0000000..8f737b7 --- /dev/null +++ b/src/com/lox/RuntimeError.java @@ -0,0 +1,10 @@ +package com.lox; + +class RuntimeError extends RuntimeException { + final Token token; + + RuntimeError(Token token, String message) { + super(message); + this.token = token; + } +} \ No newline at end of file