316 lines
8.0 KiB
Java
316 lines
8.0 KiB
Java
package lox;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
|
final Environment globals = new Environment();
|
|
private Environment environment = globals;
|
|
private final Map<Expr, Integer> locals = new HashMap<>();
|
|
|
|
Interpreter() {
|
|
globals.define(
|
|
"clock",
|
|
new LoxCallable() {
|
|
@Override
|
|
public int arity() {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public Object call(Interpreter interpreter, List<Object> arguments) {
|
|
return (double) System.currentTimeMillis() / 1000.0;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "<native fn>";
|
|
}
|
|
});
|
|
}
|
|
|
|
void interpret(List<Stmt> statements) {
|
|
try {
|
|
for (Stmt statement : statements) {
|
|
execute(statement);
|
|
}
|
|
} catch (RuntimeError error) {
|
|
Lox.runtimeError(error);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Object visitLiteralExpr(Expr.Literal expr) {
|
|
return expr.value;
|
|
}
|
|
|
|
@Override
|
|
public Object visitLogicalExpr(Expr.Logical expr) {
|
|
Object left = evaluate(expr.left);
|
|
|
|
if (expr.operator.type == TokenType.OR) {
|
|
if (isTruthy(left)) {
|
|
return left;
|
|
} else {
|
|
if (!isTruthy(left)) {
|
|
return left;
|
|
}
|
|
}
|
|
}
|
|
|
|
return evaluate(expr.right);
|
|
}
|
|
|
|
@Override
|
|
public Object visitGroupingExpr(Expr.Grouping expr) {
|
|
return evaluate(expr.expression);
|
|
}
|
|
|
|
@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;
|
|
}
|
|
// actually unreachable
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Object visitCallExpr(Expr.Call expr) {
|
|
Object callee = evaluate(expr.callee);
|
|
|
|
List<Object> arguments = new ArrayList<>();
|
|
for (Expr argument : expr.arguments) {
|
|
arguments.add(evaluate(argument));
|
|
}
|
|
|
|
if (!(callee instanceof LoxCallable)) {
|
|
throw new RuntimeError(expr.paren, "Can only call functions and classes");
|
|
}
|
|
LoxCallable function = (LoxCallable) callee;
|
|
if (arguments.size() != function.arity()) {
|
|
throw new RuntimeError(
|
|
expr.paren,
|
|
"Expected " + function.arity() + " arguments but called with " + arguments.size() + ".");
|
|
}
|
|
return function.call(this, arguments);
|
|
}
|
|
|
|
@Override
|
|
public Object visitBinaryExpr(Expr.Binary expr) {
|
|
Object left = evaluate(expr.left);
|
|
Object right = evaluate(expr.right);
|
|
|
|
switch (expr.operator.type) {
|
|
case MINUS:
|
|
checkNumberOperands(expr.operator, left, right);
|
|
return (double) left - (double) right;
|
|
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 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 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);
|
|
}
|
|
// actually unreachable
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Object visitVariableExpr(Expr.Variable expr) {
|
|
return lookUpVariable(expr.name, expr);
|
|
}
|
|
|
|
private Object lookUpVariable(Token name, Expr expr) {
|
|
Integer distance = locals.get(expr);
|
|
if (distance != null) {
|
|
return environment.getAt(distance, name.lexeme);
|
|
} else {
|
|
return globals.get(name);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Object visitAssignExpr(Expr.Assign expr) {
|
|
Object value = evaluate(expr.value);
|
|
|
|
Integer distance = locals.get(expr);
|
|
if (distance != null) {
|
|
environment.assignAt(distance, expr.name, value);
|
|
} else {
|
|
globals.assign(expr.name, value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public Void visitExpressionStmt(Stmt.Expression stmt) {
|
|
evaluate(stmt.expression);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void visitFunctionStmt(Stmt.Function stmt) {
|
|
LoxFunction function = new LoxFunction(stmt, environment);
|
|
environment.define(stmt.name.lexeme, function);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void visitIfStmt(Stmt.If stmt) {
|
|
if (isTruthy(evaluate(stmt.condition))) {
|
|
execute(stmt.thenBranch);
|
|
} else {
|
|
execute(stmt.elseBranch);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void visitPrintStmt(Stmt.Print stmt) {
|
|
Object value = evaluate(stmt.expression);
|
|
System.out.println(stringify(value));
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void visitReturnStmt(Stmt.Return stmt) {
|
|
Object value = null;
|
|
if (stmt.value != null) {
|
|
value = evaluate(stmt.value);
|
|
}
|
|
throw new Return(value);
|
|
}
|
|
|
|
@Override
|
|
public Void visitVarStmt(Stmt.Var stmt) {
|
|
Object value = null;
|
|
if (stmt.initializer != null) {
|
|
value = evaluate(stmt.initializer);
|
|
}
|
|
environment.define(stmt.name.lexeme, value);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void visitWhileStmt(Stmt.While stmt) {
|
|
while (isTruthy(stmt.condition)) {
|
|
execute(stmt.body);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void visitBlockStmt(Stmt.Block stmt) {
|
|
executeBlock(stmt.statements, new Environment(environment));
|
|
return null;
|
|
}
|
|
|
|
private void checkNumberOperand(Token operator, Object operand) {
|
|
if (operand instanceof Double) {
|
|
return;
|
|
}
|
|
throw new RuntimeError(operator, "Operator 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 a numbers.");
|
|
}
|
|
|
|
private boolean isTruthy(Object object) {
|
|
if (object == null) {
|
|
return false; // null is false
|
|
}
|
|
if (object instanceof Boolean) {
|
|
return (boolean) object; // booleans are their own truthiness
|
|
}
|
|
return true; // everything else is true
|
|
}
|
|
|
|
private boolean isEqual(Object left, Object right) {
|
|
if (left == null && right == null) {
|
|
return true; // null is equal to another null
|
|
}
|
|
if (left == null) {
|
|
return false; // null is never equal to anything else
|
|
}
|
|
return left.equals(right);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
private void execute(Stmt stmt) {
|
|
stmt.accept(this);
|
|
}
|
|
|
|
void resolve(Expr expr, int depth) {
|
|
locals.put(expr, depth);
|
|
}
|
|
|
|
void executeBlock(List<Stmt> statements, Environment environment) {
|
|
Environment previous = this.environment;
|
|
try {
|
|
this.environment = environment;
|
|
|
|
for (Stmt statement : statements) {
|
|
// could also just redesign this to accept an environment field everywhere
|
|
execute(statement);
|
|
}
|
|
} finally {
|
|
this.environment = previous;
|
|
}
|
|
}
|
|
}
|