Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ on:
pull_request:

jobs:
tests:
name: test suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Prepare local package feed
run: |
mkdir -p artifacts/packages
dotnet pack src/AstFirst.Core/AstFirst.Core.csproj -c Release -o artifacts/packages
dotnet pack src/AstFirst.Runtime/AstFirst.Runtime.csproj -c Release -o artifacts/packages
- name: Run tests
run: dotnet test AstFirst.slnx -c Release --nologo

compat:
name: ${{ matrix.sdk }} / ${{ matrix.framework }}
strategy:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/publish-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
fetch-depth: 0 # タグ存在確認のため全履歴+タグを取得

- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

- name: Version を読み取り
id: ver
Expand Down Expand Up @@ -71,6 +73,10 @@ jobs:
if: steps.check.outputs.published == 'false'
run: dotnet pack src/AstFirst.Runtime/AstFirst.Runtime.csproj -c Release -p:Version=${{ steps.ver.outputs.version }} -p:PackageReadmePath="$PWD/README.nuget.md" -o artifacts/packages

- name: Test
if: steps.check.outputs.published == 'false'
run: dotnet test AstFirst.slnx -c Release --nologo

- name: Pack Generator
if: steps.check.outputs.published == 'false'
run: dotnet pack src/AstFirst.Generator/AstFirst.Generator.csproj -c Release -p:Version=${{ steps.ver.outputs.version }} -p:PackageReadmePath="$PWD/README.nuget.md" -o artifacts/packages
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 0.4.1 — 文法ノード探索の拡張と品質改善

- **別名前空間の文法ノード**: 既定探索で `[Grammar]` ルートの派生型をアセンブリ全体から収集。
- **探索モード**: `GrammarDiscovery.TypeHierarchy` で名前空間走査を無効化。`Namespace` で従来境界も選択可能。
- **明示参加**: `[GrammarPart(typeof(Root))]` で名前空間・型階層外の `AstNode` を文法へ追加。
- **複数文法・方言**: 同じノードを共有する複数文法と、同一ルートの複数 `Mode` を重複生成なしでサポート。
- **アセンブリ共通Skip**: 宣言済みだが未収集だった `[assembly: Skip(...)]` をGeneratorへ反映。
- **安定した Core モデル**: `Grammar` が入力コレクションのスナップショットを保持し、`Production` が右辺入力を防御コピー。`GrammarBuilder.Build` を反復可能に。
- **品質ゲート**: CI と公開フローで全テストを実行。Generator の Core 型競合警告を解消。

---

## 0.4.0 — 軽量 GLR (LightGlr) モード + 読み取り専用 OnReduce

### ⚠ 破壊的変更 (後方互換性なし)
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>0.4.0</Version>
<Version>0.4.1</Version>
<Authors>actbit</Authors>
<PackageProjectUrl>https://github.com/actbit/AstFirst</PackageProjectUrl>
<RepositoryUrl>https://github.com/actbit/AstFirst</RepositoryUrl>
Expand Down
13 changes: 7 additions & 6 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ AstFirst はパーサジェネレータ・パーサコンビネータと同じ
|---|---|---|---|---|
| 文法の定義 | C# のクラス + 属性 | 外部 `.g4` DSL | C# パーサコンビネータ | 該当なし (C#/VB 構文のみ) |
| コード生成 | **コンパイル時** (Source Generator) | ビルド時コード生成ツール | **しない** (実行時に解釈) | 該当なし |
| 実行時パースコスト | **ゼロ** — 静的テーブル・ディスパッチなし | 生成コード | 解釈実行 (パース毎にアロケーション/ディスパッチ) | 該当なし |
| 実行時方式 | 静的テーブル、パーサ構築不要 | 生成コード | 解釈実行 (パース毎にアロケーション/ディスパッチ) | 該当なし |
| AOT / Native AOT | ✓ 実行時コード生成なし | △ | ✓ | 該当なし |
| アルゴリズム | LALR(1) + 軽量 GLR (LightGlr) | LL(\*) / ALL(\*) | 再帰下降コンビネータ | 該当なし |
| エラー回復 | Corchuelo et al. ER1/ER2/ER3 (組み込み) | あり | 自作が必要 | 該当なし |
| 文法の表現力 | LALR(1) + GLR — `[Precedence]` / fork で衝突解決 | LL(\*) | **チューリング完全** (任意の C# で分岐) | 該当なし |

**コンビネータ (Superpower/Pidgin) に対する強み**: パーサを*コンパイル時*に静的テーブルとして生成するため、解釈もディスパッチもパーサ構築も実行時に走らない。起動・パース毎のコストが事実上ゼロで、AOT/Native AOT にも綺麗に通る。Corchuelo et al. の高品質エラー回復 (ER1 挿入 / ER2 削除 / ER3 Forward move) を組み込み。文法は宣言的な C# なので、IDE のナビゲーションやリファクタリングが効く。
**コンビネータ (Superpower/Pidgin) に対する強み**: パーサを*コンパイル時*に静的テーブルとして生成するため、実行時の解釈・delegate dispatch・パーサ構築が不要で、AOT/Native AOT にも綺麗に通る。Corchuelo et al. の高品質エラー回復 (ER1 挿入 / ER2 削除 / ER3 Forward move) を組み込み。文法は宣言的な C# なので、IDE のナビゲーションやリファクタリングが効く。

**トレードオフ**: LALR(1) は `[Precedence]`/結合性で shift-reduce 衝突を解決する必要がある。ただし LightGlr モード (`[Grammar(ParseMode = ParseMode.LightGlr)]`) で本質的曖昧性 (cast/paren・generic 等) を並行 fork で扱える。C# 専用のツールチェイン。

Expand Down Expand Up @@ -97,14 +97,14 @@ var result = ExprParser.Parse("1+2*3");
// result.HasErrors → false

var result2 = ExprParser.Parse("1+");
// result2.HasErrors → true (panic mode で回復)
// result2.HasErrors → true (Corchuelo ER1/ER2/ER3 で回復)
```

## 意味解析

AstFirst は構文解析(AST 構築)に加え、意味解析のための標準ヘルパーと 2パスの枠組みを提供する。詳細は [docs/ja/semantic-analysis.md](docs/ja/semantic-analysis.md)。

- **属性ベースのルール `[OnReduce]` / `[Enter]` / `[Exit]` (推奨)**: 意味ルールを `[Grammar]` ルートクラスの `static` メソッドで書く。Generator がコンストラクタ / Walker から dispatch し、ctx のキャストも自動で挿入(ノード毎のボイラープレート不要)。
- **属性ベースのルール `[OnReduce]` / `[Enter]` / `[Exit]` (推奨)**: 意味ルールを `[Grammar]` ルートクラスの `static` メソッドで書く。Generator が `[OnReduce]` を Parser の reduce 処理から、`[Enter]`/`[Exit]` を Walker から dispatch し、ctx のキャストも自動で挿入(ノード毎のボイラープレート不要)。
- **1パス目 `OnReduce` (ボトムアップ)**: reduce 時に呼ばれる partial メソッド。`Accept()`/`Reject()` でこの構文を受け入れるか判定(既定 Accept)。`Reject` すると別候補へフォールバック。
- **2パス目 `[Enter]`/`[Exit]` / `OnSecondPassEnter`/`Exit` (トップダウン)**: 生成された汎用 Walker (`{Root}Walker`) が `Parse` 後に `Enter → 子 → Exit` を駆動。スコープ Push/Pop 等の正確な意味解析が書ける。意味フックのない文法では走査を省略(オーバーヘッドなし・ゼロコスト)。
- **型システム**: `TypeSymbol` は継承可能で `FunctionTypeSymbol`/`ArrayTypeSymbol`(共変・反変・構造等価)を組み込み、暗黙の型変換の分類と `OverloadResolver` も提供。`BasicSemanticContext` は `TypeContext` を標準で保持。
Expand Down Expand Up @@ -227,11 +227,12 @@ var result = ProgramParser.Parse(code, new MiniCContext());
| 属性 | 対象 | 役割 |
|---|---|---|
| `[Grammar]` | クラス | 文法の開始記号(ルート非終端)。Generator の抽出開始点。`Mode` で複数方言を切り替え。 |
| `[GrammarPart(typeof(Root))]` | クラス | 名前空間・ルート型階層外のノードを文法へ明示的に追加。`[Grammar].Discovery` で探索方法を選択可能。 |
| `[Rule]` | static メソッド | 生成規則(1クラス1つ)。メソッドの**引数**が右辺。 |
| `[Token(@"regex")]` / `[Pattern(@"regex")]` | `[Rule]` メソッドの `Token` 引数 | 字句ルール(正規表現)。`Priority` でレクサ優先度(大きいほど高優先)。 |
| `[Precedence(n)]` | クラス(演算ノード) | 演算子優先度/結合性。`n` が大きいほど高優先。`IsRightAssociative`/`IsNonAssociative` で結合性。 |
| `[Repeat]` / `[Repeat(Min=0)]` | `[Rule]` メソッドの `AstNode` 派生引数 | リスト(繰り返し)。`Min=1`(既定)は1回以上、`Min=0` は0回以上(空リスト可)。`IReadOnlyList<T>` に展開。 |
| `[Skip(@"regex")]` | クラス(`[Grammar]` と同じ) | スキップパターン(空白・コメント等)。 |
| `[Skip(@"regex")]` | `[Grammar]` クラス/アセンブリ | スキップパターン(空白・コメント等)。アセンブリ指定は全Grammar共通。 |

### `[Rule]` メソッドの引数(型ベース分類)

Expand Down Expand Up @@ -365,7 +366,7 @@ AstFirst.slnx

## テスト

352 テスト(AstFirst.Tests 299 + Generator.Tests 53)。レクサ/DFA/LALR の各段階、エンドツーエンド、エラー回復 (Corchuelo)、GLR fork/dedup、意味解析(スコープ・2パス目・型チェック・ctx → `ParseResult.Diagnostics` の統合)、`Accept`/`Reject` フォールバック、`OnAccepted` コールバック、位置情報(行・列)を検証
374 テスト(AstFirst.Tests 308 + Generator.Tests 66)。レクサ/DFA/LALR の各段階、エンドツーエンド、エラー回復 (Corchuelo)、GLR fork/dedup、意味解析、文法ノード探索、Coreビルドのスナップショット安定性、`Accept`/`Reject` フォールバック、`OnAccepted`、位置情報を検証

## ライセンス

Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ AstFirst sits in the same space as parser generators and combinator libraries, b
|---|---|---|---|---|
| Grammar in | plain C# classes + attributes | external `.g4` DSL | C# parser combinators | n/a — C#/VB syntax only |
| Code generated | **compile time** (Source Generator) | build-time codegen tool | **never** — interpreted at runtime | n/a |
| Runtime parse cost | **zero** — static tables, no dispatch | generated code | interpreted (allocation/dispatch per parse) | n/a |
| Runtime strategy | static tables, no parser construction | generated code | interpreted (allocation/dispatch per parse) | n/a |
| AOT / Native AOT | ✓ no runtime codegen | △ | ✓ | n/a |
| Algorithm | LALR(1) + Lightweight GLR (LightGlr) | LL(\*) / ALL(\*) | recursive-descent combinators | n/a |
| Error recovery | Corchuelo et al. ER1/ER2/ER3 (built-in) | yes | hand-rolled | n/a |
| Grammar power | LALR(1) + GLR — resolve conflicts with `[Precedence]` / fork | LL(\*) | **Turing-complete** (branch on any C#) | n/a |

**Strengths vs combinators (Superpower/Pidgin)**: the parser is emitted *at compile time* as static tables — no interpretation, no delegate dispatch, no per-parse construction. Startup and per-parse cost are effectively zero, it AOTs / Native-AOTs cleanly, and Corchuelo et al. error recovery (ER1 insert / ER2 delete / ER3 Forward move) is built in. The grammar is declarative C#, so the IDE can navigate and refactor it.
**Strengths vs combinators (Superpower/Pidgin)**: the parser is emitted *at compile time* as static tables, so runtime interpretation, delegate dispatch, and parser construction are unnecessary. It AOTs / Native-AOTs cleanly, and Corchuelo et al. error recovery (ER1 insert / ER2 delete / ER3 Forward move) is built in. The grammar is declarative C#, so the IDE can navigate and refactor it.

**Trade-offs**: LALR(1) needs `[Precedence]`/associativity to resolve shift-reduce conflicts. However, LightGlr mode (`[Grammar(ParseMode = ParseMode.LightGlr)]`) handles inherent ambiguity (cast/paren, generics) via parallel fork. A C#-only toolchain.

Expand Down Expand Up @@ -97,14 +97,14 @@ var result = ExprParser.Parse("1+2*3");
// result.HasErrors -> false

var result2 = ExprParser.Parse("1+");
// result2.HasErrors -> true (recovered via panic mode)
// result2.HasErrors -> true (recovered via Corchuelo ER1/ER2/ER3)
```

## Semantic analysis

AstFirst provides standard helpers and a two-pass framework for semantic analysis on top of parsing. See [docs/en/semantic-analysis.md](docs/en/semantic-analysis.md) for details.

- **Attribute-based rules `[OnReduce]` / `[Enter]` / `[Exit]` (recommended)**: write semantic rules as `static` methods on the `[Grammar]` root class. The generator dispatches them from the constructor / Walker and injects the `ctx` cast for you — no per-node boilerplate.
- **Attribute-based rules `[OnReduce]` / `[Enter]` / `[Exit]` (recommended)**: write semantic rules as `static` methods on the `[Grammar]` root class. The generator dispatches `[OnReduce]` during parser reduction and `[Enter]`/`[Exit]` from the Walker, injecting the `ctx` cast for you — no per-node boilerplate.
- **First pass `OnReduce` (bottom-up)**: a partial method called at reduce time. `Accept()`/`Reject()` decides whether to accept this interpretation (default Accept). `Reject` falls back to the next candidate.
- **Second pass `[Enter]`/`[Exit]` / `OnSecondPassEnter`/`Exit` (top-down)**: a generated generic Walker (`{Root}Walker`) drives `Enter -> children -> Exit` after `Parse`. Accurate semantic analysis like scope Push/Pop fits here. Grammars with no semantic hook skip the traversal entirely (no overhead, zero-cost).
- **Type system**: `TypeSymbol` is inheritable with built-in `FunctionTypeSymbol`/`ArrayTypeSymbol` (variance + structural equality), plus implicit-conversion classification and `OverloadResolver`. `BasicSemanticContext` carries a `TypeContext` by default.
Expand Down Expand Up @@ -227,11 +227,12 @@ See [docs/en/grammar-reference.md](docs/en/grammar-reference.md) for details.
| Attribute | Target | Role |
|---|---|---|
| `[Grammar]` | class | Start symbol (root nonterminal). Generator's extraction entry point. `Mode` switches dialects. |
| `[GrammarPart(typeof(Root))]` | class | Explicitly adds a node outside the grammar namespace/root hierarchy. `[Grammar].Discovery` selects discovery behavior. |
| `[Rule]` | static method | A production (one per class). The method's **parameters** are the RHS. |
| `[Token(@"regex")]` / `[Pattern(@"regex")]` | `Token` parameter of a `[Rule]` method | Lexical rule (regex). `Priority` sets lexer priority (higher wins). |
| `[Precedence(n)]` | class (operator node) | Operator precedence/associativity. Higher `n` binds tighter. `IsRightAssociative`/`IsNonAssociative`. |
| `[Repeat]` / `[Repeat(Min=0)]` | `AstNode`-derived parameter of a `[Rule]` method | List (repetition). `Min=1` (default) = one or more, `Min=0` = zero or more (empty list allowed). Expands to `IReadOnlyList<T>`. |
| `[Skip(@"regex")]` | class (same as `[Grammar]`) | Skip pattern (whitespace, comments). |
| `[Skip(@"regex")]` | `[Grammar]` class / assembly | Skip pattern (whitespace, comments). Assembly-level patterns apply to every grammar. |

### `[Rule]` method parameters (type-based classification)

Expand Down Expand Up @@ -316,7 +317,7 @@ Japanese versions are under `docs/ja/` and [README.md](README.md).

## Tests

352 tests (AstFirst.Tests 299 + Generator.Tests 53). Covers lexer/DFA/LALR stages, end-to-end, error recovery (Corchuelo), GLR fork/dedup, semantic analysis (scopes, two-pass, type checking, ctx -> `ParseResult.Diagnostics` integration), `Accept`/`Reject` fallback, `OnAccepted` callback, and positions (line/column).
374 tests (AstFirst.Tests 308 + Generator.Tests 66). Covers lexer/DFA/LALR stages, end-to-end, error recovery (Corchuelo), GLR fork/dedup, semantic analysis, grammar discovery, Core build snapshot stability, `Accept`/`Reject` fallback, `OnAccepted`, and positions.

## License

Expand Down
4 changes: 2 additions & 2 deletions docs/en/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ AstFirst is a parser generator built as three layers plus a generator.

## Generator pipeline

1. **Extraction** (`ModelExtraction`): traverses AstNode/Token derivatives and `[Pattern]` from the `[Grammar]` root, collects `[OnReduce]`/`[Enter]`/`[Exit]` attribute semantic rules, and converts them into an equality-comparable POCO model (`GrammarModel`/`AnalyzeRuleModel`).
1. **Extraction** (`ModelExtraction`): follows `[Grammar].Discovery` to collect nodes from the root namespace, root type hierarchy, and `[GrammarPart]`; then converts `[Rule]`/`[Token]` and semantic rules into equality-comparable POCO models.
2. **DFA build** (`ModelToDfa`): regex of each rule -> NFA (Thompson) -> DFA (subset construction) -> minimization (Hopcroft).
3. **LALR table** (`ModelToTable`): LR(0) automaton -> FIRST/NULLABLE -> DeRemer-Pennello lookahead propagation -> ACTION/GOTO tables + conflict detection.
4. **Code emission** (`CodeEmitter` / `ParserEmitter` / `WalkerEmitter`): generates C# for Lexer (DFA arrays), Parser (LALR table + shift/reduce driver), Walker (Enter/Exit/Walk + `[Enter]`/`[Exit]` dispatch), and per-node partials (including `[OnReduce]` dispatch).

## Generated code shape

- **Lexer**: embeds the DFA transition table and accepting rules in `static readonly` arrays; `Tokenize()` runs longest-match + priority-driven. Also computes each token's 1-based line/column.
- **Parser**: embeds ACTION/GOTO tables and Productions in arrays and drives shift/reduce/accept. At reduce it calls the AST class constructor to build the AST (`[OnReduce]` attribute methods are called right after partial `OnReduce`). Includes panic-mode error recovery.
- **Parser**: embeds ACTION/GOTO tables and Productions in arrays and drives shift/reduce/accept. At reduce it calls the AST class constructor to build the AST (`[OnReduce]` attribute methods are called right after partial `OnReduce`). Includes Corchuelo ER1/ER2/ER3 error repair.
- **Walker**: `EnterXxx` / `ExitXxx` (virtual, empty) per concrete node + `Walk` (iterative stack: Enter -> children -> Exit). Also invokes `IOnSecondPassEnter`/`Exit` and `[Enter]`/`[Exit]` attribute methods. Children are collected from each node's public properties of AstNode-derived types. If a grammar has no semantic hook at all, the Walker is not emitted (zero-cost).

## Caching strategy
Expand Down
Loading
Loading