package lox; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; class Interpreter implements Expr.Visitor, Stmt.Visitor { final Environment globals = new Environment(); private Environment environment = globals; private final Map locals = new HashMap<>(); Interpreter() { globals.define( "clock", new LoxCallable() { @Override public int arity() { return 0; } @Override public Object call(Interpreter interpreter, List arguments) { return (double) System.currentTimeMillis() / 1000.0; } @Override public String toString() { return ""; } }); } void interpret(List 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 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 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; } } }