Programmiersprachen und Übersetzer

Werbung
Programmiersprachen und Übersetzer
Sommersemester 2010
10. Mai 2010
Das Parsing-Problem
Gegeben ist eine kontext-freie Grammatik G = (N, T , P, S) und
ein Wort w ∈ T ∗ .
Frage: Ist w ∈ L(G )?
In der Theorie auch als Wortproblem (für kontextfreie Sprachen)
bekannt.
�
Das Problem ist entscheidbar, d.h. es gibt einen Algorithmus,
der diese Frage beantwortet.
�
Der Algorithmus läuft in einer Zeit O(n3 ) mit |w | = n.
Deterministisches Top-Down-Parsen
Forderungen an das deterministische Top-Down-Parsing:
1. Man liest das vorgegebene Wort w nur einmal von links nach
rechts.
2. Parallel dazu wird der Ableitungsbaum von oben nach unten
und von links nach rechts erzeugt. Dies bedeutet, dass eine
Linksableitung von w rekonstruiert wird.
3. Ist man an einem Knoten des Baumes angekommen, der mit
einem nichterminalen Symbol A markiert ist und der als
nächster gemäß obiger Regel 2) verarbeitet werden muss, so
kann man mit Hilfe des nächsten zu lesenden Symbols von w
(Lookahead Symbol) die anzuwendende A-Produktion
eindeutig bestimmen.
Anfangssituation beim Top-Down Parsing
Startsymbol
S
✟ der Grammatik
✟
✙
✟
Lesefenster, zeigt zu Anfang das
erste Symbol in der Eingabe
� (Lookahead Symbol)
�
�
✠
abzuleitendes Wort w
Situation im Top-Down Parsing
S
�
�
�
�
�
�
�
�❅
�
❅
✡✡
✡
✡
✡
✣
✡
✡
bereits abgeleitetes
Anfangsstück von w
nächstes nichtterminales
Symbol, auf das eine
Produktion anzuwenden ist
✑
✑
❅✑
✑
✑
✰
❅
❅
A
✑
✑
✰
✑ Rest von w
❅
■
❅ Lesefenster
Bemerkung
1. Nicht alle Grammatiken erlauben ein Parsen in dieser Art.
2. Es gibt kontextfreie Sprachen, für die es keine kontextfreie
Grammatik gibt, die ein Parsen in der oben angegebenen Art
ermöglicht.
Beispiel
Für die Sprache {an 0b n | n ≥ 0} ∪ {an 1b 2n | n ≥ 0} gibt es keine
Grammatik, die ein deterministisches Top-Down Parsing in dieser
Art ermöglicht.
Eigenschaften von Grammatiken, die deterministisches
Top-Down Parsing verhindern
1. Eine Situation, in der eine Grammatik die oben gestellten
Forderungen nicht erfüllen kann, ist gegeben, wenn es
verschiedene Produktionen mit der gleichen linken Seite gibt,
deren rechte Seiten ein gleiches Anfangsstück (Präfix) haben.
2. Deterministisches Top-Down-Parsen ist offensichtlich auch
nicht möglich, falls sogenannte linksrekursive Produktionen,
d. h. Produktionen der Form A → Aα, α ∈ (N ∪ T )+ ,
auftreten.
Entfernen gemeinsamer Präfixe
Seien A → αβ1 | . . . | αβr | γ1 | . . . | γs alle A-Produktionen der
Grammatik, wobei α �= ε und kein γi das Präfix α hat.
Ersetze diese Produktionen durch
A → αA� | γ1 | . . . | γs
�
A
und
→ β1 | . . . | βr ,
wobei A� ein neues nichtterminales Symbol ist.
Man kann leicht einsehen, dass die so umgeformte Grammatik die
gleiche Sprache erzeugt.
Entfernen linksrekursiver Produktionen
Seien A → Aα1 | . . . | Aαm | β1 | . . . | βn alle A-Produktionen,
wobei kein βi mit A beginnt und alle αi �= ε sind.
Dann ersetze man diese Produktionen durch:
A → β1 A� | . . . | βn A�
�
A
�
und
�
→ α1 A | . . . | αm A | ε ,
wobei A� ein neues nichtterminales Symbol ist.
Bemerkung
Diese Umformungen müssen nicht unbedingt zu einer Grammatik
führen, die deterministisches Parsing erlaubt.
Definition der Funktionen First und Follow
Definition
Sei α ∈ (N ∪ T )∗ . Dann ist
∗
∗
First(α) = {a | α =⇒ aβ, a ∈ T , β ∈ (N ∪ T )∗ } ∪ {ε | α =⇒ ε}.
Bemerkung
First(α) ist also die Menge derjenigen terminalen Zeichen, die bei
Linksableitungen von α als erste auftreten. Kann man aus α das
leere Wort ε herleiten, so ist ε ebenfalls in First(α).
Definition
Sei A ∈ N. Dann ist
∗
Follow(A) = {a | S =⇒ αAaβ, a ∈ T , α, β ∈ (N ∪ T )∗ }
∗
∪ {$ | S =⇒ αA, α ∈ (N ∪ T )∗ },
wobei $ ein neues Symbol ist, das das Ende der Eingabe markiert.
Bemerkung
Follow(A) ist also die Menge derjenigen terminalen Zeichen, die
bei einer Ableitung vom Startsymbol S aus direkt auf A folgen
können. Folgt A kein Zeichen, d. h. A steht ganz rechts, so ist die
Endmarkierung $ in Follow(A).
LL(1) Grammatik
Definition
Eine kontextfreie Grammatik G = (N, T , P, S) heißt
LL(1)-Grammatik (Lesen der Eingabe von Links nach rechts,
Erzeugen einer Linksableitung und 1 (ein Zeichen) Lookahead),
falls für alle A ∈ N gilt:
Seien A → α1 | . . . | αn alle A-Produktionen in P.
1. First(α1 ), . . . , First(αn ) sind paarweise disjunkt,
d. h. First(αi ) ∩ First(αj ) = ∅ falls i �= j.
2. Ist ε ∈ First(αj ), dann ist Follow(A) ∩ First(αi ) = ∅ für
1 ≤ i ≤ n, i �= j.
Berechnung der First- und Follow-Funktion:
Als erstes bestimmt man, welche nichtterminalen Symbole sich auf
das leere Wort ableiten lassen, d.h. wir bestimmen die Menge
∗
Mε = {A | A ∈ N und A =⇒ ε}.
Gegeben sei eine kontextfreie Grammatik G = (N, T , P, S).
Algorithmus zur Berechnung von Mε :
1. Bestimme M0 = {A | A → ε ist in P}
2. Berechne Mi+1 = Mi ∪ {A | A → α ist in P und α ∈ Mi∗ }
3. ist Mi+1 = Mi , dann setze Mε = Mi .
Danach ist es möglich, die First-Funktion zunächst einmal für alle
nichtterminalen Symbole der Grammatik zu berechnen.
Algorithmus zur Berechnung der First-Funktion:
Berechnung von First(A) für alle A ∈ N. Man konstruiert einen
Graphen Γ folgendermaßen
1. Jedes Symbol aus N ∪ T wird durch einen Knoten dargestellt.
2. Für jede Produktion A → X1 . . . Xn mit n ≥ 1, fügt man eine
Kante von A nach Xi hinzu, falls X1 , . . . , Xi−1 ∈ Mε .
3. Setze First(A) = {a | a ∈ T und es gibt einen Weg von A
nach a in Γ} ∪ {ε |falls A ∈ Mε }.
Damit ist aber die First-Funktion auch für alle α ∈ (N ∪ T )∗
bestimmt.
Es gilt
1. Ist α = ε, dann ist First(α) = {ε}.
2. Ist α = aβ mit a ∈ T , dann ist First(α) = {a}.
3. Ist α = Aβ �
mit A ∈ N, dann ist
First(A)
falls A ∈
/ Mε
First(α) =
(First(A) − {ε}) ∪ First(β) falls A ∈ Mε
Beispiel
Betrachte die Grammatik G = (N, T , P, S) mit N = {S, A, B, C },
T = {d, e, f , g , h, p, q} und den Produktionen S → ABCd,
A → e | f | ε, B → g | h | ε und C → p | q.
Beispiel
Betrachte die Grammatik G = (N, T , P, S) mit N = {S, A, B, C },
T = {d, e, f , g , h, p, q} und den Produktionen S → ABCd,
A → e | f | ε, B → g | h | ε und C → p | q.
Es ist Mε = {A, B} und der zur Bestimmung der First-Funktion
benötigte Graph Γ ist
S
d
e
A
f
B
g
C
h
p
q
Also gilt:
First(S) = {e, f , g , h, p, q},
First(A) = {e, f , ε},
First(B) = {g , h, ε} und
First(C ) = {p, q}.
Beispiel
Als weiteres Beispiel betrachten wir eine Grammatik mit
nichtterminalen Symbolen {S, A, B, C , D} und terminalen
Symbolen {a, b, c, d, g }. Die Produktionen sind S → AB,
B → aAB | ε, A → CD, D → bCD | ε und C → cSd | g .
S sei das Startsymbol.
Es ist leicht einzusehen, dass Mε = {B, D} gilt.
Der Graph zur Bestimmung der First-Funktion ist
S
B
a
A
b
c
D
d
C
g
Es gilt also
First(S) = First(A) = First(C ) = {c, g },
First(B) = {a, ε}, und
First(D) = {b, ε}.
Algorithmus zur Berechnung der Follow-Funktion
Es wird wieder ein Graph Γ aufgebaut, der in diesem Fall für jedes
Symbol in N ∪ T ∪ {$} einen Knoten hat.
1. Füge eine Kante von S nach $ hinzu.
2. Für jede Produktion A → αBβ mit A, B ∈ N,
α, β ∈ (N ∪ T )∗ , füge eine Kante von B nach jedem
a ∈ First(β), a ∈ T hinzu. Ist ε ∈ First(β) und A �= B, dann
füge eine Kante von B nach A hinzu.
3. Setze Follow(A) = {a | a ∈ T ∪ {$} und es gibt einen Weg
von A nach a in Γ}.
Beispiel
Der Graph zur Bestimmung der Follow-Funktion ist
S
$
B
a
A
b
D
c
C
d
g
Follow(S) = Follow(B) = {d, $},
Follow(A) = Follow(D) = {a, d, $}, und
Follow(C ) = {a, b, d, $}.
Ein tabellengesteuerter Top-Down-Parser
Idee: Der noch abzuleitenden Teils des Ableitungsbaumes wird in
einem Stack gespeichert:
S
� ❅
�
❅
A
b
C
✓ ❙
✓
❙
d D G
✁ ❆
✁
❆
f
E
E ✛
d f ?
Eingabe
$
...
✻Lesefenster
G
b
C
$ Stack
Steuerwerk
Aufbau eines tabellengesteuerten Top-Down Parsers
a
X
..
.
$
Eingabewort w
$
✛
✲
endliches
Steuerwerk
✛
❄
Ausgabe, z. B. Liste der in einer
Linksableitung angewendeten
Produktionen
ParsingTabelle
M
Die Parsing-Tabelle
�
Die Zeilen der Tabelle sind mit den nichtterminalen Symbolen
der Grammatik markiert
�
Die Spalten sind mit den terminalen Symbolen und dem
End-Symbol $ markiert.
�
Jeder Eintrag M(A, x) in M (A ∈ N, x ∈ T ∪ {$}) enthält
entweder eine Produktion der Grammatik oder das
Fehlersymbol #.
Konstruktion der Parsing-Tabelle M:
Für jede Produktion A → α in P führe die folgenden Schritte aus:
1. Für jedes a ∈ T in First(α) setze M(A, a) = A → α.
2. Ist ε in First(α), dann setze für jedes b mit b ∈ T ∪ {$} in
Follow(A) M(A, b) = A → α.
Die LL(1)-Bedingung garantiert, dass jeder Tabellenplatz
höchstens einmal besetzt wird!
Jeder dann noch undefinierte Eintrag in M wird auf # gesetzt.
Beispiel
Verwendet man die Grammatik mit den Produktionen
1: S → (S)R, 2: S → aR, 3: R → +S, 4: R → ∗S und 5: R → ε,
so erhält man die folgende Parsingtabelle M:
M
a
(
)
+
S S → aR S → (S)R
#
#
R
#
#
R → ε R → +S
∗
#
R → ∗S
bzw. die vereinfachte Form:
M
S
R
a
2
#
(
1
#
)
#
5
+
#
3
∗
#
4
$
#
5
$
#
R→ε
Arbeitsweise eines tabellengesteuerten Top-Down Parsers
Initiale Situation
�
Auf dem Eingabeband steht das zu parsende Wort w gefolgt
von der Endmarkierung $.
�
Das Lesefenster steht auf dem ersten Zeichen von w .
�
Der Stack enthält als unterstes Symbol $ und darüber das
Startsymbol S der Grammatik.
Arbeitsschritt
Sei X das oberste Stacksymbol und a das Zeichen im Lesefenster
der Eingabe.
(i) Ist X = a = $, dann beende das Parsen. Das vorgelegte Wort
ist aus der von der Grammatik erzeugten Sprache L(G ).
(ii) Ist X ∈ T und gilt X = a, dann lösche X vom Stack und
setze das Lesefenster ein Zeichen weiter.
Ist X ∈ T und X �= a, dann beende das Parsen. Es gilt
w �∈ L(G ).
(iii) Ist X ∈ N, dann betrachte M(X , a). Enthält M(X , a) die
Produktion X → A1 . . . Ar , dann lösche X und schreibe
stattdessen Ar , . . . , A1 (in dieser Reihenfolge!) auf den Stack
und gib die Produktion aus.
Enthält M(X , a) dagegen das Fehlersymbol #, dann beende
das Parsen. In diesem Fall gilt w �∈ L(G ).
Konfigurationen
Um die Arbeitsweise eines tabellengesteuerten Top-Down-Parsers
etwas kompakter darzustellen, wird der momentane Stackinhalt
und die restliche Eingabe ab dem Lookahead-Symbol als ein Paar
von Wörtern (X . . . $, a . . . $) notiert, wobei X . . . $ den Stackinhalt
mit dem obersten Symbol X und a . . . $ den Teil des
Eingabewortes ab dem Lookahead-Symbol a darstellt.
�
Ein derartiges Paar nennt man eine Konfiguration des Parsers.
�
Jeder Schritt des Parser erzeugt so eine neue Konfiguration.
Ein Schritt der Form (ii) soll durch
(aY . . . $, ab . . . $) �−→ (Y . . . $, b . . . $)
und ein Schritt der Form (iii) durch
(i)
(XY . . . $, a . . . $) �−→ (A1 . . . Ar Y . . . $, a . . . $)
bezeichnet werden, wobei i die Nummer der angewendeten
Produktion angibt.
Herunterladen