Quick syntax lookup. See AETHER.md for full explanations.

File skeleton

@grammar "name"        ; required, must be first
@root rule_name         ; required, must be second
@skip TOKEN             ; optional -- default is @skip SPACE
@noskip                 ; optional -- mutually exclusive with @skip
@case_insensitive       ; optional

; comments run to end of line

TOKEN_NAME := ...        ; ALL_CAPS -- lexer, matched by maximal munch
rule_name  := ...        ; snake-case/kebab-case -- parser, ordered choice

Operators

SyntaxMeaningValid in
a bsequencetokens & rules
<code>a &#124; b</code>ordered choice, first match winstokens & rules
a*zero or moretokens & rules
a+one or moretokens & rules
a?zero or onetokens & rules
a{3}exactly 3tokens & rules
a{3,}3 or moretokens & rules
a{3,7}3 to 7tokens & rules
&apositive lookahead (consumes nothing)tokens & rules
!anegative lookahead (consumes nothing)tokens & rules
( a )groupingtokens & rules
name:anamed capturerules only
~asuppress @skip splicing before this termrules only
@indent(a)a must start deeper than enclosing blockrules only
@samecol(a)a must start at the enclosing columnrules only
"lit"string literaltokens & rules*
[a-z]character classtokens only
[^a-z]negated character classtokens only
.any single charactertokens only
/pattern/regex-literal shorthandtokens only

* A bare string literal in a rule body is auto-promoted to a compiler-generated anonymous token.

String literal escapes

\n \r \t \\ \" \[ \] \/ \xHH \u{H+} — any other punctuation character escapes to itself (\^, \-, \*, ...).

Case sensitivity

SyntaxMeaning
"lit"case-sensitive, unless @case_insensitive is set
"lit"ialways case-insensitive
"lit"csalways case-sensitive, even under @case_insensitive

Predefined tokens

TokenDefault
DIGIT[0-9]
ALPHA[a-zA-Z]
ALNUMDIGIT | ALPHA
SPACE[ \t\r\n]
HEX[a-fA-F0-9]

Redeclare any of them (NAME := ...) before first use to override the default. @skip NAME (explicit or default) counts as an immediate use of NAME at that point in the file.

POSIX bracket classes

[:alpha:] [:alnum:] [:digit:] [:space:] [:hex:] — usable inside [...], mixed freely with ranges/literal characters: [[:alpha:]_][[:alnum:]_]*.

Regex-literal shorthand (tokens only)

SupportedRejected
literals, ., [...]/[^...], |, (...)anchors ^ $
* + ? {n} {n,} {n,m}backreferences \1-\9
(?=...) (?!...)named/unnamed groups (?<...)
\d \w \s \h \D \W \S \H

Maximal munch, in one sentence

At each lexer position: longest match wins; ties go to whichever token was declared first in the file; a zero-width match never wins.

Common gotchas

  • A literal that's also a substring another token's character class would greedily match (e.g. a keyword "end" vs. an identifier token [a-z]+) ties in length — declare the keyword as its own real token, before the identifier token, so it wins.
  • @skip NAME (even naming the default SPACE) marks NAME used immediately — you can't redeclare it afterward. Leave the pragma out entirely if you want to redefine the default SPACE's own character set.
  • Rules can't contain an inline [...], /pattern/, or bare . — give it a token name first.
  • @samecol checks against the nearest enclosing @indent's column; with no enclosing @indent, that's column 0.