/* Copyright (C) 2019-2023 Hangzhou HSH Co. Ltd. * All right reserved.*/ package com.craftinginterpreters.lox; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author simon * @date 2023-06-07 12:20 * @desc */ class Interpreter implements Expr.Visitor, Stmt.Visitor { public final Environment globals = new Environment(); private final Map locals = new HashMap<>(); private Environment environment = globals; public 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 ""; } }); } /** * public API * * @param statements statements */ void interpret(List statements) { try { for (Stmt statement : statements) { execute(statement); } } catch (RuntimeError error) { Lox.runtimeError(error); } } @Override public Object visitAssignExpr(Expr.Assign expr) { Object value = evaluate(expr.value); environment.assign(expr.name, value); return value; } @Override public Object visitBinaryExpr(Expr.Binary expr) { Object left = evaluate(expr.left); Object right = evaluate(expr.right); return switch (expr.operator.type) { case GRATER -> { checkNumberOperand(expr.operator, left, right); yield (double) left > (double) right; } case GRATER_EQUAL -> { checkNumberOperand(expr.operator, left, right); yield (double) left >= (double) right; } case LESS -> { checkNumberOperand(expr.operator, left, right); yield (double) left < (double) right; } case LESS_EQUAL -> { checkNumberOperand(expr.operator, left, right); yield (double) left <= (double) right; } case BANG_EQUAL -> !isEqual(left, right); case EQUAL_EQUAL -> isEqual(left, right); case MINUS -> { checkNumberOperand(expr.operator, left, right); yield (double) left - (double) right; } case PLUS -> { if (left instanceof Double && right instanceof Double) { yield (double) left + (double) right; } if (left instanceof String && right instanceof String) { yield left + (String) right; } throw new RuntimeError(expr.operator, "Operands must be two numbers or two Strings."); } case SLASH -> { checkNumberOperand(expr.operator, left, right); if (((Double) right) == 0D) throw new RuntimeError(expr.operator, "Division by Zero."); yield (double) left / (double) right; } case STAR -> { checkNumberOperand(expr.operator, left, right); yield (double) left * (double) right; } default -> 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 fun)) { // "totally not a function"(); throw new RuntimeError(expr.paren, "Can only call functions and classes."); } if (arguments.size() != fun.arity()) { throw new RuntimeError(expr.paren, "Expected " + fun.arity() + " arguments bug got " + arguments.size() + "."); } return fun.call(this, arguments); } @Override public Object visitGroupingExpr(Expr.Grouping expr) { return evaluate(expr.expression); } @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 visitUnaryExpr(Expr.Unary expr) { Object right = evaluate(expr.right); return switch (expr.operator.type) { case MINUS -> { checkNumberOperand(expr.operator, right); yield -(double) right; } case BANG -> !isTruthy(right); default -> null; }; } @Override public Object visitVariableExpr(Expr.Variable expr) { // return environment.get(expr.name); 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 Void visitBlockStmt(Stmt.Block stmt) { executeBlock(stmt.statements, new Environment(environment)); return null; } @Override public Void visitClassStmt(Stmt.Class stmt) { environment.define(stmt.name.lexeme, null); LoxClass klass = new LoxClass(stmt.name.lexeme); environment.assign(stmt.name, klass); return null; } @Override public Void visitExpressionStmt(Stmt.Expression stmt) { Object value = evaluate(stmt.expression); System.out.println(" " + stringify(value)); 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 if (stmt.elseBranch != null) { 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(evaluate(stmt.condition))) { execute(stmt.body); } return null; } private void execute(Stmt stmt) { stmt.accept(this); } void resolve(Expr expr, int depth) { locals.put(expr, depth); } protected void executeBlock(List statements, Environment environment) { Environment previous = this.environment; try { this.environment = environment; for (Stmt statement : statements) { execute(statement); } } finally { this.environment = previous; } } private Object evaluate(Expr expr) { return expr.accept(this); } 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(); } private void checkNumberOperand(Token operator, Object operand) { if (operand instanceof Double) return; throw new RuntimeError(operator, "Operand must be a number."); } private void checkNumberOperand(Token operator, Object left, Object right) { if (left instanceof Double && right instanceof Double) return; throw new RuntimeError(operator, "Operands must be numbers."); } }