Skip to content

Simplify the syntax so every line reads left to right - #46

Merged
brynary merged 3 commits into
peg-grammarfrom
simpler-syntax
Oct 5, 2026
Merged

brynary merged 3 commits into
peg-grammarfrom
simpler-syntax

Conversation

@brynary

@brynary brynary commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Stacked on #45.

The grammar in #45 needed a locator rule for each kind of line and many lookaheads. The cause was the syntax: one line could split in more than one way, and the parser had to look ahead to choose. This PR changes the syntax so that the parser reads each line left to right, one token at a time. PRESS is the only line that still looks one token ahead.

This changes existing flows. Whirl has no users to migrate yet, so there is no compatibility mode. The sample flows, SPEC examples, eval caches, and tests use the new syntax.

Locators

Before After
CLICK role:button "Sign in" CLICK button:"Sign in"
CLICK role~:button Sign CLICK button:~Sign
ASSERT role:dialog visible ASSERT dialog:* visible
ASSERT text~:Added visible ASSERT text:~Added visible
  • Every ARIA role that Playwright accepts is its own prefix, except generic, none, and presentation: 79 roles. role: is gone.
  • * matches any name. A quoted "*" is the literal name.
  • The ~ of a substring match follows the colon, for roles and the five text prefixes alike.
  • Prefixes are strict. In a locator, a bare colon always marks a prefix, and an unknown prefix is a parse error. Quote unprefixed text that holds a colon. >> is never a segment; quote it to match it as text.

Role options such as level are not in this PR. A later form could be heading[level=2]:"Welcome back".

Windows

Browser tabs are windows, so tab is free for the ARIA role:

Before After
TAB payment WINDOW payment
ASSERT tab:payment closed ASSERT window:payment closed

The lint codes are duplicate-window and unknown-window. The shim protocol does not change; its wire command is still named tab.

Tokens

  • A bare value cannot start with @. A bare @ token is always the step timeout and must end its line. Quote a value such as "@60s".
  • A name: value line needs a space after the colon: in options, CAPTURE lines, headers, and snapshot settings.
  • A quoted string joins a token only right after a prefix, as in label:"First name" or button:~"Sign in". This is also true for json: and xpath:, which had a rule of their own.
  • A # starts a comment only at the start of a line or after white space. Before, a # inside a token started a comment. So VISIT /docs#install visited /docs, and ASSERT url == https://shop.example.com/#/cart compared against the URL without its fragment, and whirl check passed both. Now css:#submit and URL fragments work without quotes.
  • After ACT, EXTRACT, or JUDGE, a first token with a prefix starts the scope. DRAG needs no quotes on a to that is text. After SCROLL, a bare direction or to is always the motion.

Removed

The [Asserts] and [Captures] sections are gone. They are unknown sections, whirl fmt no longer rewrites them, and the sections-removed and mixed-check-syntax errors are gone.

Result

The grammar in SPEC section 17 went from 107 rules and 46 lookaheads to 91 rules and 31 lookaheads, and that includes the new list of 79 roles. Its locator rules no longer depend on the kind of line. parse.rs is about 100 lines shorter.

Commits

  1. Simplify the syntax so every line reads left to right.
  2. Start a comment only at a # after white space.
  3. Let json: and xpath: join a quoted string like other prefixes.

Validation

  • mise run check.
  • The routine grammar test compares the grammar with the parser on every sample and about 60,000 changed copies.
  • The nightly grammar sweep (about 480,000 changed copies) passed locally on the last commit.
  • For the first commit, I ran whirl check on 185 sample files and SPEC examples: the old build on the original files, and the new build on the converted files. 182 gave the same result, down to each diagnostic's code and line. The other 3 were tests of the removed sections, which this PR deletes.
  • CI below.

🤖 Generated with Claude Code

brynary and others added 3 commits September 29, 2026 20:39
Every ARIA role that Playwright accepts, except generic, none, and
presentation, is its own locator prefix: `button:"Sign in"`,
`button:~Sign` for a substring match, and `dialog:*` for any name.
`role:` is gone. Prefixes are strict: a bare colon in a locator always
marks a prefix, and `>>` is never a segment.

Browser tabs are windows: `WINDOW name` and `window:name closed`. A bare
value cannot start with `@`, `name: value` lines need a space after the
colon, and a quoted string joins a token only after `:` or `:~`. The
removed `[Asserts]` and `[Captures]` sections are gone.

With these rules the parser reads each line left to right, so the
grammar in SPEC section 17 drops its per-context locator rules and
lookaheads. Sample flows, SPEC examples, eval caches, and tests use the
new syntax.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A `#` in the middle of a bare token started a comment, so
`VISIT /docs#install` visited `/docs`, and
`ASSERT url == https://shop.example.com/#/cart` compared against the
URL without its fragment. `whirl check` passed both files.

A `#` now starts a comment only at the start of a line or after white
space, as in the shell. Inside a token it is text, so URL fragments
and `css:#submit` work without quotes. A value that starts with `#`
still needs quotes, and `whirl fmt` keeps them there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A json: or xpath: argument had its own rule: one bare run or one quoted
string. So json:a:"b" was an error while css:a:"b" worked. The argument
now follows the token rule for every prefix value, and the grammar drops
its `single` rule.

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 simpler-syntax 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