コンパイラーの可視化
コンパイラーは文字の範囲を分類し、トークンへ階層を与え、木をたどって実行可能なプログラムを書きます。三課はソースから AST、AST からバイトコード、スタック VM での実行です。後の二課は 1 + 2 * 3 を共有し、最初は複数桁を示す 12 + 3 * 4 を使います。
最後の VM は CPU やハイパーバイザーではなく、言語の仮想機械です。操作コードを取り、スタックを変えて繰り返します。ソースの + は木の形になり、ADD は二回 pop して一回 push します。演算子をオペランドより先に出力すると、実行でアンダーフローが起きます。
エラーの担当も異なります。12 + @ 4 は完全なトークン列にならず、12 + * 4 は字句解析後に primary() で失敗します。先頭 ADD は読めるバイトコードでも、実行時にオペランドが足りません。
レッスンの比較
| レッスン | 問い | 変化 | 失敗境界 |
|---|---|---|---|
| 字句解析と構文解析 | 優先順位はどこから来るか | 文字範囲を平らなトークン列にし、文法の呼び出しで階層化 | @ は字句、オペランド位置の * は構文の失敗 |
| AST → コード | 1 + 2 * 3 が先に MUL を書く理由 | 後順走査、Fold で定数を先に畳む | 名前は親の畳み込みを止めても、別部分木は止めない |
| 仮想機械 | 命令順が必要な理由 | 命令でスタックを変え、熱いループをコンパイル | 演算子先行でアンダーフロー、冷たいループでは JIT なし |
解析器の出力とコード生成の入力は同じ演算子階層です。コード生成右側の IR は書かれるプログラム、VM 右側の Stack は実行で使うデータです。この二つを分けます。
推奨する学習順序
字句解析と構文解析で 12 + 3 * 4 を使い、1 と 2 が同じ字句に残る様子を見ます。expr が + を待つ間に term が * を処理します。括弧の例では解析器を変えず、Add が内側、Mul が根になります。字句と構文のエラーを一つずつ試し、トークン化と妥当性を分けます。
AST → コードは Naive で 1 + 2 * 3 を処理します。+ が待つ間に MUL が出ます。同じ木で Fold にすると五命令が LOAD_CONST 7 になります。x + 2 * 3 では乗算は畳めても、名前が未確定なので加算は畳めません。
仮想機械は同じ式をインタープリターで実行します。三回 push し、MUL で 2 * 3 → 6、ADD で 1 + 6 を処理します。これが後順の理由です。直後に ADD first を試すと Underflow が発生します。
スタックの規則を理解してから、任意の JIT タブへ進みます。カウントダウンは二巡を解釈して熱度に達し、コンパイル後は命令の逐次取得表示が止まります。冷たいループは閾値に届かず、コンパイルされません。
注目する点
- ソース順、トークン順、木の階層、命令順を分けます。
+が先に書かれても、*が入れ子になり、MULを先に実行します。 - 有効なトークンでも位置が無効な場合があります。
12 + * 4のSTARは認識できてもprimary()を開始できません。 - 頂上の値だけでなく高さを見ます。既定の最大 3 は、最初の演算前に待つ三つの葉です。
- 空スタック自体は失敗ではありません。初期化は空でよく、足りないオペランドを要求したときに失敗します。
- JIT 後も残る計算は実行します。ネイティブコードの一拍は逐次 fetch の省略で、O(1) の仕事や計算の飛び越しではありません。
コンパイラーの可視化一覧
式やエンジンを変え、Python のコードとトレースを見ます。字句の段階では構文解析器のコードを光らせず、文字からトークンへの証拠をキャンバスに置いています。