Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

Vorlesung Compilertechnik Sommersemester 2008

Ähnliche Präsentationen


Präsentation zum Thema: "Vorlesung Compilertechnik Sommersemester 2008"—  Präsentation transkript:

1 Vorlesung Compilertechnik Sommersemester 2008
Syntaktische Analyse M. Schölzel

2 Einbettung des Parsers in den Compiler
Syntax-baum Parser Parser Token Kontext- prüfung Zwischencode-/ Zielcodeerzeugung Symbol-tabelle

3 Aufgabe des Parsers Verarbeitung der Tokenfolge, die vom Scanner geliefert wird und Überführung in eine strukturierte Darstellung, z.B. einen Syntaxbaum, der die syntaktische Struktur des Quellprogramms repräsentiert: Deklarationen Anweisungsfolgen Anweisungen Ausdrücke Die syntaktische Struktur ist im Allgemeinen durch eine kfG spezifiziert. Der Parser muss eine Ableitung aus dem Startsymbol der kfG für die vom Scanner gelieferte Morphemfolge finden oder syntaktische Fehler erkennen und diese lokalisieren.

4 Der PDA mit Ausgabe als theoretisches Modell
Push-Down-Automat P = (Z, , z, f, , M)  – ein endliches Eingabealphabet Z – ein endliches Kelleralphabet mit   Z M – Metasymbole einer Grammatik (für die Ausgabe) z  Z* – Die Startbelegung des Kellers f  Z* – Kellerinhalt für akzeptierenden Zustand  : Z*  *  ((Z*  (M  ))  (Z*)) – Überführungsrelation Unterschiede zu klassischen PDAs: P hat nur einen Zustand P darf eine endliche Anzahl Kellersymbole in einem Takt lesen P darf eine endliche Anzahl (mehr als eins) Symbole der Eingabe sehen P erzeugt eine Ausgabe (linearisierter abstrakter Syntaxbaum) Situation (, , )  – Kellerinhalt (Kellerspitze rechts)  – Eingabe  – linearisierter abstrakter Syntaxbaum Takt (Situationsübergang) (, , )  (, , (A,i)), falls   Z* und   * und (,(A,i))  (, ) (, a, )  (, , ), falls   Z* und a  * und   (,a) Akzeptierte Sprache eines PDA P: L(P) := { | (z, , ) * (, , ) und  = f }

5 Einteilung Analyseverfahren
Berechnung des Ableitungsbaums Cocke-Younger-Kasami Algorithmus Universelle Verfahren deterministisch mit Backtracking nicht- deterministisch deterministisch mit Backtracking nicht- deterministisch Automatenbasiert deterministisch Backtracking- frei deterministisch Backtracking- frei Top-Down-Verfahren Bottom-Up-Verfahren

6 Top-Down-Verfahren Gegeben sind eine kfG G und ein Wort w.
Ausgehend vom Startsymbol S in G wird eine Linksableitung nach w gesucht. In jedem Ableitungsschritt wird das am weitesten links stehende Metasymbol durch eine seiner Alternativen ersetzt: Wahl der Alternative nichtdeterministisch oder Nach einem festen Schema; bei falscher Wahl endet die Ableitung in einer Sackgasse: Beginn der Sackgasse, falls  * w Erkennung der Sackgasse, falls   * und   ' und   w  * ' * w S *   * w

7 Grammatik in Top-Down PDA transformieren
Es sei G = (, M, S, R) eine kfG Der PDA P = (V, , z, , M) mit L(P) = L(G) ist definiert als: z := S V :=   M   (a,a) : a   ([A,i],(A,i))  (A,) : (A,[A,i])  R Der nichtdeterministische PDA P hat damit folgendes Verhalten: Startsituation (S, , ) (A, , )  ([A,i], , (A,i)) (a, a, )  (, , ) (, , ) akzeptierende Endsituation

8 Zusammenhang Analysesituation des Top-Down-PDA und Ableitung in Grammatik
Eine Analysesituation (,,) eines PDA P zur Grammatik G = (, M, S, R) mit (S,,) * (,,) entspricht der Linkssatzform  in der Linksableitung S *  * . D.h. aus S wurde ein Präfix  der Eingabe abgeleitet und es wird erwartet, dass aus  die terminale Zeichenkette  abgeleitet werden kann.  ist die Folge der angewendeten Ableitungen bei S *   ist für (S,,) * (,,) die Präfixlinearisierung des abstrakten Syntaxbaums zu .

9 Bottom-Up-Analyse  * '  S  w *    * S
Gegeben sind eine Grammatik G und ein Wort w. Ausgehend von w wird versucht eine Reduktion (Rückwärtsableitung) nach S zu finden. In jedem Rückwärtsableitungsschritt wird in der aktuellen Satzform ein Teilwort gesucht, das einer Alternative eines Metasymbols entspricht und durch das entsprechende Metasymbol ersetzt. (Reduktionsschritt): Wahl der Alternative und der Ersetzungsposition nichtdeterministisch oder Nach einem festen Schema; bei falscher Wahl endet die Rückwärtsableitung in einer Sackgasse: Beginn der Sackgasse, falls  * S Erkennung der Sackgasse, falls z.B. kein Teilwort von ' eine Alternative eines Metasymbols ist  * '  S w *   * S

10 Grammatik in Bottom-Up PDA transformieren
Es sei G = (, M, S, R) eine kfG Der PDA P = (V, , z, , M) mit L(P) = L(G) ist definiert als: z :=  V :=   M (A, (A,i))  ([A,i], ) : (A,[A,i])  R a  (,a) : a   Der nichtdeterministische PDA P hat damit folgendes Verhalten: Startsituation (, , ) ([A,i], , )  (A, , (A,i)) (, a, )  (a, , ) (S, , ) akzeptierende Endsituation

11 Zusammenhang Analysesituation und Ableitung bei Bottom-Up
Eine Analysesituation (,,) eines PDA P zur Grammatik G = (, M, S, R) mit (,,) * (,,) entspricht der Rechtssatzform  in der Rechtsableitung S *  *  (bzw.  *  * S). D.h.  wurde bereits eingestapelt (gesehen) und zu  reduziert und es wird erwartet, dass  zu S reduziert werden kann.  ist die Folge der angewendeten Ableitungen bei  * .  ist für (,,) * (S,,) die Postfixlinearisierung des abstrakten Syntaxbaums zu .

12 Top-Down-Analyse mit Backtracking durch Berechnung des Ableitungsbaums
-Regeln eliminieren Regeln der Form A  B eliminieren Dadurch: Ableitungsschritt verlängert das Wort oder überführt Meta- in Terminalsymbol(e) Abbruchkriterium: Wenn abgeleitetes Nicht-terminales Wort länger als das gegebene Wort w Untersuchung bis zu einer maximalen Anzahl von 2|w| - 1 Ableitungsschritten

13 Ableitungsbaum Ableitungsbaum zu einer Grammatik G = (, M, S, R) ist ein geordneter Baum B mit der Beschriftung  : B  V* für den gilt: B darf unendlich sein () = S  hat genau so viele Söhne, wie das in () am weitesten links stehende Metasymbol Alternativen hat Für .i  B gilt:   *   V*: (.i) = [A,i+1] und () = A und (A,[A,i+1])  R In dem Ableitungsbaum werden Linksableitungen betrachtet. Um festzustellen, ob w  L(G) genügt es in B die Beschriftungen von Adressen  zu untersuchen, für die gilt: || < 2|w|

14 Cocke-Younger-Kasami-Algorithmus
Eingabe: kfG (, M, S, R) in Chomsyk-Normalform und Wort w = w1…wn (wk  ). Konstruktion der Ableitung durch Berechnung der Mengen ti,j, wobei ti,j die Metasymbole enthält, aus denen die Zeichenkette wi…wi+j-1 abgeleitet werden kann. Berechnung durch: ti,1 := {A | (A,wi)  R} Berechnung von ti,j+1 := {A | k: 1  k < j+1 und (A,BC)  R und B  ti,k und C  ti+k,j+1-k} ist möglich, weil k < j+1 und j+1-k < j+1. Falls S  t1,n, dann ist w aus S ableitbar. Rekonstruktion der Ableitung beginnend bei t1,n.

15 Benötigte Operationen auf Wort(meng)en für Backtrackingfreie Analyse
prek : *  * ist definiert als: . k . : (*)  (*) ist definiert als: A k B := {prek() |   A und   B}

16 Backtrackingfreie Analyse
Ziel: In jeder Situation Wahl des richtigen Takts und der richtigen Alternative im Ableitungs-/Reduktionstakt, so dass Sackgassen vermieden werden. Entscheidung basiert auf: einer endlichen Vorausschau in der Eingabe um eine feste Anzahl von k vielen Morphemen (Look-Ahead), evtl. dem Wissen über die bisher gesehene Eingabe. Eingabe wird um k Morpheme der Art  verlängert, um auch am Ende des zu analysierenden Wortes immer eine Vorausschau von k Morphemen zu haben. Dafür Transformation der ursprünglichen Grammatik G in Grammatik G' mit der Regel S'  S k zum neuen Startsymbol S' und dem alten Startsymbol S. Vereinbarung: Im Folgenden wird in den Situationen des PDA der linearisierte abstrakte Syntaxbaum weggelassen.

17 Backtrackingfreie Top-Down-Analyse
Analysesituation (A,) bedeutet, dass erwartet wird, die Resteingabe  aus A ableiten zu können: A * . Das ist offensichtlich nur dann möglich, wenn durch Wahl der richtigen Alternative i von A gilt: [A,i] * . Bei Verwendung einer Vorausschau von k Zeichen muss sich aus [A,i] eine Zeichenkette ableiten lassen, die in den ersten k Terminalzeichen mit  übereinstimmt. Im folgenden Betrachtung verschiedener Klassen von Grammatiken, für die diese Entscheidung eindeutig getroffen werden kann.

18 Einfache LL(k)-Grammatiken
Eine Grammatik ist eine einfache LL(k)-Grammatik, wenn für jedes Metasymbol A gilt: |[A]| = 1 oder 1  i  |[A]|: prek([A,i])  k und 1  i  j  |[A]|: prek([A,i])  prek([A,j]) Offensichtlich ist dann in der Analysesituation (A,) die Auswahl einer Alternative eindeutig möglich durch: (A,)  ([A,i],) : prek([A,i]) = prek().

19 Beispiel: Einfache LL(1)-Grammatik
S'::= S S ::= if Id then S else S fi | while Id do S od | begin S end | Id := Id E E ::= + Id Q| * Id Q Q ::= Id := Id E | ;

20 Syntaxgebundene Implementierung
Für jedes Metasymbol A der Grammatik wird eine Funktion int A() definiert mit Rückgabe 0 für Fehler in der Eingabe oder Rückgabe 1 bei erfolgreicher Ableitung eines Präfixes der Eingabe aus A Globale Variablen für Aktuelle Vorausschau Bisher erzeugten Syntaxbaum Jede Funktion int A() trifft auf Grund der aktuellen Vorausschau die Auswahl einer Alternativ von A. Abarbeitung der Symbole der Alternative von links nach rechts durch: Terminalsymbol: Prüfen, ob aktuelles Symbol in der Eingabe mit dem Terminalsymbol in der Alternative übereinstimmt: Ja: Eingabe konsumieren Nein: Fehler in der Eingabe Metasymbol: Rekursiver Aufruf der Funktion zum Metasymbol in der Alternative und Rückgabe des Fehlerwertes

21 Beispiel für syntaxgebundene Implementierung
Quelltextausschnitt: int E() { switch(nextMor()) { case PLUS: printf("(E_1)"); if(nextMor() == ID && Q()) return 1; return 0; case MAL: printf("(E_2)"); } int Q(){ switch(nextMor()) { case ID: printf("(Q_1)"); if(nextMor() == ASSIGN && nextMor() == ID && E()) return 1; return 0; case SEMIKOLON: printf("(Q_2)"); } Vollständiger Quelltext (h, c, l) Ausführbares Programm Grammatik S'::= S S ::= if id then S else S fi | while id do S od | begin S end | id := id E E ::= + id Q| * id Q Q ::= id := id E | ;

22 Starke LL(k)-Grammatiken
Einfache LL(k)-Grammatiken sind für viele praktisch benötigte Programmstrukturen nicht ausreichend. Aufhebung der Forderung, dass die ersten k Symbole jeder Alternative Terminalsymbole sein müssen. Weiterhin muss aber anhand der Vorausschau von k Symbolen eine Alternative für das am weitesten links stehende Metasymbol in einer Linkssatzform eindeutig bestimmbar sein. Es sei G = (, M, R, S) eine Grammatik und k  . Dann ist G eine starke LL(k)-Grammatik, wenn gilt: Existieren zwei Linksableitungen mit prek() = prek('), dann folgt, dass  = ', wobei ,,','  *, S,A  M und ,,','  V* Folgerungen: Jede starke LL(k)-Grammatik ist eindeutig Jede einfache LL(k)-Grammatik ist eine starke LL(k)-Grammatik A   *  * S * 'A'  ''' * ''

23 Prüfen der starken LL(k)-Eigenschaft
Es sei G = (, M, R, S) eine Grammatik und k  . Dann ist G eine starke LL(k)-Grammatik, wenn gilt: Existieren zwei Linksableitungen mit prek() = prek('), dann folgt, dass  = ', wobei ,,','  *, S,A  M und ,,','  V* Beispielgrammatik, die für kein k die starke LL(k)-Eigenschaft besitzt Die Entscheidung zwischen zwei verschiedenen Alternative  und ' von A kann nur dann eindeutig getroffen werden, wenn alle aus  ableitbaren terminalen Zeichenketten sich in den ersten k Symbolen von allen aus '' ableitbaren terminalen Zeichenketten unterscheiden: {prek() |   * und  *})  {prek(') |   * und '' *'} =  Zur Überprüfung dieser Eigenschaft werden die Firstk- und Followk-Mengen eingeführt. A   *  * S * 'A'  ''' * ''

24 Beispielgrammatik Grammatik 1 für reguläre Ausdrücke: REG  ALT  ALT  KON | KON "|" ALT KON  SYM | SYM "." KON SYM  a | … | Z | $ | "(" ALT ")" | "(" ALT ")" "*" Grammatik 1 hat für kein k die starke LL(k)-Eigenschaft Links-Faktorisierte Grammatik 2: REG  ALT  ALT  KON A A   | "|" ALT KON  SYM K K   | "." KON SYM  a | … | Z | $ | "(" ALT ")" S S   | "*" Links-Faktorisierte Grammatik hat die starke LL(k)-Eigenschaft für k = 1 Zurück

25 Firstk-Menge Firstk() = {prek() |  *  und   *} für   V*
Berechnung durch Lösung eines Mengengleichungssystems, das sich aus den folgenden Regeln ergibt: Firstk(a1…an) = Firstk(a1) k … k Firstk(an) für ai  V mit 1  i  n Firstk(a) = {a} für a   Firstk(A) = Firstk([A,1]) …  Firstk([A,|[A]|]) für A  M Firstk() = {}

26 Beispiel: Ergebnis der Berechnung der First1-Menge für Grammatik 2
Beispielgrammatik 2 It First1(REG) First1(ALT) First1(A) First1(KON) First1(K) First1(SYM) First1(S) 1 {} {a,…,Z,$} {, *} 2 3 {, . } 4 {, | } {a,…,Z,$,(} 5 6 7 8

27 Followk-Menge Es interessieren alle terminalen Zeichenketten  der Länge k, die in irgendeiner Satzform hinter einem Metasymbol A auftreten können: S' * A und  *  Followk(A) = {Firstk() | S' * A } Berechnung durch: Followk(A) = {Firstk() k Followk(B) | B  A} Auch das ursprüngliche Startsymbol S der Grammatik hat in seiner Followk-Menge nur Zeichenketten der Länge k, weil die Grammatik um die Regel S'  S k mit dem neuen Startsymbol S' ergänzt wurde. Followk(S') = {}

28 Beispiel: Ergebnis der Berechnung der Follow1-Menge für Grammatik 2
Beispielgrammatik 2 It Follow1(REG) Follow1(ALT) Follow1(A) Follow1(KON) Follow1(K) Follow1(SYM) Follow1(S) 1 {} 2 {} 3 {, | } 4 {, | , . } 5 {, )} 6 {, ), | } 7 {, | , ), . } 8 9

29 Prüfen der starken LL(k)-Eigenschaft
Zur Erinnerung; gezeigt werden sollte für S * A   und S * 'A'  ''' : {prek() |   * und  * }  {prek(') |   * und '' * '} =   und ' waren verschiedene Alternativen des Metasymbols A an der Stapelspitze. aus  und ' lassen sich terminale Zeichenketten ableiten, deren Präfixe Elemente der Followk-Mengen des Metasymbols A sind Die obige Eigenschaft kann somit umgeschrieben werden zu: Firstk([A,i]) k Followk(A)  Firstk([A,j]) k Followk(A) =  für alle 1  i  j  |[A]| Eine Grammatik ist damit genau dann eine starke LL(k)-Grammtik, wenn letztere Eigenschaft gilt. In der Parsersituation (A,) ist damit die Alternative i von A zu wählen, für die gilt: prek()  Firstk([A,i]) k Followk(A)

30 Beispiel: Prüfen der starken LL(1)-Eigenschaft
Grammatik: REG  ALT  ALT  KON A A   | "|" ALT KON  SYM K K   | "." KON SYM  "a" | … | "Z" | $ | "(" ALT ")" S S   | "*" First1-Mengen: Follow1-Mengen: Test für REG, ALT, KON nicht notwendig. Für A, K und S ergibt sich: First1([A,1]) 1 Follow1(A)  First1([A,2]) 1 Follow1(A) =  First1([K,1]) 1 Follow1(K)  First1([K,2]) 1 Follow1(K) =  First1([S,1]) 1 Follow1(S)  First1([S,2]) 1 Follow1(S) =  Test für SYM auch klar. First1(REG) First1(ALT) First1(A) First1(KON) First1(K) First1(SYM) First1(S) {a,…,Z,$,(} {, | } {, . } {, *} Follow1(REG) Follow1(ALT) Follow1(A) Follow1(KON) Follow1(K) Follow1(SYM) Follow1(S) {} {, )} {, ), | } {, | , ), . }

31 Syntaxgesteuerter starker LL(k)-Parser
Berechnung einer Steuerfunktion T : M  k   durch: Steuerung der Analyse mit dem PDA durch diese Funktion mittels: (a,a)  (,), falls a   (A,)  ([A,i],), falls A  M und i = T(A,prek()) Fehler, in den Situationen: (a,b), falls a  b und a,b   (A,), falls A  M und T(A,prek()) = error

32 Beispiel: Steuerfunktion (-tabelle)
Z $ ( ) | . * REG 1 ALT A 2 KON K SYM 26 27 28 S Leere Felder markieren Fehlerzustände (Eintrag = error)

33 Syntaxgebundener starker LL(k)-Parser
Berechnung der Steuerfunktion T wie im syntaxgesteuerten Fall. Für jedes Metasymbol A Implementierung einer Funktion int A(): Funktion A wählt eine Alternative von A auf Grund der k Look-Ahead Symbole und leitet einen Präfix der anliegenden Eingabe aus dieser Alternative ab, indem: Für Metasymbole die zugehörige Funktion aufgerufen wird. Für Terminalsymbole überprüft wird, ob diese in der Eingabe auftreten. Rückgabewert gibt Auskunft über eine erfolgreiche Ableitung.

34 Beispiel: Syntaxgebundene Implementierung für SYM und S in Grammatik 2
int SYM() { char buf[8]; if((currMor() >= 'a' && currMor() <= 'z') || currMor() == '$') if(currMor() == '$') sprintf(buf,"SYM_27"); else sprintf(buf,"SYM_%d",currMor() - 'a' + 1); outSyn(buf); nextMor(); return 1; } if(currMor() == OPENPAR) outSyn("SYM_28"); if(!ALT()) return 0; if(currMor() != CLOSEPAR) return S(); int S() { if(nextMor() == '*') nextMor(); outSyn("S_2"); return 1; } if(currMor() == ENDOF || currMor() == CLOSEPAR || currMor() == PIPE || currMor() == DOT) outSyn("SYM_1"); return 0;

35 Beispiel: Syntaxgebundene Implementierung für Grammatik 1
Links-Faktorisierung direkt implementierbar Dadurch Erzeugung einer Postfixlinearisierung des Syntaxbaums erforderlich. int KON() { int res; if(!SYM()) return 0; if(currMor() == DOT) nextMor(); res = KON(); outSyn("KON_2"); return res; } if(currMor() == ENDOF || currMor() == CLOSEPAR || currMor() == PIPE) outSyn("KON_1"); return 1; int ALT() { int res; if(!KON()) return 0; if(currMor() == PIPE) nextMor(); res = ALT(); outSyn("ALT_2"); return res; } if(currMor() == ENDOF || currMor() == CLOSEPAR) outSyn("ALT_1"); return 1; Vollständiger Quelltext mit Fehlerbehandlung Ausführbares Programm

36 Behandlung von Linksrekursivitäten
Generell können linksrekursive Grammatiken nicht mit deterministischen tabellengesteuerten Top-Down-Parsern behandelt werden. Grammatik A  A "+" M | A "–" M | M M  M "*" T | M "/" T | T T  "n" | "(" A ")" muss entweder transformiert werden (vgl. Folie 30 im Kapitel Grundlagen) oder Lösung bei syntaxgebundener Implementierung: Behandlung der Linksrekursivität wie eine Rechtsrekursivität Aufbau des Syntaxbaums als Postfixlinearisierung Dadurch entsteht der Syntaxbaum der linksrekursiven Grammatik Vollständiger Quelltext mit Fehlerbehandlung Ausführbare Datei

37 Motivation schwache LL(k)-Grammatiken
Es gibt Grammatiken, bei denen Faktorisierung nicht hilft: S  aSab | bSba | cA A  a | ab | cA Problem ist, dass für jedes k genügend lange  und  in Followk(A) existieren, so dass: Firstk([A,i])k{}  Firstk([A,j])k{}  Aber: Bei der Entscheidung zwischen [A,i] und [A,j] folgen in der Analysesituation (A,) dem A nicht beliebige Zeichenketten  oder  aus Followk(A) sondern nur solche, die aus  (Stapelinhalt) ableitbar sind. Deshalb: Einbeziehung des Stapelinhalts in die Entscheidung für eine Alternative.

38 Definition schwache LL(k)-Grammatik
Es sei G = (, M, R, S) eine Grammatik und k  . Dann ist G eine schwache LL(k)-Grammatik, wenn gilt: Existieren zwei Linksableitungen und prek() = prek('), dann folgt, dass  = ', wobei ,,'  *, S,A  M und ,,'  V* Unterschied zur Definition einer starken LL(k)-Grammatik: Existieren zwei Linksableitungen und prek() = prek(') dann folgt, dass  = ', wobei ,,','  *, S,A  M und ,,','  V* Folgerungen: Jede schwache LL(k)-Grammatik ist eindeutig Jede starke LL(k)-Grammatik ist eine schwache LL(k)-Grammatik  *  Entscheidung in Analysesituation (A,) S * A ' * ' Entscheidung in Analysesituation (A,') A   *  Entscheidung in Analysesituation (A,) * S * 'A'  ''' * '' Entscheidung in Analysesituation ('A,')

39 Prüfen der schwachen LL(k)-Eigenschaft
Es sei G = (, M, R, S) eine Grammatik und k  . Dann ist G eine schwache LL(k)-Grammatik, wenn gilt: Existieren zwei Linksableitungen und prek() = prek('), dann folgt, dass  = ', wobei ,,'  *, S,A  M und ,,'  V* Die Entscheidung zwischen zwei verschiedenen Alternativen  und ' kann damit nur dann eindeutig getroffen werden, wenn alle aus  ableitbaren terminalen Zeichenketten sich in den ersten k Symbolen von allen aus ' ableitbaren terminalen Zeichenketten unterscheiden: {prek() |   *,  * }  {prek() |   *, ' * '} =  Zur Überprüfung dieser Eigenschaft müssen die Präfixmengen der verschiedenen Rechts-Kontexte , die hinter A in einer Linksableitung auftreten können, separat berechnet werden. Ziel: Berechnung einer Menge Fk(A) := {Firstk() | S * A ist Linksableitung}  *  S * A ' * '

40 Herleitung für Berechnung der Fk(A)-Mengen
Verschiedene Rechts-Kontexte entstehen durch die Wahl verschiedener Alternativen in vorangegangenen Ableitungsschritten: Da die Wahl einer Alternative von A in Abhängigkeit vom konkreten Rechts-Kontext getroffen werden soll, muss unterschieden werden, ob A durch die Regel B'  'nA'n oder B  nAn entstanden ist. Es müssen für diese Regeln die wohl unterschiedenen Followk-Mengen Firstk() und Firstk(') gebildet werden. Falls Firstk(') und/oder Firstk() für eine Alternative von A nicht genügend lange Zeichenketten enthalten, muss diese Betrachtung für die Entstehung von B bzw. B' wiederholt werden. Da B und B' sich selbst auch in verschiedenen Rechts-Kontexten {F1,…Fn} bzw. {F'1,…,F'n} befinden können, kann sich A in den Rechts-Kontexten Firstk()  F1,…, Firstk()  Fn, Firstk()  F'1,…, Firstk()  F'n befinden. Wir erhalten deshalb allgemein: Fk(A) = {Firstk() k F | B  A und F  Fk(B)} 1'…n-2'C'n-2'…1'  1'…n-1'B'n-1'…1'  1'…n'An'…1' = 'A' * S * 1…n-2Cn-2…1  1…n-1Bn-1…1  1…nAn…1 = A

41 Berechnung der Fk(A)-Mengen
Durch Fk(A) = {Firstk() k F | B  A und F  Fk(B)} wird für die Metasymbole einer Grammatik ein Mengengleichungssytem definiert, dessen kleinster Fixpunkt die Fk(A)-Mengen der Metasymbole A sind. Beispielgrammatik S'  S S  aSab | bSba | cA A  a | ab | cA Mengengleichungssystem für k = 3: F3(S') = {{}} F3(S) = {First3(ab) 3 F, First3(ba) 3 F | F  F3(S)}  {First3() 3 F | F  F3(S')} F3(A) = {First3() 3 F | F  F3(S)}  {First3() 3 F | F  F3(A)} It F3(S') F3(S) F3(A) 1 {{}} 2 {{}} 3 {{ab},{ba},{}} 4 {{aba},{abb},{ab},{baa},{bab},{ba},{}} 5 6

42 Prüfen der schwachen LL(k)-Eigenschaft
Eine Grammatik G hat genau dann die schwache LL(k)-Eigenschaft, wenn ein k   existiert, so dass A  M  1  i  j  |[A]|  F  Fk(A) gilt Firstk([A,i]) k F  Firstk([A,j]) k F = . Beispielgrammatik S'  S S  aSab | bSba | cA A  a | ab | cA F3-Mengen: Test für [A,1] und [A,2] und k = 3: F3(S') F3(S) F3(A) {{}} {{aba},{abb},{ab},{baa},{bab},{ba},{}} F3(A) {aba} {abb} {ab} {baa} {bab} {ba} {} Firstk([A,1]) k F {aab} {a} Firstk([A,2]) k F

43 Schwacher LL(k)-Automat
Falls die schwache LL(k)-Eigenschaft für eine Grammatik G abgesichert ist, kann der PDA wie folgt arbeiten: Steuerung der Analyse durch: (a,a)  (,), falls a   (A,)  ([A,i],), falls A  M und prek()  Firstk([A,i]) Fehler, in den Situationen: (a,b), falls a  b und a,b   (A,), falls A  M und für alle 1  i  |[A]| prek()  Firstk([A,i]) Problem: bei jedem Takt der Art 2 muss die Firstk-Menge des Kellerinhalts berechnet werden. Ziel: Berechnung einer Steuertabelle wie bei starken LL(k)-Grammatiken

44 Vereinfachung der Berechnung während der Analyse
Prinzip: Jedes Metasymbol A im Stapel wird mit der Firstk-Menge F des darunter befindlichen Stapelinhalts markiert: AF. Einfache Berechnung der Markierung neu eingestapelter Metasymbole durch: AF aktueller Kellerinhalt mit Firstk() = F Jedes Metasymbol B in Alternative B von A wird beim Ersetzen von A durch diese Alternative markiert mit Firstk() k Firstk() = Firstk() k F. Startsituation des PDA: (S'{},). Firstk() kann auch im Voraus berechnet werden.

45 Vereinfachter schwacher LL(k)-Automat
Mit den markierten Metasymbolen im Stapel kann die Arbeitsweise des PDA vereinfacht werden: Steuerung der Analyse durch: (a,a)  (,), falls a   (AF,)  ([A,i],), falls A  M und prek()  Firstk([A,i]) k F, wobei jedes Metasymbol in [A,i] beim Einstapeln wie auf voriger Folie beschrieben markiert wird. Fehler, in den Situationen: (a,b), falls a  b und a,b   (AF,), falls A  M und für alle 1  i  |[A]| prek()  Firstk([A,i]) k F

46 Transformation einer schwachen LL(k)-Grammatik
Fakt 1: In der Situation (AF,) ändert sich bei Ersetzung von AF durch Alternative  die Markierung eines Metasymbols in  nach dem Einstapeln nicht mehr und hängt nur von der Markierung von A und  ab Fakt 2: Markierungen eines Metasymbols A sind nur Elemente aus Fk(A). Dadurch ist die Berechnung aller möglichen Markierungen in  im Voraus möglich. Man erhält zur schwachen LL(k)-Grammatik (, M, S',R) die transformierte Grammatik (, Mm, S'{},Rm) durch: Mm := {AF | A  M und F  Fk(A)}

47 Beispiel für Transformation
Ursprüngliche Grammatik: Transformationsregel: S'  S S  aSab | bSba | cA A  a | ab | cA Nummerierung der F  F3-Mengen F {} {aba} {abb} {ab} {baa} {bab} {ba} {} Nummer 1 2 3 4 5 6 7 Transformierte Grammatik: S'0  S7 S7  aS3ab | bS6ba | cA7 S6  aS2ab | bS5ba | cA6 S5  aS2ab | bS5ba | cA5 S3  aS1ab | bS4ba | cA3 S4  aS2ab | bS5ba | cA4 S2  aS1ab | bS4ba | cA2 S1  aS1ab | bS4ba | cA1 A7  a | ab | cA7 A6  a | ab | cA6 A5  a | ab | cA5 A4  a | ab | cA4 A3  a | ab | cA3 A2  a | ab | cA2 A1  a | ab | cA1

48 PDA für transformierte schwache LL(k)-Grammatik
Da in jeder Regel bereits alle Metasymbole markiert sind, kann die Berechnung der Markierungen während der Analyse entfallen. Steuerung der Analyse durch: (a,a)  (,), falls a   (AF,)  ([AF,i],), falls AF  Mm und prek()  Firstk([AF,i]) k F Fehler, in den Situationen: (a,b), falls a  b und a,b   (AF,), falls AF  Mm und für alle 1  i  |[AF]| prek()  Firstk([AF,i]) k F Entscheidung für eine Alternative i von AF basiert nur noch auf den Mengen Firstk([AF,i]) und F Konsequenz: Konstruktion einer Steuerfunktion möglich

49 Steuerfunktion für transformierte schwache LL(k)-Grammatiken
Berechnung einer Steuerfunktion T : Mm  k   durch: Steuerung der Analyse mit dem PDA durch diese Funktion mittels: (a,a)  (,), falls a   (AF,)  ([AF,i],), falls AF  Mm und i = T(AF,prek()) Fehler, in den Situationen: (a,b), falls a  b und a,b   (AF,), falls AF  Mm und T(AF,prek()) = error Dieser PDA ist ein PDA für eine starke LL(k)-Grammatik, weil für jedes AF  Mm gilt: Followk(AF) = F.

50 Zusammenfassung LL(k)-Grammatiken
Jede schwache LL(k)-Grammatik kann in eine bedeutungsäquivalente starke LL(k)-Grammatik transformiert werden. Jede Sprache, die mit einer schwachen LL(k)-Grammatik analysiert werden kann, kann auch mit einer starken LL(k)-Grammatik analysiert werden. Es gibt Sprachen, die mit LL(k) aber nicht mit LL(k-1) (für 1  k) analysierbar sind. Es gibt deterministisch analysierbare Sprachen, die nicht mit LL(k)-Verfahren analysierbar sind.

51 LR(k)-Grammatik Es sei S * A   eine Rechtsableitung zu einer kontextfreien Grammatik G = (, M, R, S). Dann heißt  Griff der Rechtssatzform . Bei eindeutigen Grammatiken ist der Griff das nächste Teilwort, das im Keller des Bottom-Up-PDA ersetzt wird. Jeder Präfix von  heißt zuverlässiger Präfix von G. Solche Präfix einer Rechtssatzform, erstrecken sich nicht über den Griff dieser Rechtssatzform hinaus. Sie sind somit Kellerinhalt des Bottom-Up-PDA. Es sei G eine kfG und k  . Dann besitzt G die LR(k)-Eigenschaft, falls gilt: Existieren zwei Rechtsableitungen und prek() = prek(), dann folgt, dass A = A',  = ' und  = ', wobei S, A, A'  M und , , '  * und , , '  V*. Das bedeutet, dass für jedes w  L(G) in jeder Rechtssatzform der Ableitung von w der längste zuverlässige Präfix und der Griff eindeutig durch die ersten k Symbole nach dem Griff (in ) bestimmt sind. A   * S * 'A''  

52 LR(k)-Item [A  ., ] ist genau dann ein gültiges LR(k)-Item für die Grammatik G, wenn S * A   eine Rechtsableitung und  = prek(), wobei   *,   V* und A    R. Zu einem zuverlässigen Präfix  gehört somit die Menge der gültigen Items: Items() = {[A  ., ] | S * A   und  = prek()} Wichtig: Aus [A  .B, ]  Items() und B    R folgt: Für alle '  (Firstk() gilt [B  ., ']  Items(). (Closure) Ein Item [A  ., ]  Items() repräsentiert eine Analysesituation (, ) des Bottom-Up-PDA, in der der PDA bereits einen Anfang   * der Eingabe verarbeitet hat: (, ) * (, ) Die Analyse sich gerade in einem Teil des Wortes  befindet, der aus  abgeleitet wurde. D.h. es gibt u, v  * mit  * u und  * v, wobei : u bereits gelesen wurde ( = wu) und erwartet wird, dass  mit v beginnt ( = vz) Nachdem v gesehen und zu  reduziert wurde, kann dann  zu A reduziert werden Das entspricht der zu [A  ., ]  Items() existierenden Rechtsableitung S * Az  z * vz * , wobei A, S  M und , ,   V* und v, , z  *.

53 LR(k)-Analyse Es gilt: G hat die LR(k)-Eigenschaft, gdw. für jeden zuverlässigen Präfix  gilt: Wenn [A  ., ]  Items(), dann gilt für alle [B  ., ']  Items(), dass aus   Firstk(') folgt: A = B,  = ,  =  und damit  = '. Dadurch ist in jeder Satzform einer Rechtsableitung der Griff eindeutig bestimmt und der Bottom-Up-PDA muss wie folgt arbeiten: (, )  (A, ) gdw. [A  .,prek()]  Items() (Reduzieren) (, a)  (a, ) gdw.  = ' und [A  ., ]  Items() und prek(a)  Firstk() und   V+ (Schieben)

54 LR(k)-Analyse mit dem PDA
Damit ist abgesichert, dass für jeden möglichen Kellerinhalt (zuverlässigen Präfix) eindeutig entschieden werden kann, ob und wenn ja nach welcher Regel reduziert werden muss. Das heißt: Es gibt keine Konflikte beim Schieben und Reduzieren, weil für jeden zuverlässigen Präfix  und die Resteingabe  gilt: [A  .,prek()]  Items() und [B  .,prek()]  Items(), dann A = B und  = . [A  .,prek()]  Items() und [B  .,prek()]  Items() und prek()  Firstk(  prek()) , dann A = B und  =  und  = . Problem: Für jeden Kellerinhalt  muss während der Analyse Items() berechnet werden.

55 Mitführen des Stapelzustandes
Lösung: Zu jedem Symbol ai  V im Keller mit Inhalt a1…an wird die zugehörige Menge Items(a1…ai) mitgeführt für 1  i  n. Aus diesen bekannten Item-Mengen soll beim Einstapeln (Schieben) eines neuen Symbols a die Menge Items(a1…ana) berechnet werden, beim Reduzieren nach A  ak…an die Menge Items(a1…ak-1A) berechnet werden. Die Menge aller möglichen gültigen Items ist endlich, damit auch die Menge aller ihrer Teilmengen. Somit ist eine Vorausberechnung möglich.

56 Vorausberechnung der Items beim Einstapeln
Beziehung zwischen Items() und Items(a) beim Einstapeln von a: Aus Einstapeln von a bei Stapelinhalt  folgt: Es ex. eine Rechtssatzform S * A  a, weil [A  .a, prek()]  Items(). Falls  = b', dann gibt es eine Rechtsableitung S * A  ab' und somit [A  a., prek()]  Items(a) Falls  = , dann gibt es eine Rechtsableitung S * A  a und somit [A  a., prek()]  Items(a) Falls  = B', dann gibt es eine Rechtsableitung S * aB' * aB' und somit [A  a.B', prek()]  Items(a). Außerdem [B  ., ]  Items(a) für alle   Firstk('), weil aB'  a' eine Rechtsableitung ist (Closure auf Folie 51). Konsequenz: Konstruktion von Items(a) ist mittels der Menge Items() und der Regeln der Grammatik möglich!

57 Konstruktion der erforderlichen Item-Mengen für Schiebe-Takte
Für die zu einem Kellerinhalt  gehörenden Itemmenge I am obersten Kellersymbol kann für die Items [A  .a, ]  I, die ein Einstapeln des Symbols a verlangen, die zugehörige neue Itemmenge I' berechnet werden durch: I' := {[A  a., ] | [A  .a, ]  I} I' := closure(I'), wobei closure(I) = I  {[B  ., '] | [A  .B, ]  I und B    R und '  (Firstk()} Ist also das Symbol an der Kellerspitze mit I beschriftet und a wird eingestapelt, dann wird a mit I' beschriftet. Zu der Startsituation (,w) des Parsers, gehört die Itemmenge closure({[S'  .Sk, ]}). Es verbleibt die Vorausberechnung der Analysesituationen, die der Parser nach einem Reduktionstakt einnimmt.

58 Vorausberechnung der Items beim Reduzieren
Beziehung zwischen Items() und Items(A) beim Reduzieren nach A  : Aus Reduzieren nach A   bei Stapelinhalt  folgt: Es ex. S * A  , weil [A  ., prek()]  Items(). Damit ex. auch S' * 'B'  'A'' * 'A = A. Damit ist [B  A.', prek(')]  Items(A), weil  = '. Zur Berechnung von Items(A) wird die Menge Items() verwendet, weil gilt: [B  A.', prek(')]  Items(A), gdw. [B  .A', prek(')]  Items(). Konsequenz: Konstruktion von Items(A) ist bei Stapelinhalt  aus der Menge Items() möglich, die mit dem obersten Symbol in  gespeichert wurde.

59 Konstruktion der erforderlichen Item-Mengen für Reduktions-Takte
Falls bei Kellerinhalt  nach A   reduziert wird, so ergibt sich die Itemmenge für das neue Symbol A an der Spitze des Stapelinhalts A durch: Ausstapeln von  und Lesen der Itemmenge I an der Spitze des Stapelinhalts , I' := {[B  A.', ] | [B  .A', ]  Items()}, I' := closure(I'). Da durch Ausstapeln eines  jede zuvor eingestapelte Itemmenge an der Stapelspitze freigelegt werden kann, die ein Item der Form [B  .A', ] enthält, ergibt sich folgender Algorithmus zur Konstruktion aller Itemmengen:

60 Berechnung der Item-Mengen
Berechnung aller Itemmengen; I ist eine Menge von Itemmengen Berechnung der Übergänge von einer Itemmenge in eine andere I := {closure([S'.S,k])}  :=  do I' := I for each z  I do (I, ) := goto(z,(I, )) while(I ≠ I') goto(z,(I, )) for each x  V do z' :=  for each [A  .x,]  z do z' := z'  {[A  x.,] } od if z' ≠  then I := I  closure(z')  :=   (z,x,z') fi return (I, ); closure(z) do z' := z; for each [A  .B,]  z do z := z  {(B  .,)|B    R und   Firstk(,)} od while(z ≠ z') return z; Beispiel

61 Eigenschaften des konstruierten Automaten
Die beschriebene Konstruktion erzeugt einen Automaten (I,V,closure([S' .S,k]),I,) für den gilt: Seine Zustandsmenge ist endlich Die Zustandsübergänge sind deterministisch Der Automat erkennt genau die zuverlässigen Präfixe der Grammatik (Beweis durch Induktion über die Anzahl der Zustandsübergänge) Dieser Automat wird als kanonischer DEA bezeichnet, kurz: (I,).

62 Konstruktion der kanonischen Steuerfunktion
Es sei (I,) der kanonische DEA. Die Steuerfunktion T : I  k  I ist dann definiert als: Kellerelemente sind Zweitupel aus I  V. Startsituation ((closure([S' .S,k]),S'),). Verhalten des Automaten: ((I,x),a)  ((I,x)(I',a),), falls T(I,prek(a)) = shift und (I,a,I')   ((I0,x0)…(In,xn),a)  ((I0,x0)(I,A),a), falls T(In,prek(a)) = reduce(A  ) und | | = n und (I0,A,I)   ((I,S),k) mit (closure([S' .S,k]),S,I)   ist akzeptierender Endzustand Offensichtlich genügt es sogar nur die Items im Keller zu speichern. Damit Vereinfachung der kanonischen Steuerfunktion.

63 Vereinfachung der kanonischen Steuerfunktion
Es sei (I,) der kanonische DEA. Die Steuerfunktion T : I  (k  M)  I ist dann definiert als: Kellerelemente sind Itemmengen. Verhalten des Automaten: (I,a)  (II',), falls T(I,prek(a)) = shift I' (I0I1…In,a)  (I,a), falls T(In,prek(a)) = reduce(A  ) und | | = n und T(I0,A) = I (I,) ist akzeptierende Situation, falls T(I,prek()) = accept Error in allen anderen Situationen

64 Konflikte Schiebe-Reduktions-Konflikt: Ein Schiebe-Reduktions-Konflikt ist in der Itemmenge I vorhanden, falls es Items [A  ., ]  I und [B  .c, ']  I mit , ,   V* und c   und   Firstk(c') gibt. Reduktions-Reduktions-Konflikt: Ein Reduktions-Reduktions-Konflikt ist in der Itemmenge I vorhanden, falls es verschiedene Items [A  ., ]  I und [B  ., ']  I mit ,   V* und  = ' gibt. Konflikte treten genau dann auf, wenn die Grammatik nicht die LR(k)-Eigenschaft hat. Konstruktion des kanonische DEA ist damit ein Test für die LR(k)-Eigenschaft. Auflösen von Konflikten durch: Umschreiben der Grammatik. Definition der Steuerfunktion entsprechend eines Items. D.h., Auflösen des Konflikts bzgl. Schieben oder Reduzieren nach einer bestimmten Regel.

65 LALR (Lookahead-LR) Der Kern kern(I) einer Itemmenge I ist definiert als {[A  .] |   : [A  ., ]  I} Verkleinerung der Zustandsmenge I eines kanonischen LR(1) DEA durch Zusammenfassen von Itemmengen mit dem gleichen Kern: I' := {[I] | I  I und [I] := {I' | kern(I) = kern(I')}} '([I],x) := [I'], falls (I,x) := I' Konstruktion der LALR-Steuertabelle entspricht der Konstruktion der kanonischen Steuertabelle. Verkleinerung der Zustandsmenge in praktischen Programmiersprachen dadurch in etwa um den Faktor 10.

66 Beispiel: kanonischer DEA
[S'  .S,] [S  .CC,] [C  .cC,c] [C  .cC,d] [C  .d,c] [C  .d,d] [S'  S.,] I5 C [S  CC.,] I6 I2 I9 [C  c.C,] [C  .cC, ] [C  .d, ] C C [S  C.C,] [C  c.C,] [C  .d, ] c [C  cC., ] c d I7 d [C  d., ] I3 [C  c.C,c] [C  c.C,d] [C  .cC,c] [C  .cC,d] [C  .d,c] [C  .d,d] I8 c [C  cC.,c] [C  cC.,d] C c Grammatik: S'  S S  C C C  c C | d d I4 [C  d., c] [C  d., d] d

67 Beispiel: LALR-DEA S [S  .CC,] [C  .cC,c] [C  .cC,d] [C  .d,c]
[C  .d,d] [S'  S.,] I5 C [S  CC.,] I6 I2 I8/I9 [C  c.C,] [C  .cC, ] [C  .d, ] [C  c.C,c] [C  c.C,d] [C  .cC,c] [C  .cC,d] [C  .d,c] [C  .d,d] C C [S  C.C,] [C  c.C,] [C  .d, ] c [C  cC., ] [C  cC.,c] [C  cC.,d] c d c I4/I7 d Grammatik: S'  S S  C C C  c C | d [C  d., c] [C  d., d] [C  d., ] d

68 Eigenschaften LALR-Steuertabelle
Eigenschaften dieser Transformation: Der entstehende Endliche Automat ist wieder deterministisch. Hatten die Zustände des kanonischen DEA keine Schiebe-Reduziere-Konflikte, dann haben auch die Zustände des LALR-DEA solche Konflikte nicht. Es können Reduziere-Reduziere-Konflikte entstehen. Konsequenz: Es gibt Grammatiken, für die eine kanonische Steuerfunktion aber keine LALR-Steuerfunktion existiert.

69 Beispiel Grammatik S'  S S  a A d | b B d | a B e | b A e A  c B  c besitzt eine kanonische LR(1) Steuertabelle aber keine LALR-Steuertabelle, weil Items(ac) = {[A  c., d], [B  c., e]} Items(bc) = {[A  c., e], [B  c., d]} Damit entsteht im LALR-DEA eine Reduktions-Reduktions-Konflikt.

70 I besteht damit nur noch aus den Kernen:
SLR (Simple LR) Verkleinerung der Menge I durch Verzicht auf die Vorschau in den Itemmengen. I besteht damit nur noch aus den Kernen: I' = {kern(I) | I  I} '(kern(I),x) := kern(I'), falls (I,x) := I' Konsequenz: Der SLR-DEA hat genauso viele Zustände wie der LALR-DEA. Die Berechnung des SLR-DEA vereinfacht sich jedoch.

71 Berechnung der Item-Mengen für SLR-DEA
Berechnung aller Itemmengen; I ist eine Menge von Itemmengen Berechnung der Übergänge von einer Itemmenge in eine andere I := {closure([S'.S])}  :=  do I' := I for each z ∈ I do (I, ) := goto(z,(I, )) while(I ≠ I') goto(z,(I, )) for each x  V do z' :=  for each [A  .x]  z do z' := z'  {[A  x.]} od if z ≠  then I := I  closure(z')  :=   (z,x,z') fi return (I, ); closure(z) do z' := z; for each [A  .B]  z do z := z  {[B  .]|B    R} od while(z ≠ z') return z;

72 SLR Steuerfunktion Da in den Items die Vorausschau fehlt, muss mit den Followk-Mengen gearbeitet werden:

73 Beispiel: SLR(1)-Grammatik
Grammatik (vereinfacht) für arithmetische Ausdrücke ist SLR(1): S'  A A  A + M | M M  M * T | T T  Number | ( A ) I6 I9 M [S'  A+.M] [M  .M*T] [M  .T] [T  .Number] [T  .( A )] [S'  A+M.] [M  M.*T] I0 I1 + A T [S'  .A] [A  .A+M] [A  .M] [M  .M*T] [M  .T] [T  .Number] [T  .( A )] [S'  A.] [S'  A.+M] * I2 I7 I10 M [A  M.] [M  M.*T] [M  M*.T] [T  .Number] [T  .( A )] [M  M*T.] * T M ( I4 I5 Number Number [T  (.A)] [A  .A+M] [A  .M] [M  .M*T] [M  .T] [T  .Number] [T  .( A )] [T  Number.] Number Number + I3 T I8 I11 ) [M  T.] ( [A  A.+M] [T  (A.)] [T  (A).] T A ( (

74 Beispiel: Nicht SLR(1)-Grammatik
Grammatik, die nicht SLR(1) aber LALR(1) ist: S  L = R L  * R | Id R  L Follow1(R) enthält = (wegen der Ableitung S  L=R  *R = R) I0 I1 S [S'  .S] [S  .L=R] [S  .R] [L  .*R] [L  .Id] [R  .L] [S'  S.] I6 I9 R [S  L=.R] [R  .L] [L  .*R] [L  .Id] [S  L=R.] I2 [S  L.=R] [R  L.] I8 L = L [R  L.] * I4 R * I7 [L  *.R] [R  .L] [L  .*R] [L  .Id] R [L  *R.] I3 [S  R.] I5 Id [L  Id.]

75 Bison Zur Generierung des C-Quelltextes eines Parsers.
Die vom Parser akzeptierte Sprache wird durch die Regeln einer kfG spezifiziert. Jeder Alternative eines Metasymbols kann eine Aktion in Form von C-Quelltext zugeordnet werden. Einfache Einbindung eines mit Flex generierten Scanners. Lex Quelltext name.l Lex Aufruf: lex name.l Scanner als C-Datei: Lex.yy.c C-Compiler Aufruf: gcc lex.yy.c name.tab.c Ausführ-bare Datei: a.exe Bison Quelltext name.y Bison Aufruf: bison name.y Morphem-arten: name.tab..h -d Parser als C-Datei: name.tab.c Parser-tabelle -v

76 Struktur eines Bison-Quelltextes
Deklarationsabschnitt %% Regelabschnitt Funktionsabschnitt Deklarationsabschnitt: Token-Deklaration Vereinbarung von Typen für Grammatiksymbole C-Code-Fragmente Regelabschnitt: Grammatikregeln in BNF Optional zu jeder Regel eine Aktion in Form von C-Code Funktionsabschnitt C-Funktionen, die in die erzeugte C-Datei kopiert werden

77 Ablauf der Generierung des Parserquelltextes
%% A1 : [A1,1] {Aktion 1.1} | [A1,2] {Aktion 1.2} | … An : [An,1] {Aktion n.1} | [An,2] {Aktion n.2} LALR-DEA LALR- Steuer- tabelle Parser als C-Datei: y.tab.c Simulator für PDA Parserstapel Wertestapel Scanner Look-Ahead

78 Deklarationsabschnitt
Deklaration von C-Code Eingeschlossen in %{ und %} Deklarieren von Bezeichnern/Typen, die im Regel- oder Funktionsabschnitt benutzt werden Deklaration von Grammatiksymbolen Deklaration von Morphemen durch %token Bezeichner Bezeichner kann in der Regelsektion von bison und flex verwendet werden. Deklaration von Präzedenzen %left Bezeichner, %right Bezeichner, %nonassoc Bezeichner Deklaration von spezifischen Typen

79 Regelabschnitt BFN-Regel A ::= 1 | … | n wird geschrieben als:
A : 1 {Aktion1} | 2 {Aktion2} | n {Aktionn}; Aktioni ist C-Code, in dem verwendet werden kann: $$, $1,…,$k, wobei $$ der mit A und $i der mit n(i-1) assoziierte Wert ist. {Aktioni} ist optional; Standardaktion: $$ = $1. Terminalsymbole definiert als Morphemart oder in ' ' eingeschlossen; dann ist ASCII-Code des ersten Zeichens die Morphemart. -Regel durch Angabe einer leeren Alternative. Übrige Symbole sind Metasymbole.

80 Beispiel: Einfaches Bison-Programm
%{ #include <stdlib.h> #include <ctype.h> void yyerror(char*); int yylex(); %} %token DIGIT %% line : expr '\n' {printf("%d\n",$1);} ; expr : expr '+' term {$$ = $1 + $3;} | term term : term '*' factor {$$ = $1 * $3;} | factor factor: '(' expr ')' {$$ = $2;} | DIGIT int yylex() { int c; c = getchar(); if(isdigit(c)) yylval = c - '0'; return DIGIT; } return c; void yyerror(char* err) printf("%s",err); int main(int argc, char* argv[]) return yyparse(); Vollständiger Quelltext Ausführbares Programm

81 Beispiel: Mehrdeutige Grammatik
%{ #include <ctype.h> %} %token DIGIT %% line : expr '\n' {printf("%d\n",$1);} ; expr : expr '+' expr {$$ = $1 + $3;} | expr '-' expr {$$ = $1 - $3;} | expr '*' expr {$$ = $1 * $3;} | expr '/' expr {$$ = $1 / $3;} | DIGIT int yylex() { int c; c = getchar(); if(isdigit(c)) yylval = c - '0'; return DIGIT; } return c; Vollständiger Quelltext Ausführbares Programm Ausgabe nach Bison-Aufruf mit Option -v

82 LALR-DEA zum vereinfachten Beispiel
[S'  .E,] [E  .E+E, |+|*] [E  .E*E, |+|*] [E  .z, |+|*] [S'  E., ] [E  E.+E, |+|*] [E  E.*E, |+|*] [E  E*.E, |+|*] [E  .E+E, |+|*] [E  .E*E, |+|*] [E  .z, |+|*] z E * + * E z I3 I6 + I1 [E  E+.E, |+|*] [E  .E+E, |+|*] [E  .E*E, |+|*] [E  .z, |+|*] [E  E*E., |+|*] [E  E.+E, |+|*] [E  E.*E, |+|*] z [S'  z., |+|*] E + I4 * Grammatik: S'  E E  E + E E  E * E E  z [E  E+E., |+|*] [E  E.+E, |+|*] [E  E.*E, |+|*]

83 Auflösen von Konflikten beim Erstellen der Steuertabelle
In der Steuertabelle werden die Konflikte entweder durch Schieben oder Reduzieren aufgelöst. Durch Reduzieren wird dem letzten eingestapelten Operator eine höhere Priorität als dem nächsten Operator in der Eingabe gegeben. Falls beide Operatoren gleich sind entspricht das einer Linksassoziativität. Entsprechend wird durch Schieben dem nächsten Operator in der Eingabe eine höhere Priorität als dem letzten eingestapelten Operator gegeben. Steuertabelle zum LALR-DEA auf voriger Folie: Zustand z + * E Shift 1 2 1 Reduce E  z Shift 3 Shift 5 Accept 3 4 Shift 3* Shift 5** Reduce E  E + E 5 6 Reduce E  E * E * macht + rechtsassoziativ ** gibt * Vorrang vor +

84 Standardbehandlung von Konflikten in Bison
Konflikte in den Itemmengen werden von Bison angezeigt und beim Erstellen der Steuertabelle automatisch wie folgt aufgelöst: Reduktion-Reduktion-Konflikt: Es wird nach der Regel reduziert, die zuerst in der Bison-Datei aufgeführt wurde. Reduktion-Schiebe-Konflikt: Schieben wird bevorzugt. Zum manuellen Beheben von Konflikten erzeugen des LALR-DEA in der Datei y.output mit Option –v.

85 Auflösen der Konflikte mit Bison
Zuordnung von Assoziativitäten zu Terminalsymbolen durch folgende Vereinbarungen im Deklarationsabschnitt: %left Morphemartenfolge %right Morphemartenfolge %nonassoc Morphemartenfolge und Prioritäten durch die Reihenfolge der obigen Deklarationen (erste Deklaration hat niedrigste Priorität). Bei Konflikt zwischen zwei Items [A  .,a] und [B  .a,b] wird reduziert, falls Priorität von  höher ist, als Priorität von a oder Priorität von  und a ist gleich und Assoziativität von  ist links In allen anderen Fällen wird geschoben Priorität und Assoziativität von  entsprechen der des am weitesten rechts stehenden Terminalsymbols in . Priorität und Assoziativität von  kann explizit festgelegt werden durch Anhängen von %prec Terminalsymbol an , wobei Priorität und Assoziativität von Terminalsymbol im Deklarationsteil festgelegt wurde (dieses Symbol muss sonst nirgends verwendet werden)

86 Beispiel: Konflikte aufgelöst
%{ #include <ctype.h> %} %token DIGIT %left '+' '-' %right '*' '/' %% line : expr '\n' {printf("%d\n",$1);} ; expr : expr '+' expr {$$ = $1 + $3;} | expr '-' expr {$$ = $1 - $3;} | expr '*' expr {$$ = $1 * $3;} | expr '/' expr {$$ = $1 / $3;} | DIGIT int yylex() { int c; c = getchar(); if(isdigit(c)) yylval = c - '0'; return DIGIT; } return c; Vollständiger Quelltext Ausführbares Programm

87 Gemeinsame Nutzung von Bison und Flex
Die Funktion yyparse() fordert Token durch Aufruf von yylex() an. Flex kann abhängig von der Morphemart Werte unterschiedlicher Datentypen in yylval speichern. Dafür Deklaration der Datentypen im Deklarationsabschnitt durch: %union { C-Datentyp Bezeichner; … }; Zuordnung der Datentypen zu Morphemarten und Metasymbolen der Grammatik im Deklarationsteil durch: %token <Bezeichner> Tokenbezeichner %type <Bezeichner> Metasymbol Ausdrücke $$ bzw. $i haben dann den Typ, der dem zugehörigen Symbol zugeordnet wurde. Vollständiges Beispiel (y, l, ast.c, ast.h, main.c, exe, Quelltext)

88 Zusammenfassung LR(k)-Grammatiken
Jede starke LL(k)-Grammatik ist auch LR(k)-Grammatik. Jede mit einem deterministischen PDA analysierbare Sprache besitzt eine LR(k)-Grammatik. Jede LR(k)-Grammatik kann in eine bedeutungsäquivalente LR(1)-Grammatik transformiert werden.

89 Ende der syntaktischen Analyse
Weiter zur Kontextprüfung


Herunterladen ppt "Vorlesung Compilertechnik Sommersemester 2008"

Ähnliche Präsentationen


Google-Anzeigen