Implement Expression interpreter
This commit is contained in:
108
jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java
Normal file
108
jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package xyz.ctsk.lox;
|
||||||
|
|
||||||
|
public class Interpreter implements Expr.Visitor<Object> {
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,10 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
public class Lox {
|
public class Lox {
|
||||||
|
private static final Interpreter interpreter = new Interpreter();
|
||||||
|
|
||||||
private static boolean hadError = false;
|
private static boolean hadError = false;
|
||||||
|
private static boolean hadRuntimeError = false;
|
||||||
|
|
||||||
private static void report(int line, String where, String message) {
|
private static void report(int line, String where, String message) {
|
||||||
System.err.printf("[line %d] Error %s: %s%n", line, where, 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) {
|
private static void run(String source) {
|
||||||
var scanner = new Scanner(source);
|
var scanner = new Scanner(source);
|
||||||
var tokens = scanner.scanTokens();
|
var tokens = scanner.scanTokens();
|
||||||
@@ -36,8 +44,7 @@ public class Lox {
|
|||||||
|
|
||||||
if (hadError) return;
|
if (hadError) return;
|
||||||
|
|
||||||
System.out.println(AstPrinter.polish(expression, true));
|
interpreter.interpret(expression);
|
||||||
System.out.println(AstPrinter.reverse_polish(expression, false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void runPrompt() throws IOException {
|
private static void runPrompt() throws IOException {
|
||||||
@@ -57,9 +64,8 @@ public class Lox {
|
|||||||
byte[] bytes = Files.readAllBytes(Paths.get(path));
|
byte[] bytes = Files.readAllBytes(Paths.get(path));
|
||||||
run(new String(bytes, Charset.defaultCharset()));
|
run(new String(bytes, Charset.defaultCharset()));
|
||||||
|
|
||||||
if (hadError) {
|
if (hadError) System.exit(65);
|
||||||
System.exit(65);
|
if (hadRuntimeError) System.exit(70);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void printUsage() {
|
private static void printUsage() {
|
||||||
|
|||||||
10
jlox/lox/src/main/java/xyz/ctsk/lox/RuntimeError.java
Normal file
10
jlox/lox/src/main/java/xyz/ctsk/lox/RuntimeError.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user