Betrachten wir die kontextfreie Grammatik für arithmetische Ausdrücke mit den
Variablen und strenger Klammerung über dem Alphabet
:
Sie kann also ableiten aber eben nicht . Wie können wir nun einen
Parser für schreiben? Also einen Algorithmus, der ein Wort nimmt und einen
Ableitungsbaum konstruiert (falls )?
Wenn wir uns an das LL-Paradigma halten und eine Linksableitung bauen wollen, dann stoßen wir
schon ganz am Anfang auf ein Problem: wenn zum Beispiel
dann wissen wir nicht, ob wir als ersten Schritt oder tätigen sollen. Das geht auch nicht, wenn wir Zeichen vorauslesen dürfen, weil
der -Präfix ja länger als sein kann. Nein, wir müssen anders vorgehen.
Wir könnten beispielsweise die Grammatik ändern:
Das geht aber nicht immer:
Beispiel 6.5.1 Betrachten wir die recht einfache Sprache
also beliebig viele 's, gefolgt von gleich vielen oder weniger 's (aber mindestens
einem), abgeschlossen
mit einem . Eine
Grammatik ist schnell geschrieben:
Wenn wir jetzt die ersten Zeichen lesen: , dann
gibt es keinen Weg, zu entscheiden, ob danach gleich viele oder weniger
's folgen werden, ob wir also oder anwenden sollen.
Das lässt sich auch nicht durch Umschreiben der Grammatik lösen. Wir müssen
lesen, bis wir ein sehen.
Das LR-Paradigma
Wir brauchen einen Paradigmenwechsel. Das LL-Paradigma war ja, mit zu starten und,
geleitet von den nächsten Zeichen, zu entscheiden, welche Ableitungsregel als nächstes
anzuwenden ist. Hierbei haben wir immer versucht, für das am weitesten
links stehende Nichtterminal eine Regel zu finden. Wir beschreiben nun ein ganz anderes
Vorgehen: wir lesen das Eingabewort von links nach rechts, unterhalten also
einen Stack, auf dem ein Präfix von liegt,
bis wir am rechten Ende die rechte Seite einer Produktionsregel erkennen - bis also
und es eine Produktion gibt. Dann ersetzen
wir durch .
Unser Stack enthält nun keine Präfix von mehr, sondern eine Wortform .
Zusammen mit dem ungelesenen Teil des Eingabewortes ergibt das eine
Wortform . Solange es eine Rechtsableitung
gibt, sind wir auf dem richtigen Weg.
Am Besten betrachten wir ein Beispiel für
. Die Farbe grau bedeutet
hier, dass wir das Eingabezeichen noch nicht gelesen haben.
Betrachten wir noch ein Beispiel, nun für die etwas nützlichere Grammatik
der streng geklammerten arithmetischen Ausdrücke.
Wenn wir uns nun die Ableitung ansehen, die wir gefunden haben:
dann sehen wir, dass es sich um eine Rechtsableitung handelt. Daher der Name LR-Parsing: wir
beginnen links (daher das L) und suchen eine Rechtsableitung (daher das R), allerdings in
umgekehrter
Reihenfolge. Statt von ausgehend abzuleiten, also , versuchen
wir zu zu reduzieren, also .
Allerdings ist das nicht immer so einfach: manchmal ist nicht auf den ersten Blick erkennbar,
welche Produktionsregel wir (rückwärts) anwenden sollen. Hier ein etwas konstruiertes Beispiel:
Die erzeugte Sprache ist
Betrachten wir das Eingabewort . Wir schreiben nun immer
den bis jetzt gelesenen / geparsten Teil des Wortes, gefolgt von dem
ungelesen Teil in grau und dahinter
in Klammern ,
was wir als nächstes tun, also das nächste Zeichen lesen oder eine
Regel anwenden.
Es stellen sich einige Fragen: woher wissen wir zum Beispiel bei
, dass wir per reduzieren müssen
und nicht per ? Wir könnten ja auch auf
reduzieren. Oder in Schritt 2, bei
. Da könnten wir ja
gleich reduzieren.
Beobachtung. 6.5.2 Die Reduktion
kann nicht richtig sein, weil nie als Präfix in einer
Rechtsableitung vorkommen kann. Genauer gesagt: es gibt kein , so dass
eine Rechtsableitung ist.
Wenn wir Glück haben, gibt es immer höchstens eine Reduktionsregel
, so dass
in einer Rechtsableitung vorkommen kann.
Das hängt von der Grammatik ab. Aber selbst dann brauchen wir einen Algorithmus, der
uns sagen kann, ob ein korrekter Reduktionsschritt ist. Dies scheint
komplexer, als zu entscheiden, ist aber einfacher!
Ein zweites Problem ist, dass wir eben manchmal kein Glück haben und es mehrere
plausible Reduktionsschritte geben kann. Ein Beispiel wäre die obere Grammatik, leicht
abgewandelt:
Wenn wir jetzt als einfaches Beispiel ableiten wollen:
Jetzt liegt die Wortform auf unserem Stack und wir haben zwei Möglichkeiten:
wir könnten reduzieren, also , oder das nächste Zeichen lesen.
Ersteres wäre inkorrekt:
wobei eben keine Wortform ist, die in einer Rechtsableitung vorkommen könnte.
Das wissen wir aber erst, wenn wir gelesen haben. Wäre das Eingabewort nämlich nur ,
dann
wäre tatsächlich die korrekte Reduktion. In der ersten Grammatik
hatten wir als "Work-around" ein am Ende angehängt, um das Wortende zu erkennen.
Im Allgemeinen ist es aber leichter, dem Parser zu erlauben, das nächste Zeichen zu lesen.
All diese Gedanken theoretisch rigoros zu formulieren ist einigermaßen herausvordernd.
Daher werden wir erst einmal für eine Grammatik arithmetischer Ausdrücke einen
Parser in Java implementieren.