Die LL-Parser, die wir im letzten Teilkapitel gesehen haben,
versuchen, eine Linksableitung Schritt für
Schritt zu konstruieren. Eine Hauptschwäche ist, dass Sie von
einer Wortform
die nächste Ableitungsregel bestimmen müssen,
ohne das "Endergebnis" von überhaupt vollständig gelesen zu haben.
Sie müssen also erkennen, dass der rote / mit "?" markierte Pfeil in der Ableitung
die richtige Entscheidung ist, obwohl sie von dem aus abgeleiteten
Wort nur die ersten Buchstaben sehen.
Wäre es nicht besser, der Maxime zu folgen:
Maxime: Wende eine Regel nur dann an,
wenn Du im Eingabewort ein vollständiges Teilwort erkannt hast,
das aus ableitbar ist.
Ich will diese Maxime an einem Beispiel demonstrieren. Betrachten
wir die Grammatik
Die von der Grammatik erzeugte Sprache ist
also: mindestens ein und mindestens so viele s wie s. Die
Sprache ist nicht LL:
Die Ableitungen unterscheiden sich bereits in der ersten angewandten
Produktion; trotzdem stimmen die Endergebnisse in den ersten
(ja sogar ) Zeichen überein.
Ich will nun aber zeigen, wie man eine Ableitung findet,
wenn man die obige Maxime befolgt.
Hierfür arbeiten wir uns von hinte nach vorne durch: statt mit
anzufangen und zu hinzuarbeiten, fangen wir mit
an und versuchen, und zu hin zurückzuarbeiten.
In der folgenden Animation ist der Teil des Wortes, den
unser Parser gelesen hat, schwarz markiert; der noch nicht gelesene
Teil ist grau.
Wir erhalten eine Rechtsableitung, allerdings in verkehrter Reihenfolge,
vom Zielwort hin zum Startsymbol. Daher schreiben wir in der obigen Animation auch
statt geschrieben. Wenn Sie die Animation
in verkehrter Reihenfolge anschauen (also immer auf
klicken), dann
sehen Sie die Rechtsableitung in der "üblichen" Reihenfolge.
Notation
Da aus der Sicht unseres neuen Parsers die Rückwärtsanwendung einer Produktion
, also der Schritt
einen Fortschritt
darstellt und zeitlich auch
nach vorne geht, verwende ich ab jetzt ein neues Pfeilsymbol, das
von links nach rechts geht und daher dem in Europa üblichen Gefühlt,
dass die Zeit von links nach rechts verläuft, mehr entgegen kommt:
.
Definition 5.5.1(Reduktion und Linksreduktion)
Seien
Wortformen und
eine Produktion. Dann schreiben wir
und sagen reduziert zu .
Man nennt einen solchen Schritt auch einen Reduktionsschritt.
Wir nennen den Schritt einen Linksreduktionsschritt, wenn
rechts von nur Terminale stehen. Wenn also
und somit
Eine Folge von Linksreduktionsschritten nennen wir eine
Linksreduktion. Links, weil wir uns
von links nach rechts in den terminalen Teil hineinarbeiten.
Wir werden im folgenden nur Linksreduktionsschritte betrachten und
sagen daher manchmal auch einfach nur Reduktionsschritt.
Beobachtung: Wenn wir
einen Linksreduktionsschritt
betrachten, also
so sehen wir, dass das am weitesten rechts stehende Nichtterminal
ersetzt worden ist; Linksreduktionen
entsprechen also einer Rechtsableitung.
Vorsicht. Verfallen Sie nicht in verfrühte Euphorie.
Das Prinzip der Linksreduktion ist immer noch nichtdeterministisch.
Die folgende Abbildung illustriert, dass manchmal mehrere Reduktionen
anwendbar sind:
Uns als Betrachter ist klar, dass nur die obere Alternative zum Erfolg führt;
wie das der Parser aber im Allgemeinen entscheiden soll, ist uns
zu diesem Zeitpunkt noch nicht klar und wird uns für den Rest dieses Kapitels
beschäftigen.
Definition 5.5.2(Gültige Wortform, korrekter Reduktionsschritt)
Eine Wortform heißt gültig,
wenn es eine Rechtsableitung
gibt. Ein Linksreduktionsschritt
heißt korrekt, wenn gültig ist.
Beobachtung: Das impliziert unmittelbar,
dass die Wortform auch gültig ist.
Überlegen wir nun, welche Situationen schlecht wären für unser Vorhaben,
einen Parser ohne Sackgassen zu implementieren.
Schlechter Fall 1:
Es gibt für eine Wortform zwei
korrekte Linksreduktionsschritte:
und
.
In diesem Falle gäbe es zwei verschiedene
Rechtsableitungen
(wir nehmen an, dass man aus jedem Nichtterminal
mindestens ein Wort ableiten kann; andernfalls
kann man solche nutzlosen Nichtterminale eliminieren).
Das Wort hat also zwei verschiedene Ableitungsbäume.
Die Grammatik ist somit mehrdeutig.
Mit uneindeutigen Grammatiken beschäftigen wir zunächst gar nicht;
sie sind von einem praktischen Standpunkt aus auch
unattraktiv: wenn z.B. eine Programmiersprache eine mehrdeutige Grammatik
hätte, dann wäre ja für gewisse Programme nicht eindeutig
festzustellen, was damit gemeint ist.
Wir nehmen also an, dass die Grammatik eindeutig ist und somit
dieser Fall nicht eintreten kann.
Schlechter Fall 2:
Der Linksreduktionsschritt
ist korrekt, aber der Schritt
ist nicht korrekt, obwohl eine
gültige Wortform ist.
Dieser Fall ist schlecht, weil der Parser zum Zeitpunkt
, wenn er also
gelesen hat, aber noch nicht , sich
nicht sicher sein kann, dass der Reduktionsschritt korrekt ist.
Korrektheit hängt davon ab, was weiter rechts davon kommt.
Beispiel 5.5.3
Erinnern Sie sich: unsere obige Grammatik
ist eindeutig (das ist leicht zu sehen), allerdings tritt
Schlechte Fall 2 ein: der Reduktionsschritt
ist korrekt (hier also , aber leider ist
nicht korrekt (hier , da gültig ist,
aber nicht. Die Entscheidung für eine Reduktionsregel
kann also nicht ohne Wissen über das, was dahinter kommt,
getroffen werden.
Definition 5.5.4(-Grammatiken)
Eine kontextfreie Grammatik heißt -Grammatik,
wenn keiner der obigen schlechten Fälle eintritt.
Formal, wenn (1) die Grammatik eindeutig ist und (2) wenn ein
korrekter Linksreduktionsschritt
bedeutet, dass auch alle anderen Linksreduktionsschritte
korrekt sind, sofern selbst überhaupt gültig ist.
In Worten/Graustufen: wenn wir
guten gewissens anwenden dürfen, ohne auch nur ein Zeichen von
gelesen zu haben.
Übungsaufgabe 5.5.1
Zeigen Sie, dass die folgende Grammatik ist:
Als ersten Schritt sollten Sie sich überlegen, wie gültige
Wortformen überhaupt aussehen können.
Übungsaufgabe 5.5.2
Zeigen Sie, dass die folgende Grammatik ist:
Als ersten Schritt sollten Sie sich überlegen, wie gültige
Wortformen überhaupt aussehen können.
Übungsaufgabe 5.5.3
Wir ändern die Grammatik etwas ab:
Die erzeugte Sprache ist .
Zeigen Sie, dass die Grammatik nicht ist.
Es stellen sich unmittelbar zwei zentrale Fragen:
Wie können wir verifizieren, ob eine gegebene Grammatik
die -Bedingung erfüllt?
Falls sie es tut, wie können wir für eine gegebene Wortform
den korrekten Reduktionsschritt
finden? Insbesondere die Zerlegung in
?
Beide Aufgaben können mit Hilfe eines endlichen Automaten,
des DK-Automaten erledigt werden (DK steht als Abkürzung für Donald Knuth,
der als erstes diese Idee hatte).
Hinweis. Was nun folgt, ist mathematisch
recht herausfordernd. Lesen Sie daher gerne auch
das Kapitel 2.4 (Deterministic context free languages) im Lehrbuch
Introduction to the Theory of Computing von Michael Sipser.
Meine Darstellung des doch recht schwierigen Materials fußt auf diesem
Kapitel, weicht aber doch stark genug von Sipser ab, so dass es
womöglich hilfreich ist, beides zu lesen: dieses Vorlesungsskript und
das Kapitel in Sipers Buch.
Gültige Wortformen und deren Ableitungsbäume
Zu jeder Ableitung können wir
eindeutig einen Ableitungsbaum zeichnen. Wenn die Grammatik
eindeutig ist, so hängt auch der Baum nur vom Wort ab und
nicht von der Ableitung .
Allerdings können wir für Zwischenformen
auch einen Ableitungsbaum zeichnen,
und der unterscheidet sich stark, abhängig davon, ob eine
Rechtsableitung, Linksableitung oder sonst was ist.
Ich zeige Ihnen jetzt ein Beispiel für eine Grammatik und
eine Handvoll Ableitungen samt Ableitungsbaum.
Es ist zu diesem Zeitpunkt irrelevant, ob eindeutig
oder sogar ist. Ich interessiere mich
gerade nur für Ableitungsbäume von Wortformen.
Fällt Ihnen etwas auf? Schauen Sie sich bitte noch ein weiteres
Beispiel an für den Ableitungsbaum einer in einer
gültigen Wortform, also von einer, die in einer
Rechtsableitung vorkommen kann:
Warten Sie!
Scrollen Sie erst weiter, wenn Sie den Baum oben lang genug angeschaut haben!
Versuchen Sie selbst, die spezielle Form dieses Baumes möglichst
formal zu beschreiben!
Auflösung. Hier sehen Sie noch einmal
den gleichen Baum, nun aber gewisse Teile verschieden umrandet / eingefärbt.
Sie sehen: links vom Stamm gibt es nur Blätter.
Rechts vom Stamm ist jedes Blatt ein Terminalsymbol.
Wir erkennen auch, was der letzte Ableitungsschritt war, der zu diesem
Baum geführt hat: die Blüte ist hinzugekommen, in diesem Fall
also .
Wir definieren nun eingeführten Begriffe formal:
Definition / Beobachtung 5.5.5(Stamm, linker Rand, Blüte, rechter Rest)
Sei eine Rechtsableitung, also
eine gültige Wortform, und
der Ableitungsbaum von .
Stamm ist der Pfad von der Wurzel zu jenem inneren Knoten ,
der von allen inneren Knoten, deren Kinder allesamt Blätter sind,
am weistesten links steht.
Die Kinder von , per Definition alles Blätter, sind die Blüte.
Die Menge der Knoten, die einen Stammknoten als rechtes Geschwister haben,
heißt der linke Rand. Jeder Knoten im linken Rand
muss ein Blatt sein, ansonsten stünde er ja weiter links als ;
die Menge der rechten Geschwisterkinder von Stammknoten sowie deren Nachkommen
heißt der rechte Rand.
Im rechten Rest ist jedes Blatt ein
Terminalsymbol, ansonsten wäre es keine Rechtsableitung.
Die Beschriftung der Knoten im linken Rand ergibt eine Wortform ;
die Blüte ergibt .
Die Blätter im rechten Rand
sind ausschließlich mit Terminalen beschriftet und ergeben ein
Wort . Der ganze Baum stellt also eine Rechtsableitung
dar. Wir sagen auch, dass eine Blüte von ist,
ohne über den Ableitungsbaum selbst zu reden.
Hierbei ist zu beachten, dass in einer mehrdeutigen Grammatik
eine gültige Wortform mehrere Ableitungsbäume und somit
mehrere Blüten haben kann, die Unterteilung
also nicht eindeutig ist. Für eindeutige Grammatiken ist
die Unterteilung aber eindeutig.
Sei weiterhin die Beschriftung des Elternknoten der Blüte
(notwenigerweise ein Nichtterminal; Terminale haben keine Kinder).
Dann ist eine Produktion in der Grammatik und
eine gültige Wortform; wir erhalten den Ableitungsbaum
von , indem wir die Blüte von entfernen.
Wir schließen, dass
ein korrekter Linksreduktionsschritt ist.
Wir können also, ausgehend von der Wortform ,
eine Linksreduktion finden,
indem wir den Ableitungsbaum von zeichnen und
immer wieder die Blüte abschneiden:
Um für eine Wortform den korrekten Reduktionsschritt zu finden,
reicht es also aus, linken Rand und Blüte zu bestimmen, also
und , so dass
und korrekt ist ( steht hier
für das Nichtterminal, mit dem der Elternknoten der Blüte beschriftet ist).
Linken Rand und Blüte zu finden scheint keine leichte Aufgabe zu sein:
schließlich müssen wir dafür doch den Ableitungsbaum von bilden,
was selbst wieder eine Parsing-Aufgabe ist???
An dieser Stelle zeigt sich die Genialität des DK-Ansatzes:
der Ableitungsbaum von kann beliebig verschachtelt sein,
aber Stamm, linker Rand und Blüte haben zusammen eine einfache,
beinahe linear anmutende Struktur. Schematisch:
Die Aussage "Stamm, linker Rand und Blüte haben eine einfache Struktur"
können wir formalisieren:
Lemma 5.5.6
Für eine kontextfreie Grammatik definieren wir
die Sprache :
also die Menge aller Wortformen, die als Beschriftung des linken Randes
und der Blüte in einem Ableitungsbaum einer gültigen Wortform
vorkommen können.
Die Sprache SLRB ist regulär.
Insbesondere gibt es eine reguläre Grammatik für
SLRB, so dass die Blüte genau die im letzen Ableitungsschritt
erzeugten Terminalsymbole sind.
Hier ist etwas Mentalgymnastik vonnöten: aus Sicht der Sprache
SLRB sind Terminalsymbole. Sie können
ja schließlich in den Wörtern der Sprache auftauchen.
Die Grammatik hat also die Terminalsymbole
. Darüberhinaus hat sie
die Nichtterminale ,
also für jedes Nichtterminal von ein
Meta-Nichtterminal . Das entspricht
dem in den obigen Bäumen, wo also als Blatt vorkommt;
das entspricht dem
,
also wo als innerer Knoten vorkommt. Bevor ich
formal definiere, zeige ich den obigen Ableitungsbaum
(ohne rechten Rand, weil der ja bei SLRB eh fehlt)
und annotiere jeden Knoten auf dem Stamm mit der entsprechenden
-Produktion.
Definition 5.5.7
Sei eine kontextfreie Grammatik. Die
SLRB-Grammatik oder DK-Grammatik von ist
die reguläre Grammatik
mit , wobei
für jede -Produktion
mit die Produktionen
besitzt.
Strenggenommen ist eine erweitert reguläre Grammatik,
weil manche Regeln mehrere Terminale hintereinander haben.
Beobachtung 5.5.8 erzeugt die Sprache
.
Beispiel 5.5.9
Für unsere Grammatik oben ergeben sich folgende Produktionen
in :
Nochmals: Produktionen wie
sind regulär
(strenggenommen: erweitert regulär),
weil aus Sicht von beides
Terminalsymbole sind. Wir können nun unseren -Parser beschreiben:
Algorithmus 5.5.10- Der -Parser.
Starte mit einem leerem Stack. Sei
der Inhalt des Stacks zu einem Zeitpunkt.
Wenn , dann
betrachte die letzte angewandte -Produktion
und schreibe
. Wende die -Produktion
rückwärts an, reduziere also
Konkret: lösche vom Stack und ersetze es durch .
Falls ,
lies das nächste Eingabezeichen und lege es auf den Stack.
Der Parser endet, wenn weder Schritt 1 noch Schritt 2 möglich sind;
wenn zu diesem Zeitpunkt nur noch auf dem Stack liegt,
akzeptiert er, andernfalls lehnt er das Eingabewort ab.
Theorem 5.5.11
Wenn der -Parser akzeptiert, dann hat er eine Linksreduktion
und somit eine Rechtsableitung konstruiert; es gilt
also .
Wenn umgekehrt gilt und
die -Bedingung erfüllt,
dann findet der
Parser die Rechtsableitung ,
Beweis.
Da eindeutig ist, hat jede gültige Wortform
eine eindeutige Rechtsableitung und einen
dazugehörigen Ableitungsbaum mit
einem linken Rand , einer Blüte
und einem rechten Rest. Wir nennen die Wortform
den aktiven Teil von . Beachten Sie, dass rechts
vom aktiven Teil nur Terminalsymbole folgen.
Da eindeutig ist, nennen wir auch
den linken Rand von anstatt
den linken Rand von .
Betrachten wir einen Zeitpunkt während des Parsing-Prozesses.
Sei der Stackinhalt und der ungelesene
Teil des Eingabewortes.
Wir werden beweisen, dass zu jedem Zeitpunkt folgende
Invariante gilt:
Behauptung.
(i) ist eine gültige Wortform. (ii)
ist ein Präfix des aktiven Teils .
Beweis.
Die Behauptung gilt offensichtlich am Anfang, da und
somit eine gültige Wortform ist. Des weiteren
ist der Stack leer, also , und daher
sicherlich ein Präfix von .
Wir zeigen nun, dass, wenn die Invariante in Schritt gilt,
sie auch im nächsten Schritt gilt.
Es gibt nun zwei Möglichkeiten.
Der Parser wendet Schritt 1 an, also .
Das heißt nach Definition von SLRB, dass es ein gibt,
so dass eine gültige Wortform ist
und der aktive Teil, also ,
und ist die (eindeutige) Blüte von .
Sei
die letzte angewandte
-Produktion.
Dann ist
ein korrekter Reduktionsschritt. Da nach Annahme eine
-Grammatik ist, ist nach Definition auch
korrekt. Der Stackinhalt im nächsten Schritt ist somit
; der ungelesene Teil ist nach wie vor ,
und somit gilt Teil (i) der Invariante.
Um zu sehen, dass (ii) gilt, beachten Sie, dass nun
auf dem Stack oben ein Nichtterminal liegt: ;
da rechts vom aktiven Teil nur Terminale stehen, muss
im aktiven Teil von liegen; somit ist
Worten: der Stackinhalt ein Präfix
des aktiven Teils.
Der Parser wendet Schritt 2 an, also , er liest
und legt es auf den Stack. Im nächsten Schritt
ist der Stackinhalt und das ungelesene Wort
ist .
Teil (i) der Behauptung gilt offensichtlich, da
und somit immer noch eine gültige Wortform ist.
Um zu sehen, dass Teil (ii) gilt, beachten Sie, dass
ist. Das heißt also,
dass der aktive Teil von länger ist
als (kürzer kann er laut Invariante nicht sein).
Daher ist nun auch ein Präfix des aktiven Teils.
Wenn das Eingabewort gelesen ist, ist nun und
Stackinhalt ist selbst eine
gültige Wortform, die allerdings nicht weiter reduziert werden kann.
Also muss gelten und der Parser akzeptiert.