Skip to content

feat: 軽量 GLR (LightGlr) モードを追加 - #25

Merged
actbit merged 37 commits into
masterfrom
feat/light-glr
Jul 12, 2026
Merged

feat: 軽量 GLR (LightGlr) モードを追加#25
actbit merged 37 commits into
masterfrom
feat/light-glr

Conversation

@actbit

@actbit actbit commented Jul 11, 2026

Copy link
Copy Markdown
Owner

概要

AstFirst に 軽量 GLR (Generalized LR) モード をオプション追加。LALR(1) では扱えない本質的曖昧性 (cast/paren・generic の型/式) を並行 fork で解決。完全 GLR (SPPF) は別 version で対応予定。

設計

  • デフォルト LALR は完全不変: ParserEmitter を一行も変更せず
  • [Grammar(ParseMode = ParseMode.LightGlr)] で切替: 別エミッタ (GlrParserEmitter) + Runtime ドライバ (LightGlrDriver)
  • OnReduce はノードローカル前提: 外部状態変更禁止、意味解析は [Enter]/[Exit] Walker
  • エラー修復: Corchuelo et al. ER1/ER2/ER3 (挿入/削除/Forward move + 最小コスト)

検証

  • 全 353 テスト緑 (既存 341 + GLR 12)
  • CSharpParser の state 308 reduce-reduce が fork で解決、HasErrors=False
  • Perf.Gen が [Rule] モデルで正しく生成 (B1 修復)
  • Corchuelo 修復の既知の制限を ja/en docs に明記

actbit and others added 30 commits July 11, 2026 07:14
LightGlr モード選択の基盤を追加。GlrParserEmitter は現状 ParserEmitter
へ委譲する stub で、デフォルト LALR の生成コード・挙動は一切変更なし
(全 341 テスト合格)。

- ParseMode enum (Runtime 側 AstFirst.ParseMode + Generator 側ミラー)
- GrammarAttribute.ParseMode プロパティ (既定 Lalr)
- GrammarModel.ParseMode + Equals/GetHashCode
- ModelExtraction で ParseMode named-arg 読取 (整数値で受け取りキャスト)
- ParserGenerator で ParserEmitter / GlrParserEmitter を分岐

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tomita-lite ドライバ (LightGlrDriver) とテーブル参照 (GlrTables) を実装。
コンフリクトセルで並行 fork、(state, pos) が同一なら dedup-merge、
EOF で accept 収集。Generator からは未接続 (フェーズ3で配線)、既存 LALR は不変。

手作りテーブルで shift/reduce/accept/fork/dedup/dead を検証 (4 テスト合格)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LightGlr モードのパーサコード生成を実装。テーブル直列化は ParserEmitter と
同一で、Parse 本体は LightGlrDriver.Run に委譲。reduce は children[i] 参照で
COW リスト構築 (GSS での共有安全)。ParserEmitter は完全不変。

GlrParserEmitterTests (生成コードのコンパイル + 文字列検証) 4 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GlrExpr 文法 (電卓の部分集合、ParseMode=LightGlr) を Generator に生成させ、
LightGlrDriver でパースして単一 AST を得る E2E テスト (数値・加算・構文エラー)。

accept で $ shift を考慮 (スタックトップが null ならその下 = 開始記号の値、
LALR の Accept と同義)。全 353 テスト合格 (既存 341 + GLR 12)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LightGlr モードではコンフリクトは並行 fork で解決されるため、ParserGenerator は
ASTF001 を報告しない (確定 LALR のみ)。

検証: CSharpParser (state 308 で reduce-reduce: IdentifierExpr vs NamedType) を
一時的に LightGlr にし、ASTF001 が 0 に抑制され、fork で実コード断片がエラーなく
パースできる (HasErrors=False) ことを確認。GeneratedGrammar.cs は auto-generated
のため revert (恒久適用は CSharpFactory 側への反映が別途必要)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
両 GeneratedGrammar.cs に直接 [Grammar(ParseMode = ParseMode.LightGlr)] を付与。
state 308 の reduce-reduce (IdentifierExpr vs NamedType) が ASTF001 抑制 + fork で解決。
CSharpParser は実コード断片を HasErrors=False でパース成功。

注: GeneratedGrammar.cs は auto-generated 扱いだが、生成元の GrammarSpec
(Perf.Grammars) が古い ctor モデルのまま [Rule] static モデルに未対応のため、
Perf.Gen 再生成では壊れる。GrammarSpec の [Rule] モデル対応は別課題
([Rule] モデル移行の一部)。当面は直接編集で運用。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GrammarSpec.ToCsSource を ctor モデルから [Rule] static メソッドモデルに更新
(partial クラス + [Rule] public static void Reduce)。ParseMode プロパティを追加。
CSharpFactory に ParseMode=LightGlr を設定。

これで Perf.Gen が [Rule] モデルの GeneratedGrammar.cs を正しく生成し、
HEAD の手作業 GeneratedGrammar と一致 (差分ゼロ)。Perf.Gen 再生成が壊れる
問題 ([Rule] モデル移行の残り) を解決。sample/benchmark 両方で LightGlr が
ASTF001 抑制 + fork パース成功。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
shift 先のない dead スタックで、LALR の panic mode と同様にスタックを
shift 可能状態まで pop して回復を試みる (見つからなければトークンを進める)。
構文エラー時の挙動を LALR と厳密一致させる方向に改善。全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LightGlr モードで複数解釈が accept まで残った場合、候補 AST リストを観察可能に
(先頭 = Ast と同じ)。GlrParserEmitter で候補2個以上のとき渡す。
LALR モード・非曖昧入力では空。optional 引数で既存呼出しに影響なし。全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ja/en の grammar-reference.md に ParseMode (Lalr/LightGlr) と LightGlr の振る舞い、
「1クラス1モード」制約、OnReduce はノードローカル前提 (外部状態変更禁止、意味解析は
[Enter]/[Exit] で) を明記。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LightGlrDriver のエラー回復を panic mode から Corchuelo et al. の修復に置換:
- ER1 (Insertion): shift 可能な終端を入力前に挿入する候補を生成
- ER2 (Deletion): 現トークンを削除する候補を生成
- ER3 (Forward move): 各候補で N シンボル先までパースし、修復可能性を確認
最小コスト (挿入 < 削除) の修復を採用。トークンを捨てない高品質修復。

バグ修正:
1. SimulateForward で実 reduce を呼ぶと挿入トークン (null) で OnReduce 例外
2. 本番 reduce でも挿入トークン null で GlrNum ctor が例外
→ SimulateForward を実 reduce + try/catch にし、OnReduce 例外なら修復不可として弾く

全 353 テスト合格。CSharpParser も HasErrors=False で動作。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 つの既知の制限を文法リファレンスの ParseMode 節に追記:
- 挿入トークンは値 null (OnReduce で NullReferenceException の可能性)
- N=3・コスト固定 (言語別チューニング未対応)
- 1 回修復 (再帰なし、Corchuelo 本来は再帰)
- SimulateForward は最初の経路のみ確認 (fork 差で本番と完全一致しない)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corchuelo 修復の挿入トークン (ER1) を null から BasicToken("") に変更。
これにより本番 reduce で Token.Text アクセス時の NullReferenceException を防止
(SimulateForward は最初の経路のみ確認するため、本番 fork で違う経路を通ると
null で NRE になるリスクがあった)。

SimulateForward (ER3) に toToken を渡し、元の入力 shift に実際のトークン値を
使うよう修正 (probe の確認精度向上)。

全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- GlrParserEmitter を public → internal に (Generator 内部エミッタ)
  InternalsVisibleTo("AstFirst.Generator.Tests") でテスト対応
- ParseResult.AmbiguousCandidates の XML コメント明確化
  (LALR モードでは常に空、LightGlr モードでのみ意味を持つ)
- docs にエラー回復の挙動差 (LALR panic mode vs LightGlr Corchuelo) を明記

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LightGlrDriver の2つのバグを修正:
1. ReduceAll の fork で i==0 が s を変更後に s.Clone() するバグ →
   snapshot を reduce 前に取って修正
2. Accept/Reject を無視していた → accept 時と ReduceAll で
   Rejected な候補を破棄 (FallbackTests 互換)

両モード LightGlrDriver 統一は撤回 (DiagnosticBag 重複で既存テストが壊れる)。
LALR は ParserEmitter.EmitParser のまま。LALR のエラー回復改善は別途。

全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ErrorRepair クラス (Runtime) に Corchuelo ER1/ER2/ER3 を切り出し。
LightGlrDriver と ParserEmitter.EmitParse の panic ブロックの両方から
呼ぶ。LALR モードでも Corchuelo の高品質エラー修復が使えるようになった。

- LightGlrStack を public に (生成コードから ErrorRepair に渡すため)
- ParserEmitter.EmitParse: panic ブロックに ErrorRepair.TryRepair 呼び出しを追加
  修復失敗時は従来の panic fallback (スタック pop + トークン進め)
- LightGlrDriver: Repair/SimulateForward を ErrorRepair に委譲

fork で OnReduce 複数回問題は回避 (ErrorRepair は probe で動く、本番 fork とは独立)。

全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… を削除

ErrorRepair への切り出しで不要になった LightGlrDriver.Repair/SimulateForward
(修正前のバグを含む dead code) を削除。全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LightGlrDriver と ErrorRepair で重複していた ApplyReduce/LookaheadSym を
ErrorRepair の internal static メソッドに集約。LightGlrDriver は
ErrorRepair.ApplyReduce/ErrorRepair.LookaheadSym を呼ぶ。全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AstNode.MarkAccepted() (internal) を追加。
ReduceAll の dedup で、他が破棄されて生き残った候補を
Undecided → Accepted に確定。

fork で複数経路を追跡 → 収束 (dedup) で1本に絞られた時点で、
生き残った候補 = 正解として確定。これを AST に伝える。
収束後は単一スタック (= LALR レベル) で動く。

全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ApplyReduce (reduce 時) でなく、ReduceAll の dedup で2本目が
破棄された時 (= ルートが1つに確定した時) に MarkAccepted を呼ぶ。
「経路が確定した」= 他が消えて生き残った、の意味が正確になる。

全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AstNode に partial void OnAccepted() を追加。ルートが1つに確定した
(fork が収束、または LALR で reduce 時) に MarkAccepted → OnAccepted
が呼ばれる。ユーザーは OnAccepted 内で確定時の処理を書ける。

- AstNode: partial class 化 + partial void OnAccepted() + MarkAccepted (public)
- ParserEmitter.EmitPartial: 各ノードに partial void OnAccepted() を生成
- ParserEmitter.EmitParse: LALR reduce 時に MarkAccepted を呼ぶ (fork しない = 即確定)
- LightGlrDriver: dedup でルートが1つになった時に MarkAccepted

ユーザーの書き方:
  partial void OnAccepted() { /* ルート確定時の処理 */ }

全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MarkAccepted (AcceptState 設定 + OnAccepted 呼び出し) を
NotifyAccepted (OnAccepted 呼び出しのみ) に変更。
AcceptState の設定は不要 (Undecided も Accepted も IsAccepted = true で同じ)。

全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OnAccepted を OnReduce と同じパターンに: partial void OnAccepted(ctx)。
AstNode.NotifyAccepted(ctx) が virtual で、各派生クラスの EmitPartial が
override して OnAccepted((ctxType)ctx) を呼ぶ。

ユーザーの書き方:
  partial void OnAccepted(MyCtx ctx) { /* 確定時の処理 */ }

全 353 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SemanticContext を読み取り専用に変更:
- Symbols を IReadOnlySymbolTable に (Lookup のみ、TryDeclare 不可)
- Diagnostics を SemanticContext から削除 (BasicSemanticContext に移動)
- BasicSemanticContext.WritableSymbols / Diagnostics で [Enter]/[Exit] から書き込み

OnReduce/OnAccepted の生成コードを常に SemanticContext (基底) に変更。
ユーザーの OnReduce は読み取り専用 ctx を受け取り、宣言・診断の追加は不可。

移行:
- MiniC: OnReduce(MiniCContext ctx) → OnReduce(SemanticContext ctx)
- MiniC: ctx.Symbols.PushScope/TryDeclare → ctx.WritableSymbols.*
- SemanticContextGrammar: OnReduce の宣言・診断を [Enter] (Walker) に移行
- AttributeSemanticGrammar: [OnReduce] Declare → [Enter] Declare に移行
- テスト: 新しい API に合わせて更新

※この変更はバージョン互換性がありません。既存の OnReduce(MyCtx ctx) は
OnReduce(SemanticContext ctx) に、ctx.Symbols.Xxx() は [Enter]/[Exit] 内で
ctx.WritableSymbols.Xxx() に変更する必要があります。

全 348 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OnReduce の ctx 読み取り専用化に伴う移行手順を文法リファレンスに明記:
- OnReduce(MyCtx ctx) → OnReduce(SemanticContext ctx)
- ctx.Symbols.TryDeclare → [Enter] で ctx.WritableSymbols.TryDeclare
- ctx.Diagnostics.Error → [Enter] で ctx.Diagnostics.Error

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
バグ1: ctx なしノードで NotifyAccepted override が生成されず、OnAccepted が
発火しなかった。常に override を生成するよう修正。

バグ2: LALR (ParserEmitter) の List_T → List_T item が破壊的 Add を使って
いた。ErrorRepair の probe (浅いコピー Clone) でリストが共有されるため、
シミュレーション中の Add が元のスタックのリストを破壊する。
COW (copy-on-write) に変更 (GlrParserEmitter と同じ)。

全 348 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
バグ3: GLR ReduceAll に try/catch 追加 (ダミートークン例外でスタック破棄)
- LALR 側は goto panic を削除 (無限ループ/不正ASTのリスク > クラッシュ)
- N=3 以外で reduce 例外が出るケースは極めて稀 → 例外伝播 (クラッシュ) が妥当

回帰テスト:
- OnAccepted_Called_ForCtxLessNode: ctx なしノードで OnAccepted 発火を検証
- OnAccepted_Called_WhenRouteConverges: GlrNum(1) の OnAccepted を検証
- ErrorRepair_DoesNotCrash_OnMalformedInput: "1++2" でクラッシュしないことを検証
- LALR_ErrorRepair_DoesNotCrash_WithIntParseInOnReduce: Calc "1++2" でクラッシュしないことを検証

全 352 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corchuelo 修復失敗時のフォールバックを「トークン1つ進め」に変更。
スタックを pop するパニックモードは使用しない (Corchuelo で十分)。

全 352 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
[OnReduce] 属性の dispatch がユーザーの ctx 型にキャストしていたため、
OnReduce 中に ctx.WritableSymbols.TryDeclare() 等の書き込みが可能だった。
GLR の fork で重複副作用が起きる問題があった。

dispatch のキャストを (CtxType) → (SemanticContext) に変更。
ユーザーは [OnReduce] 属性メソッドの ctx 型を SemanticContext にする必要がある。
書き込みが必要な場合は [Enter]/[Exit] を使用する。

全 352 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OnAccepted は「ルートが確定した」タイミングなので、ctx の書き込みを許可。
OnReduce (解釈未確定) は引き続き読み取り専用 SemanticContext。

ユーザーの書き方:
  partial void OnAccepted(MyCtx ctx) { ctx.WritableSymbols.TryDeclare(...); }

全 352 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
actbit and others added 7 commits July 12, 2026 22:25
ja/en の破壊的変更セクションを更新:
OnReduce は読み取り専用 SemanticContext、OnAccepted はユーザーの ctx 型 (書き込み可)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
単一アクション時の Clone を廃止:
- shift: shiftCount==1 && !hasAccept → in-place push (Clone 不要)
- reduce: reduceActs.Count==1 → in-place apply (snapshot 不要)

効果 (direct benchmark):
- CSharp: 84.8ms → 74.1ms、10,750KB → 4,435KB (-59%)
- MegaLang: 0.79ms → 0.55ms
- DeepNest: 0.38ms → 0.24ms
- DeepPrec: 4.02ms → 3.34ms

非コンフリクト経路 (99%+) は Clone ゼロに。fork が必要なコンフリクト経路のみ Clone。

全 352 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
単一スタック時に List/Queue/HashSet を全バイパスする fast path を追加:
- ProcessSingleStack: reduce cascade + shift/accept/error をインライン処理
- fork が必要 (reduce-reduce/shift 多数) なときのみ slow path へ
- 非コンフリクト経路はアロケーションほぼゼロ

効果 (direct benchmark):
- CSharp: 74.1ms → 11.4ms (6.5倍)、10,750KB → 1,931KB (-82%)
- MegaLang: 0.55ms → 0.40ms
- DeepNest: 0.24ms → 0.17ms
- DeepPrec: 3.34ms → 2.33ms

全 352 テスト合格。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
破壊的変更 (SemanticContext 読み取り専用化、OnReduce/OnAccepted API 変更)
を含むため、マイナーバージョンアップ。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0.4.0 のリリースノート:
- 破壊的変更: OnReduce 読み取り専用化、SemanticContext API 変更
- 新機能: LightGlr モード、Corchuelo エラー修復、OnAccepted、AmbiguousCandidates
- パフォーマンス: fast path による単一スタック最適化

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- LALR(1) → LALR(1) + 軽量 GLR (LightGlr)
- panic mode → Corchuelo et al. ER1/ER2/ER3
- 比較表・特徴・トレードオフの記述を更新

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PerfSummary.md 実行パフォーマンス表:
- CSharp: 0.68ms → 11.4ms (LightGlr)、627KB → 1,931KB
- DeepPrec: 1.94ms → 2.3ms (COW + NotifyAccepted)
- MegaLang: 0.193ms → 0.40ms
- DeepNest: 0.104ms → 0.17ms
- WideRules/ManyTokens: 未再計測 (—)

README (ja/en):
- ベンチマーク数値を更新
- テスト数 293 → 352
- 生成コードサイズ 6.0MB/8011行 → 6.2MB/15363行

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@actbit
actbit merged commit 5e4264c into master Jul 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant