diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java new file mode 100644 index 0000000..176877b --- /dev/null +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java @@ -0,0 +1,108 @@ +package xyz.ctsk.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); + } + } + + private Object evaluate(Expr expr) { + return expr.accept(this); + } + + private String stringify(Object object) { + if (object == null) return "nil"; + + var text = object.toString(); + + if (object instanceof Double && text.endsWith(".0")) { + text = text.substring(0, text.length() - 2); + } + return text; + } + + + @Override + public Object visitBinaryExpr(Expr.Binary binary) { + var left = evaluate(binary.left()); + var right = evaluate(binary.right()); + + switch (binary.operator().type()) { + case MINUS, SLASH, STAR, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL -> + checkNumberOperands(binary.operator(), left, right); + } + + return switch (binary.operator().type()) { + case MINUS -> (double) left - (double) right; + case PLUS -> { + if (left instanceof Double leftD && right instanceof Double rightD) { + yield leftD + rightD; + } + + if (left instanceof String leftStr && right instanceof String rightStr) { + yield leftStr + rightStr; + } + + throw new RuntimeError(binary.operator(), "Operands must be two numbers or two strings."); + } + case SLASH -> (double) left / (double) right; + case STAR -> (double) left * (double) right; + case GREATER -> (double)left > (double)right; + case GREATER_EQUAL -> (double)left >= (double)right; + case LESS -> (double)left < (double)right; + case LESS_EQUAL -> (double)left <= (double)right; + case BANG_EQUAL -> !isEqual(left, right); + case EQUAL_EQUAL -> isEqual(left, right); + default -> null; + }; + } + + @Override + public Object visitGroupingExpr(Expr.Grouping grouping) { + return evaluate(grouping.expression()); + } + + @Override + public Object visitLiteralExpr(Expr.Literal literal) { + return literal.value(); + } + + @Override + public Object visitUnaryExpr(Expr.Unary unary) { + var right = evaluate(unary.right()); + + return switch(unary.operator().type()) { + case MINUS -> -asNumber(unary.operator(), right); + case BANG -> !isTruthy(right); + default -> null; + }; + } + + + private boolean isTruthy(Object object) { + if (object == null) return false; + if (object instanceof Boolean bool) return bool; + 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 double asNumber(Token operator, Object operand) { + if (operand instanceof Double d) return d; + 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"); + } +} diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Lox.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Lox.java index e130870..3c43e75 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/Lox.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Lox.java @@ -8,7 +8,10 @@ import java.nio.file.Files; import java.nio.file.Paths; public class Lox { + private static final Interpreter interpreter = new Interpreter(); + private static boolean hadError = false; + private static boolean hadRuntimeError = false; private static void report(int line, String where, String message) { System.err.printf("[line %d] Error %s: %s%n", line, where, message); @@ -27,6 +30,11 @@ public class Lox { } } + static void runtimeError(RuntimeError error) { + System.err.printf("%s%n[line %d]%n", error.getMessage(), error.token.line()); + hadRuntimeError = true; + } + private static void run(String source) { var scanner = new Scanner(source); var tokens = scanner.scanTokens(); @@ -36,8 +44,7 @@ public class Lox { if (hadError) return; - System.out.println(AstPrinter.polish(expression, true)); - System.out.println(AstPrinter.reverse_polish(expression, false)); + interpreter.interpret(expression); } private static void runPrompt() throws IOException { @@ -57,9 +64,8 @@ public class Lox { byte[] bytes = Files.readAllBytes(Paths.get(path)); run(new String(bytes, Charset.defaultCharset())); - if (hadError) { - System.exit(65); - } + if (hadError) System.exit(65); + if (hadRuntimeError) System.exit(70); } private static void printUsage() { diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/RuntimeError.java b/jlox/lox/src/main/java/xyz/ctsk/lox/RuntimeError.java new file mode 100644 index 0000000..d421e33 --- /dev/null +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/RuntimeError.java @@ -0,0 +1,10 @@ +package xyz.ctsk.lox; + +public class RuntimeError extends RuntimeException { + final Token token; + + RuntimeError(Token token, String message) { + super(message); + this.token = token; + } +}