diff --git a/src/lox/Interpreter.java b/src/lox/Interpreter.java new file mode 100644 index 0000000..97bb0af --- /dev/null +++ b/src/lox/Interpreter.java @@ -0,0 +1,125 @@ +package lox; + +public class Interpreter implements Expr.Visitor { + void interpret(Expr expression) { + try { + Object value = evaluate(expression); + System.out.println(stringify(value)); + } catch (RuntimeError error) { + Lox.runtimeError(error); + } + } + + @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); + default: + return null; + } + } + + @Override + public Object visitGroupingExpr(Expr.Grouping expr) { + return evaluate(expr.expression); + } + + @Override + public Object visitLiteralExpr(Expr.Literal expr) { + return expr.value; + } + + @Override + public Object visitUnaryExpr(Expr.Unary expr) { + Object right = evaluate(expr.right); + + return switch (expr.operator.type) { + case BANG -> !isTruthy(right); + case MINUS -> { + checkNumberOperand(expr.operator, right); + yield -(double) right; + } + default -> 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(); + } + + private Object evaluate(Expr expr) { + return expr.accept(this); + } +} diff --git a/src/lox/Lox.java b/src/lox/Lox.java index 03f3b87..235af2d 100644 --- a/src/lox/Lox.java +++ b/src/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) { @@ -27,6 +29,7 @@ public class Lox { run(new String(bytes, Charset.defaultCharset())); if (hadError) System.exit(65); + if (hadRuntimeError) System.exit(70); } private static void runPrompt() throws IOException { @@ -51,13 +54,19 @@ public class Lox { if (hadError) return; - System.out.println(new AstPrinter().print(expression)); + interpreter.interpret(expression); } 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/RuntimeError.java b/src/lox/RuntimeError.java new file mode 100644 index 0000000..9828fab --- /dev/null +++ b/src/lox/RuntimeError.java @@ -0,0 +1,10 @@ +package lox; + +public class RuntimeError extends RuntimeException { + final Token token; + + RuntimeError(Token token, String message) { + super(message); + this.token = token; + } +}