第7天 添加函數(shù)功能
基本的函數(shù)定義與調(diào)用執(zhí)行而昨、引入閉包使Stone語言可以將變量賦值為函數(shù)赦役,或?qū)⒑瘮?shù)作為參數(shù)傳遞給其他函數(shù)
有些函數(shù)將有返回值的歸為函數(shù),沒有返回值的歸為子程序
7.1 擴充語法規(guī)則
函數(shù)定義語句的語法規(guī)則
此書將函數(shù)定義語句稱為def語句。def語句僅能用于最外層代碼,用戶無法在代碼塊中定義函數(shù)
Stone語言將最后執(zhí)行語句(表達式)的計算結(jié)果將作為函數(shù)的返回值返回
代碼清單 7.1 與函數(shù)相關(guān)的語法規(guī)則
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)
形參param是一種標識符(變量名)喝峦。形參序列params至少包含一個param,各個參數(shù)之間通過逗號分隔宣谈。
param_list可以是以括號括起的params愈犹,也可以是空括號對()键科。
函數(shù)定義語句def由def闻丑、標識符(函數(shù)名)、param_list及block組成勋颖。
實參args由若干個通過逗號分隔的expr組成嗦嗡。
postfix可以是以括號括起的args,也可以是省略了args的空括號對
非終結(jié)符primary需要在原有基礎(chǔ)上增加對表達式中含有的函數(shù)調(diào)用的支持饭玲。因此侥祭,本章修改了代碼清單5.1中primary的定義。在原先的primary之后增加若干個(可以為0)postfix(后綴)得到的依然是一個primary茄厘。這里的postfix是用括號括起的實參序列
此外矮冬,表達式語句simple也需要支持函數(shù)調(diào)用語句。因此次哈,本章修改了之前的定義胎署,使simple不僅能由expr組成,expr后接args的組合也是一種simple語句
與primary不同窑滞,simple不支持由括號括起的實參args琼牧。也就是說
simple : expr [ "(" [ args ] ")" ]
是不正確的,應(yīng)該使用下面的形式
simple : expr [ args ]
代碼清單7.2是根據(jù)代碼清單7.1的語法規(guī)則設(shè)計的語法分析程序哀卫。其中FuncParser類繼承于第5章代碼清單5.2中的Basicparser類巨坊。也就是說,語法分析器的基本部分利用了Basicparser類中已有的代碼此改,F(xiàn)uncParser類僅定義了新增的功能趾撵。和之前一樣,新定義的非終結(jié)符也通過parser庫實現(xiàn)共啃。代碼清單7.3鼓寺、代碼清單7.4與代碼清單7.5是更新后的抽象語法樹的節(jié)點類
代碼清單7.2中,paramList字段與postfix字段的初始化表達式使用了maybe方法勋磕。
例如妈候,paramList字段的定義如下所示
Parser paramList = rule().sep("(").maybe(params).sep(")");
與option方法一樣,maybe方法也用于向模式中添加可省略的非終結(jié)符挂滓。
paramList字段對應(yīng)的非終結(jié)符param_list實際的語法規(guī)則如下所示
param_list : "(" [ params ] ")"
省略params創(chuàng)建的子樹是一棵以ParameterList對象為根節(jié)點的樹苦银。根節(jié)點是該子樹唯一的節(jié)點,這棵子樹除根節(jié)點外沒有其他子節(jié)點。parameterList(參數(shù)列表)對象的子節(jié)點原本用于表示參數(shù)幔虏,params被省略時纺念,根節(jié)點的子節(jié)點數(shù)為0,恰巧能夠很好地表示沒有參數(shù)
即使params被省略想括,抽象語法樹仍將包含一個params的子樹來表示這個實際不存在的成分陷谱。根據(jù)第5章介紹的特殊規(guī)定,為了避免創(chuàng)建不必要的節(jié)點瑟蜈,與params對應(yīng)的子樹將直接作為與非終結(jié)符param_list對應(yīng)的子樹使用
非終結(jié)符定義的修改由構(gòu)造函數(shù)完成烟逊。構(gòu)造函數(shù)首先需要為reserved添加右括號),以免將它識別為標識符铺根。之后宪躯,primary與simple模式的末尾也要添加非終結(jié)符,為此需要根據(jù)相應(yīng)的字段調(diào)用合適的方法位迂。例如访雪,simp1e字段應(yīng)調(diào)用option方法
simple.option(args)
通過這種方式,option方法將在由Basicparser類初始化的simple模式末尾添加一段新的模式掂林。也就是說臣缀,BasicParser在進行初始化時,將不再執(zhí)行下面的語句
Parser simple = rule(PrimaryExpr.class).ast(expr);
而執(zhí)行下面的代碼
Parser simple = rule(PrimaryExpr.class).ast(expr).option(args)
構(gòu)造函數(shù)的最后一行調(diào)用了program字段的insertChoice方法泻帮,將用于表示def語句的非終結(jié)符def添加到了program中精置。該方法將把def作為or的分支選項,添加到與program對應(yīng)的模式之前
通過insertChoice方法添加def之后刑顺,program表示的模式將與下面定義等價
Parser program = rule().or(def,statement,rule(NullStmnt.class)).sep(";",Token.EOL)
算上def氯窍,表達式中or的分支選項增加到了3個。新增的選項和原有的兩個一樣蹲堂,都是or方法的直接分支狼讨,語法分析器在執(zhí)行語句時必須首先判斷究競選擇哪個分支
代碼清單7.2 支持函數(shù)功能的語法分析器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();
}
}
7.2 作用域與生存周期
環(huán)境是變量名與變量的值的對應(yīng)關(guān)系表。大部分程序設(shè)計語言都支持僅在函數(shù)內(nèi)部有效的局部變量柒竞。為了讓Stone語言也支持局部變量政供,我們必須重新設(shè)計環(huán)境
設(shè)計環(huán)境
作用域(scope)
變量的作用域是指該變量能在程序中有效訪問的范圍。例如朽基,Java語言中方法的參數(shù)只能在方法內(nèi)部引用布隔。也就是說,一個方法的參數(shù)的作用域限定于該方法內(nèi)部稼虎。
生存周期(extent)
變量的生存周期則是該變量存在的時間期限衅檀。例如,Java語言中某個方法的參數(shù)p的生存周期就是該方法的執(zhí)行期霎俩。換言之哀军,參數(shù)p在方法執(zhí)行過程中將始終有效沉眶。如果該方法中途調(diào)用了其他方法,就會離開原方法的作用域杉适,新調(diào)用的方法無法引用原方法中的參數(shù)p谎倔。不過,雖然參數(shù)p此時無法引用猿推,它仍會繼續(xù)存在片习,保存當前值。當程序返回原來的方法后蹬叭,又回到了參數(shù)p的作用域藕咏,將能夠再次引用參數(shù)p。引用參數(shù)p得到的自然是它原來的值具垫。方法執(zhí)行結(jié)束后侈离,參數(shù)p的生存周期也將一同結(jié)束试幽,參數(shù)p不再有效筝蚕,環(huán)境中保存的相應(yīng)名值對也不復(fù)存在。事實上铺坞,環(huán)境也沒有必要繼續(xù)保持該名值對起宽。之后如果程序再次調(diào)用該方法,參數(shù)p將與新的值(實參)關(guān)聯(lián)
通常济榨,變量的作用域由嵌套結(jié)構(gòu)實現(xiàn)坯沪。Stone語言支持在整個程序中都有效的全局變量作用域及僅在函數(shù)內(nèi)部有效的局部變量與函數(shù)參數(shù)作用域
為表現(xiàn)嵌套結(jié)構(gòu),我們需要為每一種作用域準備一個單獨的環(huán)境擒滑,并根據(jù)需要嵌套環(huán)境髓窜。在查找變量時结蟋,程序?qū)⑹紫炔檎遗c最內(nèi)層作用域?qū)?yīng)的環(huán)境,如果沒有找到,再接著向外逐層查找崖媚。目前的Stone語言尚不支持在函數(shù)內(nèi)定義函數(shù),因此僅有兩種作用域睁宰,即全局變量作用域及局部變量作用域缰贝。而在支持函數(shù)內(nèi)定義函數(shù)的語言中,可能存在多層環(huán)境嵌套
Java等一些語言中柠衍,大括號{}括起的代碼塊也具有獨立的作用域洋满。代碼塊中聲明的變量只能在該代碼塊內(nèi)部引用。Stone語言目前沒有為代碼塊設(shè)計專門的作用域珍坊,之后也不會為每個代碼塊提供單獨的作用域牺勾。因此,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;
}
}
為了使環(huán)境支持嵌套結(jié)構(gòu)驻民,需要重新定義了Environment接口的類實現(xiàn)腺怯。代碼清單7.6是今后需要使用的NestedEnv類的定義
與BasicEnv類不同,NestedEnv類除了value字段川无,還有一個outer字段呛占。該字段引用的是與外側(cè)一層作用域?qū)?yīng)的環(huán)境。此外懦趋,get方法也需要做相應(yīng)的修改晾虑,以便查找與外層作用域?qū)?yīng)的環(huán)境。為確保put方法能夠正確更新變量的值仅叫,我們也需要對它做修改帜篇。如果當前環(huán)境中不存在參數(shù)指定的變量名稱,而外層作用域中含有該名稱诫咱,put方法應(yīng)當將值賦給外層作用域中的變量笙隙。為此,我們需要使用輔助方法where坎缭。該方法將查找包含指定變量名的環(huán)境并返回竟痰。如果所有環(huán)境中都不含該變量名,where方法將返回nul1
NestedEnv類提供了一個putNew方法掏呼。該方法的作用與BasicEnv類的put方法相同坏快。它在賦值時不會考慮outer字段引用的外層作用域環(huán)境。無論外層作用域?qū)?yīng)的環(huán)境中是否存在指定的變量名憎夷,只要當前環(huán)境中沒有該變量莽鸿,putNew方法就會新增一個變量。
此外拾给,為了能讓NestedEnv類的方法經(jīng)由Environment接口訪問祥得,我們需要向Environment接口中添加一些新的方法。代碼清單7.7定義的FuncEvaluator修改器定義了一個EnvEx修改器蒋得,它添加了這些新的方法级及。
7.3 執(zhí)行函數(shù)
為了讓解釋器能夠執(zhí)行函數(shù),必須為抽象語法樹的節(jié)點類添加eva1方法窄锅。這由代碼清單7.7的FuncEvaluator修改器實現(xiàn)创千。
函數(shù)的執(zhí)行分為定義與調(diào)用兩部分。程序在通過def語句定義函數(shù)時入偷,將創(chuàng)建用于表示該函數(shù)的對象追驴,向環(huán)境添加該函數(shù)的名稱并與該對象關(guān)聯(lián)。也就是說疏之,程序會向環(huán)境添加一個變量殿雪,它以該對象為變量值,以函數(shù)名為變量名锋爪。函數(shù)由Function對象表示丙曙。代碼清單7.8定義了Function類爸业。
代碼清單7.7的FuncEvaluator修改器包含多個子修改器。其中亏镰,DefstmntEX修改器用于向 Defstmnt類添加eva1方法扯旷。
PrimaryEx修改器將向PrimaryExpr類添加方法。函數(shù)調(diào)用表達式的抽象語法樹與非終結(jié)符primary對應(yīng)索抓。非終結(jié)符primary原本只表示字面量與變量名等最基本的表達式成分钧忽,現(xiàn)在,我們修改它的定義逼肯,使函數(shù)調(diào)用表達式也能被判斷為一種primary耸黑。即primary將涵蓋由primary后接括號括起的實參序列構(gòu)成的表達式,下圖是一個例子篮幢,它是由函數(shù)調(diào)用語句fact(9)構(gòu)成的抽象語法樹大刊。為了支持這一修改,我們需要為PrimaryExpr類添加若干新方法三椿。
圖 7.1 fact(9)的抽象語法樹
operand方法將返回非終結(jié)符primary原先表示的字面量與函數(shù)名等內(nèi)容缺菌,或返回函數(shù)名稱。postfix方法返回的是實參序列(若存在)赋续。eval方法將首先調(diào)用operand方法返回的對象的eval方法男翰。如果函數(shù)存在實參序列另患,eval方法將把他們作為參數(shù)纽乱,進一步調(diào)用postfix方法(在上圖中即Arguments對象)返回的對象的eval方法
PrimaryExpr類新增的postfix方法的返回值為Postfix類型。Postfix是一個抽象類(代碼清單7.9)昆箕,它的子類Arguments類是一個用于表示實參序列的具體類鸦列。ArgumentsEx修改器為Arguments類添加的eva1方法將實現(xiàn)函數(shù)的執(zhí)行功能
Arguments類新增的eval方法是函數(shù)調(diào)用功能的核心。它的第2個參數(shù)value是與函數(shù)名對應(yīng)的抽象語法樹的eval方法的調(diào)用結(jié)果鹏倘。希望調(diào)用的函數(shù)的Function對象將作為value參數(shù)傳遞給eval方法薯嗤。Function對象由def語句創(chuàng)建。函數(shù)名與變量名的處理方式相同纤泵,因此解釋器僅需調(diào)用eval方法就能從環(huán)境中獲取Function對象
之后骆姐,解釋器將以環(huán)境callerEnv為實參計算函數(shù)的執(zhí)行結(jié)果。首先捏题,F(xiàn)unction對象的parameters 方法將獲得形參序列玻褪,實參序列則由自身提供iterator方法獲取。然后解釋器將根據(jù)實參的排列順序依次調(diào)用eval并計算求值公荧,將計算結(jié)果與相應(yīng)的形參名成對添加至環(huán)境中带射。ParameterList類新增的eval方法將執(zhí)行實際的處理
實參的值將被添加到新創(chuàng)建的用于執(zhí)行函數(shù)調(diào)用的newEnv環(huán)境,而非callerEnv環(huán)境(表7.1)循狰。newEnv環(huán)境表示的作用域為函數(shù)內(nèi)部窟社。如果函數(shù)使用了局部變量券勺,它們將被添加到該環(huán)境
表7.1 函數(shù)調(diào)用過程中設(shè)計的環(huán)境
environment | mean |
---|---|
newEnv | 調(diào)用函數(shù)時新創(chuàng)建的環(huán)境。用于記錄函數(shù)的參數(shù)及函數(shù)內(nèi)部使用的局部變量 |
newEnv.outer | newEnv的outer字段引用的環(huán)境灿里,能夠表示函數(shù)外層作用域关炼。該環(huán)境通常用于記錄全局變量 |
callerEnv | 函數(shù)調(diào)用語句所處的環(huán)境。用于計算實參 |
最后匣吊,Arguments類的eval方法將在新創(chuàng)建的環(huán)境中執(zhí)行函數(shù)體盗扒。函數(shù)體可以通過調(diào)用Function對象的body方法獲得。函數(shù)體是def語句中由大括號{}括起的部分缀去,body方法將返回與之對應(yīng)的抽象語法樹侣灶。調(diào)用返回的對象的eva1方法即可執(zhí)行該函數(shù)
用于調(diào)用函數(shù)的環(huán)境newEnv將在函數(shù)被調(diào)用時創(chuàng)建,在函數(shù)執(zhí)行結(jié)束后舍棄缕碎。這與函數(shù)的參數(shù)及局部變量的生存周期相符褥影。若解釋器多次遞歸調(diào)用同一個函數(shù),它將在每次調(diào)用時創(chuàng)建新的環(huán)境咏雌。只有這樣才能正確執(zhí)行函數(shù)的遞歸調(diào)用
有時凡怎,用于計算實參的環(huán)境callerEnv與執(zhí)行def語句的是同一個環(huán)境,但也并非總是如此赊抖。callerEnv是用于計算調(diào)用了函數(shù)的表達式的環(huán)境统倒。如果在最外層代碼中調(diào)用函數(shù),callerEnv環(huán)境將同時用于保存全局變量氛雪。然而房匆,如果函數(shù)由其他函數(shù)調(diào)用,callerEnv環(huán)境則將保存調(diào)用該函數(shù)的外層函數(shù)的局部變量报亩。環(huán)境雖然支持嵌套結(jié)構(gòu)浴鸿,但該結(jié)構(gòu)僅反映了函數(shù)定義時的作用域嵌套情況。在函數(shù)調(diào)用其他函數(shù)時弦追,新創(chuàng)建的環(huán)境不會出現(xiàn)在這樣的嵌套結(jié)構(gòu)中
代碼清單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);
}
}
7.4 計算斐波那契數(shù)
至此岳链,Stone語言已支持函數(shù)調(diào)用功能。
代碼清單7.10是解釋器的程序代碼劲件,
代碼清單7.11是解釋器的啟動程序掸哑。
解釋器所處的環(huán)境并不是一個BasicEnv對象,而是一個由啟動程序創(chuàng)建的NestedEnv對象
下面我們以計算斐波那契數(shù)為例測試一下函數(shù)調(diào)用功能零远。代碼清單7.12是由Stone語言寫成的斐波那契數(shù)計算程序苗分。程序執(zhí)行過程中,將首先定義fib函數(shù)遍烦,并計算fib(10)的值俭嘁。最后輸出如下結(jié)果
=> 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 計算斐波那契數(shù)列的Stone語言程序
def fib(n) {
if n < 2 {
n
} else {
fib(n - 1) + fib(n - 2)
}
}
fib(10)
7.5 為閉包提供支持
簡單來講,閉包是一種特殊的函數(shù)服猪,它能被賦值給一個變量供填,作為參數(shù)傳遞至其他函數(shù)拐云。閉包既能在最外層代碼中定義,也能在其他函數(shù)中定義近她。通常叉瘩,閉包沒有名稱
如果Stone語言支持閉包,下面的程序?qū)⒛苷_運行
inc = fun (x) { x + 1 }
inc(3)
這段代碼將創(chuàng)建一個新的函數(shù)粘捎,它的作用是返回一個比接收的參數(shù)大1的值薇缅。該參數(shù)將被賦值給變量inc。賦值給變量的就是一個閉包攒磨。inc并非函數(shù)的名稱泳桦,事實上,這種函數(shù)沒有名稱娩缰。不過灸撰,程序能夠通過inc(3)的形式,以3為參數(shù)調(diào)用該函數(shù)拼坎。讀者可以將其理解為浮毯,程序從名為inc的變量中獲得了一個閉包,并以3為參數(shù)調(diào)用這個閉包
代碼清單7.13是閉包的語法規(guī)則泰鸡。該規(guī)則修改了primary债蓝,向其中添加了閉包的定義
代碼清單7.13 閉包的語法規(guī)則
primary : " fun " param_list block | 原本的primary定義
7.6 實現(xiàn)閉包
代碼清單7.14是支持閉包功能的語法分析器程序。它修改了非終結(jié)符primary的定義盛龄,使語法分析器能夠解析由fun起始的閉包饰迹。代碼清單7.15的Fun類是用于表示閉包的抽象語法樹的節(jié)點類。
Fun類的eval方法通過代碼清單7.16的ClosureEvaluator修改器增加讯嫂。與def語句的eval方法一樣蹦锋,它也會創(chuàng)建一個Function對象。Function對象的構(gòu)造函數(shù)需要接收一個env參數(shù)欧芽,它是定義了該閉包的表達式所處的執(zhí)行環(huán)境。
def語句在創(chuàng)建Function對象后會向環(huán)境添加由該對象與函數(shù)名組成的鍵值對葛圃,而在創(chuàng)建閉包時千扔,eval方法將直接返回該對象。這樣一來库正,Stone語言就能將函數(shù)賦值給某個變量曲楚,或?qū)⑺鳛閰?shù)傳遞給另一個函數(shù),實現(xiàn)閉包的語法功能褥符。這時龙誊,實際賦值給變量或傳遞給函數(shù)的就是新創(chuàng)建的Function 對象。
代碼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());
}
}
從具體實現(xiàn)的角度來看喷楣,整數(shù)值有Java語言的Integer對象表現(xiàn)趟大,字符串由String對象表現(xiàn)鹤树,而新添加的函數(shù)則由Function對象表現(xiàn)。
代碼清單7.17是支持閉包功能的Stone語言解釋器逊朽。代碼清單7.18是相應(yīng)的啟動程序
代碼清單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);
}
}
雖然現(xiàn)在程序已經(jīng)支持函數(shù)何閉包了罕伯,Stone語言和其他很多變量無需聲明即可使用的語言一樣,但如果已經(jīng)存在某個全局變量叽讳,就是無法在創(chuàng)建同名變量追他,比方說下面的例子
x = 1
def foo (i) {
x = i;
x + 1
}
函數(shù)foo無法創(chuàng)建名為x的局部變量。函數(shù)中的x將引用第一行的全局變量x岛蚤。如果調(diào)用foo(3)邑狸,全局變量x的值就會是3,這可就麻煩了涤妒。想用的是局部變量推溃,實際使用的是全局變量,這里似乎存在大量錯誤隱患届腐。如果非要區(qū)分兩者铁坎,只要更改定義,讓全局變量的變量名必須以$開始就行了