兩週自制腳本語言-第9天 設計面嚮對象語言

第9天 設計面嚮對象語言

目標:爲Stone語言添加類和對象的支持。僅支持單一繼承java

9.1 設計用於操做類與對象的語法

添加的類與對象的處理功能後,下面的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.2 實現類所需的語法規則

代碼清單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

代碼清單9.1 與類相關的語法規則

member  :def | simple
class_body  : "{" [ member ] {(";" | EOL) [ member ]} "}"
defclass  : "class" IDENTIFIER [ "extends" IDENTIFIER ] class_body
postfix  :"." IDENTIFIER | "(" [ args ] ")"
program  :[ defclass | def | statement ] (";" | EOL)

代碼清單9.2 支持類的語法分析器ClassPraser.java

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);
    }
}

9.3 實現eval方法

下一步,須要爲新增的抽象語法樹的類添加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總結了至今爲止介紹過的全部的值。

代碼清單9.3 ClassBody.java

package Stone.ast;
import java.util.List;

public class ClassBody extends ASTList {

    public ClassBody(List<ASTree> c) {
        super(c);
    }
}

代碼清單9.4 ClassStmnt.java

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() + ")";
    }
}

Dot.java

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();
    }
}

file

接下來須要添加一個新的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的右值計算結果。

代碼清單9.6 ClassEvaluator.java

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);
                }
            }
        }
    }
}

代碼清單9.7 ClassInfo.java

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() + ">";
    }
}

代碼清單9.8 StoneObject.java

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();
    }
}

9.4 經過閉包表示對象

從實現的角度來看,如何設計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等語言中的構造函數調用。主體部分執行後,類定義中出現的字段名與方法名以及相應的值都將被環境記錄。

在執行過程當中,若是須要爲首次出現的變量賦值,解釋器將像環境添加有該變量的名稱與值組成的名值對。

file

9.5 運行包含類的程序

至此,Stone語言已經能夠支持類與對象的使用。與以前同樣,最後將要介紹的是解釋器主體程序與相應的啓動程序。參見代碼清單9.9與代碼清單9.10

代碼清單9.9 ClassInterperter.java

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()));
    }
}

代碼清單9.10 ClassRunner.java

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);
    }
}
相關文章
相關標籤/搜索