Skip to content

Replace the SPEC grammar with a PEG that tests check against the parser - #45

Merged
brynary merged 1 commit into
ci-flakesfrom
peg-grammar
Oct 5, 2026
Merged

brynary merged 1 commit into
ci-flakesfrom
peg-grammar

Conversation

@brynary

@brynary brynary commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Stacked on #44.

The EBNF in SPEC section 17 left out tokens, white space, escapes, step timeouts, and where each kind of locator ends, and nothing checked it against the parser. This PR replaces it with a complete grammar and a test that keeps the two in step.

The grammar

Section 17 is now a parsing expression grammar (PEG) in pest notation, with a short table that explains the notation. A PEG fits Whirl better than EBNF: its ordered choice and lookahead express the rules that decide how a line splits, such as "a final bare @duration is the timeout" and "a bare to separates the two locators of DRAG". Each kind of line ends its locator in a different place, so each has its own locator rule; one shared rule checks that nth: is never first, ai: is last, and a frame: is followed by an element segment.

Section 17.1 lists 11 value rules that the grammar leaves out, such as valid regexes, JSONPath queries, option value forms, and unique header names.

The test

crates/whirl/src/lang/grammar_tests.rs reads the grammar from SPEC.md with pest_vm. For each input, the grammar and the parser must both accept it or both reject it, and when both accept, they must find the same entries and steps. The parser may reject a file that the grammar accepts only with an error from a section 17.1 rule.

  • Routine, about 20 s: every .whirl file and Markdown whirl block in the repository, about 360 edge cases, and about 60,000 copies of the samples with one line changed.
  • Nightly (#[ignore], about 8 min): about 480,000 copies with a tricky token or character put in or swapped in.

The sweeps found one error in my first draft of the grammar (a final >> before a comment), now fixed. One gap stays unchecked: a step timeout whose number overflows 64 bits is a value to the parser but a timeout to the grammar; rule 10 of section 17.1 states it.

New dev dependencies: pest, pest_meta, and pest_vm 2.9.2, pinned.

Parser fixes

The comparison found three places where the parser differed from the SPEC:

  • JSON array bodies. A body line after HTTP or MOCK that starts with [ was read as a section header, so every array body was a parse error. Section 7.3 allows arrays.
  • MOCK lines with no VISIT. A file of only MOCK lines, or MOCK lines followed by HTTP, PAGE, or a check line before the first VISIT, parsed. Section 4 requires the first browser entry to start with VISIT after any MOCK lines. These are now parse errors, with a new case in check_mock_invalid.
  • \\{{name}} in a JSON string. The parse check and the run read it as an escaped backslash before a reference, but the parsed value kept the literal text \{{name}}. So whirl check did not see the reference, and a JSON literal in a text comparison compared the wrong text. The value now matches.

Two SPEC sentences were also inaccurate against the parser, and now say what it does: section 3.1 on final @duration tokens, and section 7.3 on where a JSON body starts.

Validation: mise run check; the nightly grammar sweep passed locally; CI below.

🤖 Generated with Claude Code

SPEC section 17 now defines the whole syntax of a .whirl file as a
parsing expression grammar in pest notation. Section 17.1 lists the
value rules that the grammar leaves out. grammar_tests.rs reads the
grammar from SPEC.md and requires the grammar and the parser to accept
the same files, with the same entries and steps, on the repository's
samples, edge cases, and changed copies of the samples.

The comparison found three places where the parser differed from the
SPEC, now fixed: JSON array bodies after HTTP and MOCK were a parse
error; MOCK lines with no VISIT after them parsed; and a JSON string's
\\{{name}} lost its reference in the parsed value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brynary
brynary added this pull request to stack #49 October 1, 2026 20:57
@brynary
brynary merged commit 55320f0 into mock Oct 5, 2026
3 checks passed
@brynary
brynary deleted the peg-grammar branch October 5, 2026 18:29
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