私の意見では、通常のレクサーやパーサー ジェネレーターは、通常、アセンブラーを作成する際にはあまり役に立ちません。それらは主に比較的複雑な文法を扱うのに役立ちますが、アセンブラーの場合、「文法」は通常非常に些細なものであるため、そのようなジェネレーターは助けになるよりも邪魔になります。
典型的なアセンブラはほとんどがテーブル駆動型です。まず、定義済みのオペコードと、それが生成する命令の特性 (指定する必要があるレジスタの数とタイプ) のテーブルを作成することから始めます。通常、アドレス指定モードなどをエンコードする方法を定義する(シェーダーの場合はもっと小さい、おそらくはるかに小さい) テーブルがあります。
ほとんどのアセンブラは、そのテーブルを参照して動作します。つまり、入力から何かを読み取り、テーブルで検索しようとします。存在しない場合は、不明なオペコードであることを示すエラー メッセージが表示されます。見つかった場合は、そのオペコードに関連付けられているオペランドの数に関する情報をテーブルから取得します。その数のオペランドを読み取ろうとします。できない場合は、命令に問題があるというエラーが発生します。可能であれば、命令をエンコードし、最初からやり直します。
もちろん、それ以上に処理しなければならない場所がいくつかあります。ラベルのようなものを定義する場所/ときに、そのラベルの名前と位置をシンボルテーブルに記録する必要があります。そのアドレスへの分岐のようなものに遭遇すると、ターゲットを調べてそのアドレスを適切にエンコードする必要があります。
マクロをサポートすることを決定した場合にのみ、その基本モデルから大きく逸脱します。それらをどれだけ精巧に扱うかによっては、マクロ展開機能にパーサージェネレーターなどを使用する価値があるかもしれません。繰り返しになりますが、シェーダーはほとんどが非常に小さいため、そのようなアセンブラーにとってマクロの優先度はあまり高くありません。
編集:それを読み直して、おそらく1つの点を明確化/修正する必要があります。パーサー ジェネレーターの使用は、文法が複雑なステートメントを許可する場合のように、文法自体が複雑になる場合にはそれほど多くありません。本当に些細な文法を考えてみましょう:
expression := expression '+' value
| expression '-' value
| value
これは加算と減算のみを許可しますが、任意に複雑なステートメント (または、加算または減算される値の少なくとも任意の長い文字列) を定義します。もちろん、かなり些細な実際の言語であっても、通常は乗算、除算、関数呼び出しなどがあります。
これは、各命令の形式が固定されている一般的なアセンブリ言語とは大きく異なります。たとえば、加算または減算演算には、正確に 2 つのソース オペランドと 1 つのデスティネーション オペランドがあります。