Skip to content
42 changes: 40 additions & 2 deletions docs/en/grammar-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ AstFirst grammars are written with C# classes and attributes. The generator emit
|---|---|---|
| `[Grammar]` | class | Start symbol (root nonterminal). Generator's extraction entry point. `Mode` switches dialects. |
| `[Rule]` | static method | A production. The method's **parameters** are the RHS. Multiple per class allowed (see below). |
| `[Token(@"regex")]` / `[Pattern(@"regex")]` | `Token` parameter of a `[Rule]` method | Lexical rule (regex). `Priority` sets lexer priority. |
| `[Token(@"regex")]` / `[Pattern(@"regex")]` | `Token` parameter of a `[Rule]` method | Lexical rule (regex). `Priority` sets lexer priority, `Kind` sets token category. |
| `[Precedence(n)]` | class (operator node) | Operator precedence/associativity. Higher `n` binds tighter. |
| `[Repeat]` / `[Repeat(Min=0)]` | `AstNode`-derived parameter of a `[Rule]` method | List (repetition). `Min=1` (default) = one or more, `Min=0` = zero or more. Expands to `IReadOnlyList<T>`. |
| `[Skip(@"regex")]` | class (same as `[Grammar]`) | Skip pattern (whitespace, comments). |
Expand Down Expand Up @@ -44,7 +44,7 @@ The `ParseMode` named property selects the parser execution mode. Default is `La
- **One mode per class**: `Lalr` and `LightGlr` cannot be specified simultaneously on the same `[Grammar]` root (choose one). Combinable with `Mode` (dialect).
- **OnReduce constraint**: In LightGlr, `OnReduce` is invoked at reduce time even for undetermined branches. Therefore `OnReduce` (partial) must only set the node's own properties (`Name`/`Value`/`Span`, etc.) and must **not** mutate external state (`ScopedSymbolTable` / `DiagnosticBag`, etc.). Do semantic analysis in the second pass via `[Enter]`/`[Exit]` (Walker) to avoid leftover side effects from discarded branches.
- **Error repair (Corchuelo et al. ER1/ER2/ER3) known limitations**:
- **Inserted tokens have null value**: Tokens inserted by ER1 have no value (the user did not write them). `OnReduce` that accesses `Token.Text` on such a token will throw `NullReferenceException`. SimulateForward (ER3) validates with real reduce + try/catch to reject candidates that throw, but fork divergence means zero risk is not guaranteed. Write `OnReduce` null-safe.
- **Inserted tokens have empty text + estimated Span**: Tokens inserted by ER1 are `BasicToken("", ...)` (empty text) since the user did not write them. The Span is interpolated from surrounding tokens (prev token's End ~ next token's Start). `Token.Text` is empty string `""`, so `int.Parse("")` throws `FormatException`, but ER3 SimulateForward validates with real reduce + try/catch to reject such candidates.
- **N=3 and costs are fixed**: Forward move symbols `N=3`, insert cost=1/delete cost=2 are hardcoded. The Corchuelo paper recommends per-language tuning; not yet supported.
- **Single-pass repair (no recursion)**: The original Corchuelo applies ER1/ER2/ER3 recursively; this implementation applies one round only. Consecutive errors are repaired one at a time on subsequent dead states.
- **SimulateForward checks the first path only**: Does not fork at conflict cells during simulation, so full agreement with production fork paths is not guaranteed.
Expand Down Expand Up @@ -113,10 +113,48 @@ Attach to a `Token` parameter of a `[Rule]` method to specify the lexical rule (
Named properties:

- `Priority` — lexer priority (higher wins). Used to resolve when several tokens match the same input.
- `Kind` — token category (string). Set on `Token.Kind`. Checkable in OnReduce/OnAccepted via `token.Kind`.

```csharp
[Token(@"[A-Za-z_]\w*", Priority = 0)] // identifier (low priority)
[Token(@"if", Priority = 1)] // keyword if (high priority, beats identifier)
[Token(@"[0-9]+", Kind = "number")] // number literal (Kind = "number")
[Token(@"\+", Kind = "operator")] // operator (Kind = "operator")
```

### Token properties

Generated `Token` instances have the following properties:

| Property | Type | Description |
|---|---|---|
| `Text` | `string` | Matched text |
| `Span` | `SourceSpan` | Source range (position/line/column) |
| `Kind` | `string?` | Category from `[Token]`/`[Pattern]`'s `Kind` |
| `IsInserted` | `bool` | Whether inserted by ErrorRepair |

```csharp
partial void OnReduce(SemanticContext ctx)
{
if (Num.Kind == "number" && !Num.IsInserted)
Value = int.Parse(Num.Text);
}
```

### Token-derived types

A `Token` subclass can be used as a `[Rule]` parameter type. The generator reconstructs the derived type from `BasicToken`, carrying over `Text` + `Kind` + `IsInserted`.

```csharp
public sealed class NumberToken : Token
{
public int Value { get; }
public NumberToken(string text) : base(text, default) { Value = int.Parse(text); }
}

[Rule]
public static void Num([Token(@"[0-9]+")] NumberToken num) { }
// at reduce: new NumberToken(basicToken.Text) + Kind/IsInserted copied
```

## `[Precedence]`
Expand Down
53 changes: 52 additions & 1 deletion docs/en/semantic-analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ AstFirst offers three ways to author semantic logic (they compose):

| Approach | Timing | Where to write | Use case |
|---|---|---|---|
| `partial void OnReduce(ctx)` | Pass 1, at reduce (bottom-up) | Inside each node class | Node-local syntactic work (`Name = Tok.Text`, `Span` computation) |
| `partial void OnReduce(ctx)` | Pass 1, at reduce (bottom-up) | Inside each node class | Node-local syntactic work (`Name = Tok.Text`, `Span` computation). ctx is read-only |
| `partial void OnAccepted(ctx)` | After reduce, route determined | Inside each node class | Post-determination work. ctx is writable (declarations/diagnostics allowed) |
| `[OnReduce]` / `[Enter]` / `[Exit]` attributes | Pass 1 (OnReduce) / Pass 2 (Enter/Exit) | Inside the `[Grammar]` root class ★ recommended | Grammar-wide semantics (declaration, resolution, type checks). The ctx cast is injected automatically |
| `IOnSecondPassEnter` / `IOnSecondPassExit` | Pass 2 (top-down) | Inside each node class (interface impl) | Backward compatible. Attribute style recommended |

Expand Down Expand Up @@ -45,6 +46,56 @@ public sealed partial class Decl : MyLang

`[OnReduce]` and partial `OnReduce` **coexist** (partial `OnReduce` runs first, then `[OnReduce]`).

## OnAccepted — route-determined callback

`partial void OnAccepted(ctx)` is called when the interpretation of a node is **determined**.

### Timing

| Mode | When called |
|---|---|
| **LALR** (default) | Right after reduce (single stack = immediately determined = just after `OnReduce`) |
| **LightGlr** | When forked candidates converge to one (may be later than `OnReduce`) |

### Difference from OnReduce

| | `OnReduce` | `OnAccepted` |
|---|---|---|
| **ctx** | `SemanticContext` (read-only) | User's ctx type (writable) |
| **Declarations/diagnostics** | Not allowed | Allowed |
| **Timing** | At reduce (interpretation may be undetermined) | Route determined |
| **LALR** | Per reduce | Right after each reduce |
| **LightGlr** | Once per fork candidate | Only for the surviving candidate |

### Usage

```csharp
public sealed partial class MyDecl : MyLang
{
public string Name { get; private set; } = "";
[Rule] public static void DeclRule([Token("[A-Za-z]+")] Token name, MyCtx ctx) { }

// At reduce: node-local initialization only (ctx is read-only)
partial void OnReduce(SemanticContext ctx)
{
Name = NameTok.Text;
Span = NameTok.Span;
}

// Route determined: ctx writes are allowed
partial void OnAccepted(MyCtx ctx)
{
// Declare symbol only after this interpretation is confirmed
ctx.WritableSymbols.TryDeclare(Name, Span, null, out _);
}
}
```

### When to use OnAccepted

- **OnReduce is enough** (most cases): setting `Name`, `Value`, computing `Span` — node-local work.
- **OnAccepted is useful**: when you want declarations/diagnostics only after forked candidates converge in LightGlr mode. In LALR mode, it runs right after `OnReduce`, so `[Enter]`/`[Exit]` (2nd-pass Walker) is generally more appropriate.

## SemanticContext injection

Declare a `SemanticContext`-derived parameter on a `[Rule]` and the generator injects `ctx` from the parser (no attribute needed — decided by **type**). `ctx.Symbols` (`ScopedSymbolTable`) and `ctx.Diagnostics` (`DiagnosticBag`) are available. `BasicSemanticContext` also provides `Types` (`TypeContext`) by default.
Expand Down
42 changes: 40 additions & 2 deletions docs/ja/grammar-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ AstFirst では C# のクラスと属性で文法を書く。Generator がコン
|---|---|---|
| `[Grammar]` | クラス | 文法の開始記号(ルート非終端)。Generator の抽出開始点。`Mode` で複数方言を切り替え。 |
| `[Rule]` | static メソッド | 生成規則。メソッドの**引数**が右辺。1クラスに複数置ける(後述)。 |
| `[Token(@"regex")]` / `[Pattern(@"regex")]` | `[Rule]` メソッドの `Token` 引数 | 字句ルール(正規表現)。`Priority` でレクサ優先度。 |
| `[Token(@"regex")]` / `[Pattern(@"regex")]` | `[Rule]` メソッドの `Token` 引数 | 字句ルール(正規表現)。`Priority` でレクサ優先度、`Kind` でトークン種別。 |
| `[Precedence(n)]` | クラス(演算ノード) | 演算子優先度/結合性。`n` が大きいほど高優先。 |
| `[Repeat]` / `[Repeat(Min=0)]` | `[Rule]` メソッドの `AstNode` 派生引数 | リスト(繰り返し)。`Min=1`(既定)= 1回以上、`Min=0` = 0回以上。`IReadOnlyList<T>` に展開。 |
| `[Skip(@"regex")]` | クラス(`[Grammar]` と同じ) | スキップパターン(空白・コメント等)。 |
Expand Down Expand Up @@ -44,7 +44,7 @@ public abstract partial class Expr : AstNode { }
- **1 クラス 1 モード**: `Lalr` と `LightGlr` を同じ `[Grammar]` ルートに同時指定はできない(1 モード選択)。`Mode`(方言)とは併用可。
- **OnReduce の制約**: LightGlr では未確定の分岐でも reduce 時に `OnReduce` が呼ばれる。そのため `OnReduce`(partial)は**ノード自身のプロパティ設定(`Name`/`Value`/`Span` 等)のみ**とし、外部の mutable 状態(`ScopedSymbolTable` / `DiagnosticBag` 等)の変更を行ってはならない。意味解析は 2 パス目の `[Enter]`/`[Exit]`(Walker)で行うこと(破棄された分岐の副作用が残るのを防ぐ)。
- **エラー修復 (Corchuelo et al. ER1/ER2/ER3) の既知の制限**:
- **挿入トークンは値 null**: ER1 で補完されたトークンはユーザーが書いていないため値が `null`。これを子に持つノードの `OnReduce` で `Token.Text` 等を呼ぶと `NullReferenceException` になる。修復検証 (ER3 SimulateForward) で実 reduce を try/catch して例外を出す候補は弾くが、fork 差により本番で漏れる可能性がゼロではない。`OnReduce` は null 安全に書くことが望ましい
- **挿入トークンは空文字 + 推測 Span**: ER1 で補完されたトークンはユーザーが書いていないため `BasicToken("", ...)` (空文字) になる。Span は前後のトークンから推測して補間される (前トークンの End 〜 次トークンの Start)。`Token.Text` は空文字 `""` なので `int.Parse("")` 等は `FormatException` を投げるが、ER3 SimulateForward で実 reduce を try/catch して例外を出す候補は弾く
- **N=3・コスト固定**: ER3 の Forward move 確認シンボル数 `N=3`、挿入コスト=1/削除コスト=2 は固定値。Corchuelo 論文では言語に応じたチューニングを推奨しているが、本実装では未対応。
- **1 回修復 (再帰なし)**: Corchuelo 本来は ER1/ER2/ER3 を再帰的に適用するが、本実装は1回の ER1/ER2 + ER3 のみ。連続エラーは次の dead で順次修復される。
- **SimulateForward は最初の経路のみ確認**: コンフリクトセルでも fork せず最初の shift/reduce のみ追うため、本番の fork 経路との完全一致は保証しない。
Expand Down Expand Up @@ -113,10 +113,48 @@ public sealed partial class BinaryExpr : Expr
名前付きプロパティ:

- `Priority` — レクサ優先度(大きいほど高優先)。同じ入力で複数トークンが受理した際の解決に使う。
- `Kind` — トークン種別(文字列)。`Token.Kind` に設定される。OnReduce/OnAccepted で `token.Kind` で判定可能。

```csharp
[Token(@"[A-Za-z_]\w*", Priority = 0)] // 識別子(低優先)
[Token(@"if", Priority = 1)] // キーワード if(高優先、識別子に勝つ)
[Token(@"[0-9]+", Kind = "number")] // 数字リテラル (Kind = "number")
[Token(@"\+", Kind = "operator")] // 演算子 (Kind = "operator")
```

### Token のプロパティ

生成された `Token` は以下のプロパティを持つ:

| プロパティ | 型 | 説明 |
|---|---|---|
| `Text` | `string` | マッチした文字列 |
| `Span` | `SourceSpan` | ソース上の範囲 (位置・行・列) |
| `Kind` | `string?` | `[Token]`/`[Pattern]` の `Kind` で指定した種別 |
| `IsInserted` | `bool` | ErrorRepair で挿入されたトークンか |

```csharp
partial void OnReduce(SemanticContext ctx)
{
if (Num.Kind == "number" && !Num.IsInserted)
Value = int.Parse(Num.Text);
}
```

### Token 派生型

`Token` の派生クラスを `[Rule]` の引数に使える。Generator が `BasicToken` から派生型を再構築する際、`Text` + `Kind` + `IsInserted` を引き継ぐ。

```csharp
public sealed class NumberToken : Token
{
public int Value { get; }
public NumberToken(string text) : base(text, default) { Value = int.Parse(text); }
}

[Rule]
public static void Num([Token(@"[0-9]+")] NumberToken num) { }
// reduce 時: new NumberToken(basicToken.Text) + Kind/IsInserted コピー
```

## `[Precedence]`
Expand Down
53 changes: 52 additions & 1 deletion docs/ja/semantic-analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ AstFirst では意味解析ロジックを3つの方法で書ける(併用可

| 方法 | タイミング | 記述場所 | 用途 |
|---|---|---|---|
| `partial void OnReduce(ctx)` | 1パス目・reduce 時(ボトムアップ) | 各ノードクラス内 | ノード局所の構文処理(`Name = Tok.Text`・`Span` 計算) |
| `partial void OnReduce(ctx)` | 1パス目・reduce 時(ボトムアップ) | 各ノードクラス内 | ノード局所の構文処理(`Name = Tok.Text`・`Span` 計算)。ctx は読み取り専用 |
| `partial void OnAccepted(ctx)` | reduce 後・ルート確定時 | 各ノードクラス内 | ルート確定後の処理。ctx は書き込み可能(宣言・診断の追加が可能) |
| `[OnReduce]` / `[Enter]` / `[Exit]` 属性 | 1パス(OnReduce)・2パス(Enter/Exit) | `[Grammar]` ルートクラス内 ★推奨 | 文法全体の意味処理(宣言登録・参照解決・型チェック)。ctx キャストが自動注入される |
| `IOnSecondPassEnter` / `IOnSecondPassExit` | 2パス目(トップダウン) | 各ノードクラス内(インターフェース実装) | 後方互換。属性方式推奨 |

Expand Down Expand Up @@ -45,6 +46,56 @@ public sealed partial class Decl : MyLang

`[OnReduce]` と partial `OnReduce` は**共存**する(partial `OnReduce` → `[OnReduce]` 属性の順)。

## OnAccepted — ルート確定時のコールバック

`partial void OnAccepted(ctx)` は、そのノードの解釈が**確定した時**に呼ばれるコールバックです。

### タイミング

| モード | 呼ばれるタイミング |
|---|---|
| **LALR** (既定) | reduce の直後 (単一スタックなので即確定 = `OnReduce` の直後) |
| **LightGlr** | fork した複数候補が1つに収束した時 (`OnReduce` より遅れる場合あり) |

### OnReduce との違い

| | `OnReduce` | `OnAccepted` |
|---|---|---|
| **ctx** | `SemanticContext` (読み取り専用) | ユーザーの ctx 型 (書き込み可) |
| **宣言・診断の追加** | ✗ | ✓ |
| **タイミング** | reduce 時 (解釈未確定の可能性) | ルート確定時 |
| **LALR での呼ばれ方** | reduce の都度 | reduce の直後 (毎回) |
| **LightGlr での呼ばれ方** | fork の各候補で1回ずつ | 収束した候補のみ |

### 使い方

```csharp
public sealed partial class MyDecl : MyLang
{
public string Name { get; private set; } = "";
[Rule] public static void DeclRule([Token("[A-Za-z]+")] Token name, MyCtx ctx) { }

// reduce 時: ノード局所の初期化のみ (ctx は読み取り専用)
partial void OnReduce(SemanticContext ctx)
{
Name = NameTok.Text;
Span = NameTok.Span;
}

// ルート確定時: ctx の書き込みが可能
partial void OnAccepted(MyCtx ctx)
{
// この解釈が確定した後にシンボルを宣言
ctx.WritableSymbols.TryDeclare(Name, Span, null, out _);
}
}
```

### いつ OnAccepted を使うべきか

- **OnReduce で足りる場合** (ほとんど): `Name` や `Value` の設定、`Span` 計算など、ノード局所の処理は `OnReduce` で十分。
- **OnAccepted が有用な場合**: LightGlr モードで fork した複数候補が収束した後にのみ宣言や診断を追加したい場合。LALR モードでは `OnReduce` の直後に呼ばれるため、基本的には `[Enter]`/`[Exit]` (2パス目 Walker) の方が適している。

## SemanticContext の注入

`[Rule]` の引数に `SemanticContext` 派生型を宣言すると、Generator がパーサから `ctx` を注入する(属性は不要、**型**で判定)。`ctx.Symbols`(`ScopedSymbolTable`)と `ctx.Diagnostics`(`DiagnosticBag`)が使える。`BasicSemanticContext` はさらに `Types`(`TypeContext`)を標準装備。
Expand Down
Loading
Loading