java interpreter
This commit is contained in:
parent
491f75f6e9
commit
d5f7809ebb
@ -0,0 +1,136 @@
|
||||
package ch.vanwa.lox_interpreter;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitLiteralExpr(Expr.Literal expr) {
|
||||
return expr.value;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
@ -12,11 +12,18 @@ import java.util.List;
|
||||
* A Lox interpreter.
|
||||
*/
|
||||
public class Lox {
|
||||
private static final Interpreter interpreter = new Interpreter();
|
||||
|
||||
/**
|
||||
* True if a run produced an error, false otherwise.
|
||||
*/
|
||||
static boolean hadError = false;
|
||||
|
||||
/**
|
||||
* True if a run produced a runtime error, false otherwise.
|
||||
*/
|
||||
static boolean hadRuntimeError = false;
|
||||
|
||||
/**
|
||||
* Run a file with Lox source code.
|
||||
*
|
||||
@ -28,6 +35,9 @@ public class Lox {
|
||||
if (hadError) {
|
||||
System.exit(65);
|
||||
}
|
||||
if (hadRuntimeError) {
|
||||
System.exit(70);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,10 +70,11 @@ public class Lox {
|
||||
List<Token> tokens = scanner.scanTokens();
|
||||
Parser parser = new Parser(tokens);
|
||||
Expr expression = parser.parse();
|
||||
if (hadError)
|
||||
if (hadError) {
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println(new AstPrinter().print(expression));
|
||||
interpreter.interpret(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,6 +101,16 @@ public class Lox {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a runtime error.
|
||||
*
|
||||
* @param error Runtime error to print.
|
||||
*/
|
||||
static void runtimeError(RuntimeError error) {
|
||||
System.err.println(String.format("%s\n[line %d]", error.getMessage(), error.token.line()));
|
||||
hadRuntimeError = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a message to stderr.
|
||||
*
|
||||
|
@ -0,0 +1,13 @@
|
||||
package ch.vanwa.lox_interpreter;
|
||||
|
||||
/**
|
||||
* Represent an error at runtime (evaluating).
|
||||
*/
|
||||
class RuntimeError extends RuntimeException {
|
||||
final Token token;
|
||||
|
||||
RuntimeError(Token token, String message) {
|
||||
super(message);
|
||||
this.token = token;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user