Aufbau einer Datei
Eine Grammatikdatei besteht aus einem Kopf und Definitionen. Der Kopf benennt die Grammatik und ihre Startregel und setzt grammatikweite Optionen; jede Definition gibt einem Token oder einer Regel ihren Rumpf.
Der Kopf
Jede Datei beginnt mit @grammar und einem Namen in Anführungszeichen, dann @root und der Regel, bei der das Erkennen beginnt. Danach folgen optionale Pragmas, in beliebiger Reihenfolge, jedes höchstens einmal:
| Pragma | Wirkung |
|---|---|
@skip TOKEN | dieses Token zwischen den Teilen einer Regel überspringen — siehe Leerraum |
@noskip | nichts überspringen; Leerraum ist Syntax |
@case_insensitive | Literale ohne Suffix ignorieren Groß-/Kleinschreibung |
@engine peg | lr | glr | der Parse-Algorithmus — siehe Engines |
@skip und @noskip schließen einander aus. Ohne beide verhält sich die Grammatik, als stünde dort @skip SPACE. Alle Pragmas gehören in den Kopf; eines nach der ersten Definition ist ein Fehler:
@grammar "x" @root a a := "a" @skip SPACE
4:1: expected a token or rule definition (NAME := ...)
Kommentare
Ein ; beginnt überall in der Datei einen Kommentar bis zum Zeilenende. Blockkommentare gibt es nicht. Leerzeichen, Tabulatoren und Zeilenumbrüche trennen die Teile einer Grammatik und bedeuten sonst nichts — eine Definition darf sich über beliebig viele Zeilen erstrecken.
Namen
| Schreibweise | Deklariert | Beispiele |
|---|---|---|
[A-Z_][A-Z0-9_]* | ein Token | NUMBER DQ_STRING _WS |
[a-z][a-z0-9_-]* | eine Regel | expr map_entry key-path |
Jede andere Schreibweise ist ein Fehler, keine dritte Art von Namen:
Foo := "a"
3:1: invalid identifier "Foo" -- must be ALL_UPPERCASE (a token) or snake_case/kebab-case (a rule)
Definitionen
Eine Definition ist ein Name, := und ein Ausdruck. Ein Abschlusszeichen gibt es nicht: Eine Definition endet dort, wo das nächste NAME := beginnt. Definitionen stehen in beliebiger Reihenfolge, und ein Rumpf darf einen weiter unten deklarierten Namen verwenden. Einen Namen doppelt zu deklarieren oder einen nie deklarierten zu verwenden ist ein Fehler:
a := "a" a := "b"
4:1: rule a is already declared
a := b
3:6: reference to undefined token or rule :b
Unter Tokens zählt die Reihenfolge trotzdem, denn sie entscheidet Gleichstände im Lexer — siehe längste Übereinstimmung.
Vordefinierte Tokens
| Token | Standard |
|---|---|
DIGIT | [0-9] |
ALPHA | [a-zA-Z] |
ALNUM | DIGIT | ALPHA |
SPACE | [ \t\r\n] |
HEX | [a-fA-F0-9] |
Alle fünf gibt es in jeder Grammatik ohne Deklaration. Jedes darf neu deklariert werden, aber nur vor seiner ersten Verwendung — und @skip SPACE, ob geschrieben oder mitgedacht, zählt als Verwendung. Diese Grammatik verengt SPACE auf Leerzeichen und Tabulatoren; ein Zeilenumbruch ist damit kein Leerraum mehr:
@grammar "words" @root line SPACE := [ \t]+ WORD := ALPHA+ line := WORD+
"two words" → ok "two\nlines" → 1:4: no token matches here
Steht @skip SPACE über der Neudeklaration, wird die Grammatik stattdessen abgelehnt:
@grammar "words" @root line @skip SPACE SPACE := [ \t]+
4:1: cannot override SPACE: already used at line 3, column 7