| name | rebrgen-new-lang |
| description | 新しい言語ジェネレーター (ebm2<lang>) の追加手順とフック実装の方法。新言語対応を始めるときに参照する。 |
セットアップ
python script/ebmcodegen.py <lang_name>
python script/build.py
mkdir -p save
python script/unictest.py --target-runner ebm2<lang_name> --print-stdout > save/unictest.txt 2>&1
grep "unimplemented" save/unictest.txt
手動で生成を確認するとき
必ず --debug-unimplemented を付ける。 これがないと未実装フックがサイレントに空文字を返すため、出力を見ても何が欠けているか分からない。
./tool/ebm2<lang>.exe -i save/some.ebm --debug-unimplemented 2>/dev/null
実装のゴールは2段階:
--debug-unimplemented 付きで {{Unimplemented ...}} が一切出なくなり、生成コードが構文的に正しい
- unictest が通る (
python script/unictest.py --target-runner ebm2<lang>)
フックを実装するごとにこのフラグ付きで確認し、段階的にゴール1→2を目指す。
フックの実装方針
🚨 新規 visitor フックファイルは必ず ebmtemplate.py で生成する 🚨
Editor/Write ツールで手書きしないこと。手書きするとビルドは通るがフックがサイレントに呼ばれない。
python script/ebmtemplate.py <hook>_class <lang>
理由: main.cpp は __has_include("visitor/<hook>_class.hpp") でフックを取り込むが、cmake は __has_include の依存を追跡しない。新規ファイル追加時に main.cpp を touch しないと再コンパイルされず、新規フックは include されず、何のエラーも出ずにスキップされる。ebmtemplate.py はこの touch まで自動でやる。
実用上のスケール感: hooklist は EBM kind × stage suffix × DSL 有無の cartesian で 4000+ 種類になるが、実際に各 backend が override してるのは 10-30 程度。default_codegen_visitor が 99 hook で大半を済ませているので、新言語追加時に書くのも一桁〜十数個になることが多い。
./tool/ebmcodegen --mode hooklist
python script/ebmtemplate.py list <lang>
python script/ebmtemplate.py list all
python script/ebmtemplate_ip.py list all
python script/ebmtemplate.py list default
新言語の hook を考えるときは、近い既存言語 (例: 静的型なら rust / go、動的型なら python) の list <lang> を眺めて差分を埋める発想が早い。
緊急で手書きするしかない場合のフォールバック
ebmtemplate.py が動かない・テンプレートに無いフックを増設したい等で手書きが避けられない場合のみ:
touch src/ebmcg/ebm2<lang>/main.cpp
python script/build.py
entry_before_class.hpp でまとめる場合
src/ebmcg/ebm2<lang>/visitor/entry_before_class.hpp の DEFINE_VISITOR(entry_before) 内で config.*_visitor に設定する。これは ebmcodegen.py のスケルトン生成時点で既に存在するファイルなので、追加 touch なしで編集 OK。量が少ない間はここに集約してよい。
DEFINE_VISITOR(entry_before) {
config.variable_define_keyword = "let mut";
config.endof_statement = ";";
config.read_data_visitor = [](Context_Statement_READ_DATA& ctx) -> expected<Result> {
using namespace CODEGEN_NAMESPACE;
if (auto lw = ctx.read_data.lowered_statement()) {
return ctx.visit(lw->io_statement.id);
}
return pass;
};
return pass;
}
ファイルが大きくなったら個別フックに切り出す:
python script/ebmtemplate.py Statement_FOO_class <lang>
DEFINE_VISITOR(Statement_FOO) { ... } に実装する。
フック実装のコツ
ctx オブジェクトのメンバーは IDE のオートコンプリートで確認できる
- 型エラーが出たら必ず
extended_binary_module.hpp で定義を確認する(推測しない)
- フィールドか関数かを混同しやすい(例:
unit はフィールド、unit() は誤り)
MAYBE(result, expr) は expected の伝搬マクロ(Rust の ? 相当)
ファイル構成
src/ebmcg/ebm2<lang>/
├── main.cpp # 自動生成(編集禁止)
├── codegen.hpp # 自動生成(編集禁止)
├── visitor/
│ ├── entry_before_class.hpp # 言語設定 + シンプルなフック
│ ├── Flags.hpp # フラグ定義
│ └── *_class.hpp # 専用ファイルに分割したフック
└── config.json # 言語設定(任意)
default_codegen_visitor の活用
src/ebmcodegen/default_codegen_visitor/ にデフォルト実装がある。
まずデフォルト動作を確認してから、差分だけ上書きする方針が効率的。