Themen
PEG-Tutorial für Einsteiger
Dieses Kapitel ist absichtlich für Leute geschrieben, die PEG-Grammatiken noch gar nicht kennen.
Wichtig als Einordnung: Die Grundideen und viele Operatoren von Antra::PEG
sind zu großen Teilen von Janet übernommen. Das heißt nicht, dass Antra Janet
kopiert, sondern dass sich das PEG-Modell stark an Janet orientiert und dann in
normale Antra-Syntax übersetzt wurde.
Wenn du das Originalmodell nachlesen willst, sind die offizielle Janet-Seite janet-lang.org und speziell die PEG-Doku unter janet-lang.org/docs/peg.html die besten Startpunkte.
Die Kurzversion ist:
- Eine PEG-Grammatik ist eine Beschreibung dafür, wie ein Text gelesen werden soll.
- Du beschreibst kleine Bausteine.
- Daraus setzt du dann größere Regeln zusammen.
- Am Ende bekommst du keine "Magie", sondern einfach Captures als Ergebnis.
Wenn dir das Wort "Grammatik" zu groß vorkommt, lies es erstmal einfach als:
"eine Bauanleitung zum Lesen eines Textes"
Das erste Gefühl#
Nimm diesen Text:
helloWenn du prüfen willst, ob genau hello drinsteht, reicht schon das hier:
import Antra::PEG as PEG
const grammar = %{
main: [:sequence, "hello", -1]
}
println(PEG.match(grammar, "hello"))Das Ergebnis ist:
[]Das wirkt erst seltsam. Warum ein leeres Array?
Weil zwar alles erfolgreich gematcht wurde, aber noch nichts gecaptured wurde.
Die wichtigste Idee: kleine Regeln bauen#
Eine Grammatik ist einfach eine Map. Die wichtigste Regel heißt :main.
const grammar = %{
main: [:sequence, :word, ":", :number, -1],
word: [:some, :w],
number: [:some, :d]
}Das liest sich so:
mainerwartet erst ein Wort- dann einen Doppelpunkt
- dann eine Zahl
- dann muss der Input zu Ende sein
Was die Grundbausteine bedeuten#
String#
"hello"Das matcht genau diesen Text.
Optional: Regex als einzelner Baustein#
Manchmal willst du nicht jeden kleinen Zeichenschritt mit :some, :choice
oder Ranges ausformulieren, sondern an einer Stelle bewusst einen Regex
verwenden. Dafuer gibt es in Text-PEGs [:regex, regex"..."].
[:regex, regex"[A-Z][a-z]+"]Wichtig ist die Denkweise dabei:
- der Regex prueft nur ab der aktuellen Position
- er konsumiert nur den gematchten Praefix
- er schreibt noch nichts ins Ergebnis
Wenn du den Treffer spaeter sehen willst, nutzt du weiter :capture:
[:capture, [:regex, regex"[A-Z][a-z]+"]]Regelreferenz#
:wordDas heißt: spring in die Regel word.
:sequence#
[:sequence, a, b, c]Das heißt: erst a, dann b, dann c.
:choice#
[:choice, a, b, c]Das heißt: probiere a, sonst b, sonst c.
:some#
[:some, :d]Das heißt: eine oder mehr Ziffern.
Der erste echte Capture#
Jetzt wollen wir nicht nur prüfen, sondern auch etwas herausziehen.
import Antra::PEG as PEG
const grammar = %{
main: [:group, [:sequence,
[:capture, :word],
":",
[:capture, :number],
-1
]],
word: [:some, :w],
number: [:some, :d]
}
println(PEG.match(grammar, "age:42"))Ergebnis:
[["age", "42"]]Das ist die wichtigste Sache überhaupt:
[:capture, ...]legt den gelesenen Text auf den Capture-Stack[:group, ...]packt mehrere Captures in ein Unter-Array
So denkst du beim Schreiben einer PEG#
Nicht gleich alles auf einmal bauen.
Besser ist diese Reihenfolge:
- Wie sieht ein einzelnes kleines Teil aus?
- Wie sehen mehrere davon hintereinander aus?
- Was soll wirklich im Ergebnis landen?
Beispiel: einfache Komma-Liste#
Wir wollen so etwas lesen:
apfel,banane,kirscheDie Grammatik kann so aussehen:
import Antra::PEG as PEG
const liste = %{
main: [:group, [:sequence,
[:capture, :wort],
[:any, [:sequence, ',', [:capture, :wort]]],
-1
]],
wort: [:some, :w]
}
println(PEG.match(liste, "apfel,banane,kirsche"))Ergebnis:
[["apfel", "banane", "kirsche"]]Beispiel: Zeile mit Name und Zahl#
Ada:36import Antra::PEG as PEG
const person = %{
main: [:group, [:sequence,
[:capture, [:some, :w]],
":",
[:capture, [:some, :d]],
-1
]]
}
println(PEG.match(person, "Ada:36"))Ergebnis:
[["Ada", "36"]]-1 nicht vergessen#
-1 ist wichtig, wenn du wirklich den ganzen Input lesen willst.
Ohne -1 würde das hier trotzdem matchen:
age:42kaputtweil die Grammatik schon nach age:42 erfolgreich wäre.
Mit -1 sagst du:
"es darf nichts mehr übrig bleiben"
Häufige Anfängerfehler#
1. Janet-Beispiele ohne Kommas kopieren#
Falsch für Antra:
[:sequence [:some :w] ":" [:some :d]]Richtig in Antra:
[:sequence, [:some, :w], ":", [:some, :d]]2. :capture vergessen#
Dann matcht die Grammatik zwar, aber dein Ergebnis ist leer.
3. -1 vergessen#
Dann kann der Anfang passen, obwohl hinten noch Müll übrig ist.
4. Zu groß anfangen#
Wenn du gleich HTML, JSON oder eine komplizierte Config-Datei bauen willst, verlierst du leicht den Überblick.
Starte lieber mit:
- einer Zeile
- einem Feld
- einem Separator
Ein gutes Arbeitsmuster#
Wenn du eine neue PEG baust, arbeite so:
- Baue zuerst nur
mainmit einem ganz kleinen Beispiel. - Prüfe mit
println(PEG.match(...)), was wirklich zurückkommt. - Erst dann
:captureund:groupergänzen. - Danach Stück für Stück auf mehrere Fälle erweitern.
Wo du weitermachen solltest#
Wenn du jetzt das Grundgefühl verstanden hast, lies als Nächstes die Referenz in 24-peg-grammatiken.md.
Wenn du lieber Beispiele anschaust, sind diese Dateien gut geeignet:
examples/peg/csv_parser.antexamples/peg/math_parser.antexamples/peg/url_parser.antexamples/peg/ini_parser.ant
Mini-Spickzettel#
"text" # exakter Text
:rule # andere Regel
[:sequence, a, b] # hintereinander
[:choice, a, b] # entweder oder
[:some, x] # eins oder mehr
[:any, x] # null oder mehr
[:opt, x] # optional
[:capture, x] # Ergebnis mitschreiben
[:group, x] # mehrere Captures gruppieren
-1 # Input muss zu Ende sein