Betrachten wir die kontextfreie Grammatik $G$ für arithmetische Ausdrücke mit den Variablen $x, y, z$ und strenger Klammerung über dem Alphabet $\Sigma = \{x, y, z, (, ), +, *\}$:
\begin{align*} S & \rightarrow x \ |\ y\ |\ z \\ S & \rightarrow (S+S) \\ S & \rightarrow (S*S) \end{align*}Sie kann also $(x + (y*z))$ ableiten aber eben nicht $(x + y + z)$. Wie können wir nun einen Parser für $G$ schreiben? Also einen Algorithmus, der ein Wort $w \in \Sigma^*$ nimmt und einen Ableitungsbaum konstruiert (falls $w \in L(G)$)? 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
\begin{align*} w = ((((\dots \end{align*}dann wissen wir nicht, ob wir als ersten Schritt $S \rightarrow (S+S)$ oder $S \rightarrow (S*S)$ tätigen sollen. Das geht auch nicht, wenn wir $k$ Zeichen vorauslesen dürfen, weil der $(((\dots$-Präfix ja länger als $k$ sein kann. Nein, wir müssen anders vorgehen. Wir könnten beispielsweise die Grammatik ändern:
\begin{align*} S & \rightarrow x \ | \ y \ | \ z \\ S & \rightarrow (SOS) \\ O & \rightarrow + \ | \ * \end{align*}Das geht aber nicht immer:
Beispiel Betrachten wir die recht einfache Sprache
\begin{align*} L_2 := \{a^{m+k} b^m c \ | \ m \geq 1, k \geq 0 \} \ , \end{align*}also beliebig viele $a$'s, gefolgt von gleich vielen oder weniger $b$'s (aber mindestens einem), abgeschlossen mit einem $c$ . Eine Grammatik ist schnell geschrieben:
\begin{align} S & \rightarrow aS \\ S & \rightarrow Xc \\ X & \rightarrow aXb \\ X & \rightarrow ab \end{align}Wenn wir jetzt die ersten $k$ Zeichen lesen: $aaaa \dots$, dann gibt es keinen Weg, zu entscheiden, ob danach gleich viele oder weniger $b$'s folgen werden, ob wir also $S \rightarrow sS$ oder $S \rightarrow Xc$ anwenden sollen. Das lässt sich auch nicht durch Umschreiben der Grammatik lösen. Wir müssen lesen, bis wir ein $b$ sehen.
Das LR-Paradigma
Wir brauchen einen Paradigmenwechsel. Das LL-Paradigma war ja, mit $S$ zu starten und, geleitet von den nächsten $k$ 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 $v$ von links nach rechts, unterhalten also einen Stack, auf dem ein Präfix $\gamma$ von $v$ liegt, bis wir am rechten Ende die rechte Seite einer Produktionsregel erkennen - bis also $\gamma = \alpha \beta$ und es eine Produktion $X \rightarrow \beta$ gibt. Dann ersetzen wir $\alpha \beta$ durch $\alpha X$. Unser Stack enthält nun keine Präfix von $v$ mehr, sondern eine Wortform $\gamma$. Zusammen mit dem ungelesenen Teil $w$ des Eingabewortes ergibt das eine Wortform $\gamma w$. Solange es eine Rechtsableitung $S \Pets{} \gamma w \Pets{} v$ gibt, sind wir auf dem richtigen Weg. Am Besten betrachten wir ein Beispiel für $L_2 = \{a^{m+k} b^m c \ | \ m \geq 1, k \geq 0\}$. 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:
\begin{align*} \texttt{S} \Step{} \texttt{(S+S)} \Step{} \texttt{(S+(S*S))} \Step{} \texttt{(S+(S*z))} \Step{} \texttt{(S+(y*z))} \Step{} \texttt{(x+(y*z))} \end{align*}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 $S$ ausgehend $w$ abzuleiten, also $S \Step{}^* w$, versuchen wir $w$ zu $S$ zu reduzieren, also $w \Pets{}^* S$. 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:
\begin{align*} S & \rightarrow XYz \\ X & \rightarrow aXa \ | \ bXb \ | \ c \\ Y & \rightarrow Ya \ | \ Yb \ | \ a \ | \ b \end{align*}Die erzeugte Sprache ist
\begin{align*} L(G) = \{vcv^Rwz \ | \ v, w \in \{a,b\}^* \} \end{align*}Betrachten wir das Eingabewort $acaba$. 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 $XYa\textcolor{darkgray}{z}$, dass wir per $Y \rightarrow Ya$ reduzieren müssen und nicht per $Y \rightarrow a$? Wir könnten ja auch auf $XYa \Pets{} XYY$ reduzieren. Oder in Schritt 2, bei $a\textcolor{darkgray}{cabaz}$. Da könnten wir ja gleich $a \Pets{} Y$ reduzieren.
Beobachtung. Die Reduktion $XYa \Pets{} XYY$ kann nicht richtig sein, weil $XYY$ nie als Präfix in einer Rechtsableitung vorkommen kann. Genauer gesagt: es gibt kein $w \in \Sigma^*$, so dass
\begin{align*} S \Step{}^* XYYw \Step{} XYaw \end{align*}eine Rechtsableitung ist.
Wenn wir Glück haben, gibt es immer höchstens eine Reduktionsregel $\alpha \beta w \Pets{} \alpha X w$, so dass $S \Step{}^* \alpha X w \Step{} \alpha \beta w$ in einer Rechtsableitung vorkommen kann. Das hängt von der Grammatik ab. Aber selbst dann brauchen wir einen Algorithmus, der uns sagen kann, ob $XYa \Pets{} XYY$ ein korrekter Reduktionsschritt ist. Dies scheint komplexer, als $w \stackrel{?}{\in} L$ 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:
\begin{align*} S & \rightarrow XY \tag{beachten Sie: oben hatten wir $S \rightarrow XYz$}\\ X & \rightarrow aXa \ | \ bXb \ | \ c \\ Y & \rightarrow Ya \ | \ Yb \ | \ a \ | \ b \end{align*}Wenn wir jetzt als einfaches Beispiel $cab$ ableiten wollen:
\begin{align*} \textcolor{darkgray}{cab} \tag{lesen} \\ c\textcolor{darkgray}{ab} \tag{reduzieren per $X \rightarrow c$} \\ X\textcolor{darkgray}{ab} \tag{lesen} \\ Xa\textcolor{darkgray}{ab} \tag{reduzieren per $Y \rightarrow a$} \\ XY\textcolor{darkgray}{b} \tag{reduzieren per $Y \rightarrow a$} \end{align*}Jetzt liegt die Wortform $XY$ auf unserem Stack und wir haben zwei Möglichkeiten: wir könnten reduzieren, also $XY \Pets{} S$, oder das nächste Zeichen lesen. Ersteres wäre inkorrekt:
\begin{align*} XY\textcolor{darkgray}{b} \Pets{} S \textcolor{darkgray}{b} \ , \end{align*}wobei $Sb$ eben keine Wortform ist, die in einer Rechtsableitung vorkommen könnte. Das wissen wir aber erst, wenn wir $b$ gelesen haben. Wäre das Eingabewort nämlich nur $ca$, dann wäre $XY \Pets{} S$ tatsächlich die korrekte Reduktion. In der ersten Grammatik hatten wir als "Work-around" ein $z$ 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.