†さて、シンボルテーブル作成が予想以上に長かったですがともかくできたので次のステップです。*/2.まで終わって、次は3.のcompiler_modです。co;}compiler_enter_scopeは長いので代わりにcompiler構造体の上に書いてあるコメントを掲載。compiler_exit_scope().*/名前の通り、スコープへのインとアウトを行っています。その際、前回作ったシンボルテーブルの情報を取得しています。いうわけでcompiler_enter_scopeはさらっと流して次、VISIT_SEQ_IN_SCOPEです。\}というわけでcompiler_visit_stmtに進みます。†compiler_visit_stmtはシンボルテーブル作成と同じでswitch文です。Num(10)Exprです。break;interactiveでnestlevelも1なのでExprをたどった後にPRINT_EXPRが追加されています。シェルだと何か書いた後その値が表示されるあれと予想されますね。ADDOPを先に見ておきます。1;}basicblockはASTをたどった結果作られる命令列を格納する構造体です。先ほど詳細に踏み込まなかったcompiler_enter_scope(から呼ばれているcompiler_use_new_block)で作られています。instrは紛らわしいですが、instructionの略で1命令を表す構造体です。compiler_next_instrはその名の通り、次にinstrをセットする場所を返す関数です。必要なら命令列を入れる領域を広げるようになっています。†さて、compiler_visit_exprに進みます。こちらもcompiler_visit_stmt同様switch文なので先に進んで、ListCompなのでcompiler_listcomp経由でcompiler_comprehensionが呼び出されます。0;}新しいスコープを作っているのは予想通りです。その後は少しややこしいです。ADDOP(BUILD_LIST)compiler_comprehension_generator呼び出しADDOP(RETURN_VALUE)この後にcompiler_exit_scopeがあるのでスコープが戻ります。続いて、compiler_make_closure呼び出しVISIT(outermost_iter)ADDOP(GET_ITER)ADDOP(CALL_FUNCTION)を行っています。これらは元々のスコープに対して行われます。†まずはcompiler_comprehension_generatorを見てみましょう。1;}急にいろいろ出てきました。ひとつずつ確認していきましょう。anchorというblockが作成されています。gen->target);という記述から、ジャンプの飛び先のラベルとして使われているようです。target、今回の場合はName(n)、compiler_visit_exprを経由してcompiler_visit_nameopが呼び出されます。†compiler_visit_nameopはスコープによってどのように参照するかのopcodeが変わるようです。今回の場合、スコープはLOCALで、FunctionBlockで、Storeなので、STORE_FASTが使われます。最終的に以下のコードが実行されます。varnames);ADDOP_Oを追っかけましょう。\}OとTYPEがひっくり返るのが地味にめんどくさいです。arg;}nという名前から0番目というインデックスに置き換えられ、opcodeではそのインデックスを使って変数にアクセスをするようになっています。†さて、compiler_comprehension_generatorに戻りましょう。1;まず、ifsの処理をします。今回の場合、以下のASTです。10」のように書けるのに対応する分、複雑になっていますがそこをさくっと省略すると非常に素直です。1;}左辺を処理して、右辺を処理して、比較を行うということが想像できます。BinOpはもうそんなに難しくないので説明は省くとして、CompareのASTから以下のバイトコードが生成されることになります。>0(n)というのはバイトコードの引数としては0が設定される、それが指している変数はn、という意味です。ifsの処理が終わって、最終的にcompiler_comprehensionの初めに作ったスコープに対応するバイトコードは以下のようになるはずです。RETURN_VALUERELとかABSとか書いてあるのはジャンプです。:startなどはblockの先頭のラベルのつもり。見ていて大体わかりますがいまいちわからないところもあるので動作については実行編で確認することにします。†さてだいぶ長くなってきましたがまだ続きます。compiler_comprehension_generatorから戻ってきた後はassembleが呼び出されていますがそれは後から見ることにしてcompiler_comprehensionの続きは以下の通りです。1);まずはcompiler_make_closure、ブロックの外の変数の取り込みとかを処理していますが今回はないので以下が実行されます。}}次にoutermost_iterを処理、対応するASTは以下です。Num(10)プログラミングの華、関数呼び出しです。対応するのはcompiler_visit_call。e->v.Call.keywords);}compiler_call_helperはキーワード変数など様々な呼び出しの処理が行われていますが今回は単純に10という即値なので中身は省略。最終的に、compiler_comprehensionが呼ばれて出来上がったバイトコードは以下になります。最後に、飛ばしたassembleを見ます。assembleは今までに2回出てきました。b;}blockを次がある間再帰、次にinstrを見てジャンプが行われていたら飛び先を引数に再帰、この時に未訪問のことがあるのかな?ともかく今回の場合はblockが逆順にpostorderに設定されるようです。assembleに戻って次に呼んでいるのはassemble_jump_offsets、visitの時点だとblockのポインタとして表現されていたジャンプ先を実際にジャンプする量に変換しています。見ればわかるのでコードは省略。続いて個々のinstrに対してassemble_emitを呼び出しています。これも地道に数値を埋め込んでいるだけなので省略。最後にmakecode。簡単な最適化を行ったうえで最終的なPyCodeを作成しています。PyCodeを作る際にスタックの最大深さを計算しているようですがかなり長くなってしまったのでもう終わりにします。