本文分析 Ruby 如何解析頂層方法定義,假定讀者具有《編譯原理》基礎知識,瞭解 yacc,bison(自動語法分析器)工具的基本使用ruby
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 語法定義內存
// parse.y f_arglist : '(' f_args rparen | f_args term ;
Ruby 函數定義能夠省略掉左右括號作用域
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 是否在塊做用域或局部做用域內作相應的處理