MENU

《两周自制脚本语言》—— 第七天(添加函数功能)

December 20, 2018 • Read: 4435 • 编译原理阅读设置

笔记、源码同步在 github 上,欢迎 Star,蟹蟹~



本章将为 Stone 语言添加函数功能。此外,除了基本的函数定义与调用执行,本章还会引入名为闭包(closure)的语法功能,使 Stone 语言可以将变量赋值为函数,或将函数作为参数传递给其他函数

扩充语法规则展开目录

首先将函数定义语句称为 def 语句。def 语句仅能用于最外层代码。也就是说,用户无法在代码块中定义函数

例如,下面的代码定义了函数 fact

  • def fact (n) {
  • f = 1
  • while n > 0 {
  • f = f * n
  • n = n - 1
  • }
  • f
  • }

Stone 语言没有 return 语句。代码块中最后执行的语句(表达式)的计算结果将作为函数的返回值返回。下面调用函数 fact 时传入参数 9

  • fact(9)

如果希望以 9 为参数调用函数 fact 并将返回值赋值给 n,则可以按下面这样书写代码

  • n = fact(9)

如果语句只调用了一个函数,即该函数不是其他更复杂的表达式的组成部分且不会产生歧义,实参两侧的括号就能省略。也就是说,仅含函数调用的语句无需用括号标识实参

  • fact 9

代码清单 7.1 与函数相关的语法规则

  • param -> IDENTIFIER
  • params -> param { "," param }
  • param_list -> "(" [ params ] ")"
  • def -> "def" IDENTIFIER param_list block
  • args -> expr { "," expr }
  • postfix -> "(" [ args ] ")"
  • primary -> ( "(" expr ")" | NUMBER | IDENTIFIER | STRING ) { postfix }
  • simple -> expr [ args ]
  • program -> [ def | statement ] (";" | EOL)

这里只显示了与代码清单 5.1 不同的部分

形参 param 是一种标识符(变量名)。形参序列 params 至少包含一个 param,各个参数之间通过逗号分隔。param_list 可以是以括号括起的 params,也可以是空括号对 ()。函数定义语句 def 由 def、标识符(函数名)、param_list 及 block 组成。实参 args 由若干个通过逗号分隔的 expr 组成。postfix 可以是以括号括起的 args,也可以是省略了 args 的空括号对

非终结符 primary 需要在原有基础上增加对表达式中含有的函数调用的支持。因此,本章修改了代码清单 5.1 中 primary 的定义。在原先的 primary 之后增加若干个(可以为 0)postfix(后缀)得到的依然是一个 primary。这里的 postfix 是用括号括起的实参序列

此外,表达式语句 simple 也需要支持函数调用语句。因此,本章修改了之前的定义,使 simple 不仅能由 expr 组成,expr 后接 args 的组合也是一种 simple 语句

与 primary 不同,simple 不支持由括号括起的实参 args。也就是说

  • simple -> expr [ "(" [ args ] ")" ]

是不正确的,应该使用下面的形式

  • simple -> expr [ args ]

代码清单 7.2 是根据代码清单 7.1 的语法规则设计的语法分析程序。其中 FuncParser 类继承于第 5 章代码清单 5.2 中的 Basicparser 类。也就是说,语法分析器的基本部分利用了 Basicparser 类中已有的代码,FuncParser 类仅定义了新增的功能。和之前一样,新定义的非终结符也通过 parser 库实现。代码清单 7.3、代码清单 7.4 与代码清单 7.5 是更新后的抽象语法树的节点类

代码清单 7.2 中,paramList 字段与 postfix 字段的初始化表达式使用了 maybe 方法。例如,paramList 字段的定义如下所示

  • Parser paramList = rule().sep("(").maybe(params).sep(")");

与 option 方法一样,maybe 方法也用于向模式中添加可省略的非终结符。paramList 字段对应的非终结符 param_list 实际的语法规则如下所示

  • param_list -> "(" [ params ] ")"

省略 params 创建的子树是一棵以 ParameterList 对象为根节点的树。根节点是该子树唯一的节点,这棵子树除根节点外没有其他子节点。parameterList(参数列表)对象的子节点原本用于表示参数,params 被省略时,根节点的子节点数为 0,恰巧能够很好地表示没有参数

即使 params 被省略,抽象语法树仍将包含一个 params 的子树来表示这个实际不存在的成分。根据第 5 章介绍的特殊规定,为了避免创建不必要的节点,与 params 对应的子树将直接作为与非终结符 param_list 对应的子树使用

非终结符定义的修改由构造函数完成。构造函数首先需要为 reserved 添加右括号),以免将它识别为标识符。之后,primary 与 simple 模式的末尾也要添加非终结符,为此需要根据相应的字段调用合适的方法。例如,simp1e 字段应调用 option 方法

  • simple.option(args)

通过这种方式,option 方法将在由 Basicparser 类初始化的 simple 模式末尾添加一段新的模式。也就是说,BasicParser 在进行初始化时,将不再执行下面的语句

  • Parser simple = rule(PrimaryExpr.class).ast(expr);

而执行下面的代码

  • Parser simple = rule(PrimaryExpr.class).ast(expr).option(args)

构造函数的最后一行调用了 program 字段的 insertChoice 方法,将用于表示 def 语句的非终结符 def 添加到了 program 中。该方法将把 def 作为 or 的分支选项,添加到与 program 对应的模式之前

通过 insertChoice 方法添加 def 之后,program 表示的模式将与下面定义等价

  • Parser program = rule().or(def,statement,rule(NullStmnt.class)).sep(";",Token.EOL)

算上 def,表达式中 or 的分支选项增加到了 3 个。新增的选项和原有的两个一样,都是 or 方法的直接分支,语法分析器在执行语句时必须首先判断究竞选择哪个分支

代码清单 7.2 支持函数功能的语法分析器 FuncParser.java

  • package Stone;
  • import static Stone.Parser.rule;
  • import Stone.ast.ParameterList;
  • import Stone.ast.Arguments;
  • import Stone.ast.DefStmnt;
  • public class FuncParser extends BasicParser {
  • Parser param = rule().identifier(reserved);
  • Parser params = rule(ParameterList.class)
  • .ast(param).repeat(rule().sep(",").ast(param));
  • Parser paramList = rule().sep("(").maybe(params).sep(")");
  • Parser def = rule(DefStmnt.class)
  • .sep("def").identifier(reserved).ast(paramList).ast(block);
  • Parser args = rule(Arguments.class)
  • .ast(expr).repeat(rule().sep(",").ast(expr));
  • Parser postfix = rule().sep("(").maybe(args).sep(")");
  • public FuncParser() {
  • reserved.add(")");
  • primary.repeat(postfix);
  • simple.option(args);
  • program.insertChoice(def);
  • }
  • }

代码清单 7.3 ParameterList.java

  • package Stone.ast;
  • import java.util.List;
  • public class ParameterList extends ASTList {
  • public ParameterList(List<ASTree> list) {
  • super(list);
  • }
  • public String name(int i) {
  • return ((ASTLeaf) child(i)).token().getText();
  • }
  • public int size() {
  • return numChildren();
  • }
  • }

代码清单 7.4 DefStmnt.java

  • package Stone.ast;
  • import java.util.List;
  • public class DefStmnt extends ASTList {
  • public DefStmnt(List<ASTree> list) {
  • super(list);
  • }
  • public String name() {
  • return ((ASTLeaf) child(0)).token().getText();
  • }
  • public ParameterList parameters() {
  • return (ParameterList) child(1);
  • }
  • public BlockStmnt body() {
  • return (BlockStmnt) child(2);
  • }
  • public String toString() {
  • return "(def )" + name() + " " + parameters() + " " + body() + ")";
  • }
  • }

代码清单 7.5 Arguments.java

  • package Stone.ast;
  • import java.util.List;
  • public class Arguments extends Postfix {
  • public Arguments(List<ASTree> c) {
  • super(c);
  • }
  • public int size() {
  • return numChildren();
  • }
  • }

作用域与生存周期展开目录

环境是变量名与变量的值的对应关系表。大部分程序设计语言都支持仅在函数内部有效的局部变量。为了让 Stone 语言也支持局部变量,我们必须重新设计环境

在设计环境时,必须考虑两个重要的概念,即作用域(scope)与生存周期(extent)。变量的作用域是指该变量能在程序中有效访问的范围。例如,Java 语言中方法的参数只能在方法内部引用。也就是说,一个方法的参数的作用域限定于该方法内部。而变量的生存周期则是该变量存在的时间期限。例如,Java 语言中某个方法的参数 p 的生存周期就是该方法的执行期。换言之,参数 p 在方法执行过程中将始终有效。如果该方法中途调用了其他方法,就会离开原方法的作用域,新调用的方法无法引用原方法中的参数 p。不过,虽然参数 p 此时无法引用,它仍会继续存在,保存当前值。当程序返回原来的方法后,又回到了参数 p 的作用域,将能够再次引用参数 p。引用参数 p 得到的自然是它原来的值。方法执行结束后,参数 p 的生存周期也将一同结束,参数 p 不再有效,环境中保存的相应名值对也不复存在。事实上,环境也没有必要继续保持该名值对。之后如果程序再次调用该方法,参数 p 将与新的值(实参)关联

通常,变量的作用域由嵌套结构实现。Stone 语言支持在整个程序中都有效的全局变量作用域及仅在函数内部有效的局部变量与函数参数作用域

为表现嵌套结构,我们需要为每一种作用域准备一个单独的环境,并根据需要嵌套环境。在查找变量时,程序将首先查找与最内层作用域对应的环境,如果没有找到,再接着向外逐层查找。目前的 Stone 语言尚不支持在函数内定义函数,因此仅有两种作用域,即全局变量作用域及局部变量作用域。而在支持函数内定义函数的语言中,可能存在多层环境嵌套

Java 等一些语言中,大括号 {} 括起的代码块也具有独立的作用域。代码块中声明的变量只能在该代码块内部引用。Stone 语言目前没有为代码块设计专门的作用域,之后也不会为每个代码块提供单独的作用域

代码清单 7.6 NestedEnv.java

  • package chap7;
  • import chap6.Environment;
  • import java.util.HashMap;
  • import chap7.FuncEvaluator.EnvEx;
  • public class NestedEnv implements Environment {
  • protected HashMap<String, Object> values;
  • protected Environment outer;
  • public NestedEnv() {
  • this(null);
  • }
  • public NestedEnv(Environment e) {
  • values = new HashMap<String, Object>();
  • outer = e;
  • }
  • public void setOuter(Environment e) {
  • outer = e;
  • }
  • public void put(String name, Object value) {
  • Environment e = where(name);
  • if (e == null)
  • e = this;
  • ((EnvEx)e).putNew(name,value)
  • }
  • public void putNew(String name, Object value) {
  • values.put(name, value);
  • }
  • public Environment where(String name) {
  • if (values.get(name) != null)
  • return this;
  • else if (outer == null)
  • return null;
  • return ((EnvEx) outer).where(name);
  • }
  • public Object get(String name) {
  • Object v = values.get(name);
  • if (v == null && outer != null)
  • return outer.get(name);
  • return v;
  • }
  • }

为了使环境支持嵌套结构,需要重新定义了 Environment 接口的类实现。代码清单 7.6 是今后需要使用的 NestedEnv 类的定义

与 BasicEnv 类不同,NestedEnv 类除了 value 字段,还有一个 outer 字段。该字段引用的是与外侧一层作用域对应的环境。此外,get 方法也需要做相应的修改,以便查找与外层作用域对应的环境。为确保 put 方法能够正确更新变量的值,我们也需要对它做修改。如果当前环境中不存在参数指定的变量名称,而外层作用域中含有该名称,put 方法应当将值赋给外层作用域中的变量。为此,我们需要使用辅助方法 where。该方法将查找包含指定变量名的环境并返回。如果所有环境中都不含该变量名,where 方法将返回 nul1

NestedEnv 类提供了一个 putNew 方法。该方法的作用与 BasicEnv 类的 put 方法相同。它在赋值时不会考虑 outer 字段引用的外层作用域环境。无论外层作用域对应的环境中是否存在指定的变量名,只要当前环境中没有该变量,putNew 方法就会新增一个变量

此外,为了能让 NestedEnv 类的方法经由 Environment 接口访问,我们需要向 Environment 接口中添加一些新的方法。代码清单 7.7 定义的 FuncEvaluator 修改器定义了一个 EnvEx 修改器,它添加了这些新的方法

执行函数展开目录

为了让解释器能够执行函数,必须为抽象语法树的节点类添加 eva1 方法。这由代码清单 7.7 的 FuncEvaluator 修改器实现

函数的执行分为定义与调用两部分。程序在通过 def 语句定义函数时,将创建用于表示该函数的对象,向环境添加该函数的名称并与该对象关联。也就是说,程序会向环境添加一个变量,它以该对象为变量值,以函数名为变量名。函数由 Function 对象表示。代码清单 7.8 定义了 Function 类

代码清单 7.7 的 FuncEvaluator 修改器包含多个子修改器。其中,DefstmntEX 修改器用于向 Defstmnt 类添加 eva1 方法

PrimaryEx 修改器将向 PrimaryExpr 类添加方法。函数调用表达式的抽象语法树与非终结符 primary 对应。非终结符 primary 原本只表示字面量与变量名等最基本的表达式成分,现在,我们修改它的定义,使函数调用表达式也能被判断为一种 primary。即 primary 将涵盖由 primary 后接括号括起的实参序列构成的表达式,下图是一个例子,它是由函数调用语句 fact (9) 构成的抽象语法树。为了支持这一修改,我们需要为 PrimaryExpr 类添加若干新方法

operand 方法将返回非终结符 primary 原先表示的字面量与函数名等内容,或返回函数名称。postfix 方法返回的是实参序列(若存在)。eval 方法将首先调用 operand 方法返回的对象的 eval 方法。如果函数存在实参序列,eval 方法将把他们作为参数,进一步调用 postfix 方法(在上图中即 Arguments 对象)返回的对象的 eval 方法

PrimaryExpr 类新增的 postfix 方法的返回值为 Postfix 类型。Postfix 是一个抽象类(代码清单 7.9),它的子类 Arguments 类是一个用于表示实参序列的具体类。ArgumentsEx 修改器为 Arguments 类添加的 eva1 方法将实现函数的执行功能

Arguments 类新增的 eval 方法是函数调用功能的核心。它的第 2 个参数 value 是与函数名对应的抽象语法树的 eval 方法的调用结果。希望调用的函数的 Function 对象将作为 value 参数传递给 eval 方法。Function 对象由 def 语句创建。函数名与变量名的处理方式相同,因此解释器仅需调用 eval 方法就能从环境中获取 Function 对象

之后,解释器将以环境 callerEnv 为实参计算函数的执行结果。首先,Function 对象的 parameters 方法将获得形参序列,实参序列则由自身提供 iterator 方法获取。然后解释器将根据实参的排列顺序依次调用 eval 并计算求值,将计算结果与相应的形参名成对添加至环境中。ParameterList 类新增的 eval 方法将执行实际的处理

实参的值将被添加到新创建的用于执行函数调用的 newEnv 环境,而非 callerEnv 环境(表 7.1)。newEnv 环境表示的作用域为函数内部。如果函数使用了局部变量,它们将被添加到该环境

表 7.1 函数调用过程中设计的环境

environmentmean
newEnv 调用函数时新创建的环境。用于记录函数的参数及函数内部使用的局部变量
newEnv.outernewEnv 的 outer 字段引用的环境,能够表示函数外层作用域。该环境通常用于记录全局变量
callerEnv 函数调用语句所处的环境。用于计算实参

最后,Arguments 类的 eval 方法将在新创建的环境中执行函数体。函数体可以通过调用 Function 对象的 body 方法获得。函数体是 def 语句中由大括号 {} 括起的部分,body 方法将返回与之对应的抽象语法树。调用返回的对象的 eva1 方法即可执行该函数

用于调用函数的环境 newEnv 将在函数被调用时创建,在函数执行结束后舍弃。这与函数的参数及局部变量的生存周期相符。若解释器多次递归调用同一个函数,它将在每次调用时创建新的环境。只有这样才能正确执行函数的递归调用

有时,用于计算实参的环境 callerEnv 与执行 def 语句的是同一个环境,但也并非总是如此。callerEnv 是用于计算调用了函数的表达式的环境。如果在最外层代码中调用函数,callerEnv 环境将同时用于保存全局变量。然而,如果函数由其他函数调用,callerEnv 环境则将保存调用该函数的外层函数的局部变量。环境虽然支持嵌套结构,但该结构仅反映了函数定义时的作用域嵌套情况。在函数调用其他函数时,新创建的环境不会出现在这样的嵌套结构中

代码清单 7.7 FuncEvaluator.java

  • package chap7;
  • import java.util.List;
  • import javassist.gluonj.*;
  • import Stone.StoneException;
  • import Stone.ast.*;
  • import chap6.BasicEvaluator;
  • import chap6.Environment;
  • import chap6.BasicEvaluator.ASTreeEx;
  • import chap6.BasicEvaluator.BlockEx;
  • @Require(BasicEvaluator.class)
  • @Reviser public class FuncEvaluator {
  • @Reviser public static interface EnvEx extends Environment {
  • void putNew(String name, Object value);
  • Environment where(String name);
  • void setOuter(Environment e);
  • }
  • @Reviser public static class DefStmntEx extends DefStmnt {
  • public DefStmntEx(List<ASTree> c) { super(c); }
  • public Object eval(Environment env) {
  • ((EnvEx)env).putNew(name(), new Function(parameters(), body(), env));
  • return name();
  • }
  • }
  • @Reviser public static class PrimaryEx extends PrimaryExpr {
  • public PrimaryEx(List<ASTree> c) { super(c); }
  • public ASTree operand() { return child(0); }
  • public Postfix postfix(int nest) {
  • return (Postfix)child(numChildren() - nest - 1);
  • }
  • public boolean hasPostfix(int nest) { return numChildren() - nest > 1; }
  • public Object eval(Environment env) {
  • return evalSubExpr(env, 0);
  • }
  • public Object evalSubExpr(Environment env, int nest) {
  • if (hasPostfix(nest)) {
  • Object target = evalSubExpr(env, nest + 1);
  • return ((PostfixEx)postfix(nest)).eval(env, target);
  • }
  • else
  • return ((ASTreeEx)operand()).eval(env);
  • }
  • }
  • @Reviser public static abstract class PostfixEx extends Postfix {
  • public PostfixEx(List<ASTree> c) { super(c); }
  • public abstract Object eval(Environment env, Object value);
  • }
  • @Reviser public static class ArgumentsEx extends Arguments {
  • public ArgumentsEx(List<ASTree> c) { super(c); }
  • public Object eval(Environment callerEnv, Object value) {
  • if (!(value instanceof Function))
  • throw new StoneException("bad function", this);
  • Function func = (Function)value;
  • ParameterList params = func.parameters();
  • if (size() != params.size())
  • throw new StoneException("bad number of arguments", this);
  • Environment newEnv = func.makeEnv();
  • int num = 0;
  • for (ASTree a: this)
  • ((ParamsEx)params).eval(newEnv, num++,
  • ((ASTreeEx)a).eval(callerEnv));
  • return ((BlockEx)func.body()).eval(newEnv);
  • }
  • }
  • @Reviser public static class ParamsEx extends ParameterList {
  • public ParamsEx(List<ASTree> c) { super(c); }
  • public void eval(Environment env, int index, Object value) {
  • ((EnvEx)env).putNew(name(index), value);
  • }
  • }
  • }

代码清单 7.8 Function.java

  • package chap7;
  • import Stone.ast.BlockStmnt;
  • import Stone.ast.ParameterList;
  • import chap6.Environment;
  • public class Function {
  • protected ParameterList parameters;
  • protected BlockStmnt body;
  • protected Environment env;
  • public Function(ParameterList parameters,BlockStmnt body,Environment env) {
  • this.parameters = parameters;
  • this.body = body;
  • this.env = env;
  • }
  • public ParameterList parameters() {
  • return parameters;
  • }
  • public BlockStmnt body() {
  • return body;
  • }
  • public Environment makeEnv() {
  • return new NestedEnv(env);
  • }
  • public String toString() {
  • return "<fun:" + hashCode() + ">";
  • }
  • }

代码清单 7.9 Postfix.java

  • package Stone.ast;
  • import java.util.List;
  • public class Postfix extends ASTList {
  • public Postfix(List<ASTree> list) {
  • super(list);
  • }
  • }

计算斐波那契数列展开目录

至此,Stone 语言已支持函数调用功能。代码清单 7.10 是解释器的程序代码,代码清单 7.11 是解释器的启动程序。解释器所处的环境并不是一个 BasicEnv 对象,而是一个由启动程序创建的 NestedEnv 对象

下面我们以计算斐波那契数为例测试一下函数调用功能。代码清单 7.12 是由 Stone 语言写成的斐波那契数计算程序。程序执行过程中,将首先定义 fib 函数,并计算 fib(10)的值。最后输出如下结果

  • => fib
  • => 55

代码清单 7.10 FuncInterpreter.java

  • package chap6;
  • import Stone.*;
  • import Stone.ast.ASTree;
  • import Stone.ast.NullStmnt;
  • public class BasicInterpreter {
  • public static void main(String[] args) throws ParseException {
  • run(new BasicParser(), new BasicEnv());
  • }
  • public static void run(BasicParser bp, Environment env)
  • throws ParseException
  • {
  • Lexer lexer = new Lexer(new CodeDialog());
  • while (lexer.peek(0) != Token.EOF) {
  • ASTree t = bp.parse(lexer);
  • if (!(t instanceof NullStmnt)) {
  • Object r = ((BasicEvaluator.ASTreeEx)t).eval(env);
  • System.out.println("=> " + r);
  • }
  • }
  • }
  • }

代码清单 7.11 FunRunner.java

  • package chap7;
  • import javassist.gluonj.util.Loader;
  • public class FuncRunner {
  • public static void main(String[] args) throws Throwable {
  • Loader.run(FuncInterpreter.class, args, FuncEvaluator.class);
  • }
  • }

代码清单 7.12 计算斐波那契数列的 Stone 语言程序

  • def fib(n) {
  • if n < 2 {
  • n
  • } else {
  • fib(n - 1) + fib(n - 2)
  • }
  • }
  • fib(10)

为闭包提供支持展开目录

简单来讲,闭包是一种特殊的函数,它能被赋值给一个变量,作为参数传递至其他函数。闭包既能在最外层代码中定义,也能在其他函数中定义。通常,闭包没有名称

如果 Stone 语言支持闭包,下面的程序将能正确运行

  • inc = fun (x) { x + 1 }
  • inc(3)

这段代码将创建一个新的函数,它的作用是返回一个比接收的参数大 1 的值。该参数将被赋值给变量 inc。赋值给变量的就是一个闭包。inc 并非函数的名称,事实上,这种函数没有名称。不过,程序能够通过 inc (3) 的形式,以 3 为参数调用该函数。读者可以将其理解为,程序从名为 inc 的变量中获得了一个闭包,并以 3 为参数调用这个闭包

代码清单 7.13 是闭包的语法规则。该规则修改了 primary,向其中添加了闭包的定义

代码清单 7.13 闭包的语法规则

  • primary -> " fun " param_list block | 原本的primary定义

实现闭包展开目录

代码清单 7.14 是支持闭包功能的语法分析器程序。它修改了非终结符 primary 的定义,使语法分析器能够解析由 fun 起始的闭包。代码清单 7.15 的 Fun 类是用于表示闭包的抽象语法树的节点类

Fun 类的 eval 方法通过代码清单 7.16 的 ClosureEvaluator 修改器增加。与 def 语句的 eval 方法一样,它也会创建一个 Function 对象。Function 对象的构造函数需要接收一个 env 参数,他是定义了该闭包的表达式所处的执行环境

def 语句在创建 Function 对象后会向环境添加由该对象与函数名组成的键值对,而在创建闭包时,eval 方法将直接返回该对象。这样一来,Stone 语言就能将函数赋值给某个变量,或将它作为参数传递给另一个函数,实现闭包的语法功能

代码 7.14 支持必爆的语法分析器 ClosureParser.java

  • package Stone;
  • import static Stone.Parser.rule;
  • import Stone.ast.Fun;
  • public class ClosureParser extends FuncParser {
  • public ClosureParser() {
  • primary.insertChoice(rule(Fun.class).sep("fun").ast(paramList).ast(block));
  • }
  • }

代码清单 7.15 Fun.java

  • package Stone.ast;
  • import java.util.List;
  • public class Fun extends ASTList {
  • public Fun(List<ASTree> c) {
  • super(c);
  • }
  • public ParameterList parameters() {
  • return (ParameterList)child(0);
  • }
  • public BlockStmnt body() {
  • return (BlockStmnt)child(1);
  • }
  • public String toString() {
  • return "(fun " + parameters() + " " + body() + ")";
  • }
  • }

代码清单 7.16 ClosureEvaluator.java

  • package chap7;
  • import java.util.List;
  • import Stone.ast.ASTree;
  • import Stone.ast.Fun;
  • import chap6.Environment;
  • import javassist.gluonj.*;
  • @Require(FuncEvaluator.class)
  • @Reviser public class ClosureEvaluator {
  • @Reviser public static class FunEx extends Fun {
  • public FunEx(List<ASTree> c) {
  • super(c);
  • }
  • public Object eval(Environment env) {
  • return new Function(parameters(),body(),env);
  • }
  • }
  • }

代码清单 7.17 ClosureInterpreter.java

  • package chap7;
  • import Stone.ClosureParser;
  • import Stone.ParseException;
  • import chap6.BasicInterpreter;
  • public class ClosureInterpreter extends BasicInterpreter {
  • public static void main(String[] args) throws ParseException {
  • run(new ClosureParser(),new NestedEnv());
  • }
  • }

代码清单 7.17 是支持闭包功能的 Stone 语言解释器。代码清单 7.18 是相应的启动程序

代码清单 7.18 ClosureRunner.java

  • package chap7;
  • import javassist.gluonj.util.Loader;
  • public class ClosureRunner {
  • public static void main(String[] args) throws Throwable {
  • Loader.run(ClosureInterpreter.class, args, ClosureEvaluator.class);
  • }
  • }

虽然现在程序已经支持函数何闭包了,Stone 语言和其他很多变量无需声明即可使用的语言一样,但如果已经存在某个全局变量,就是无法在创建同名变量,比方说下面的例子

  • x = 1
  • def foo (i) {
  • x = i;
  • x + 1
  • }

函数 foo 无法创建名为 x 的局部变量。函数中的 x 将引用第一行的全局变量 x。如果调用 foo (3),全局变量 x 的值就会是 3,这可就麻烦了。想用的是局部变量,实际使用的是全局变量,这里似乎存在大量错误隐患。如果非要区分两者,只要更改定义,让全局变量的变量名必须以 $ 开始就行了

第七天的思维导图展开目录

Last Modified: February 2, 2020
Archives Tip
QR Code for this page
Tipping QR Code