环境
- Time 2023-07-11
- Java 17
前言
说明
参考:
- https://craftinginterpreters.com/contents.html
- https://github.com/GuoYaxiang/craftinginterpreters_zh
- https://space.bilibili.com/44550904
目标
接上一节,实现变量的解析和绑定,实现正确的作用域。
Environment
package com.jiangbo.lox;import java.util.HashMap;
import java.util.Map;class Environment {private final Map<String, Object> values = new HashMap<>();final Environment enclosing;// 新增部分开始Environment() {enclosing = null;}Environment(Environment enclosing) {this.enclosing = enclosing;}void define(String name, Object value) {values.put(name, value);}Object get(Token name) {if (values.containsKey(name.lexeme)) {return values.get(name.lexeme);}if (enclosing != null) return enclosing.get(name);throw new RuntimeError(name,"Undefined variable '" + name.lexeme + "'.");}void assign(Token name, Object value) {if (values.containsKey(name.lexeme)) {values.put(name.lexeme, value);return;}if (enclosing != null) {enclosing.assign(name, value);return;}throw new RuntimeError(name,"Undefined variable '" + name.lexeme + "'.");}Object getAt(int distance, String name) {return ancestor(distance).values.get(name);}void assignAt(int distance, Token name, Object value) {ancestor(distance).values.put(name.lexeme, value);}Environment ancestor(int distance) {Environment environment = this;for (int i = 0; i < distance; i++) {environment = environment.enclosing;}return environment;}
}
FunctionType
package com.jiangbo.lox;enum FunctionType {NONE,FUNCTION}
Interpreter
package com.jiangbo.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() {@Overridepublic int arity() { return 0; }@Overridepublic Object call(Interpreter interpreter, List<Object> arguments) {return (double)System.currentTimeMillis() / 1000.0;}@Overridepublic String toString() { return "<native fn>"; }});}void interpret(List<Stmt> statements) {try {for (Stmt statement : statements) {execute(statement);}} catch (RuntimeError error) {Lox.runtimeError(error);}}private void execute(Stmt stmt) {stmt.accept(this);}void resolve(Expr expr, int depth) {locals.put(expr, depth);}@Overridepublic Void visitBlockStmt(Stmt.Block stmt) {executeBlock(stmt.statements, new Environment(environment));return null;}void executeBlock(List<Stmt> statements,Environment environment) {Environment previous = this.environment;try {this.environment = environment;for (Stmt statement : statements) {execute(statement);}} finally {this.environment = previous;}}@Overridepublic Object visitLiteralExpr(Expr.Literal expr) {return expr.value;}@Overridepublic 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);}@Overridepublic Object visitGroupingExpr(Expr.Grouping expr) {return evaluate(expr.expression);}@Overridepublic Void visitVarStmt(Stmt.Var stmt) {Object value = null;if (stmt.initializer != null) {value = evaluate(stmt.initializer);}environment.define(stmt.name.lexeme, value);return null;}@Overridepublic Void visitWhileStmt(Stmt.While stmt) {while (isTruthy(evaluate(stmt.condition))) {execute(stmt.body);}return null;}@Overridepublic 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;}@Overridepublic 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);}}@Overridepublic Object visitUnaryExpr(Expr.Unary expr) {Object right = evaluate(expr.right);checkNumberOperand(expr.operator, right);return switch (expr.operator.type) {case BANG -> !isTruthy(right);case MINUS -> -(double) right;default -> null;};}@Overridepublic 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 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 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;case BANG_EQUAL:return !isEqual(left, right);case EQUAL_EQUAL:return isEqual(left, right);}// Unreachable.return null;}@Overridepublic 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 got " +arguments.size() + ".");}return function.call(this, arguments);}private Object evaluate(Expr expr) {return expr.accept(this);}@Overridepublic Void visitExpressionStmt(Stmt.Expression stmt) {evaluate(stmt.expression);return null;}@Overridepublic Void visitFunctionStmt(Stmt.Function stmt) {LoxFunction function = new LoxFunction(stmt, environment);environment.define(stmt.name.lexeme, function);return null;}@Overridepublic Void visitIfStmt(Stmt.If stmt) {if (isTruthy(evaluate(stmt.condition))) {execute(stmt.thenBranch);} else if (stmt.elseBranch != null) {execute(stmt.elseBranch);}return null;}@Overridepublic Void visitPrintStmt(Stmt.Print stmt) {Object value = evaluate(stmt.expression);System.out.println(stringify(value));return null;}@Overridepublic Void visitReturnStmt(Stmt.Return stmt) {Object value = null;if (stmt.value != null) value = evaluate(stmt.value);throw new Return(value);}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 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 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();}
}
Resolver
package com.jiangbo.lox;import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {private final Interpreter interpreter;private final Stack<Map<String, Boolean>> scopes = new Stack<>();private FunctionType currentFunction = FunctionType.NONE;Resolver(Interpreter interpreter) {this.interpreter = interpreter;}@Overridepublic Void visitBlockStmt(Stmt.Block stmt) {beginScope();resolve(stmt.statements);endScope();return null;}@Overridepublic Void visitVarStmt(Stmt.Var stmt) {declare(stmt.name);if (stmt.initializer != null) {resolve(stmt.initializer);}define(stmt.name);return null;}@Overridepublic Void visitAssignExpr(Expr.Assign expr) {resolve(expr.value);resolveLocal(expr, expr.name);return null;}@Overridepublic Void visitVariableExpr(Expr.Variable expr) {if (!scopes.isEmpty() &&scopes.peek().get(expr.name.lexeme) == Boolean.FALSE) {Lox.error(expr.name,"Can't read local variable in its own initializer.");}resolveLocal(expr, expr.name);return null;}@Overridepublic Void visitFunctionStmt(Stmt.Function stmt) {declare(stmt.name);define(stmt.name);resolveFunction(stmt, FunctionType.FUNCTION);return null;}@Overridepublic Void visitExpressionStmt(Stmt.Expression stmt) {resolve(stmt.expression);return null;}@Overridepublic Void visitIfStmt(Stmt.If stmt) {resolve(stmt.condition);resolve(stmt.thenBranch);if (stmt.elseBranch != null) resolve(stmt.elseBranch);return null;}@Overridepublic Void visitPrintStmt(Stmt.Print stmt) {resolve(stmt.expression);return null;}@Overridepublic Void visitReturnStmt(Stmt.Return stmt) {if (currentFunction == FunctionType.NONE) {Lox.error(stmt.keyword, "Can't return from top-level code.");}if (stmt.value != null) {resolve(stmt.value);}return null;}@Overridepublic Void visitWhileStmt(Stmt.While stmt) {resolve(stmt.condition);resolve(stmt.body);return null;}@Overridepublic Void visitBinaryExpr(Expr.Binary expr) {resolve(expr.left);resolve(expr.right);return null;}@Overridepublic Void visitCallExpr(Expr.Call expr) {resolve(expr.callee);for (Expr argument : expr.arguments) {resolve(argument);}return null;}@Overridepublic Void visitGroupingExpr(Expr.Grouping expr) {resolve(expr.expression);return null;}@Overridepublic Void visitLiteralExpr(Expr.Literal expr) {return null;}@Overridepublic Void visitLogicalExpr(Expr.Logical expr) {resolve(expr.left);resolve(expr.right);return null;}@Overridepublic Void visitUnaryExpr(Expr.Unary expr) {resolve(expr.right);return null;}private void beginScope() {scopes.push(new HashMap<String, Boolean>());}void resolve(List<Stmt> statements) {for (Stmt statement : statements) {resolve(statement);}}private void resolve(Stmt stmt) {stmt.accept(this);}private void resolve(Expr expr) {expr.accept(this);}private void endScope() {scopes.pop();}private void declare(Token name) {if (scopes.isEmpty()) return;Map<String, Boolean> scope = scopes.peek();if (scope.containsKey(name.lexeme)) {Lox.error(name,"Already variable with this name in this scope.");}scope.put(name.lexeme, false);}private void define(Token name) {if (scopes.isEmpty()) return;scopes.peek().put(name.lexeme, true);}private void resolveLocal(Expr expr, Token name) {for (int i = scopes.size() - 1; i >= 0; i--) {if (scopes.get(i).containsKey(name.lexeme)) {interpreter.resolve(expr, scopes.size() - 1 - i);return;}}}private void resolveFunction( Stmt.Function function, FunctionType type) {FunctionType enclosingFunction = currentFunction;currentFunction = type;beginScope();for (Token param : function.params) {declare(param);define(param);}resolve(function.body);endScope();currentFunction = enclosingFunction;}
}
Lox
package com.jiangbo.lox;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;public class Lox {private static final Interpreter interpreter = new Interpreter();static boolean hadError = false;static boolean hadRuntimeError = false;public static void main(String[] args) throws IOException {// if (args.length > 1) {//     System.out.println("Usage: jlox [script]");//     System.exit(64);// } else if (args.length == 1) {//     runFile(args[0]);// } else {//     runPrompt();// }// 根据实际情况填写runFile("C:\\work\\workspace\\demo\\src\\main\\java\\com\\jiangbo\\lox\\test.lox");}private static void runFile(String path) throws IOException {byte[] bytes = Files.readAllBytes(Paths.get(path));run(new String(bytes, Charset.defaultCharset()));// Indicate an error in the exit code.if (hadError) System.exit(65);if (hadRuntimeError) System.exit(70);}private static void runPrompt() throws IOException {InputStreamReader input = new InputStreamReader(System.in);BufferedReader reader = new BufferedReader(input);for (; ; ) {System.out.print("> ");String line = reader.readLine();if (line == null) break;run(line);hadError = false;}}private static void run(String source) {Scanner scanner = new Scanner(source);List<Token> tokens = scanner.scanTokens();Parser parser = new Parser(tokens);List<Stmt> statements = parser.parse();Resolver resolver = new Resolver(interpreter);resolver.resolve(statements);// Stop if there was a resolution error.if (hadError) return;interpreter.interpret(statements);}static void error(Token token, String message) {if (token.type == TokenType.EOF) {report(token.line, " at end", message);} else {report(token.line, " at '" + token.lexeme + "'", message);}}static void runtimeError(RuntimeError error) {System.err.println(error.getMessage() +"\n[line " + error.token.line + "]");hadRuntimeError = true;}private static void report(int line, String where, String message) {System.err.println("[line " + line + "] Error" + where + ": " + message);hadError = true;}
}
总结
省略了其余的类,程序现在可以支持函数的定义和调用。