NODE_ITER, NODE_FOR
ついでだからブロックについてもここで読んじゃいます。NODE_FORはRubyのこの形の式
for 変数s in 式 本体 end
を表す構文木ノードで、意味的には
式.each { |変数s| 本体 }
と同じです。NODE_ITERは、メソッド名がeachとは限りませんが↑の形のブロックつき式全般を表す構文木ノードです。コンパイル処理の流れはこう。
case NODE_ITER: case NODE_FOR:{ VALUE prevblock = iseq->compile_data->current_block; LABEL *retry_label = NEW_LABEL(nd_line(node)); LABEL *retry_end_l = NEW_LABEL(nd_line(node)); ADD_LABEL(ret, retry_label); if (nd_type(node) == NODE_FOR) { ... 略 ... } else { ... 略 ... } ADD_LABEL(ret, retry_end_l); iseq->compile_data->current_block = prevblock; ADD_CATCH_ENTRY(CATCH_TYPE_RETRY, retry_label, retry_end_l, 0, retry_label); break; }
retry(ブロックつき処理の繰り返しを最初からやりなおすRubyの構文)をサポートするために、retryのときのジャンプ先ラベルを用意しています。retryも例外として実装されます。CATCH_ENTRYの使い方はwhile文でのbreak等々のコンパイルの時と同じですね。
ブロック内breakからの例外の処理は、ここではなく、ブロックを使う箇所(Rubyのyield)で処理されるようです。breakはともかくredoやnextは、ここ(ループの外側)では扱いきれないですし。
NODE_FORのコンパイルは以下の通り。
COMPILE(ret, "iter caller (for)", node->nd_iter); iseq->compile_data->current_block = NEW_CHILD_ISEQVAL(node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK); mid = idEach; ADD_SEND_R(ret, nd_line(node), ID2SYM(idEach), INT2FIX(0), iseq->compile_data->current_block, INT2FIX(0)); if (poped) { ADD_INSN(ret, nd_line(node), pop); }
まず、forで回る対象オブジェクトの式をコンパイルします。実行するとスタックの一番上にそのオブジェクトが置かれた状態になります。次に、ループ本体のブロックを、別のiseqオブジェクトとしてコンパイルしておきます。最後に、オブジェクトに:eachメッセージを送る命令を生成します。ここで、今コンパイルしたブロックをブロック引数として渡してやるようになっています。
NODE_ITERの場合は簡単。
iseq->compile_data->current_block =
NEW_CHILD_ISEQVAL(node, make_name_for_block(iseq),
ISEQ_TYPE_BLOCK);
COMPILE_(ret, "iter caller", node->nd_iter, poped);
まずブロックを別ブロックとしてコンパイルします。そのブロックをcurrent_blockに記憶しておきます。nd_iterは普通のメソッド呼び出し式なはずで、そこをコンパイルしてやります。あとの方で出てくるメソッド呼び出し式(NODE_CALL)のコンパイル時には、current_blockを参照して、ブロックがあればそれも渡すような命令が出力されるようになっています。