Skip to main content

petitparser-debug

Identifies, diagnoses, and remediates parser failures, infinite execution loops, stack overflows, and subtle PEG grammar design flaws using tracing, profiling, and minimal reproduction workflows.

Quellinformationen

Repository
serverpod/wonky
Letzte Quellaktivität
20. Oktober 2026 um 10:16
Erkannte Sprache von SKILL.md
Englisch
Sterne
6
Forks
5

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
petitparser-debug
description
Identifies, diagnoses, and remediates parser failures, infinite execution loops, stack overflows, and subtle PEG grammar design flaws using tracing, profiling, and minimal reproduction workflows.
user-invocable
false
# Diagnostics & Debugging ## Trigger Conditions - Parser throws `StackOverflowError` during initialization or execution. - Execution hangs or enters an infinite loop on specific input strings. - Valid input unexpectedly yields `Failure`, or error positions report unintuitive offsets. - Diagnosing grammar performance bottlenecks or heavily backtracking rules. ## Execution Tracing with `trace()` Import `package:petitparser/debug.dart` to trace parser execution: ```dart import 'package:petitparser/debug.dart'; import 'package:petitparser/petitparser.dart'; final parser = seq2(letter(), word().starString()); trace(parser).parse('f1'); ``` Output visualization: ```text SequenceParser2<String, String> SingleCharacterParser[letter expected] Success<String>[1:2]: f RepeatingCharacterParser[letter or digit expected, 0..*] Success<String>[1:3]: 1 Success<(String, String)>[1:3]: (f, 1) ``` - Indentation indicates activating/entering a parser combinator. - Dedentation indicates exiting with `Success` or `Failure`. - Use the `predicate` parameter to filter low-level parsers: `trace(parser, predicate: (p) => p is! CharacterParser).parse(input)`. ### Profiling and Progress Tracking - **`profile(parser)`**: - Counts invocations, successes, failures, and elapsed execution time for every parser in the graph. - Pinpoints catastrophic backtracking hotspots. ```dart import 'package:petitparser/debug.dart'; final profiled = profile(rootParser); profiled.parse(largeSource); ``` - **`progress(parser)`**: - Visualizes parsing progress and high-water mark position through the input stream. ### Failure Diagnostics & Coordinate Mapping When a parse returns `Failure`: - `failure.position`: 0-based character offset where the parser failed. - `failure.message`: Descriptive reason for the failure. - Convert character offset to 1-based line and column: ```dart final [line, column] = Token.lineAndColumnOf(input, failure.position); print('Failed at $line:$column: ${failure.message}'); ``` - **Furthest Position Analysis**: In alternatives (`[a, b, c].toChoiceParser()`), the parser that progressed furthest into the input often pinpoints the real syntax mistake. ### Zero-Width Repetition Loops (Nullable Repeaters) A major cause of execution hangs is wrapping a zero-width (nullable) matcher inside a repetition combinator (`star`, `plus`, `repeat`): ```dart // BUG: Non-terminating loop! optional() matches "" with 0 characters consumed. final badOptional = char('a').optional().star(); // BUG: Nested repetition! star() matches 0 characters on empty input, looping forever. final badNested = char('a').star().plus(); // BUG: Lookahead predicate! and() succeeds without consuming any characters. final badLookahead = char('a').and().star(); ``` **Resolution**: Ensure that the repeated matcher strictly guarantees positive character consumption ($\ge 1$ character): ```dart // FIXED: final good1 = char('a').star(); // 0 or more characters directly final good2 = char('a').plus(); // 1 or more characters directly ``` ### Left-Recursion Elimination PEG parsers are top-down recursive descent and cannot handle left-recursive rules directly. #### Direct Left-Recursion ```dart // BUG: Infinite call stack! Parser<Expr> expr() => seq3(ref0(expr), char('+'), ref0(term)).map3(...); // FIXED: Refactor to separated repetition: Parser<Expr> expr() => ref0(term).plusSeparated(char('+')).map((list) => ...); ``` Alternatively, use `ExpressionBuilder` for operator expressions (see `petitparser-architecting`). #### Indirect Left-Recursion Rule `A` calls `B`, and `B` calls `A` without consuming input (`A -> B; B -> A`). Run the PetitParser linter (`linter(grammar.build())`) to statically detect indirect left-recursive cycles before execution. ### Prefix Shadowing in Ordered Choice Because PEG choices (`[a, b].toChoiceParser()`) commit to the first successful match, shorter prefixes declared before longer compound symbols mask the longer symbol: ```dart // BUG: '<' greedily matches '<=' and '<>', leaving '=' or '>' as unexpected trailing input: final comparison = [char('<'), string('<='), string('<>')].toChoiceParser(); // FIXED: Declare longer, more specific multi-character tokens before shorter prefixes: final comparison = [string('<='), string('<>'), char('<')].toChoiceParser(); ``` When unexpected parse failures occur immediately following a single-character operator or keyword prefix, trace the choice rule to verify compound tokens are not shadowed. ### Minimal Reproduction Workflow 1. **Extract to Unit Test**: When a full file fails to parse, copy the failing snippet into a test case. 2. **Binary Reduction**: Delete lines iteratively until the failure isolates to 1-2 lines. 3. **Dual Pair**: Create a passing example and failing example differing by a single token. 4. **Trace Execution**: Apply `trace(production)` to observe the exact combinator branch where parsing diverged. 5. **Fix & Verify**: Fix the rule, re-run the minimal pair test, and only then evaluate against the full codebase. ## Critical Heuristics & Anti-Patterns - **Trace Before Refactoring**: Never speculate on why a rule failed; run `trace()` on minimal input to inspect actual parser decisions. - **Guarantee Loop Consumption**: Always verify that every rule passed to `star()` or `plus()` strictly consumes $\ge 1$ character on success. - **Check Furthest Offset**: When a choice fails, inspect the alternative that achieved the greatest parse position offset to find the real syntax error.
Auf GitHub ansehen