ワンクリックで
rebrgen-dev-tips
開発上重要なtips。例えばどの機能がどこに定義されていてどんな構造になっているかの概要です。マクロやContext_XXXなどについてわかります。開発中に「どこに何があるんだっけ or let me know...」となったらここを参照すること。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
開発上重要なtips。例えばどの機能がどこに定義されていてどんな構造になっているかの概要です。マクロやContext_XXXなどについてわかります。開発中に「どこに何があるんだっけ or let me know...」となったらここを参照すること。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
rebrgen の設計判断を ADR として記録するか検討する。設計方針の議論、選択肢の比較、既存判断の変更、判断理由を残す場面で使用する。
rebrgen のビルド、初期セットアップ、EBM再生成、ebmcodegen復旧、新言語ジェネレーター追加を行うときに使用する。
rebrgen のコンパイルエラー、テスト失敗、想定外の挙動を調査するときに使用する。型や構造の定義から原因を追跡するデバッグ手順を提供する。
rebrgen 内部の機能、マクロ、Context型、実装場所や構造を調べるときに使用する。開発中に関連定義やAPIの所在を確認するための知識を提供する。
rebrgen の複数言語ジェネレーターにまたがるリファクタリング、共通部分の整理、EBM構造変更の影響調査を行うときに使用する。
rebrgen のコードジェネレーターへ新機能を追加するときに使用する。共通処理と言語固有処理の配置、EBM変更の判断、実装時の注意点を提供する。
SOC 職業分類に基づく
| name | rebrgen-dev-tips |
| description | 開発上重要なtips。例えばどの機能がどこに定義されていてどんな構造になっているかの概要です。マクロやContext_XXXなどについてわかります。開発中に「どこに何があるんだっけ or let me know...」となったらここを参照すること。 |
MAYBE/MAYBE_VOIDはエラーハンドリング用マクロで実体はsrc/ebmgen/common.hppにある。 おおよそ内容としてはこういう感じで実際はこれを識別子衝突の回避やexprがポインタ型の場合等の場合の考慮など拡張したやつが定義されている。MAYBE_VOIDはexpected用。
#define MAYBE_VOID(ident,expr)
auto ident##__ = (expr); \
if (!_result) return unexpected(_result.error());
#define MAYBE(ident, expr) \
MAYBE_VOID(ident, expr) \
auto ident = *ident##__;
DEFINE_VISITORはコードジェネレーターのビジターフレームワーク用のマクロでsrc/ebmcodegen/stub/visitor.hppにある。実体はテンプレートクラスの特殊化でおおよそ以下のような感じ。
#define DEFINE_VISITOR(name) \
template<> \
struct Visitor<Tag_##name> { \
expected<Result> visit(Context_##name& ctx); \
} \
expected<Result> Visitor<Tag_##name>::visit(Context_##name& ctx)
Context_XXXはコードジェネレーターのビジターフレームワーク用の構造体でsrc/ebmcodegen/stub/visitor/context.hppに基底クラスとなるContextBaseクラステンプレートが定義されており各ebm2/codegen.hppに実体が定義されている。例えば以下のような感じ。具体的にはsrc/ebmcodegen/class_based.cppのコード生成ロジックで生成されているが生成ロジックを見たところであんまり理解の助けにはならないので素直にcodegen.hppの定義を見たほうがいいと思う。
struct Context_Expression_HOGEHOGE : ebmcodegen::util::ContextBase<Context_Expression_HOGEHOGE> {
BaseVisitor& visitor;
const ebm::ExpressionRef& other_expr;
};
src/ebmcodegen/stub/util.hppに定義されている便利関数群。例えば以下のようなものがある。
各ebm2<lang>/には2つの自動生成ファイルがある。どちらもebmcodegenが生成するので手動編集禁止。
namespace ebm2 内に以下が定義される:
namespace ebm2<lang> {
struct MergedVisitor; // 前方宣言のみ(実体はmain.cpp)
struct Result { ... };
struct Flags : ebmcodegen::Flags { ... }; // コマンドラインフラグ
struct Output : ebmcodegen::Output { ... };
// ★ 全 Context_XXX の定義(数百個)
// 例: Context_Statement_STRUCT_DECL, Context_Expression_BINARY_OP, ...
// 各Contextにはvisitor, item_id, そしてノード固有のフィールドがメンバとして入る
// ★ 各Contextに対応する VisitorTag_XXX(空struct、テンプレートのタグ)
// ★ BaseVisitor 構造体
struct BaseVisitor {
static constexpr const char* program_name = "ebm2<lang>";
Flags& flags;
Output& output;
WriterManager<CodeWriter> wm;
MappingTable module_;
#include "visitor/Visitor_before.hpp" // 言語固有の追加フィールド
#include "visitor/Visitor.hpp" // config(std::functionフック群)
#include "visitor/Visitor_after.hpp"
};
}
ctx.config() は BaseVisitor& を返す。
つまり Visitor.hpp のフック群も Visitor_before.hpp のフィールドも全て BaseVisitor のメンバ。
namespace ebm2<lang> {
// ★ 各 *_class.hpp をincludeしてVisitor特殊化を定義
// entry_before_class.hpp → Visitor<UserHook<VisitorTag_entry_before>>
// Statement_STRUCT_DECL_class.hpp → Visitor<UserHook<VisitorTag_Statement_STRUCT_DECL>>
// ...(全EBMノード種別 × {before, main, after} の組み合わせ)
// ★ VisitorsImpl: 全Visitor特殊化をまとめたディスパッチテーブル
// ★ MergedVisitor : BaseVisitor
// visit(Context_XXX&) → VisitorsImpl経由で対応するVisitor::visit()を呼ぶ
// つまり ctx.visit(ref) → MergedVisitor::visit() → Visitor<Tag>::visit(ctx)
}
| ファイル | include先 | 役割 |
|---|---|---|
Visitor_before.hpp | codegen.hpp内 BaseVisitor構造体のメンバ | 言語固有フィールド追加(enum, bool等) |
Visitor.hpp | codegen.hpp内 BaseVisitor構造体のメンバ | configフック定義(通常はdefault使用) |
Flags.hpp | codegen.hpp内 Flags構造体のメンバ | コマンドラインフラグ定義 |
entry_before_class.hpp | main.cpp | configフック設定(ここが言語実装の主要ファイル) |
Statement_XXX_class.hpp | main.cpp | 個別ノードのvisitor実装 |
includes.hpp | codegen.hpp冒頭(namespace外) | 追加#include |
全てのvisitor includeポイントで3段階フォールバック:
visitor/<Name>.hpp — 言語固有(最優先)visitor/dsl/<Name>_dsl.hpp — DSL生成版ebmcodegen/default_codegen_visitor/visitor/<Name>.hpp — デフォルトファイルが存在しなければ次の段階へ。つまり言語固有ファイルを置けばデフォルトを上書きできる。
Visitor_before.hppに書いた定義はBaseVisitorのメンバになる。
つまり enum class Foo を書くと BaseVisitor::Foo になり、ラムダからは直接 Foo と書けない。
ラムダで使うには:
// entry_before_class.hpp 内のDEFINE_VISITOR内
using Foo = std::remove_reference_t<decltype(ctx.config())>::Foo;
// これ以降、ラムダ内で Foo が使える
デフォルトvisitorにおけるフック呼び出しの一般パターン:
*_custom フック(CALL_OR_PASS)if (ctx.config().foo_custom) {
CALL_OR_PASS(result, ctx.config().foo_custom(ctx));
}
// custom が pass を返した → ここに来る(デフォルト処理に進む)
// custom が Result を返した → この関数全体からそのResultが返る
// custom がエラーを返した → エラーが伝播する
passを返す = 「自分は処理しない、デフォルトに任せる」。
Resultを返す = デフォルト処理を完全にスキップしてその結果を使う。
条件分岐で一部だけカスタムしたい場合は、条件に合わない場合にpassを返す。
*_wrapper フック(部分カスタマイズ)if (ctx.config().foo_start_wrapper) {
MAYBE(start, ctx.config().foo_start_wrapper(ctx));
w.write(start.to_writer());
} else {
// デフォルトの開始部分
}
// ... デフォルトの中身 ...
if (ctx.config().foo_end_wrapper) {
return ctx.config().foo_end_wrapper(ctx, w);
}
wrapperは呼ばれたら必ず結果を使う(passの概念がない)。 構造の一部(開始/終了)だけ変えたい場合に使う。
*_visitor フック(旧式、非推奨予定)if (ctx.config().foo_visitor) {
return ctx.config().foo_visitor(ctx); // 完全に乗っ取る
}
注意: *_visitorは*_customの下位互換であり、今後*_customに統一予定。
*_visitorは設定されると無条件に乗っ取る(pass不可)。
*_customはpassで「やっぱりデフォルトで」ができるため柔軟。
新規コードでは*_customを使うこと。既存の*_visitorも順次*_customにリネーム予定。
DEFINE_VISITOR(Statement_STRUCT_DECL):
1. struct_decl_custom → CALL_OR_PASS(passならデフォルトへ)
2. struct_definition_start_wrapper → 構造体定義の開始行
3. ctx.visit(fields) → フィールド定義
4. methods_inner_class == true → emit_struct_methods() → メソッド
5. "}" + endof_struct_definition
6. methods_inner_class == false → emit_struct_methods() → メソッド
7. struct_definition_end_wrapper → 最終加工
DEFINE_VISITOR(Statement_FUNCTION_DECL):
1. function_decl_custom → CALL_OR_PASS(passならデフォルトへ)
2. params組み立て
3. function_definition_start_wrapper → シグネチャ+開き括弧
4. ctx.visit(body) → 関数本体
5. "}"
6. wrapper_function があれば再帰visit
DEFINE_VISITOR(Statement_PROGRAM_DECL):
1. program_decl_custom → CALL_OR_PASS(passならデフォルトへ)
2. program_decl_start_wrapper → ヘッダ(#pragma once等)
3. for each stmt in block.container → ctx.visit(stmt) + decl_toplevel処理
4. program_decl_end_wrapper → 最終加工
前方参照の解決や宣言/定義の分離が必要な言語(C, C++等)では、 configにフェーズフラグを持たせて同じvisitorを複数回呼ぶパターンが使われる。
forward_declフラグ// Visitor_before.hpp
bool forward_decl = false;
PROGRAM_DECLのカスタム処理内で:
Phase 1: forward_decl = true → foreach_function() → シグネチャのみ(";"で終わる)
Phase 2: forward_decl = false → foreach_function() → 完全な関数定義
FUNCTION_DECL内部で分岐:
if (ctx.config().forward_decl) {
w.writeln(ret_type, " ", name, "(", params, ");"); // シグネチャだけ
return w;
}
w.writeln(ret_type, " ", name, "(", params, ") {");
// ... body ...
OutputPhase enum// Visitor_before.hpp
enum class OutputPhase { Normal, DeclarationOnly, FunctionBodyOnly };
OutputPhase output_phase = OutputPhase::Normal;
PROGRAM_DECLのprogram_decl_custom内で3パス:
Phase 1: enum出力 + sorted_structで前方宣言
Phase 2: output_phase = DeclarationOnly → struct定義 + メソッドシグネチャ
Phase 3: output_phase = FunctionBodyOnly → out-of-classメソッド定義
function_decl_customとstruct_decl_customで分岐:
*_customフックでフェーズに応じて出力を切り替えpassを返せばデフォルト処理にフォールバック可能ebmcodegen/stub/dependency.hppに定義。構造体の依存関係を解析しトポロジカルソートする。
MAYBE(sorted, sorted_struct(ctx)); // ctx は PROGRAM_DECL等のcontext
// sorted: vector<StatementRef> — 依存先が先に来る順序
依存として追跡するもの:
循環参照(RECURSIVE_STRUCT)は追跡しない(ポインタで解決されるため)。
src/ebmcodegen/stub/make_visitor.hpp のフルーエントビルダ。特定の Context_ だけ拾い、それ以外は自動で子孫へ降りる* visitor をその場で組めるやつ。
on_default_traverse_children() が StatementRef コンテナも自動で辿る。自前で BLOCK/IF/LOOP ハンドラを書かなくて済む。DEFINE_VISITOR(...) + visitor/*_class.hpp で書く。make_visitor は常設フックの代替ではない。std::string などで単発的に木をたどるだけなら TRAVERSAL_VISITOR_BASE_WITHOUT_FUNC の方が軽い。#include "ebmcodegen/stub/make_visitor.hpp"
std::vector<std::string> constraints;
ExprStringer stringer{ctx.visitor};
auto collector = ebmcodegen::util::make_visitor<void>(ctx.visitor)
.name("AssertCollector") // ログ用(省略可)
.not_before_or_after() // before/after コンテキスト除外
.not_context("Expression") // 名前に "Expression" を含むコンテキスト除外
.not_context("Type")
.on([&](auto&&, Context_Statement_ASSERT& ac) -> expected<void> {
MAYBE(cond, ac.visit<std::string>(stringer, ac.assert_desc.condition.cond));
constraints.push_back(std::move(cond));
return {};
})
.on_default_traverse_children() // ← 必ず最後。未ヒットは自動で子へ
.build();
MAYBE_VOID(_, ctx.visit<void>(collector, some_statement_ref));
.on([&](auto&& self, Context_X& ctx) -> expected<R> { ... }) で拾いたい型を列挙。self は再帰呼び出し用(ctx.visit<R>(self, ref) で子を明示的にたどる)。.on_default_traverse_children() はビルダの最後にだけ置ける(内部 static_assert)。未ハンドルは自動で traverse_children 相当。.not_context("...") は名前部分一致のフィルタ。Expression / Type を一括除外する常套句。.not_before_or_after() を付けないと before/after 相当のコンテキストまで自前で処理することになり事故の元。特に理由がなければ付ける。R は make_visitor<R>(...) のテンプレート引数。void / std::string / 任意の型を置ける。src/ebmgen/transform/array_setter.cpp — ASSIGNMENT ノードを拾って array setter 検出src/ebmcg/ebm2zig/visitor/entry_before_class.hpp — FUNCTION_DECL / READ_DATA を拾って allocation 要否判定src/ebmip/ebm2ascii/visitor/Statement_STRUCT_DECL_class.hpp — ASSERT を拾って制約一覧