Skip to content

feat: add SLR(1) parsing strategy and item-set viewer - #18

Open
raiga0310 wants to merge 1 commit into
masterfrom
feat/slr-parser-and-item-sets
Open

feat: add SLR(1) parsing strategy and item-set viewer#18
raiga0310 wants to merge 1 commit into
masterfrom
feat/slr-parser-and-item-sets

Conversation

@raiga0310

Copy link
Copy Markdown
Owner

概要

LR(0) だけだった解析エンジンに SLR(1) を追加し、GUI に 項集合 (Item Sets) ビューを追加しました。あわせて、競合エラーが「どこで何と何が競合したか」を報告できるようになっています。

変更内容

ライブラリ側

  • lr::compilecompile_lr0 / compile_slr に分割
    内部は Strategy enum を受け取る compile_with に共通化しました。項集合の構築とテーブル生成は完全に共有し、両者の差は「還元の先読みをどう決めるか」の一点だけです。
    • LR(0): 還元を全終端記号に対して行う
    • SLR: FOLLOW(A) に含まれる終端記号にのみ還元する
  • src/follow.rs を新規追加
    nullable 判定 → FIRST 集合 → FOLLOW 集合を不動点反復で計算します。FOLLOW(S') = {$} を起点に伝播させる標準的な構成です。ライブラリ内部専用 (pub(crate)) で、公開 API は増やしていません。
  • ParserError::ConflictReducerParserError::Conflict(Conflict)
    Conflict は競合した状態番号・先読み終端・既存の action・投入しようとした action を保持します。kind()"Shift/Reduce" / "Reduce/Reduce" を返します。
  • ParserError::NoAvailableStartSymbol を追加
    26 文字の大文字がすべて非終端記号として使われていると拡張開始記号 S' を作れないため、その場合を明示的なエラーにしました。
  • grammar::EOF 定数を公開
    ハードコードされていた Terminal('$') を定数に切り出しました。

GUI 側

  • src/pages/item_sets.rs を新規追加
    各状態の LR 項をドット位置つきで表示します。還元可能な項(ドットが末尾)には先読み集合を併記するため、SLR が LR(0) より通る理由が画面上で追えます。現在の遷移元状態をハイライトします。
  • pages/parser.rs: 解析法の選択を compile 関数の注入に変更
    ParserKindCompileFn のマッピングだけで解析法が決まる形にしたので、Slr が「未実装」から実装済みに変わりました。Lalr / Lr1 は引き続き NotImplemented です。
  • 競合エラーメッセージを詳細化
    従来の「grammar is not LR(0)」だけの表示から、状態番号・先読み・競合する 2 つの action を出すようにしました。

レビュアーに見てほしい点

  • handle_parse_with の処理順を入れ替えています。 従来は run が失敗すると state_infos を空にして返していましたが、状態機械と項集合は compile 成功の時点で確定しており run の成否とは独立です。むしろ入力エラーのときこそ項集合を見たい場面なので、run より前に組み立てるようにしました。この挙動は GUI を目視する以外に観測手段がないため、run_failure_still_exposes_state_infos テストで固定しています。
  • Conflict を公開型として lib.rs から re-export しています。 競合の詳細を UI に渡すために必要ですが、公開 API を増やす判断なので妥当かご確認ください。
  • SLR の検証文法は S -> aA / S -> a / A -> b を使っています (SLR_ONLY_GRAMMAR)。LR(0) では b で Shift/Reduce 競合、SLR では通る、という差分を parser_kind_selects_the_compile_function で確認しています。

テスト

cargo test は全て通過しています(ライブラリ側 + GUI クレート 17 件)。

補足

CLAUDE.md の Architecture セクションが lr::compile / ParserError::ConflictReducer という旧名のままです。本 PR のスコープ外としましたが、別途更新が必要です。

`lr::compile` を `compile_lr0` / `compile_slr` に分割し、内部を
`Strategy` enum で切り替える `compile_with` に共通化した。LR(0) は
還元を全終端記号に対して行い、SLR は FOLLOW 集合を先読みに使う。

- `follow.rs` を新規追加。nullable / FIRST / FOLLOW を不動点反復で計算し、
  SLR の還元先読みを供給する。ライブラリ内部専用 (`pub(crate)`)。
- `ParserError::ConflictReducer` を `ParserError::Conflict(Conflict)` に変更。
  競合した状態・先読み終端・既存 action・投入 action を保持し、
  UI で「どこで何が競合したか」を提示できるようにした。
- 拡張開始記号を確保できない場合の `NoAvailableStartSymbol` を追加。
- `pages/item_sets.rs` を新規追加。各状態の LR 項集合をドット位置つきで
  表示し、SLR では還元項に先読みを併記する。
- `pages/parser.rs`: `ParserKind` から compile 関数を注入する形に変更し、
  `Slr` を実装済みとして有効化。あわせて、run が失敗しても compile が
  成功していれば状態機械と項集合を表示するよう順序を入れ替えた
  (入力エラー時こそ項集合を見たいため)。
- `grammar.rs`: EOF 終端 `$` を `grammar::EOF` 定数として公開。

Co-Authored-By: Claude <noreply@anthropic.com>
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