ユーザー: pegで親切なエラーメッセージを作る方法について教えて
AIの思考プロセス
Analyzing PEG Error Messages
I'm currently focused on how PEG's greedy nature and prioritized choice impact error message design. It's a key challenge. I'm considering scenarios where the parser's choices lead to unexpected outcomes, particularly for developers new to the intricacies of PEG. The goal is to make these error messages as helpful as possible, guiding users towards understanding and fixing parser issues.
Improving Error Reporting
I've been drilling down on how to improve PEG error reporting. The challenge is pinpointing the right location of an error given backtracking. I'm prioritizing "Furthest Failure" tracking to identify the most deeply parsed point before a total backtrack. I'm also considering expected vs. found errors and using rule metadata for descriptive labels. Using sentinels/cut operators to prevent excessive backtracking, and how to enable error recovery are also high priority items.
親切なエラーメッセージを作るための5つの主要なテクニックを解説します。
PEGにおける最も一般的で強力な手法です。
- 仕組み: パーサが解析中に「入力のどこまで読み進めたか」の最大値を常に記録しておきます。解析が最終的に失敗したとき、その最大値(最も深く読み進めた場所)をエラー位置として報告します。
- なぜ有効か: PEGは失敗するとバックトラックしますが、失敗するまでに「最も長くマッチしたパス」が、ユーザーの意図に最も近いはずだからです。
パーサが「何を探していたか」を人間にわかる言葉にする必要があります。
- 方法: 文法定義の中で、抽象的なルールにラベルを付けます。
- 例(PEG.js風):
このようにすると、エラー時に「Expected [0-9] but 'a' found」ではなく、**「Expected 数値 but 'a' found」**と表示できるようになります。
integer "数値" = [0-9]+ identifier "識別子" = [a-z]+
PEGの欠点は、一度マッチしかけたのに後半で失敗した際、別の選択肢(/)へ戻ってしまうことです。これによりエラー箇所が分かりにくくなります。
- 方法: 「ここを過ぎたらもう後戻り(バックトラック)させない」というポイントを作ります。
- 例:
if_statement = "if" "(" expression ")" blockもし"if ("まで解析して、その後のexpressionが間違っていた場合、他のルールを試すのではなく、即座に「if文の式が不正です」というエラーを投げます。 - 実装: 多くのライブラリでは、セマンティックアクション(コード実行)の中で明示的に例外をスローすることで実現します。
最遠地点で期待されていたトークンをすべて列挙すると、「数値、演算子、改行、コメント...」のように候補が多すぎて親切ではありません。
- 戦略:
- 優先順位をつける(キーワードよりも識別子を優先するなど)。
- 重複を削除する。
- 「文法的なトークン」のみを表示し、内部的な中間ルールは隠す。
エラー位置の行番号と列番号だけでなく、実際のコードの断片を表示するのが「親切」の極みです。
- 表示例:
Error: 期待されたのは ';' ですが '}' が見つかりました。 line 12, column 5: 11 | x = 10 > 12 | } | ^ - 実装のコツ:
- 入力テキストを改行で分割し、エラー行の前後1〜2行を取り出す。
- キャレット(
^)を使って場所を指し示す。
あえて「間違った書き方」を文法に組み込む手法も有効です。
assignment = id "=" expression
/ id "=" !expression { error("式が未完成か、不正な記号があります") }このように「よくある間違い」を明示的にキャッチすることで、具体的で分かりやすいアドバイス(「,を忘れていませんか?」など)を出すことができます。
もしこれから作る、あるいは改善したいのであれば、以下のライブラリがエラーハンドリングの参考になります。
- PEG.js (or Peggy):
expectedの情報を自動で集約してくれるため、初心者でも扱いやすいです。 - Chevrotain: PEGではありませんが、エラーリカバリ(間違えても解析を続行する機能)が非常に強力で、IDEのような親切なエラーを出す際の参考になります。
結論として、まずは「最遠到達地点の記録」と「ルールへの日本語ラベル付与」から始めるのが最もコスパが良い改善策です。
(返答終了理由: STOP)
ユーザー: pythonとかはどうしてるの?
AIの思考プロセス
Investigating PEG's Implementation
I'm now focusing on how Python uses PEG parsers (from 3.9 onwards) for user-friendly error messages. My investigation started with the history of Python's parsing approach, noting its switch from LL(1). I aim to understand the specifics of PEG implementation in Python for generating better error feedback. I'm focusing on the PEG usage in generating the parse error messages.
Analyzing Error Reporting Methods
I'm now diving into the specifics of Python's enhanced error messages. I've noted that the PEG parser, introduced in 3.9, offered more flexibility than LL(1) and it's allowing for much better error feedback. I've pinpointed "invalid" rules within Grammar/python.gram as a significant technique for catching common syntax mistakes. Now I'm focusing on the implementation details, including techniques that are akin to PEG's "cut". I intend to follow the implementation details.
実はPythonは、3.9からパーサ(構文解析器)をLL(1)からPEGに切り替えました。そして、3.10や3.11で劇的にエラーメッセージが分かりやすくなったのは、PEGの仕組みをうまく活用しているからです。
Pythonがどのようにして「親切なエラー」を実現しているのか、その裏側にある3つの主な戦略を解説します。
PythonのPEG文法ファイル(python.gram)には、正しい構文だけでなく、「初心者がやりがちな間違い」をキャッチするための専用ルールが大量に書き込まれています。
これを "Invalid rules" と呼びます。
- 仕組み: 本来の正しい構文の解析に失敗しそうになったとき、あえて「間違い用のパターン」にマッチさせ、独自のメッセージを表示します。
- 具体例:
このように、「# 簡略化したイメージ if_stmt: | 'if' condition ':' block | 'if' condition !':' { raise_error("if文の最後に ':' が必要ですよ!") }ifがあって、条件があるのに、のちに:がない場合」というパターンを明示的に定義しておくことで、ピンポイントなアドバイスが可能になっています。
Python 3.10以降では、エラー箇所を ^ だけでなく ~~~~ でハイライトするようになりました。
- 仕組み: PEGパーサがバックトラック(やり直し)を行う際、単に「失敗した」と捨てるのではなく、「どのトークンで」「どのような理由で」失敗したかのメタデータを内部で保持し続けます。
- 工夫: 最終的に解析が止まった場所が、単なる「文法ミス」なのか「括弧の閉じ忘れ」なのかを判定するために、スタック(積み上げ)情報を詳しく解析しています。
Pythonのパーサは、文法的にエラーを見つけた瞬間、即座にメッセージを出すのではなく、**「より良いメッセージを作るための再調査」**を行うことがあります。
- 第一段階(解析): PEGパーサが「ここがおかしい」という場所を見つける。
- 第二段階(専門家による診断): その周辺のトークンを調べ、例えば「
print "hello"と書いているなら、Python 2の書き方だな」とか「変数名が似ているから打ち間違い(Did you mean...)を提案しよう」といった「親切心」を付け加えます。
Pythonには match や case といったキーワードがありますが、これらは「変数名」としても使えてしまいます(ソフトキーワード)。
これをPEGで扱う際、もし match を変数として使っているのに match 文(パターンマッチング)として解析しようとして失敗した場合、PEGは自動的にバックトラックして「変数」として読み直します。この「柔軟なやり直し」ができるPEGの性質を活かしつつ、**「ここまでは確実にmatch文として読んでいたのに、ここで辻褄が合わなくなった」**というポイントを正確に掴むように設計されています。
もしあなたがPEGでパーサを作るなら、Pythonの手法を以下のように取り入れられます:
invalid_ルールの追加: 「よくある間違い」を文法定義の最後に書いておき、そこに到達したら専用のエラーを投げる。- コンテキストの保持: エラー時には「今どのルールの解析中だったか(if文なのか、関数定義なのか)」を例外オブジェクトに持たせる。
- 後処理: パーサが止まった位置の周辺文字列を見て、
Levenshtein距離(編集距離)などで「もしかしてこれ?」と提案する処理をパーサの外側に書く。
Pythonのコア開発者は「PEGはバックトラックがあるからエラーメッセージが不正確になりやすい」という弱点を、**「バックトラックする前に間違いをキャッチするルールを書き込む」**という力技と工夫で解決したといえます。
(返答終了理由: STOP)
ユーザー: なるほどな。文法の定義をエラー部分まで拡張したのか
AIの思考プロセス
Analyzing Parser Design
I've been examining Python's PEG parser, specifically how error handling is integrated. My core insight is that the grammar definitions themselves are extended to include error-specific components. This 'trap' approach allows for more graceful error recovery. I'm now exploring the underlying mechanics of why this works and weighing the benefits against the potential costs and trade-offs.
これを専門用語で 「Error Trees」 や 「Invalid Productions(不正な生成規則)」 と呼んだりします。
この手法の「何がすごくて、何が大変なのか」を少し深掘りすると、自分でパーサを作る際のヒントになります。
PEGは上から順番に試していくので、普通はこうなります。
if_stmt(正しいif文)を試す → 失敗for_stmt(正しいfor文)を試す → 失敗- ...
- 全部失敗したので、**「最後にちょっとだけ試した何かの場所」**でエラーを出す(これが分かりにくい原因)。
Pythonの「拡張された文法」ではこうなります。
if_stmt(正しいif文)を試す → 失敗invalid_if_stmt(わざと間違えたif文)を試す → マッチ!- マッチした瞬間に「if文のコロンを忘れてるよ!」と確信を持ってエラーを出せる。
実際のPythonのソースコード(python.gram)を見ると、以下のような記述が大量にあります。
# 正しい代入文
assignment:
| NAME '=' expression
| NAME ':' expression '=' expression
# 「間違い」をキャッチするための定義
invalid_assignment:
| NAME '=' !expression { raise_syntax_error("式の右側が空っぽだよ") }
| NAME ':=' expression { raise_syntax_error("ここでは代入演算子 ':=' は使えないよ") }
このように、「人間がやりがちなミス」を予測して、先回りして網を張っているわけです。
メリット:
- 圧倒的に親切: ユーザーの意図を汲み取ったメッセージが出せる。
- 誤魔化しがない: 「ここが違う」とピンポイントで指せる。
デメリット(開発者の苦労):
- 文法定義が肥大化する: 本来の文法の2倍くらいの量になることがあります。
- メンテが大変: 正しい文法を修正したとき、エラー用の文法も直さないと、変なエラーメッセージが出るようになってしまいます。
- 予期せぬマッチ: エラー用のルールが強力すぎると、別の正しい文法を「エラー」と誤認してしまうリスクがある(ので、書く順番が非常に重要)。
Pythonのやり方は、**「パーサを賢くするのではなく、文法定義に『教育的指導』を書き込む」**という、非常に泥臭くも効果的なアプローチです。
もし自分でPEGを作るなら、まずは一番間違いやすい1〜2箇所(例:セミコロンの忘れ、括弧の閉じ忘れ)だけに対して、この「エラー用ルール」を足してみるのが一番効果を実感できると思います。
(返答終了理由: STOP)
ユーザー: 最遠到達地点を覚えておくというアドバイスもここから来てる?
AIの思考プロセス
Analyzing Error-Handling Strategy
I'm currently focused on the "record the furthest failure point" approach. I'm thinking about its connection to Python's grammar extension, specifically, if the technique is derived from the Python's methodology of extending the grammar with dedicated error rules. It is a key PEG technique, and I am considering it in context of backtracking.
少し整理すると、PEGのエラー対策には**「守りの技術」と「攻めの技術」**の2段階があると言えます。
これはPythonがPEGを採用するずっと前から、PEGというアルゴリズムの**「最低限の義務」**として知られていた手法です。
- なぜ必要か: PEGはバックトラックする際、失敗した理由をすべて捨ててしまいます。何もしないと「解析に失敗しました(位置:0文字目)」という絶望的なエラーになります。
- 役割: 「どこで間違えたか分からないけど、とりあえず一番奥まで進めた場所がここだから、この周辺が怪しいよ」と伝える、最低限のセーフティネットです。
Pythonの開発チームは、この「最遠到達地点」だけでは満足しませんでした。
- なぜ満足しなかったか: 最遠地点だけだと、「
:が期待されていましたが[が見つかりました」のような、機械的なメッセージしか出せないからです。 - 役割: 「最遠地点にたどり着く前に、あえて間違いをキャッチして、人間に寄り添った理由を説明する」という、積極的な診断です。
現代の優れたパーサ(Python含む)では、以下のようにこの2つを組み合わせています。
- 基本戦略: 解析中は常に「最遠到達地点」を更新し続ける。
- 特攻戦略: 「エラー用ルール」にマッチした場合は、最遠地点に関係なく、即座に具体的なエラーを表示して終了する。
- フォールバック: もしエラー用ルールにも引っかからず、すべての選択肢が全滅した場合は、最後に記録していた「最遠到達地点」を表示する。
あなたがパーサを作る際のアドバイスとしては、以下のステップになります。
- ステップ1(守り): まず「最遠到達地点」を記録する機能を実装する。これで「どこがエラーか全く不明」という状態は脱出できます。
- ステップ2(攻め): ユーザーがよく出すエラー(Pythonの例のようなコロン忘れなど)に対して、個別に「エラー用ルール」を書き足していく。
「最遠到達地点」は「どこで?」を教え、「エラー用ルール」は「なぜ・どうやって直すか?」を教えるためのもの、と考えると分かりやすいかもしれません。
Pythonはこの「ステップ2」を極限まで突き詰めた結果、あのような親切なメッセージを実現した、というわけです。
(返答終了理由: STOP)
ユーザー: 攻めのステップを徐々に埋めていけば便利になると思いますが、うまく同じようなエラーを同じようなエラーidのような何かに変換することは可能でしょうか?ただし注意してほしいのはこのエラーは未知の未対応の箇所で起きるものだとします。
AIの思考プロセス
Refining Error Handling
I'm focusing on refining the error handling in the PEG parser, specifically the proactive (aggressive) approach. My goal is to map similar errors to a consistent error ID or category. The main challenge is ensuring this works correctly for unknown or unhandled error locations, addressing unforeseen mistakes that might arise during parsing.
Categorizing Parse Failures
I'm now exploring how to meaningfully categorize PEG parsing failures, even without explicitly defined "invalid rules." I'm evaluating several strategies. First, semantic grouping via rule hierarchy offers contextual insights. Second, I'm considering distance-based classification by identifying common ancestors of multiple failures at the furthest point. Third, a heuristic message strategy would pattern-match on expected tokens. Finally, I'm considering error recovery.
これは非常に賢い設計思想です。すべてに対して「専用ルール」を書くのは限界があるため、**「文法構造からエラーの性質を推論する」**というアプローチを取ることになります。
以下の3つの戦略を組み合わせることで実現可能です。
PEGの各ルールに「カテゴリ(エラーID)」を紐付けます。たとえ具体的な間違いの内容が分からなくても、**「どの大きな目的のルールで失敗したか」**で分類します。
- 実装イメージ:
各ルールに属性を持たせます。
expressionルール →ID: ERR_EXPR(式のエラー)statementルール →ID: ERR_STMT(文のエラー)type_defルール →ID: ERR_TYPE(型定義のエラー)
解析が失敗したとき、**「最遠地点に到達したときに、呼び出しスタックの最も上にあった主要ルール」**のIDをエラーIDとして採用します。これにより、未知のエラーでも「式の中のどこかがおかしい(ERR_EXPR)」と分類できます。
PEGで失敗した最遠地点では、「次に何が来る可能性があったか」のリスト(Expected Tokens)が得られます。この**リストの組み合わせをハッシュ化(またはパターンマッチ)**することで、未知のエラーにIDを振ることができます。
- 例:
- 期待されるのが
[')', ']', '}']のいずれかだった場合 → 「閉じ括弧の欠如または不一致」としてERR_BRACKET_MISMATCHを割り当てる。 - 期待されるのが
['if', 'for', 'while', 'NAME']などの文の開始トークンだった場合 → 「文法的に不正な開始」としてERR_INVALID_STMT_STARTを割り当てる。
- 期待されるのが
このように、個別の場所(どこで)ではなく、**「何が足りなかったか(何の集合か)」**で分類すると、未対応の箇所でも共通のIDを振れます。
主要なセクションの最後に、「何でもいいから1文字消費してエラーを投げる」というルールを置きます。これが「未知のエラー」をキャッチする網になります。
# 文(Statement)の定義
statement = if_stmt
/ for_stmt
/ assignment
/ invalid_generic_statement # ← ここが「未知」の受け皿
# 未知の文エラーをキャッチ
invalid_generic_statement = (!statement_boundary .)+ {
report_error(id: "ERR_UNKNOWN_STMT", msg: "文の形式が正しくありません")
}
statement_boundary = ";" / "\n" / "}" # 文の区切り
このように、**「文の区切りまでは読み飛ばすが、既存のルールにはどれもマッチしなかったもの」**を一つの ID (ERR_UNKNOWN_STMT) に集約します。
具体的に実装するなら、以下のようなロジックをパーサに組み込むのが理想的です。
- コンテキスト(文脈)を保持:
パーサが
function_definitionルールに入ったら、フラグを立てる。 - 失敗時の情報を収集: 最遠地点での「期待されるトークンリスト」と「現在のコンテキスト」を取得。
- エラーIDの判定(ヒューリスティック):
- もし期待リストが
[':']だけなら →ERR_MISSING_COLON - もしコンテキストが
IN_EXPRESSIONなら →ERR_EXPR_SYNTAX - どちらにも当てはまらない、全くの未知なら →
ERR_GENERIC_SYNTAX
- もし期待リストが
- 記録と改善:
ERR_GENERIC_SYNTAXが発生したログを収集し、後からそのパターンを「攻めのルール」に昇格させて専用IDを振る。
この手法の利点は、**「最初はざっくりとしたID(ERR_EXPRなど)で返しつつ、ユーザーの入力傾向を見ながら、後からピンポイントなIDを切り出していける」**という拡張性にあります。
(返答終了理由: STOP)
ユーザー: なるほど今までの例を元にjsonの親切なパーサーの実装はできますか?
AIの思考プロセス
Designing a Friendly Parser
I'm currently focused on designing a JSON parser that is exceptionally user-friendly. My goal is to use Parsing Expression Grammar (PEG) principles to handle errors gracefully and provide informative feedback. I'm thinking of providing a high-level conceptual implementation or a common language context, like Python or JavaScript.
Refining Error Handling
I'm now diving deep into specific error scenarios. I am planning for detailed, actionable feedback, including pinpointing the furthest point of failure and using named rules for clarity. I'm focusing on common pitfalls like trailing commas, missing quotes, and single quotes. My plan is to define "invalid" rules within the PEG structure to gracefully catch these mistakes and provide contextual IDs and displays. I'm aiming for a readable pseudo-PEG notation and illustrating "fall through" error logic.
JSONは構造がシンプルなので、**「期待されるもの(正しい文法)」と「やりがちなミス(エラー用ルール)」**の対比が非常に作りやすいです。
ここでは、概念をコード化した例(擬似コード/Peggy.js風)を示します。
ただの object ではなく、**「カンマを忘れた場合」や「末尾に余計なカンマがある場合」**を個別にキャッチするルールを混ぜます。
// --- 基本構造 ---
JSON_Text = _ value:Value _ { return value; }
Value = Object / Array / String / Number / True / False / Null
// --- オブジェクトの定義 ---
Object
= "{" _ pairs:PairList? _ "}" { return Object.fromEntries(pairs || []); }
/ "{" _ PairList _ ! "}" { error("ERR_BRACKET_OBJ", "オブジェクトの閉じ括弧 '}' がありません。") }
PairList
= first:Pair rest:(_ "," _ Pair)* _ ","? {
// 末尾カンマをチェック(JSONでは本来NG)
if (input.peek(-1) === ",") {
// ここで警告を出したり、エラーIDを振る
reportWarning("ERR_TRAILING_COMMA", "末尾に余計なカンマがあります。");
}
return [first, ...rest.map(r => r[3])];
}
/ first:Pair (_ Pair)+ {
error("ERR_MISSING_COMMA", "要素の間にカンマが必要です。");
}
Pair
= key:String _ ":" _ val:Value { return [key, val]; }
/ key:String _ ! ":" _ val:Value { error("ERR_MISSING_COLON", "キーの後に ':' が必要です。"); }
/ key:UnquotedKey _ ":" { error("ERR_UNQUOTED_KEY", `キー '${key}' はダブルクォートで囲む必要があります。`); }
// --- 配列の定義 ---
Array
= "[" _ elements:ElementList? _ "]" { return elements || []; }
/ "[" _ ElementList _ ! "]" { error("ERR_BRACKET_ARR", "配列の閉じ括弧 ']' がありません。") }
ElementList
= first:Value rest:(_ "," _ Value)* _ ","? {
return [first, ...rest.map(r => r[3])];
}文法定義の最後に、**「何にもマッチしなかった時の最終手段」**を置きます。ここで「最遠到達地点」の情報を使い、未知のエラーにIDを振ります。
// パーサの外部(またはエラーハンドラ)での処理イメージ
function generateFriendlyError(error, inputText) {
const { location, expected, found } = error;
// 1. すでに文法内の「攻めのルール」でIDが振られている場合
if (error.id) return { id: error.id, message: error.message };
// 2. 未知のエラー(最遠地点での失敗)を分析
let errorId = "ERR_GENERIC_SYNTAX";
let hint = "文法が正しくありません。";
if (expected) {
const tokens = expected.map(e => e.description || e.text);
// 期待されるトークンの組み合わせからIDを推論
if (tokens.includes('":"')) {
errorId = "ERR_EXPECTED_COLON";
hint = "コロン ':' が抜けている可能性があります。";
} else if (tokens.includes('","') || tokens.includes('"]"')) {
errorId = "ERR_EXPECTED_COMMA_OR_BRACKET";
hint = "カンマまたは閉じ括弧が必要です。";
}
}
return {
id: errorId,
message: `${hint} (場所: ${location.start.line}行 ${location.start.column}列)`,
context: getCodeSnippet(inputText, location)
};
}このパーサに間違ったJSONを流し込むと、次のようなレスポンスが返るようにします。
入力: {"id": 1 "name": "test"}
出力:
[ERR_MISSING_COMMA] 2行目: 要素の間にカンマが必要です。
1 | {
> 2 | "id": 1 "name": "test"
| ^
入力: {"id": 1 # }
出力:
[ERR_GENERIC_SYNTAX] 1行目: 文法が正しくありません。ハッシュ記号 '#' は使用できません。
> 1 | {"id": 1 # }
| ^
- 「閉じ忘れ」を専用ルールにする:
PairListの後に}が無いパターンを明示的に書く。これで「最後まで読んだけど終わらなかった」という曖昧なエラーを防げます。 - UnquotedKey ルール:
[a-zA-Z0-9]+にマッチするルールをあえて作り、それにマッチしたら「クォートが必要だよ」というエラーを出すようにします。 - エラーIDの階層化:
ERR_BRACKET_*(括弧系)ERR_COMMA_*(カンマ系)ERR_UNEXPECTED_*(予期せぬトークン) という風に分類すると、後でログを見た時に「ユーザーがどこで躓いているか」を統計的に分析できるようになります。
このように、「正しい道から外れた瞬間に、どの脇道(エラー)に足を踏み入れたか」を判定する罠を文法の中に仕掛けておくのが、PEGで親切なパーサを作るコツです。
(返答終了理由: STOP)
ユーザー: 発生したタイミングで入力を記録し修整後の出力を記録しこれらを元にLLMなどに渡すようにすることで自動的に拡張部分を付与することはできないでしょうか?(負例のようなものを集めていくイメージ)
AIの思考プロセス
Extending Grammar Automatically
I'm focusing on automatically improving error handling in the PEG. The core idea is to collect failed inputs and their corresponding fixed outputs. I'm aiming to use a large language model to analyze these and generate extended grammar rules, effectively addressing those specific failure cases. It's about learning from errors and refining the grammar dynamically.
Refining Error Handling
I'm now zeroing in on an "offensive" strategy for error message improvement using negative examples. The plan is a 6-step process: detection, collection, correction, pattern synthesis, code generation, and integration. LLMs are key, especially in identifying errors and crafting PEG rules. I'm starting Phase A, which focuses on data collection via a feedback loop, logging inputs when GENERIC_ERROR occurs.
「負例(エラーになった入力)」と「修正後の正例」をペアにしてLLMに渡し、PEGの「invalidルール」を自動生成して文法を拡張し続けるサイクルは十分に可能です。
具体的にどのようなシステム構成にすればよいか、3つのステップで提案します。
パーサがエラーを吐いた際、単に終了するのではなく、以下の情報をセットでログに残します。
- Bad Input: エラーが発生した入力文字列。
- Error Context: 最遠到達地点の周辺、およびその時の「期待されていたトークン」。
- Fixed Input (修正後の出力): ユーザーが修正して最終的にパースが成功した文字列、またはLLMに「これを直して」と頼んで得られた正解。
これらをDBに蓄積し、同じような「Bad → Fixed」のパターンが一定数(例:3回以上)溜まったら、拡張のトリガーを引きます。
蓄積されたペアを元に、LLMに以下のようなプロンプトを投げます。
プロンプト例: 以下のJSONパーサの文法において、ユーザーがよくやる間違いのパターンが見つかりました。
間違いの例:
{"key" = "value"}(:の代わりに=を使っている) 正しい例:{"key": "value"}この間違いをピンポイントでキャッチし、"ERR_EQUAL_AS_COLON" というIDと「コロンの代わりにイコールが使われています」というメッセージを返す PEGの追加ルール を作成してください。既存の正しいルールを壊さないように注意してください。
LLMが生成したルールをそのまま本番に投入するのは危険(正しい構文までエラーにしてしまう可能性がある)なため、以下の「バリデーション」を自動で行います。
- 再現テスト: 新しいルールが、過去の「Bad Input」を正しくキャッチし、期待したエラーIDを出すか。
- 回帰テスト: 新しいルールを追加した状態で、既存の「正しいテストケース」がすべてパスするか。(← ここが最も重要)
- 優先順位の決定: PEGはルールの順番が重要なので、既存の「正しいルール」の直後に「新しいInvalidルール」を挿入する。
- 「未知のエラー」が「既知の親切なエラー」に育つ: 開発者がコードを書かなくても、ユーザーが間違えれば間違えるほどパーサが親切になっていきます。
- エッジケースの網羅: 開発者が想像もしなかった変な書き方(特定の国籍の人がやりがちな記号の間違いなど)にも対応できます。
- 過学習(Overfitting):
LLMが「その特定の入力」だけにマッチするルールを作ってしまうことがあります。正規表現のように、ある程度一般化されたルール(例:
"="だけでなく、前後のスペースも許容するなど)を出力させる指示が必要です。 - ルールの順序問題:
PEGは
/(選択)の左側が優先されるため、エラーキャッチの網を置く場所を間違えると、正しい構文を横取りしてしまうリスクがあります。- 対策: 基本的に「正しいルール」を先に試し、失敗した場合のバックトラック先に「エラー用ルール」を配置する構造をテンプレート化しておきます。
「エラーログを教師データにして、パーサの文法定義(ソースコード)を自己進化させる」 というのは、非常に強力な自動化戦略です。
まずは、「エラーが起きた時の入力」と「修正後の入力」をペアで保存するログ機能から実装を始めてみてはいかがでしょうか?それが溜まってきたら、LLMを使って「この差分をキャッチするPEGルールを書いて」と試行錯誤するフェーズに進めます。
(返答終了理由: STOP)
ユーザー: ああ、たしかに汎用性の低い文法が挿入されてしまうと困りすね。あとバックトラックであるはずなのに先にそちらに入ってはダメですね
AIの思考プロセス
Analyzing Rule Interactions
I'm currently focused on the perils of overly specific rules, potentially leading to overfitting. The order of rules, especially in a PEG context, is also critical; I'm pondering how error rules can inadvertently capture valid syntax if positioned incorrectly. A new avenue involves automatic generation of "invalid rules" using LLMs, informed by error logs to differentiate between bad and good inputs.
Refining Error Generation
I'm now delving into the intricacies of LLM-generated rules, particularly concerning their generality and order. To combat overfitting, I'm exploring template-based generation, abstraction of error "concepts", and a validation step. Regarding PEG order pitfalls, I'm focusing on strategies to prevent error rules from inadvertently shadowing valid syntax, especially using negative lookahead. Specifically, I am refining a design around placing the error rules last in the rule set.
これらを防ぎつつ、安全に「攻めのルール」を自動挿入するための、より実戦的なテクニックを3つ紹介します。
本番の解析用文法を直接書き換えるのではなく、**「エラーが起きた時だけ動く、エラー診断専用の文法」**を別に生成する手法です。
- 仕組み:
- メインのパーサが失敗する。
- 失敗した地点の周辺(例:前後20文字)を切り出す。
- **LLMが生成した「エラー診断専用の小さなパーサ」**にその断片を放り込む。
- メリット:
- メインの文法を一切汚さないので、安全性が100%保たれる。
- 「バックトラックで先にエラー用ルールに入ってしまう」という事故が物理的に起こらない。
もしメインの文法の中にエラー用ルールを組み込む場合は、「正しい構文ではないこと」を明示的に確認してからエラー判定に入るようにLLMに指示します。
- LLMへの指示イメージ:
「エラー用ルールを作るときは、必ず
!CorrectRule(正しい構文ではない)ことを確認してからマッチさせてください」 - 生成されるルールのイメージ:
このように# 正しい代入 assignment = identifier ":" value # 自動追加されたエラールール # 「:」ではない何かが続いている場合のみ、このエラーを判定する invalid_assignment = identifier !":" . { error("ERR_COLON", "コロンを忘れていませんか?") }!":"を入れることで、正しい構文を横取りするリスクを劇的に下げられます。
LLMが作ったルールが「特定の文字列(例:"id" = 1)」だけに反応するダメなルール(過学習)かどうかを、以下のテストで自動判定します。
- 汎用性テスト:
- LLMに「このエラーを抽象化して、正規表現やPEGルールにして」と頼む。
- 生成されたルールに対し、**「似たような別のダメな入力(例:
"name" = 2)」**を数パターン作ってぶつける。 - どれにもマッチするなら「汎用的である」と判定し、採用する。
- 干渉テスト(回帰テスト):
- 生成されたルールに対し、**「正しい入力(例:
"id": 1)」**をぶつける。 - もし誤ってマッチしてしまったら、そのルールは即座に却下(破棄)する。
- 生成されたルールに対し、**「正しい入力(例:
いきなりPEGファイルを書き換えるのはハードルが高いので、最初は**「エラー地点の指紋(Fingerprint)辞書」**を作るのが現実的かもしれません。
- エラー発生:
expected: [':'], found: '=' - ハッシュ化:
failed_at_pair_separator_with_equalというキーを作る。 - LLMに相談: 「この失敗パターンに対する親切なメッセージを考えて」
- 辞書保存:
key: {expected: ':', found: '='}, message: "コロンの代わりにイコールが..." - 次回以降: 同じ場所で同じトークンで失敗したら、LLMを通さず辞書からメッセージを出す。
これなら、**文法を汚さず、バックトラックの挙動も変えず、かつユーザーには「昨日より賢くなった」**と思わせるパーサが作れます。
この「辞書(データベース)方式」から始めて、パターンの共通項が見えてきたら、それを正式な invalid_ ルールとして文法に統合していく、という進化のさせ方が一番安全です。
(返答終了理由: STOP)
ユーザー: 今回の対話で分かったことをまとめて
AIの思考プロセス
Considering Error Handling in PEG
I'm currently focused on friendly error messages within PEG parsers, specifically looking at Python's approach to invalid rules and improved messages. The idea of expanding the grammar to incorporate error productions is also under consideration. Using "expect" directives seems promising, but I'm unsure of any implementation details.
Analyzing Error Messages in JSON
I'm now implementing the "invalid rules" concept within a JSON parser, to proactively address common errors. I'm exploring an LLM to auto-generate these rules from error logs and user corrections, envisioning a self-improving parser. I'm mindful of overfitting risks and grammar pollution, planning to isolate diagnosis from the core parsing logic.
PEGはバックトラックにより「どこで失敗したか」が隠蔽されがちですが、まずは以下の2点を徹底するのがスタートラインです。
- 最遠到達地点 (Furthest Failure): 解析が最も深く進んだ場所をエラー位置として特定する。
- ルールの人間化: 各ルールに「数値」「式」といった名前(ラベル)を付け、「
[を期待した」ではなく「配列を期待した」と言い換える。
Python 3.10以降が実現している「非常に親切なエラー」の正体は、「正しい文法」の中に「よくある間違いの定義」を組み込むという手法でした。
- Invalid Rules: 「ここでコロンを忘れたらエラー」という専用のルール(罠)を文法に書く。
- 先回り: エラーが起きてから考えるのではなく、起きそうな場所に「教育的指導」をあらかじめ配置する。
すべての間違いを予見することは不可能なため、未知のエラーを整理する仕組みが必要です。
- エラーIDの付与: 文脈(現在の階層)や「期待されていたトークンの組み合わせ」から、未知のエラーを
ERR_MISSING_COLONのようなIDに分類する。 - フォールバック: どの特定のミスにも当てはまらない場合でも、「大きな枠組み(例:文、式)」の単位でエラーを特定し、統計的に処理できるようにする。
今回の議論で最も先進的だったのは、**「ユーザーの修正履歴を元に文法を自動拡張する」**というアイデアです。
- フィードバックループ: 「間違った入力」と「ユーザーによる修正後」のペアを収集し、LLMにその差分をキャッチするPEGルールを書かせる。
- 安全性の確保:
- 回帰テスト: LLMが作った新ルールが「正しい構文」を壊さないか自動検証する。
- 診断専用パーサ: メインの文法を汚さないよう、エラー発生時のみ起動する「別動隊の診断パーサ」として拡張ルールを運用する。
「良いパーサとは、正しい書き方を知っているだけでなく、間違え方にも詳しいパーサである」
まずは手動で「よくある間違い」を数点定義することから始め、徐々にエラーログとLLMを組み合わせて「自動的に親切になっていくパーサ」を目指すのが、現代的で最も効率的なアプローチと言えそうです。
(返答終了理由: STOP)
{
"runSettings": {
"temperature": 1,
"model": "models/gemini-3-flash-preview",
"topP": 0.95,
"topK": 64,
"maxOutputTokens": 65536,
"safetySettings": [
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "OFF"
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "OFF"
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "OFF"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "OFF"
}
],
"enableCodeExecution": false,
"enableSearchAsATool": false,
"enableBrowseAsATool": false,
"enableAutoFunctionResponse": false,
"thinkingBudget": -1,
"outputResolution": "1K",
"thinkingLevel": "THINKING_HIGH"
},
"systemInstruction": {}
}