6.9 Der DK-Automat

Von der Grammatik G^ zu einem nichtdeterministischen endlichen Automaten

Um den LR(0)-Parser gemäß Algorithmus 5.8.7 zu implementieren, müssen wir den Test γ?Front(G) durchführen und, falls die Antwort ja ist, die Blüte finden. Dies gelingt uns, indem wir die Grammatik G^ in einen endlichen Automaten umwandeln. Theorem 4.3.8 und die ihm vorhergehende Konstruktion zeigen, wie man das macht. Anstatt allerdings das Theorem als "black box" zu verwenden, gehen wir die Konstruktion anhand zweier fiktiver Produktion XaBcDe und XuVw Schritt für Schritt durch. Wir erkennen die Terminale Σ={a,c,e,u,w} und die Nichtterminale N={X,B,D,V}. In G^ sind \{a,c,e,u,w,A,B,D,V\} alles Terminale, und die Nichtterminale sind N^={X^,B^,D^,V^}. Die Produktionen von G^ sind X^aB^X^aBcD^X^aBcDeX^uV^X^uVw

Diese übersetzen wir in einen verallgemeinert nichtdeterministischen Automaten, bei dem jede Kante mit einem Wort β(ΣN), also aus Terminalen der Grammatik G^ beschriftet ist:

Wir brechen die Kanten in Stücke, so dass jede mit genau einem Zeichen beschriftet ist:

Nochmal: jede Kante ist mit einem Terminalsymbol beschriftet. Die Symbole B,D,V sind Terminale in der Grammatik G^. Wenn wir ϵ-Übergänge zulassen, können wir den Automaten übersichtlicher gestalten und mehrere Zustände zusammenfassen:

Wir werden uns noch bessere Namen für die Zwischenzustände ausdenken. Wenn wir uns an die Rechtsableitungsbäume mit Stamm, linkem Rand, Blüte und rechtem Rest erinnern, dann haben die Zwischenzustände eine klare Bedeutung: der Zustand 4 oben zum Beispiel bedeutet, dass der Knoten X im Stamm mit a,B,c,D,e beschriftete Kinder hat und wir uns bereits entschieden haben, dass a, B und c Blätter sein sollen, also zum linken Rand gehören. Der Übergang 4D5 entspricht dann der Entscheidung, auch D zu einem Blatt zu machen, während 4ϵD^ der Entscheidung entspricht, D zu einem Knoten im Stamm zu machen und eine D-Produktion anzuwenden. Die beiden ϵ-Übergänge von X^ nach 1 bzw. 7 stellen einfach sicher, dass wir uns erst entscheiden, mit welcher Produktion wir den Stammknoten X expandieren, bevor wir weitermachen. Daher nennen wir Zustand 4 um in XaBc.De, wobei der Punkt . markiert, welchen Teil der rechten Seite wir bereits gelesen haben. Wir erhalten folgendes Bild:

Die am+kbm-Grammatik

Wir illustrieren obige Vorgehensweise nun anhand der Grammatik

G:SaSSBBaBbBab

Laut Gebrauchsanweisung aus dem letzten Teilkapitel hat die Grammatik G^ die Terminalsymbole ΣN={a,b,S,B} und die Nichtterminale N^={S^,B^}. Die Produktionen von G^ ergeben sich wie folgt:

Produktion in GProduktion in G^SaSS^aS^S^aSSBS^B^S^BBaBbB^aB^B^aBbBabB^ab

In dieser Grammatik betrachten wir die Rechtsableitung

SaSaaSaaBaaaBbaaaaBbbaaaaaBbbb

Es gilt front(aaaaaBbbb)=aaaaaBb. In G^ entspricht die obige Rechtsableitung der Ableitung

S^aS^aaS^aaB^aaaB^aaaaBb

Den nichtdeterministischen DK-Automaten (mit ϵ-Übergängen) können wir nun Schritt für Schritt zeichnen:

Der nichtdeterministische DK-Automat

Definition 6.9.1 Für eine kontextfreie Grammatik ist der nichtdeterministische DK-Automat (NDK-Automat) ein nichtdeterministischer endlicher Automat mit ϵ-Übergängen, den wir wie folgt konstruieren:

  1. Für jede Produktion Xβ gibt es einen Zustandsübergang XϵX.β
  2. Für jede Zerlegung β=β1σβ2 gibt es den Zustandsübergang Xβ1.σβ2σXβ1σ.β2
  3. Falls σ ein Nichtterminal Y ist, also β=β1Yβ2, gibt es zusätzlich noch den Übergang Xβ1.Yβ2ϵY

Die Interpretation dieser Übergänge in Rechtsableitungsbaum-Begriffen ist: (1) heißt, dass X ein Stammknoten ist und wir ihn anhand der Regel Xβ expandieren; wir erschaffen also Kinder, die mit den Symbolen von β beschriftet sind. (2) heißt, dass wir uns bereits entschieden haben, die Kinder β1 von X nicht zu expandieren, sie also zum linken Rand werden zu lassen, und diese Entscheidung nun auch für das Symbol σ treffen; (3) bedeutet, dass wir uns entschließen σ (das hier ein Nichtterminal Y ist) weiter zu expandieren, es also nicht dem linken Rand zuordnen, sondern zu einem Stammknoten werden lassen, uns aber noch nicht entschlossen haben, welche Produktion Y? wir anwenden wollen.

Der Startzustand ist S. Jeder Zustand der Form Xβ. ist ein akzeptierender Zustand.

Den NDK-Automaten deterministisch machen

Wir wissen ja bereits, wie man einen nichtdeterministischen Automaten deterministisch macht: die Potenzmengenkonstruktion aus Kapitel 4.3. Hier können wir diese leider nicht direkt anwenden, da der obige Automat ϵ-Übergänge hat. Wie geht das also nun? Im deterministischen Automaten ist wie bei der Potenzmengenkonstruktion jeder Zustand eine Menge R von Zuständen des nichtdeterministischen Automaten. Wenn wir nun in eine solche Menge R einen Zustand q einfügen, dann fügen wir auch alle Zustände q hinzu, zu denen es einen ϵ-Übergang qϵq gibt. Für den obigen nichtdeterministischen Automaten sieht das dann so aus:

Und hier nochmal das Endergebnis, wobei wir die Zustände im DK-Automaten durchnummeriert haben:

Streng genommen ist das Ergebnis kein deterministischer Automat, da zum Beispiel der Startzustand keinen b-Übergang hat. Die Zustandsübergangsfunktion δ:Q×ΣQ ist gar keine Funktion, sondern eine partielle Funktion, da δ(q,σ) für manche Eingabewerte undefiniert ist. Wir verwenden aber die Konvention, dass der Automat in diesem Falle in einen Todeszustand wechselt, aus dem er nicht wieder herauskommt und der nicht akzeptierend ist. Dies ist übersichtlicher, als überall Todeskanten hinzuzufügen.

Den DK-Automaten verwenden

Wie verwenden wir jetzt den DK-Automaten? In jedem Parser-Schritt füttern wir den DK-Automaten mit dem Stack-Inhalt. Landet der Automat auf einem nichtaktzeptierenden Zustand, müssen wir ein weiteres Zeichen lesen, d.h. auf den Stack legen. Landet er auf einem akzeptierenden Zustand, so enthält dieser ein Xβ. und wir reduzieren per βX. Nehmen wir aaabb als Beispiel:

DK:1a3a3a3b4

erst nach vier Zeichen landet der Automat in dem akzeptierenden Zustand 4. Wir reduzieren per abB; nun liegt aaB auf dem Stack. Wir füttern das dem DK-Automaten:

DK:1a3a3B5

Wir landen in dem akzeptierenden Zustand 5 und reduzieren per BS. Der Stack ist nun aaS. Das bringt uns nach

1a3a3S7

und wir reduzieren per aSS; der Stack ist nun aS; jetzt wiederholt sich der letzte Schritt, wir reduzieren noch einmal per aSS, und nun liegt S auf dem Stack. Das Eingabewort ist noch nicht vollständig gelesen: das zweite b fehlt noch. Wenn wir allerdings S dem DK-Automaten füttern, landet der im Todeszustand und weiß nicht mehr weiter. Der Parse-Vorgang ist fehlgeschlagen. Was ist passiert?

Der Knackpunkt liegt bei Zustand 5. Dieser suggiert eine Reduktion BS. Allerdings wäre es auch möglich, ein weiteres Zeichen zu lesen (falls dies b ist) und in Zustand 6 zu landen. Sehen Sie: unser Parser hat im zweiten Schritt

aaBbaaSb

reduziert, was kein korrekter Linksreduktionsschritt ist, weil aaSb keine gültige Wortform ist. Wenn das b aber nicht da wäre, wäre

aaBaS

korrekt. Wir sehen also: aaBwaSw ist ein korrekter Schritt für w=ϵ, aber nicht für w=b, obwohl aaBb selbst eine gültige Wortform ist. Die Grammatik verletzt also die LR(0)-Bedingung aus Definition 5.7.4. Die Korrektheit eines Linksreduktionsschritts αβwαXw kann also ohne Kenntnis von w gar nicht sichergestellt werden.

Die Grammatik abändern: Stopper-Zeichen am Schluss

In diesem Fall lässt sich das Dilemma flicken, indem wir ein Stopperzeichen am Ende einfügen. Wir ändern die Grammatik leicht ab:

Gz:SaSSBzBaBbBab

Es gilt also L(Gz)=L(G){z}, wir hängen also jedem G-Wort ein z an. Wie sieht der NDK- und der DK-Automat aus?

Das z verhindert, dass der Parser verfrüht BS reduziert und zwingt ihn, damit zu warten, bis das Ende des Inputs erreicht ist. Wenn wir den Automaten betrachten, sehen wir, dass kein akzeptierender Zustand eine ausgehende Kante hat: wenn also γFront(Gz) ist, dann gibt es kein γγ1Front(Gz) mit |γ1|>0. In anderen Worten: die Sprache Front(Gz) ist präfixfrei. Es kann also keinen Zweifel geben, dass bei Erreichen eines akzeptierenden Zustandes reduziert werden muss.

Die Klammern-Grammatik

Als nächstes Beispiel schauen wir uns eine Grammatik an, die die wohlgeformten Klammerausdrücke generiert:

G():SϵSS(S)

mit den Terminalen {(,)} und dem einen Nichtterminal S. Diese Grammatik hat mit Sϵ die gewöhnungsbedürftige Eigenschaft, dass der Reduktionsschritt wSw immer technisch möglich wäre (wenn auch nicht immer korrekt). NDK- und DK-Automat sind schnell gezeichnet:

Auch in diesem Automaten haben akzeptierende Zustände ausgehende Kanten, die zu weiteren akzeptierenden Zuständen führen. Somit ist Front(G()) auch nicht präfixfrei. In der Tat sind

ϵ,S(,S(S(,S(S(S)

alles Wörter in Front(G()). Das ist in diesem Kontext aber nicht schlimm: wenn wir entscheiden wollen, ob

αβwαXw

ein gültiger Linksreduktionsschritt ist, dann kennen wir w zwar noch nicht, wissen aber, dass wΣ gilt, da w ja der ungelesene Teil des Eingabewortes ist; insbesondere kann das erste Zeichen von w kein Nichtterminal sein. Insofern können wir dem DK-Automaten für G() trauen: wenn er uns in einen akzeptierenden Zustand führt, können wir reduzieren.

Übungsaufgabe 6.9.1 Lassen Sie den LR-Parser auf (()())() laufen und protokollieren Sie Stackinhalt und Zustand des DK-Automaten.

Übungsaufgabe 6.9.2 Zeichnen Sie NDK-Automaten und DK-Automaten für die Grammatik

SϵSS(S)

und zeigen Sie, dass diese nicht LR(0) ist. Finden Sie also eine Produktion Xβ und gültige Wortformen αβw und αβw, so dass αXw gültig ist, nicht aber αXw.

Übungsaufgabe 6.9.3 Untersuchen Sie unsere Arithmetik-Grammatik:

E(E+E)E(EE)ENNDNNDD0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Zeichnen Sie NDK- und DK-Automaten und zeigen Sie, dass die Grammatik nicht LR(0) ist.

Challenge. Fügen wir den arithmetischen Ausdrücken noch ein Schlusszeichen ; hinzu, also (2+(43)); statt (2+(43)). Wenn wir obige Grammatik um die Regel SE; ergänzen und S zum Startsymbol machen, ist sie dann LR(0)? Wenn nicht: können Sie die Grammatik umschreiben (also eine äquivalente, d.h. die gleiche Sprache produzierende Grammatik angeben), so dass sie LR(0) wird?

Der DK-Test

Wir beschreiben nun, wie man algorithmisch testet, ob eine gegebene Grammatik die LR(0)-Bedingungen erfüllt.

Theroem 6.9.2 (DK-Test). Sei G eine kontextfreie Grammatik ohne nutzlose Nichtterminale und ei M der DK-Automat für die Grammatik G. Die Grammatik G ist LR(0) genau dann, wenn folgende zwei Bedingungen gelten:

  • (DK.1) Ein akzeptierender Zustand von M (der ja eine Menge von Zuständen des NDK-Automaten ist) enthält genau einen akzeptierenden NDK-Zustand, also genau ein Xβ.
  • (DK.2) Wenn q ein akzeptierender Zustand von M ist und qσq, dann ist σ ein Nichtterminal.

Wenn diese beiden Bedingungen gelten, sagen wir, dass G den DK-Test bestanden hat. Das Theorem sagt also: G ist LR(0) genau dann, wenn es den DK-Test besteht.

Beweisskizze. Wir erinnern den Leser noch einmal an die alternative Charakterisierung von LR(0)-Sprachen, nämlich :

Lemma 5.7.5, noch einmal (LR(0), äquivalente Formulierung). Eine Grammatik G ist LR(0) genau dann, wenn für alle korrekten Linksreduktionsschritte αβwαXw und αβwαXw gilt:

  1. Falls αβ=αβ dann auch β=β und X=X; in Worten: wenn die Fronten identisch sind, dann auch die Reduktionsschritte.
  2. Wenn αβ=αβφ und |φ|1, dann φΣ; in Worten: wenn front(γ) ein echter Präfix von front(γ) ist, dann muss in dem überstehenden Teil von front(γ) mindestens ein Nichtterminal vorkommen.

Es ist nicht schwer zu sehen, dass (DK.1) äquivalent zu Punkt 1 des Lemmas ist. Wenden wir uns (DK.2) und Punkt 2 zu. Wenn Punkt 2 nicht gilt, dann gibt es korrekte Reduktionsschritte

αβwαXwαβσw=αβwαXw

Wenn wir dem Automaten den Präfix αβ füttern, bringt er uns in einen Zustand, der die Xβ. enthält, da αβ ja eine Front ist. Dieser Zustand muss allerdings einen Übergang haben, der mit σ gelabelt ist, dem ersten Zeichen von φ, da ja αβσ ein Präfix der Front αβ ist. Somit gilt (DK.2) nicht.

Wenn umgekehrt (DK.2) nicht gilt, dann gibt es einen akzeptierenden Zustand q (der also Xβ. enthält) und eine ausgehende Kante qσq mit einem Terminal σ.

Übungsaufgabe 6.9.4 Zeigen Sie: wenn G keine nutzlosen Nichtterminale hat, dann gibt es im NDK-Automaten für jeden Zustand q eine Übergangsfolge qvq zu einem akzeptierenden Zustand q, wobei v ausschließlich aus G-Terminalen besteht, also vΣ.

Zeigen Sie das selbe für den DK-Automaten.

Es gibt also einen Weg qσqvq für vΣ und einen akzeptierenden Zustand q. Es sind also sowohl αβ als auch αβσv Fronten von G, und σv besteht nur aus Terminalen. Das heißt, dass Punkt 2 der Schlussfolgerung nicht gilt.

LR(1)-Grammatiken

Hier ist der nichtdeterministische Automat für G mit Lookahead 1.
Jetzt machen wir den Automaten deterministisch: