24日目: aliasとback_refとnth_ref
ずっと制御構文をやっていたので、たまには気分を変えてaliasの書き換えをやっていきたいと思います1。
aliasのノードを書き換える
書き換え前の世界ではグローバル変数のaliasを表すNODE_VALIASとそれ以外のaliasを表すNODE_ALIASという2つのノードがありました。
# @ NODE_ALIAS (id: 2, line: 1, location: (1,0)-(1,11))* # +- nd_1st: # | @ NODE_SYM (id: 0, line: 1, location: (1,6)-(1,8)) # | +- string: :a # +- nd_2nd: # | @ NODE_SYM (id: 1, line: 1, location: (1,9)-(1,11)) # | +- string: :b alias :a :b # @ NODE_VALIAS (id: 3, line: 2, location: (2,0)-(2,11))* # +- nd_alias: :$a # +- nd_orig: :$b alias $a $b # @ NODE_VALIAS (id: 7, line: 3, location: (3,0)-(3,11))* # +- nd_alias: :$a # +- nd_orig: :$& alias $a $&
書き換え後はグローバル変数の場合はAliasGlobalVariableNode、それ以外の場合はAliasMethodNodeというノードを使うことになります。
ノードの構造が変わるわけではないので、書き換え自体はさほど難しくありません。
グローバル変数のaliasを表すノードのフィールドについては注意が必要です。
NODE_VALIASではnd_alias, nd_origともに型がIDでしたが、AliasGlobalVariableNodeではどちらもノードへのポインターになります。
そのためAliasGlobalVariableNodeの生成時にIDをGlobalVariableReadNodeでラップするようにします。
| keyword_alias tGVAR tGVAR
{
- $$ = NEW_VALIAS($2, $3, &@$, &@1);
+ $$ = NEW_RB_ALIAS_GLOBAL_VARIABLE(NEW_RB_GLOBAL_VARIABLE_READ($2, &@2), NEW_RB_GLOBAL_VARIABLE_READ($3, &@3), &@$, &@1);
/*% ripper: var_alias!($:2, $:3) %*/
}
ここで同時にback ref(バックレファレンス)2に関するノードも変更しておきましょう。
back refを表すNODE_BACK_REFでは&や'といった文字をlong nd_nthというフィールドで保持しています3。
書き換え後のBackReferenceReadNodeに合わせてIDに変換してからノードを生成するようにしておきます。
| keyword_alias tGVAR tBACK_REF
{
- char buf[2];
- buf[0] = '$';
- buf[1] = (char)RNODE_BACK_REF($3)->nd_nth;
- $$ = NEW_VALIAS($2, rb_intern2(buf, 2), &@$, &@1);
+ $$ = NEW_RB_ALIAS_GLOBAL_VARIABLE(NEW_RB_GLOBAL_VARIABLE_READ($2, &@2), $3, &@$, &@1);
/*% ripper: var_alias!($:2, $:3) %*/
}
+static rb_back_reference_read_node_t *
+rb_new_node_back_reference_read_new(struct parser_params *p, long nd_nth, const YYLTYPE *loc)
+{
+ char buf[2];
+ buf[0] = '$';
+ buf[1] = (char)nd_nth;
+
+ rb_back_reference_read_node_t *n = RB_NEW_NODE_NEWNODE((enum rb_node_type)RB_BACK_REFERENCE_READ_NODE, rb_back_reference_read_node_t, loc);
+ n->name = rb_intern2(buf, 2);
+
+ return n;
+}
ここまでで一度ノードを確認しておきましょう。
alias :a :b alias $a $b alias $a $& # @ StatementsNode (location: (1,0)-(3,11)) # +-- body: (length: 3) # +-- @ AliasMethodNode (location: (1,0)-(1,11))* # | +-- new_name: # | | @ SymbolNode (location: (1,6)-(1,8)) # | | +-- SymbolFlags: nil # | | +-- opening_loc: (0,0)-(0,0) = "" # | | +-- value_loc: (0,0)-(0,0) = "" # | | +-- closing_loc: (0,0)-(0,0) = "" # | | +-- unescaped: "a" # | +-- old_name: # | | @ SymbolNode (location: (1,9)-(1,11)) # | | +-- SymbolFlags: nil # | | +-- opening_loc: (0,0)-(0,0) = "" # | | +-- value_loc: (0,0)-(0,0) = "" # | | +-- closing_loc: (0,0)-(0,0) = "" # | | +-- unescaped: "b" # | +-- keyword_loc: (1,0)-(1,5) = "" # +-- @ AliasGlobalVariableNode (location: (2,0)-(2,11))* # | +-- new_name: # | | @ GlobalVariableReadNode (location: (2,6)-(2,8)) # | | +-- name: :$a # | +-- old_name: # | | @ GlobalVariableReadNode (location: (2,9)-(2,11)) # | | +-- name: :$b # | +-- keyword_loc: (2,0)-(2,5) = "" # +-- @ AliasGlobalVariableNode (location: (3,0)-(3,11))* # +-- new_name: # | @ GlobalVariableReadNode (location: (3,6)-(3,8)) # | +-- name: :$a # +-- old_name: # | @ BackReferenceReadNode (location: (3,9)-(3,11)) # | +-- name: :$& # +-- keyword_loc: (3,0)-(3,5) = ""
aliasとバックレファレンスをコンパイルする
aliasに関するバイトコードはaliasに続く値をそれぞれスタックにつんで、特殊なメソッドを呼び出すだけです。
呼び出すメソッドがグローバル変数の場合はset_variable_alias、それ以外の場合はset_method_aliasという違いはありますが、シンプルな構造だと思います。
グローバル変数の場合はスタックにはシンボルを積むことになるので、GlobalVariableReadNodeとBackReferenceReadNodeからnameを取り出すための補助的な関数を用意します。
そのほかは書き換え前と変わりません。
case RB_ALIAS_METHOD_NODE: { ADD_INSN1(ret, node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); ADD_INSN1(ret, node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CBASE)); CHECK(COMPILE(ret, "alias arg1", RB_NODE_ALIAS_METHOD(node)->new_name)); CHECK(COMPILE(ret, "alias arg2", RB_NODE_ALIAS_METHOD(node)->old_name)); ADD_SEND(ret, node, id_core_set_method_alias, INT2FIX(3)); if (popped) { ADD_INSN(ret, node, pop); } break; } case RB_ALIAS_GLOBAL_VARIABLE_NODE: { ADD_INSN1(ret, node, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); ADD_INSN1(ret, node, putobject, ID2SYM(get_valias_id(RB_NODE_ALIAS_GLOBAL_VARIABLE(node)->new_name))); ADD_INSN1(ret, node, putobject, ID2SYM(get_valias_id(RB_NODE_ALIAS_GLOBAL_VARIABLE(node)->old_name))); ADD_SEND(ret, node, id_core_set_variable_alias, INT2FIX(2)); if (popped) { ADD_INSN(ret, node, pop); } break; } static VALUE get_valias_id(const NODE *node) { switch (nd_type(node)) { case RB_GLOBAL_VARIABLE_READ_NODE: return RB_NODE_GLOBAL_VARIABLE_READ(node)->name; case RB_BACK_REFERENCE_READ_NODE: return RB_NODE_BACK_REFERENCE_READ(node)->name; default: rb_bug("unexpected node: %s", ruby_node_name(nd_type(node))); } }
BackReferenceReadNodeそのものもコンパイルもできるようにしておきましょう。
もともとはlongで持っていた情報がIDになっているので、一度文字列に戻してからcharを取得する必要があります。
// Before case NODE_BACK_REF:{ if (!popped) { ADD_INSN2(ret, node, getspecial, INT2FIX(1) /* '~' */, INT2FIX(0x01 | (RNODE_BACK_REF(node)->nd_nth << 1))); } break; } // After case RB_BACK_REFERENCE_READ_NODE: { if (!popped) { const char *str = rb_id2name(RB_NODE_BACK_REFERENCE_READ(node)->name); ADD_INSN2(ret, node, getspecial, INT2FIX(1) /* '~' */, INT2FIX(0x01 | ((long)str[1] << 1))); } break; }
getspecial命令の実装であるところのvm_getspecial関数をみるとわかるのですが、1bit分シフトをして下位1bitの値でnth ref4とback refの区別がつくようにしています(下位1bitがたっている場合がback ref)。
一度コンパイル結果を確認しておきましょう。
# 0000 putspecialobject 1 ( 1)[Li] # 0002 putspecialobject 2 # 0004 putobject :a # 0006 putobject :b # 0008 opt_send_without_block <calldata!mid:core#set_method_alias, argc:3, ARGS_SIMPLE> # 0010 pop alias :a :b # 0011 putspecialobject 1 ( 2)[Li] # 0013 putobject :$a # 0015 putobject :$b # 0017 opt_send_without_block <calldata!mid:core#set_variable_alias, argc:2, ARGS_SIMPLE> # 0019 pop alias $a $b # 0020 putspecialobject 1 ( 3)[Li] # 0022 putobject :$a # 0024 putobject :$& # 0026 opt_send_without_block <calldata!mid:core#set_variable_alias, argc:2, ARGS_SIMPLE> # 0028 leave alias $a $& # 0029 putself ( 4)[Li] # 0030 getspecial 1, 77 # 0033 opt_send_without_block <calldata!mid:p, argc:1, FCALL|ARGS_SIMPLE> # 0035 leave p $&
nth refに対応する
back refの対応をしたので、ついでにnth refについてもやっておきましょう。
nth ref、つまり$1などですが、これにはNODE_NTH_REFというノードが使われています。
書き換え後のノードはNumberedReferenceReadNodeで、どちらも1の部分をフィールドに保持しています。
$1 # @ NODE_NTH_REF (id: 0, line: 1, location: (1,0)-(1,2))* # +- nd_nth: $1 # @ NumberedReferenceReadNode (location: (1,0)-(1,2)) # +-- number: 1
BackReferenceReadNodeと異なり、NumberedReferenceReadNodeのnumberはuint32_tと整数型なのでIDと文字列間の変換が不要です。
BackReferenceReadNodeのときはINT2FIX(0x01 | ((long)str[1] << 1))と0x01との論理和をとっていましたが、NumberedReferenceReadNodeではINT2FIX(RB_NODE_NUMBERED_REFERENCE_READ(node)->number << 1)と1ビットシフトするだけになっているのがわかります。
// Before case NODE_NTH_REF:{ if (!popped) { if (!RNODE_NTH_REF(node)->nd_nth) { ADD_INSN(ret, node, putnil); break; } ADD_INSN2(ret, node, getspecial, INT2FIX(1) /* '~' */, INT2FIX(RNODE_NTH_REF(node)->nd_nth << 1)); } break; } // After case RB_NUMBERED_REFERENCE_READ_NODE: { if (!popped) { if (!RB_NODE_NUMBERED_REFERENCE_READ(node)->number) { ADD_INSN(ret, node, putnil); break; } ADD_INSN2(ret, node, getspecial, INT2FIX(1) /* '~' */, INT2FIX(RB_NODE_NUMBERED_REFERENCE_READ(node)->number << 1)); } break; }
コンパイル結果もとくに問題なさそうです。
# 0000 getspecial 1, 2 ( 1)[Li] # 0003 leave $1
いい時間になったので今日はこのへんで。
まとめ
今日の成果です。
- aliasの対応をした
- back ref(
$&など)の対応をした - nth ref(
&1など)の対応をした