前言:函数的实现又是一个难点,我们一点一点攻破!
0X00 函数调用
按照惯例我们写出函数调用的「文法」:
unary → ( "!" | "-" ) unary | call ;
call → primary ( "(" arguments? ")" )* ;
arguments → expression ( "," expression )* ;
这样的文法可以匹配这样的函数调用:
A();
A()()();
A(a, b);
符合我们的语言。
接着,写出函数调用「抽象语法树」的格式:
static class Call extends Expr {
Call(Expr callee, Token paren, List<Expr> arguments) {
this.callee = callee;
this.paren = paren;
this.arguments = arguments;
}
<R> R accept(Visitor<R> visitor) {
return visitor.visitCallExpr(this);
}
final Expr callee;
final Token paren;
final List<Expr> arguments;
}
然后在 parser 中,实现生成抽象语法树的代码:
private Expr unary() {
// unary → ( "!" | "-" ) unary
// | primary ;
if (match(BANG, MINUS)) {
Token operator = previous();
Expr right = unary();
return new Expr.Unary(operator, right);
}
return call();
}
private Expr call() {
// 匹配 primary ( "(" arguments? ")" )* ;
Expr expr = primary();
// A() 中 A 的部分叫做 callee
// A()() 中 A() 的部分也可以叫做 callee
// 所以我们要,不断的匹配左侧 callee 的部分,并生成抽象语法树
Expr callee = expr;
while (true) {
if (match(LEFT_PAREN)) {
callee = getCallee(callee);
} else {
expr = callee;
break;
}
}
return expr;
}
private Expr getCallee(Expr callee) {
// 匹配所有的参数
List<Expr> arguments = new ArrayList<>();
if (!check(RIGHT_PAREN)) {
do {
if (arguments.size() >= 255) {
error(peek(), "Cannot have more than 255 arguments.");
}
arguments.add(expression());
} while (match(COMMA));
}
Token paren = consume(RIGHT_PAREN, "Expect ')' after arguments.");
return new Expr.Call(callee, paren, arguments);
}
最后执行这个「抽象语法树」
@Override
public Object visitCallExpr(Expr.Call expr) {
// 首先执行 callee
Object callee = evaluate(expr.callee);
// 计算所有参数
List<Object> arguments = new ArrayList<>();
for (Expr argument : expr.arguments) {
arguments.add(evaluate(argument));
}
// 如果最后的 primary 是 "" 之类的东西,要报错,不能执行
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);
}
其中 LoxCallable
的接口如下:
interface LoxCallable {
int arity();
Object call(Interpreter interpreter, List<Object> arguments);
}
0X01 实现一个内嵌函数
按照上面的步骤我们生成了调用函数的「抽象语法树」,以及执行这个「抽象语法树」。
由于我们现在没有实现如何定义一个函数,所以我们没有函数可调用。
但是我们可以实现一个简单的「内嵌函数」clock()
,原理如下:
- 实现一个全局环境
global
- 在这个全局环境中定义一个
clock
的函数接口,实现
代码如下:
private Environment environment = globals;
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>";
}
});
}
这样就能使用 clock
函数了。
var a = clock();
print a;
0X02 函数的声明
现在我们写一下函数声明的「文法」:
declaration → funDecl
| varDecl
| statement ;
funDecl → "fun" function ;
function → IDENTIFIER "(" parameters? ")" block ;
接着我们写出「函数声明」的「抽象语法树」的格式:
static class Function extends Stmt {
Function(Token name, List<Token> params, List<Stmt> body) {
this.name = name;
this.params = params;
this.body = body;
}
<R> R accept(Visitor<R> visitor) {
return visitor.visitFunctionStmt(this);
}
final Token name;
final List<Token> params;
final List<Stmt> body;
}
然后在 parser 中,实现生成「抽象语法树」的代码:
private Stmt declaration() {
// 在这里抓错误
try {
if (match(VAR)) {
return varDeclaration();
}
if (match(FUN)) {
return function("function");
}
return statement();
} catch (ParseError e) {
// 进入 panic mode
synchronize();
return null;
}
}
private Stmt.Function function(String kind) {
Token name = consume(IDENTIFIER, "Expect " + kind + " name.");
consume(LEFT_PAREN, "Expect '(' after " + kind + " name.");
List<Token> parameters = new ArrayList<>();
if (!check(RIGHT_PAREN)) {
do {
if (parameters.size() >= 255) {
error(peek(), "Cannot have more than 255 parameters.");
}
parameters.add(consume(IDENTIFIER, "Expect parameter name."));
} while (match(COMMA));
}
consume(RIGHT_PAREN, "Expect ')' after parameters.");
consume(LEFT_BRACE, "Expect '{' before " + kind + " body.");
List<Stmt> body = block();
return new Stmt.Function(name, parameters, body);
}
最后执行「抽象语法树」(定义函数):
@Override
public Void visitFunctionStmt(Stmt.Function stmt) {
LoxFunction function = new LoxFunction(stmt);
environment.define(stmt.name.lexeme, function);
return null;
}
这里的 LoxFunction
如下:
class LoxFunction implements LoxCallable {
private final Stmt.Function declaration;
LoxFunction(Stmt.Function declaration) {
this.declaration = declaration;
}
@Override
public int arity() {
return declaration.params.size();
}
@Override
public String toString() {
return "<fn " + declaration.name.lexeme + ">";
}
@Override
public Object call(Interpreter interpreter, List<Object> arguments) {
Environment environment = new Environment(interpreter.globals);
for (int i = 0; i < declaration.params.size(); i++) {
environment.define(declaration.params.get(i).lexeme, arguments.get(i));
}
interpreter.executeBlock(declaration.body, environment);
return null;
}
}
而当我们执行函数的时候,会执行 function 的 call 函数,我们来看看 call 函数做了什么:
// 创建了一个新的环境,并把全局环境给了他
// 在新的环境中定义传过来的参数
public Object call(Interpreter interpreter, List<Object> arguments) {
Environment environment = new Environment(interpreter.globals);
for (int i = 0; i < declaration.params.size(); i++) {
environment.define(declaration.params.get(i).lexeme, arguments.get(i));
}
interpreter.executeBlock(declaration.body, environment);
return null;
}
这样我们就实现了一个可定义,可调用的函数。最后我们再来看看如何返回值
0X03 函数的返回
首先,我们写出 return 的文法:
statement → exprStmt
| forStmt
| ifStmt
| printStmt
| returnStmt
| whileStmt
| block ;
returnStmt → "return" expression? ";" ;
在我们实现的语言中,不管有没有写 return 函数,其实都有返回值等价于:
return nil;
写出 return 的「抽象语法树」的格式:
static class Return extends Stmt {
Return(Token keyword, Expr value) {
this.keyword = keyword;
this.value = value;
}
<R> R accept(Visitor<R> visitor) {
return visitor.visitReturnStmt(this);
}
final Token keyword;
final Expr value;
}
实现生成抽象语法树的代码:
private Stmt returnStatement() {
Token keyword = previous();
Expr value = null;
if (!check(SEMICOLON)) {
value = expression();
}
consume(SEMICOLON, "Expect ';' after return value.");
return new Stmt.Return(keyword, value);
}
if (match(RETURN)) {
return returnStatement();
}
执行抽象语法树:
@Override
public Void visitReturnStmt(Stmt.Return stmt) {
Object value = null;
if (stmt.value != null)
value = evaluate(stmt.value);
throw new Return(value);
}
并在 call 中抓住这个 Return exception:
try {
interpreter.executeBlock(declaration.body, environment);
} catch (Return returnValue) {
return returnValue.value;
}
这样就实现了 return 的语法
0X04 局部函数的实现
对于函数我们还有一个点没有实现:
fun makeCounter() {
var i = 0;
fun count() {
i = i + 1;
print i;
}
return count;
}
var counter = makeCounter();
counter(); // "1".
counter(); // "2".
这个叫做「闭包」
原理如下:
-
当执行一个函数的时候,会产生一个属于这个函数的新环境,比如调用 makeCounter() 会产生一个新的环境,这个新的环境,会保存定义的所有变量
-
当在外面执行内部函数的时候,由于内部函数保存了,定义它时的环境,所以就能够,访问之前环境定义的变量
代码如下:
@Override
public Void visitFunctionStmt(Stmt.Function stmt) {
// environment 是当前环境
LoxFunction function = new LoxFunction(stmt, environment);
environment.define(stmt.name.lexeme, function);
return null;
}
class LoxFunction implements LoxCallable {
private final Stmt.Function declaration;
private final Environment closure;
LoxFunction(Stmt.Function declaration, Environment closure) {
this.declaration = declaration;
this.closure = closure;
}
@Override
public int arity() {
return declaration.params.size();
}
@Override
public String toString() {
return "<fn " + declaration.name.lexeme + ">";
}
@Override
public Object call(Interpreter interpreter, List<Object> arguments) {
Environment environment = new Environment(closure);
for (int i = 0; i < declaration.params.size(); i++) {
environment.define(declaration.params.get(i).lexeme, arguments.get(i));
}
try {
interpreter.executeBlock(declaration.body, environment);
} catch (Return returnValue) {
return returnValue.value;
}
return null;
}
}
至此我们「函数」相关知识的与代码已完成!
网友评论