目標:爲Stone語言添加類和對象的支持。僅支持單一繼承java
添加的類與對象的處理功能後,下面的Stone語言就能被正確執行了segmentfault
class Position { x = y = 0 def move(nx,ny) { x = nx; y = ny } } p = Position.new p.move(3, 4) p.x = 10 print p.x + p.y
首先定義一個Position類,方法由def語句定義。類中字段經過變量表示,並賦了初始值。上面的例子定義了move方法以及字段x與y。閉包
類名後接.new組成的代碼表示建立一個對象。爲簡化實現,這裏規定Stone語言沒法定義帶參數的構造函數。ide
若是但願繼承其餘的類,只需在類名以後接着寫上extends便可。例如,下面的代碼可以定義一個及程序Position類的子類Pos3D函數
class Pos3D extends Position { z = 0 def set(nx,ny,nz) { x = nx;y = ny;z = nz } } p = Pos3D.new p.move(3,4) print p.x p.set(5,6,7) print p.z
Stone不支持方法重載。在同一個類中沒法定義參數個數或類型不一樣的同名方法post
代碼清單9.1是與類相關的語法規則修改。
這裏只顯示了代碼清單7.1和代碼清單7.13的不一樣之處。
其中,非終結符postfix與program的定義發生了變化,同時語法規則中新增了一些其餘的非終結符。ui
非終結符class_body表示由大括號{}括起的由分號或換行符分割組成的若干個member。非終結符postfix通過修改,支持基於句點.
的方法調用與字段訪問。this
代碼清單9.2是根據代碼清單9.1的語法規則更新的語法分析器程序。代碼清單9.三、代碼清單9.4與代碼清單9.5是其中用到的類定義。lua
postfix與program經過insertChoice方法添加了新的or分支選項。spa
member :def | simple class_body : "{" [ member ] {(";" | EOL) [ member ]} "}" defclass : "class" IDENTIFIER [ "extends" IDENTIFIER ] class_body postfix :"." IDENTIFIER | "(" [ args ] ")" program :[ defclass | def | statement ] (";" | EOL)
package Stone; import static Stone.Parser.rule; import Stone.ast.ClassBody; import Stone.ast.ClassStmnt; import Stone.ast.Dot; public class ClassParser extends ClosureParser { Parser member = rule().or(def, simple); Parser class_body = rule(ClassBody.class).sep("{").option(member) .repeat(rule().sep(";", Token.EOL).option(member)) .sep("}"); Parser defclass = rule(ClassStmnt.class).sep("class").identifier(reserved) .option(rule().sep("extends").identifier(reserved)) .ast(class_body); public ClassParser() { postfix.insertChoice(rule(Dot.class).sep(".").identifier(reserved)); program.insertChoice(defclass); } }
下一步,須要爲新增的抽象語法樹的類添加eval方法。代碼清單9.6是所需的修改器。
首先,修改器爲用於類定義的class語句添加了eval方法。class語句以class一詞起始,它對應的非終結符是defclass,在抽象語法樹中以ClassStmnt(代碼清單9.4)類的形式表現。ClassStmnt類新增的eval方法將建立一個ClassInfo對象,向環境添加由類名與該對象組成的名值對。class語句定義的類的名稱。以後,解釋器經過.new從環境中獲取類的信息。例如
class Position { 省略 }
這條語句可以建立一個ClassInfo對象,該對象保存了Stone語言中Position類的定義信息。對象在建立後,將與類名Position一塊兒添加至環境中。
ClassInfo對象保存了class語句的抽象語法樹。它與保存函數定義的抽象語法樹的Function類有些類似(第七章的代碼清單7.8)。包括本章新增的ClassInfo對象,如今的環境已經可以記錄各類類型的名值對。表9.1總結了至今爲止介紹過的全部的值。
package Stone.ast; import java.util.List; public class ClassBody extends ASTList { public ClassBody(List<ASTree> c) { super(c); } }
package Stone.ast; import java.util.List; public class ClassStmnt extends ASTList { public ClassStmnt(List<ASTree> c) { super(c); } public String name() { return ((ASTLeaf) child(0)).token().getText(); } public String superClass() { if (numChildren() < 3) return null; else return ((ASTLeaf) child(1)).token().getText(); } public ClassBody body() { return (ClassBody) child(numChildren() - 1); } public String toStirng() { String parent = superClass(); if (parent == null) parent = "*"; return "(class " + name() + " " + parent + " " + body() + ")"; } }
package Stone.ast; import java.util.List; public class Dot extends Postfix { public Dot(List<ASTree> c) { super(c); } public String name() { return ((ASTLeaf) child(0)).token().getText(); } public String toString() { return "." + name(); } }
接下來須要添加一個新的eval方法,使程序可以經過句點.進行實現方法調用與字段訪問。相應的抽象語法樹是一個Dot類(代碼清單9.5)。Dot類是Postfix的一個子類。Dot類的eval方法由PrimaryExpr類的evalSubExpr方法直接調用,PrimaryExpr類的eval方法會經過evalsubExpr方法來獲取調用結果
修改器向Dot類添加的eval方法須要兩個參數。其中一個是環境,另外一個是句點左側的計算結果。
若是句點右側是new,句點表達式將用於建立一個對象。其中句點左側是須要建立的類,它的計算結果是一個ClassInfo對象。eval方法將根據該ClassInfo對象提供的信息建立對象並返回。
若是句點的右側不是new,該句點表達式將用於方法調用或字段訪問。句點左側是須要訪問的對象,它的計算結果是一個StoneObject對象。若是這是一個字段,解釋器將調用的read方法獲取字段的值並返回。
代碼清單9.6中的AssignEx修改器實現了字段賦值功能。該修改器繼承於BinaryEx,同時,BinaryEx自己也是一個修改器(第6章代碼清單6.3)。AssignEx修改器將修改BinaryExpr類。AssignEx修改器覆蓋了由BinaryEx修改器添加的computeAssign方法,使字段的賦值功能得以實現
通過AssignEx修改器修改的computeAssign方法將在賦值運算的左側爲一個字段時調用stoneobject的write方法,執行賦值操做。若是不是,它將經過super調用原先的computeAssign方法
在爲字段賦值時必須注意的是,賦值運算的左側並不必定老是單純的字段名稱。例如,字段能夠經過下面的方式表現
table.get().next.x = 3
解釋器將首先調用變量table所指對象的get方法,再將返回對象中next字段指向的對象包含的字段x賦值爲3。其中,僅有.x將計算運算符的左值並賦值,table.get().next仍以一般方式計算最右側的值。computeAssign方法經過內部的evalsubExpr方法執行這一計算。賦值給變量t的返回值同時也是上面例子中table.get().next的右值計算結果。
package chap9; import java.util.List; import Stone.StoneException; import Stone.ast.*; import chap6.BasicEvaluator.ASTreeEx; import chap6.BasicEvaluator; import chap6.Environment; import chap7.FuncEvaluator; import chap7.FuncEvaluator.EnvEx; import chap7.FuncEvaluator.PrimaryEx; import chap7.NestedEnv; import chap9.StoneObject.AccessException; import javassist.gluonj.*; @Require(FuncEvaluator.class) @Reviser public class ClassEvaluator { @Reviser public static class ClassStmntEx extends ClassStmnt { public ClassStmntEx(List<ASTree> c) { super(c); } public Object eval(Environment env) { ClassInfo ci = new ClassInfo(this, env); ((EnvEx) env).put(name(), ci); return name(); } } @Reviser public static class ClassBodyEx extends ClassBody { public ClassBodyEx(List<ASTree> c) { super(c); } public Object eval(Environment env) { for (ASTree t : this) ((ASTreeEx) t).eval(env); return null; } @Reviser public static class DotEx extends Dot { public DotEx(List<ASTree> c) { super(c); } public Object eval(Environment env,Object value) { String member = name(); if (value instanceof ClassInfo) { if ("new".equals(member)) { ClassInfo ci = (ClassInfo)value; NestedEnv e = new NestedEnv(ci.environment); StoneObject so = new StoneObject(e); e.putNew("this", so); initObject(ci,e); return so; } } else if (value instanceof StoneObject) { try { return ((StoneObject)value).read(member); } catch (AccessException e) {} } throw new StoneException("bad member access: " + member,this); } protected void initObject(ClassInfo ci,Environment env) { if (ci.superClass() != null) initObject(ci.superClass(),env); ((ClassBodyEx)ci.body()).eval(env); } } @Reviser public static class AssignEx extends BasicEvaluator.BinaryEx { public AssignEx(List<ASTree> c) { super(c); } protected Object computeAssign(Environment env,Object rvalue) { ASTree le = left(); if (le instanceof PrimaryExpr) { PrimaryEx p = (PrimaryEx) le; if (p.hasPostfix(0) && p.postfix(0) instanceof Dot) { Object t = ((PrimaryEx)le).evalSubExpr(env, 1); if (t instanceof StoneObject) return setField((StoneObject)t,(Dot)p.postfix(0),rvalue); } } return super.computeAssign(env, rvalue); } protected Object setField(StoneObject obj,Dot expr,Object rvalue) { String name = expr.name(); try { obj.write(name,rvalue); return rvalue; } catch (AccessException e) { throw new StoneException("bad member access " + location() + ": " + name); } } } } }
package chap9; import Stone.StoneException; import Stone.ast.ClassBody; import Stone.ast.ClassStmnt; import chap6.Environment; public class ClassInfo { protected ClassStmnt definition; protected Environment environment; protected ClassInfo superClass; public ClassInfo(ClassStmnt cs, Environment env) { definition = cs; environment = env; Object obj = env.get(cs.superClass()); if (obj == null) superClass = null; else if (obj instanceof ClassInfo) superClass = (ClassInfo) obj; else throw new StoneException("unkonw super class: " + cs.superClass(), cs); } public String name() { return definition.name(); } public ClassInfo superClass() { return superClass; } public ClassBody body() { return definition.body(); } public Environment environment() { return environment; } public String toString() { return "<class " + name() + ">"; } }
package chap9; import chap6.Environment; import chap7.FuncEvaluator.EnvEx; public class StoneObject { public static class AccessException extends Exception { } protected Environment env; public StoneObject(Environment e) { env = e; } public String toString() { return "<object:" + hashCode() + ">"; } public Object read(String member) throws AccessException { return getEnv(member).get(member); } public void write(String member, Object value) throws AccessException { ((EnvEx) getEnv(member)).putNew(member, value); } protected Environment getEnv(String member) throws AccessException { Environment e = ((EnvEx) env).where(member); if (e != null && e == env) return e; else throw new AccessException(); } }
從實現的角度來看,如何設計StoneObject對象的內部結構纔是最重要的。也就是說,如何經過Java語言的對象來表現Stone語言的對象。其實,實現的方式多種多樣,咱們將利用環境可以保存字段值的特性來表示對象。
StoneObject對象主要應保存Stone語言中對象包含的字段值,能夠說它是字段名稱與字段值的對應關係表。從這個角度來看,環境做爲變量名稱與變量值的對應關係表,與對象的做用很是相似。
若是將對象視做一種環境,就很容易實現對該對象自身(也就是Java語言中this指代的對象)的方法調用與字段訪問。方法調用與字段訪問能夠經過this.x實現,其中,指代自身的this.可以省略。下面是一個例子。
class Positon { x = y = 0 def move(nx,ny) { x = ny;y = ny } }
move方法內的x乍看是一個局部變量,其實它是this.x的省略形式,表示x字段。這類x的實現比較麻煩。若是將move方法的定義視做函數定義,x與y都屬於自由變量(自由變量指的是函數參數及局部變量之外的函數)。參數nx與ny則是約束變量。
若是方法內部存在x這樣的自由變量,該變量就必須指向(綁定)在方法外部定義的字段。這與閉包的機制相似。例如,下面的函數position將返回一個閉包。
def position () { x = y = 0 fun (nx,ny) { x = ny;y = ny } }
此時,position函數的局部變量x將賦值給返回的閉包中的變量x(與x綁定)。對比二者便可發現,閉包與方法都會將內部的變量名與外部的變量(字段)綁定。
在經過.new建立新的StoneObject對象時,解釋器將首先建立新的環境。StoneObject對象將保存該環境,並向該環境添加由名稱this與自身組成的鍵值對。
以後,解釋器將藉助該環境執行類定義中由大括號{}括起的主體部分。與執行函數體時同樣,只需調用表示主體的調用表示主體的抽象語法樹的eval方法便可完成這一操做。這對應於Java等語言中的構造函數調用。主體部分執行後,類定義中出現的字段名與方法名以及相應的值都將被環境記錄。
在執行過程當中,若是須要爲首次出現的變量賦值,解釋器將像環境添加有該變量的名稱與值組成的名值對。
至此,Stone語言已經能夠支持類與對象的使用。與以前同樣,最後將要介紹的是解釋器主體程序與相應的啓動程序。參見代碼清單9.9與代碼清單9.10
package chap9; import Stone.ClassParser; import Stone.ParseException; import chap6.BasicInterpreter; import chap7.NestedEnv; import chap8.Natives; public class ClassInterpreter extends BasicInterpreter { public static void main(String[] args) throws ParseException { run(new ClassParser(), new Natives().environment(new NestedEnv())); } }
package chap9; import chap7.ClosureEvaluator; import chap8.NativeEvaluator; import javassist.gluonj.util.Loader; public class ClassRunner { public static void main(String[] args) throws Throwable { Loader.run(ClassInterpreter.class, args, ClassEvaluator.class,NativeEvaluator.class,ClosureEvaluator.class); } }