Ruby 2.x 源代碼學習:語法分析 & 中間代碼生成 之 方法解析

前言

本文分析 Ruby 如何解析頂層方法定義,假定讀者具有《編譯原理》基礎知識,瞭解 yacc,bison(自動語法分析器)工具的基本使用ruby

BNF 語法

parser.y 包含了 Ruby 語言全部的語法,下面是和函數相關的片斷(parser.y 文件有 1 W 多行)
咱們將注意力集中在 函數定義的語法上,先忽略掉 YACC 語法動做(下同)函數

// parse.y

primary : k_def fname f_arglist bodystmt k_end
  • k_def,關鍵字 def工具

  • fname,函數名稱指針

  • f_arglist,函數參數列表code

  • bodystmt,函數內部語句塊orm

  • k_end,關鍵字 endip

f_arglist

從名字能夠看出 f_arglist 表示函數參數列表,下面是 f_arglist 語法定義內存

// parse.y

f_arglist    : '(' f_args rparen
        |  f_args term
        ;

Ruby 函數定義能夠省略掉左右括號作用域

f_args

Ruby 支持各類 "奇葩" 的函數參數傳遞方式,f_args 的語法定義考慮了各類組合狀況,先從最簡單的開始:get

// parse.y

f_args : f_arg opt_args_tail

f_arg : f_arg_item | f_arg ',' f_arg_item

f_arg_item : f_arg_asgn | tLPAREN  f_margs rparen

f_arg_asgn : f_norm_arg

f_norm_arg : f_bad_arg | tIDENTIFIER

每一個函數參數使用 逗號 分割,若是不考慮 (x) 這種類型的參數,每一個參數都是一個 tIDENTIFIER(標識符)

做用域

語法分析的上下文

語法分析是個極其複雜,繁瑣的過程,Ruby 使用 parser_params 結構體做爲語法分析上下文(context)的抽象,它保存了語法分析(包括詞法)過程當中的狀態變量,下面僅列出和做用域相關的字段

// parse.y or parse.c

struct parser_params {
    ...
    struct local_vars *lvtbl;
    ...
}

struct local_vars {
    struct vtable *args;
    struct vtable *vars;
    struct vtable *used;

    struct local_vars *prev;
    stack_type cmdargs;
}

struct vtable {
    ID *tbl;
    int pos;
    int capa;
    struct vtable *prev;
};

local_vars 結構體保存了參數和本地變量,並經過 prev 指針指向上一級 local_vars(棧)

做用域鏈(棧)

如今能夠來看看函數定義的 YACC 語法動做

// parse.y

k_def fname
    {
        local_push(0);
        $<id>$ = current_arg;
        current_arg = 0;
    }
    {
        $<num>$ = in_def;
        in_def = 1;
    }
f_arglist
bodystmt
k_end

local_push 會新建一個 做用域,並鏈接到做用域棧中

// parse.y or parse.c

static void local_push_gen(struct parser_params*,int);
#define local_push(top) local_push_gen(parser,(top))

#define lvtbl            (parser->lvtbl)

static void
local_push_gen(struct parser_params *parser, int inherit_dvars)
{
    struct local_vars *local;

    // 分配內存
    local = ALLOC(struct local_vars);
    // 將 local 連接到做用域鏈
    local->prev = lvtbl;
    // 分配內存
    local->args = vtable_alloc(0);
    local->vars = vtable_alloc(inherit_dvars ? DVARS_INHERIT : DVARS_TOPSCOPE);
    local->used = !(inherit_dvars &&
            (ifndef_ripper(compile_for_eval || e_option_supplied(parser))+0)) &&
    RTEST(ruby_verbose) ? vtable_alloc(0) : 0;
# if WARN_PAST_SCOPE
    local->past = 0;
# endif
    local->cmdargs = cmdarg_stack;
    CMDARG_SET(0);
    // 更新當前做用域,注意:lvtbl 是一個宏定義!!!
    lvtbl = local;
}

參數

咱們已經知道在定義一個函數的時候,語法分析程序會新建一個 local_vars 並添加到做用於鏈中,那函數參數是如何添加到做用域中的呢?咱們來看一下 函數參數的一條語法規則:

// parse.y

f_arg_asgn    : f_norm_arg
{
    ID id = get_id($1);
    arg_var(id);
    current_arg = id;
    $$ = $1;
}
;

答案就在 arg_var 方法裏頭:

// parse.y or parse.c

static void arg_var_gen(struct parser_params*, ID);
#define arg_var(id) arg_var_gen(parser, (id))

static void arg_var_gen(struct parser_params *parser, ID id)
{
    vtable_add(lvtbl->args, id);
}

static void vtable_add(struct vtable *tbl, ID id)
{
    if (!POINTER_P(tbl)) {
        rb_bug("vtable_add: vtable is not allocated (%p)", (void *)tbl);
    }
    if (VTBL_DEBUG) printf("vtable_add: %p, %"PRIsVALUE"\n", (void *)tbl, rb_id2str(id));

    // tbl 空間不夠,擴容~
    if (tbl->pos == tbl->capa) {
        tbl->capa = tbl->capa * 2;
        REALLOC_N(tbl->tbl, ID, tbl->capa);
    }
    將 id 放入 tbl
    tbl->tbl[tbl->pos++] = id;
}

局部變量

上文介紹了函數參數如何加入到做用域中,那局部變量呢?局部變量是否是也有相似 arg_var 方法調用呢?咱們先想一下一般狀況下何時會建立一個局部變量:對於 Ruby 這類動態腳本語言,沒有像C語言中的變量聲明語法,因此在變量賦值(首次使用)的時候就會自動建立。咱們來驗證一下這個猜測,仍是先來看一段語法規則:

// parse.y

lhs : user_variable
{
    $$ = assignable($1, 0);
    /*%%%*/
    if (!$$) $$ = NEW_BEGIN(0);
}

assignable 函數比較複雜,下面僅列出和局部變量定義相關的代碼段:

// parse.y or parse.c

static NODE* assignable_gen(struct parser_params *parser, ID id, NODE *val) {
    switch (id_type(id)) {
        case ID_LOCAL:
            if (dyna_in_block()) {
                if (dvar_curr(id)) {
                    ...
                } else if (dvar_defined(id)) {
                    ...
                } else if (local_id(id)) {
                    ...
                } else {
                    dyna_var(id)
                }
            } else {
                if (!local_id(id)) {
                    local_var(id);
                }
            }
    }
}

根據 id 是否在塊做用域或局部做用域內作相應的處理

生成 AST

生成 YARV 虛擬機指令

相關文章
相關標籤/搜索