两周自制脚本语言-第9天 设计面向对象语言

第9天 设计面向对象语言

目标:为Stone语言添加类和对象的支持。仅支持单一继承

9.1 设计用于操作类与对象的语法

添加的类与对象的处理功能后,下面的Stone语言就能被正确执行了

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语言无法定义带参数的构造函数。

如果希望继承其他的类,只需在类名之后接着写上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不支持方法重载。在同一个类中无法定义参数个数或类型不同的同名方法

9.2 实现类所需的语法规则

代码清单9.1是与类相关的语法规则修改。
这里只显示了代码清单7.1和代码清单7.13的不同之处。
其中,非终结符postfix与program的定义发生了变化,同时语法规则中新增了一些其他的非终结符。

非终结符class_body表示由大括号{}括起的由分号或换行符分割组成的若干个member。非终结符postfix经过修改,支持基于句点.的方法调用与字段访问。

代码清单9.2是根据代码清单9.1的语法规则更新的语法分析器程序。代码清单9.3、代码清单9.4与代码清单9.5是其中用到的类定义。

postfix与program通过insertChoice方法添加了新的or分支选项。

代码清单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();
    }
}

接下来需要添加一个新的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等语言中的构造函数调用。主体部分执行后,类定义中出现的字段名与方法名以及相应的值都将被环境记录。

在执行过程中,如果需要为首次出现的变量赋值,解释器将像环境添加有该变量的名称与值组成的名值对。

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

原文地址:https://www.cnblogs.com/ZCWang/p/12227125.html

时间: 2024-09-30 14:51:08

两周自制脚本语言-第9天 设计面向对象语言的相关文章

两周自制脚本语言-第5天 设计语法分析器

第5天 设计语法分析器 5.1 Stone语言的语法 代码清单 5.1 Stone 语言的语法定义 primary : "(" expr ")" | NUMBER | IDENTIFIER | STRING factor : "-" primary | primary expr : factor { OP factor } block : "{" [ statement ] { (";" | EOL) [

两周自制脚本语言-第7天 添加函数功能

第7天 添加函数功能 基本的函数定义与调用执行.引入闭包使Stone语言可以将变量赋值为函数,或将函数作为参数传递给其他函数 有些函数将有返回值的归为函数,没有返回值的归为子程序 7.1 扩充语法规则 函数定义语句的语法规则 此书将函数定义语句称为def语句.def语句仅能用于最外层代码,用户无法在代码块中定义函数 Stone语言将最后执行语句(表达式)的计算结果将作为函数的返回值返回 代码清单 7.1 与函数相关的语法规则 param : IDENTIFIER params : param {

两周自制脚本语言-第6天 通过解释器执行程序

第6天 通过解释器执行程序 解释器从抽象语法树的根节点开始遍历该树直至叶节点,并计算各节点的内容 6.1 eval方法与环境对象 eval方法:eval是evaluate(求值)的缩写.eval方法将计算与该节点为根的子树对应的语句.表达式及子表达式,并返回执行结果. eval方法递归调用子节点的eval方法 不同类型的节点的类,对eval方法有着不同的定义 eval方法的简化版本 public Object eval(Environment env){ Object left = left()

两周自制脚本语言-第10天 无法割舍的数组

第10天 无法割舍的数组 目标:为Stone语言添加简单的数组功能,下标(index)只能使用整数值. 10.1扩展语法分析器 代码清单10.1 与数组相关的语法规则 elements : expr { "," expr } primary : ( "[" [ elements ] "]" | "(" expr ")" | NUMBER | IDENTIFIER | STRING ) { postfix }

两周自制脚本语言-第11天 优化变量读写性能

第11天 优化变量读写性能 以变量值的读写为例,向读者介绍基于这种理念的语言处理器性能优化方式. 11.1 通过简单数组来实现环境 假如函数包含局部变量x与y,程序可以事先将x设为数组的第0个元素,将y设为第1个元素,以此类推.这样一来,语言处理器引用变量时就无需计算哈希值.也就是说,这是一个通过编号,而非名称来查找变量值的环境 为了实现这种设计,语言处理器需要在函数定义完成后遍历对应的抽象语法树节点,获取该节点使用的所有函数参数与局部变量.遍历之后程序将得到函数中用到的参数与局部变量的数量,于

如何在Java平台上使用脚本语言做Java开发

如何在Java平台上使用脚本语言做Java开发     最近开始流行区分Java平台和Java语言,但很多Java开发者还是不能确定如何在 Java应用程序开发中结合脚本.本篇文章,Gregor Roth给出了在Java平台上使用脚本的方法.通过这篇文章,你可以了解怎样在你的Java应用程序中使用脚本,是否你要通过使用Groovy和 Jython把不同的Java应用程序模块粘合在一起,或者写一个你自己的基于JRuby的应用程序,适用于Java平台. 作为一个Java开发者,你可能已经注意到了,J

java脚本语言学习心得

第一篇技术博客,一定要认真! 第一篇技术博客,一定要认真! 第一篇技术博客,一定要认真! 好了,进入正题: 一 什么是脚本语言? 程序的运行方式有两种:编译运行和解释运行 1.1 前者的典型代表是java, 从文件角度看分为三步: write[编写]: a.java文件(拿个记事本就能写,扩展名是.java), compile[编译]: 编译(cmd命令是java a.java,ide集成了编译器运行之前自动编译)之后产生了a.class文件(是一堆二进制码,人看不懂,是给虚拟机看的) 运行[r

关于JS脚本语言的基础语法

JS脚本语言的基础语法:输出语法  alert("警告!");  confirm("确定吗?");   prompt("请输入密码");为弱类型语言: 开始时要嵌入JS代码:<script type="text/javascript"></script>: 关于写程序是需注意的基本语法:1.所有的字符全都是英文半角的:2.大部分情况下每条语句结束后要加分号:3.每一块代码结束后加换行:4.程序前呼后应:

shell、cmd、dos和脚本语言区别和联系

问题一:DOS与windows中cmd区别   在windows系统中,"开始-运行-cmd"可以打开"cmd.exe",进行命令行操作. 操作系统可以分成核心(kernel)和Shell(外壳)两部分,其中,Shell是操作系统与外部的主要接口,位于操作系统的外层,为用户提供与操作系统核心沟通的途径.在windows系统中见到的桌面即explorer.exe(资源管理器)是图形shell,而cmd就是命令行shell.这算是cmd与dos的最大区别,一个只是接口.