ひとり勉強会

ひとり楽しく勉強会

NODE_WHILE など

続いて、ループ構文です。

while 条件式
  本体
end

のような形のwhile式をコンパイルします。untilは、whileと終了条件の真偽が逆になったバージョンで、NODE_OPT_Nは、rubyコマンドを-nオプションで起動したときに自動で作られるループ構造

while gets
  スクリプト本体
end

に対応するノードです。どれも同じことをやってるだけなので、コンパイル処理は1カ所にまとまっています。
あと、後置のwhile/until(while/until修飾子)

print while gets

などは、構文解析の段階で普通のwhile文と同じ構文木に直されているので、YARVでは考えません。

本体のコンパイルにはおなじみの COMPILE_ マクロ、条件判定のコンパイルは if のところでやった compile_branch_condition が使われています。あとはいつもどおりのADD_INSNとADD_LABELの列で、特に新しい部分はないのでコンパイラのコードは省略しちゃいます。生成されるマシン語列は、以下の順番になります。

jmp next_label
redo_label:
  本体
next_label:
  条件判定
end_label:
  putnil
break_label:
  if(poped) pop

Rubyのループ制御文である redo, next, break で飛べるジャンプ先に、それぞれラベルがついています。普通にループを抜けると全体の評価値はnilですが、breakで抜けるとbreakに指定された値が評価値となります。このため、end_labelとbreak_labelが完全に同じ位置にはなりません。

おなじみのコード生成のあと、最後はこんなコードで終わっています。

case NODE_WHILE: {
  ... (略) ...

  ADD_CATCH_ENTRY(CATCH_TYPE_BREAK, redo_label, break_label,
                  0, break_label);
  ADD_CATCH_ENTRY(CATCH_TYPE_NEXT | 0x10000, redo_label,
                  break_label, 0,
                  iseq->compile_data->start_label);
  ADD_CATCH_ENTRY(CATCH_TYPE_REDO, redo_label, break_label, 0,
                  iseq->compile_data->redo_label);

  iseq->compile_data->start_label = prev_start_label;
  iseq->compile_data->end_label = prev_end_label;
  iseq->compile_data->redo_label = prev_redo_label;
  iseq->compile_data->loopval_popped = prev_loopval_popped;
  iseq->compile_data->ensure_node_stack = enlp;
  break;
}

さて、これはなんでしょう?

...というところで時間が無くなったので次回に続きます。