C-Ruby源码分析-2( RNode 结构浅析)

http://hi.baidu.com/xosjfkixdgbqvyr/item/16367dfaf8e56b0984d278f0

最近结合语法树的生成及解释执行的过程分析了一下C-Ruby中RNode结构体的设计。

RNode是用于记录C-Ruby解析源文件过程中所生成的语法树结点的重要数据结构。

在C-Ruby解析源文件的过程中,会在读入源文件内容的同时建立相应的语法结点,

即RNode变量,并以链表的形式建立起一个语法树,维护语法结点的依赖关系。

在C-Ruby要对源文件进行解释执行的时候,就会从这个语法树链表的首结点开始,逐个

遍历各个语法结点并解释执行。

RNode 的数据结构的定义如下:    // 位于 node.h

typedef struct RNode {
    unsigned long flags;    //标志位,用于标识结点类型,具体结点类型也定义
                            //在node.h中,从C-Ruby的源码中来看,语法结点所对应
                            //的源码行号也是存放在的flags中的

    char *nd_file; // 结点所对应的Ruby源文件名
    // 在RNode结构体中,定义了三个Union成员,比较有趣。
    // 之所以使用Union是希望减少语法树所占用的内存开销 
    //因为C-Ruby是一个解释器,其解释的对象就是RNode构成的
    //语法树,为了降低语法树的内存开销,松本在设计RNode的时候采用
    //了三个Union变量。针对不同类型的语法结点,u1,u2,u3会使用不同
    //的Union成员,而且,即便使用了同一个成员,也会有不同的含义,
    //在node.h中专门定义了一组宏来简化对RNode的操作:
    // 如nd_head, nd_next, nd_cond,还有我们下面会提到的
    //nd_mid, nd_args 等等
    // 在u1, u2, u3的定义中,VALUE成员提供了对u1, u2, u3执行写操作的
    //统一接口,而其他的Union成员定义则提供了可能被不同语法结点
    //使用的多种读接口,
    //比如函数调用结点会使用u2的id 成员,
    //而对于另外一种结点,则可能会使用u2的argc成员。
    //但是向u2中写入内容,则都是通过VALUE成员写入的.
    //我理解这样设计的目的是既希望通过Union来节约内存,还希望能够简化
    //语法结点的创建工作。

    union {
        struct RNode *node;
        ID id; 
        VALUE value; 
        VALUE (*cfunc)(ANYARGS);
        ID *tbl;
    } u1;
    union {
        struct RNode *node;
        ID id;
        long argc;
        VALUE value;
    } u2;
    union {
        struct RNode *node;
        ID id;
        long state;
        struct global_entry *entry;
        long cnt;
        VALUE value;
    } u3;
} NODE;


对RNode的基本结构有了一定了解之后,不妨以一个简单的例子来分析RNode,我采用的

例子文件test.rb仅包含一行内容:

    print "hello";

对于test.rb,C-Ruby会先建立起一个由RNode结点构成的语法树,然后再对其进行解释执

行。

语法树的生成流程大致可以描述如下:
    
   词法模块对源码流输入print "hello";进行分解,返回语法模块可以识别的终结符,
   
语法模块则根据parse.y中编写的语法规则执行相应的移入,归约动作,在执行归约动

作的时候调用相应语法规则的语义动作,从而完成语法树生成的工作。

从源码输入规约到顶层语法描述的完整过程序列有些繁杂,中间夹杂一些中转规则

和状态,为了简化描述,我对整个语法归约过程进行了一定的简化:

源码输入:   print "hello";
==>   operation "hello"; ( operation: print )
注:括号里表示为了得到本条语法序列,执行语法归约动作所使用的相应语法规则)
==>    operation strings; (strings: "hello" )
==>     operation primary;
 (primary: strings)
==>      operation arg; 
(arg: primary)
==>       operation arg_value;
( arg_value: arg)
==>        operation args;
 ( args: arg_value )
==>         operation call_args;
 ( call_args: args )
==>          operation open_args; 
( open_args: call_args )
==>           operation command_args;
 ( command_args: open_args )
==>            command;
 (command: operation command_args )
==>             command_call;
 ( command_call: command )
==>              expr; 
( expr : command_call )
==>               stmt; 
( stmt : expr )
==>                stmts;
 ( stmts: stmt )
==>                 stmts opt_terms
 ( opt_terms: ';' )
==>                  compstmt 
( compstmt: stmts opt_terms )
==>                   program
 (program: compstmt)

从源码print "hello"; 开始逐步归约,每执行一个相应动作,就会创建一个相应

的RNode结点(也有一些归约动作并不会创建RNode结点,而只是传递已经创

建好的RNode结点指针
),及至归约到program语法符号之时,整个语法树就已经建

立起来了。语法树的根结点存放在一个全局的RNode指针变量, ruby_eval_tree

定义在parse.y中。

test.rb中仅包含一个函数调用,所以在语法树生成过程中会调用new_fcall()(定义于

parse.y中)创建一个NODE_FCALL类型的结点。

static NODE*
new_fcall(m,a)
    ID m;       // 函数ID,具体到test.rb即是print函数的相应ID
    NODE *a;    // 函数参数列表对应的语法结点,具体到test.rb,对应于
                // "hello"
{
    return NEW_FCALL(m,a);
}

可以看到new_fcall()又调用了NEW_FCALL来完成实际的生成函数调用结点的动作,

NEW_FCALL则是一个定义在node.h的宏。

#define NEW_NODE(t,a0,a1,a2) rb_node_newnode((t),(VALUE)(a0),(VALUE)(a1),(VALUE)(a2))
#define NEW_FCALL(m,a) NEW_NODE(NODE_FCALL,0,m,a)
NODE*

rb_node_newnode(type, a0, a1, a2)   // 定义在parse.y
    enum node_type type;
    VALUE a0, a1, a2;
{
    NODE *n = (NODE*)rb_newobj();

    n->flags |= T_NODE;
    nd_set_type(n, type);
    nd_set_line(n, ruby_sourceline);
    n->nd_file = ruby_sourcefile;

    n->u1.value = a0;
    n->u2.value = a1;
    n->u3.value = a2;

    return n;
}


从NEW_FCALL中可以看出,对于函数调用结点,RNodeu1成员是不会使用的,u2成员则

会用来存放标识函数的ID,u3则用来存放函数调用的相关参数列表的信息。在解释

执行的过程中,对于NODE_FCALL类型的结点也会通过访问u2u3中相应的Union成员来获得

函数调用的相关信息。

最后再来看一下语法树的解释执行过程(后面简称为eval过程):

eval过程始于ruby_run()函数(定义于eval.c),基本的调用图如下所示:

    


eval_node()方法以ruby_eval_tree作为起始结点调用rb_eval()函数,rb_eval()内部

又会根据结点类型去执行相应的解释动作,并且通过RNode中定义的指针成员获得语法树

中后续需要处理的RNode对象的指针,如此往复,直至完成对整个语法树的解释执行动作。

具体到test.rb这个例子,对于print "hello"所生成的NODE_FCALL的语法结点,eval过程

会执行如下的动作:

    case NODE_FCALL:    // 定义于eval.c中的rb_eval()函数
    {
        // node 是一个定义在rb_eval()局部的RNode *
        int argc; VALUE *argv; /* used in SETUP_ARGS */
        TMP_PROTECT;

        BEGIN_CALLARGS;
        // nd_args是一个定义在node.h中的宏
        //#define nd_args u3.node
        SETUP_ARGS(node->nd_args);      // 配置参数, 会向argc, argv
                                        //中写入数据
        END_CALLARGS;
        
        ruby_current_node = node;
        SET_CURRENT_SOURCE();
        // nd_mid是一个定义在node.h中的宏
        //#define nd_mid   u2.id

        // 通过nd_mid获得语法结点中存放的函数ID,
        // 通过nd_args获得语法结点中存放的参数信息
        // 如此一来,就具备解释执行NODE_FCALL结点所需的足够信息了
        result = rb_call(CLASS_OF(self),self,node->nd_mid,argc,argv,1,self);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值