NODE_BREAK
break文のコンパイルでやるべきことのおさらいです。
- ジャンプ脱出コードを生成するか、例外脱出コードを生成するか
- ジャンプ脱出の場合、ensureの扱い
case NODE_BREAK:{ unsigned long level = 0; if (iseq->compile_data->redo_label != 0) { /* while/until */ add_ensure_iseq(ret, iseq); COMPILE_(ret, "break val (while/until)", node->nd_stts, iseq->compile_data->loopval_popped); ADD_INSNL(ret, nd_line(node), jump, iseq->compile_data->end_label); }
まずジャンプ脱出の場合。ブロック等の別命令列の中ではなく、whileループの中なら、redo_labelのようなループ脱出先ラベルが記憶されているので、それで判断してます。end_labelの有無で判断する方が好みなような(どうでもいい)。
で、次に、add_ensure_iseqでensureの命令列をここに展開します。思いっきり簡略化すると、この関数は次のようにひたすらensureの命令列をコンパイルしてつなげていく関数です。begin〜ensureは何重にもネストしている可能性があるので、ループで全てのensure節をたどる必要があります。
static void add_ensure_iseq(LINK_ANCHOR *ret, yarv_iseq_t *iseq) #超簡略化 { struct iseq_compile_data_ensure_node_stack *enlp = iseq->compile_data->ensure_node_stack; while (enlp) { COMPILE_POPED(ensure_part, "ensure part", enlp->ensure_node); enlp = enlp->prev; } }
続けて、break に渡す式をコンパイル。最後に、end_labelへのジャンプ命令を付け加えます。
else if (iseq->type == ISEQ_TYPE_BLOCK) { break_by_insn: /* escape from block */ COMPILE(ret, "break val (block)", node->nd_stts); ADD_INSN1(ret, nd_line(node), throw, INT2FIX(level | 0x02) /* TAG_BREAK */ ); }
お次は、例外で脱出するコードの場合。ブロックの中のbreakなら例外で脱出です。まずbreakの値式をコンパイルして、throw命令を最後に一個出力します。level変数については後述。
else if (iseq->type == ISEQ_TYPE_EVAL) { COMPILE_ERROR(("Can't escape from eval with break")); }
eval("break") はコンパイルエラーになるそうです。YARVだとこれは難しいのかな。
最後に、それ以外の場合。命令列の親をたどっていって、ループかブロックが見つかるまで探します。以下概要。見つかったら、goto break_by_insnで、さっきの例外で脱出する場合のコードへとコンパイルします。
else { yarv_iseq_t *ip = iseq->parent_iseq; while (ip) { level++; if (ip->compile_data->redo_label != 0) { ... (略) ... goto break_by_insn; } else if (ip->type == ISEQ_TYPE_BLOCK) { ... (略) ... goto break_by_insn; } ip = ip->parent_iseq; } COMPILE_ERROR(("Illegal break")); }
(... (略) ...)のところではlevelを命令に埋め込むためのビット演算がいろいろありましたが省略。level変数は、「何個親の命令列まで戻って例外を飛ばすか」をカウントしています。levelを数える必要がある理由はちょっとまだ把握できてません。ユーザーの書いたbegin〜rescueがbreakの例外などを捕まえたりしないように、かな?