[ Inhalt ] [ Index ]

Next: Codeerzeugung Up: Syntaktische Analyse Previous: Top-down-Analyse

Bottom-up-Analyse

Ein Nachteil des Topdown-Verfahrens aus dem letzten Abschnitt ist, daß dieses Verfahren nicht immer angewendet werden kann.

Wir wenden uns jetzt einem Parserprinzip zu, daß eine noch größere Klassen von kontextfreien Grammatiken abdeckt.

Bei diesem Vorgehen wird eine Reduktionsfolge   gesucht, die eine Zeichenreihe, die von links nach rechts verarbeitet wird, auf das Startsymbol reduziert. Hierbei wird immer das rechteste Nichtterminal ersetzt - daher auch die Bezeichnung LR-Parsing  .

Da hier von den Blättern des Ableitungsbaums her in Richtung Wurzel gearbeitet wird, spricht man auch von einem Bottom-up-Verfahren  .

 

LR-Parser

LR-Parser sind aus verschiedenen Gründen interessant:

  1. LR-Parser können für praktisch alle Programmiersprachen, für die kontextfreie Grammatiken existieren, konstruiert werden.
  2. LR-Parser sind allgemeiner als viele andere Techniken, besitzen aber dennoch die gleiche Effizienz.
  3. LR-Parser erkennen Syntaxfehler zum frühest möglichen Zeitpunkt.

Für reale Programmiersprachen kann man diese Parser jedoch nicht mehr manuell konstruieren. Man benötigt ein Werkzeug, nämlich einen Parsergenerator.   yacc   bzw. bison   ist der wohl prominenteste Vertreter dieser Gattung.

Selbst wenn die Grammatik Mehrdeutigkeiten enthält, können - mit notfalls manuellen Eingriffen - effiziente Parser erzeugt werden.

Ein solcher Parser besteht aus zwei Teilen: einem Treiber, der immer gleich ist, und einer Tabelle, die aus der Grammatik generiert wird.

Wir werden drei Methoden kennenlernen, Parsertabellen zu erzeugen:

  1. Simple LR   oder SLR  
    ist am einfachsten zu implementieren, funktioniert aber möglicherweise für einige Grammatiken nicht.
  2. (kanonisches) LR      
    ist das mächtigste Verfahren, aber in der Implementierung sehr aufwendig.
  3. LALR   oder lookahead LR  
      liegt in der Komplexität zwischen den beiden Verfahren und wird von yacc   verwendet

Arbeitsweise eines LR-Parsers

Wir betrachten die prinzipielle Arbeitsweise eines LR-Parsers [3] .

Der Parser verarbeitet eine Eingabe von links nach rechts. Der Keller   enthält eine Zeichenreihe der Form tex2html_wrap_inline2788 . Die tex2html_wrap_inline2582 sind Symbole der Grammatik, die tex2html_wrap_inline2792 Zustände des Parsers. tex2html_wrap_inline2794 ist das oberste Kellersymbol (TOS).

Die Parser-Tabelle besteht aus einer ACTION- und einer GOTO-Komponente.
   

Wenn TOS = tex2html_wrap_inline2794 ist und tex2html_wrap_inline2798 das aktuelle Eingabesymbol,
so legt ACTION tex2html_wrap_inline2800 eine von vier möglichen Aktionen fest:

  1. shift   s
  2. reduce   tex2html_wrap_inline2804
  3. accept  
  4. error  

Die Funktion GOTO berechnet aus einem Zustand und einem Symbol einen Folgezustand des Parsers.

Eine Konfiguration   eines LR-Parsers ist ein Paar aus Kellerinhalt und unverbrauchter Eingabe:

tex2html_wrap_inline2806

Die Folgekonfiguration nach jeder der vier möglichen Aktionen ist dann:

  1. ACTION tex2html_wrap_inline2808
    tex2html_wrap_inline2810
    Dabei ist s = GOTO tex2html_wrap_inline2800 .
  2. ACTION tex2html_wrap_inline2816
    tex2html_wrap_inline2818
    Dabei ist s = GOTO tex2html_wrap_inline2822 und tex2html_wrap_inline2824 .
    Das aktuelle Eingabesymbol bleibt unverändert.
  3. ACTION tex2html_wrap_inline2826 accept
    Eingabe akzeptiert, Parsing beendet
  4. ACTION tex2html_wrap_inline2826 error
    Fehlerfall

Ein Beispiel

Wir betrachten wieder einmal die Grammatik für arithmetische Ausdrücke:

  1. tex2html_wrap_inline2838
  2. tex2html_wrap_inline2840
  3. tex2html_wrap_inline2842
  4. tex2html_wrap_inline2844
  5. tex2html_wrap_inline2846
  6. tex2html_wrap_inline2848

Die Codes für die Aktionen sind:

  1. si: shift und Kellern von Zustand i
  2. rj: reduce mit Produktion j
  3. acc: accept
  4. blank: error

Die Parsertabelle

Zustand ACTION GOTO
id + * ( ) $ E T F
0 s5 s4 1 2 3
1 s6 acc
2 r2 s7 r2r2
3 r4 r4 r4r4
4 s5 s4 8 2 3
5 r6 r6 r6r6
6 s5 s4 9 3
7 s5 s4 10
8 s6 s11
9 r1 s7 r1r1
10 r3 r3 r3r3
11 r5 r5 r5r5

Der Kellerinhalt

1.    0                 id * id + id $
2.    0 id 5               * id + id $
3.    0 F  3               * id + id $
4.    0 T  2               * id + id $
5.    0 T  2 * 7             id + id $
6.    0 T  2 * 7 id 5           + id $
7.    0 T  2 * 7 F  10          + id $
8.    0 T  2                    + id $
9.    0 E  1                    + id $
10.   0 E  1 + 6                  id $
11.   0 E  1 + 6 id 5                $
12.   0 E  1 + 6 F  3                $
13.   0 E  1 + 6 T  9                $
14    0 E  1                         $

LR(k)-Grammatiken

   

Wenn die Parsertabelle keine doppelten Einträge enthält, kann man eine Zeichenreihe ohne Backtracking   analysieren.

Um zu entscheiden, welche Aktion ausgeführt werden soll, muß man nur den obersten Eintrag auf dem Keller aus Zustand und Symbol sowie (möglicherweise) die k nächsten Eingabesymbole betrachten.

In der Praxis kommt man mit k <= 1 aus.

Ein Grammatik, die von einem LR-Parser   mit einem Lookahead von k Symbolen geparst werden kann, bezeichnen wir als LR(k)-Grammatik.

Man kann zeigen, daß man für jede LR(k)-Grammatik eine äquivalente LR(1)-Grammatik erzeugen kann. Allerdings existieren Grammatiken, die nicht LR(k) für alle k >= 0 sind.

Beispiel: (dangling else)

tex2html_wrap_inline2954

Hier kann man nicht erkennen, ob geshiftet oder reduziert werden muß, wenn in der Eingabe ein ELSE erscheint.

Handles

 

Ein Handle einer Rechtsableitung   tex2html_wrap_inline2962 ist eine Produktion tex2html_wrap_inline2804 und eine Position in tex2html_wrap_inline2962 , an der die Zeichenreihe tex2html_wrap_inline2379 gefunden und durch A ersetzt wird, um die vorhergehende Satzform in der Rechtsableitung von tex2html_wrap_inline2962 zu erhalten.

Wenn S tex2html_wrap_inline2976 tex2html_wrap_inline2978 tex2html_wrap_inline2980 tex2html_wrap_inline2982 eine Rechtsableitung ist, so ist tex2html_wrap_inline2804 in der Position, die auf tex2html_wrap_inline2046 folgt, ein Handle.

Wenn die Grammatik eindeutig ist, so hat jede Satzform in einer Rechtsableitung genau ein Handle.

Wenn der Zusammenhang klar ist, sagen wir auch tex2html_wrap_inline2379 ist ein Handle von tex2html_wrap_inline2982 .

Beispiel:
tex2html_wrap_inline2992

Diese Grammatik ist bekanntlich mehrdeutig. Man betrachte die Rechtsableitung

tex2html_wrap_inline2994

tex2html_wrap_inline2996

Hier ist z.B. tex2html_wrap_inline2998 Handle von tex2html_wrap_inline3000 da man durch Ersetzung von tex2html_wrap_inline2998 durch E die vorhergehende Satzform erhalten kann.

Da die Grammatik mehrdeutig ist, gibt es eine weitere Rechtsableitung für die gleiche Zeichenreihe:

tex2html_wrap_inline3006

Im ersten Fall war tex2html_wrap_inline3008 ein Handle für tex2html_wrap_inline3010 , jetzt ist
tex2html_wrap_inline3012 Handle für die gleiche Satzform.

Die kanonische Kollektion von LR(0)-Items

   

In diesem Abschnitt beschreiben wir ein Verfahren, mit dem ein ''einfacher'' LR-Parser (Simple LR-Parser  ) konstruiert werden kann.

Die grundlegende Idee ist die Konstruktion eines DEA   aus der Grammatik, den wir dann in eine Parsertabelle   umwandeln.

Dieser DEA erkennt die brauchbaren Präfixe (viable prefixes  )         der Grammatik, das sind diejenigen Anfangsworte von Satzformen in Rechtsableitungen, die keine Symbole rechts vom Handle enthalten.

Definition:
Ein LR(0)-Item   (kurz Item) einer Grammatik G ist eine Produktion aus G, die an einer Stelle der rechten Seite einen Punkt enthält.

Beispiel:
tex2html_wrap_inline3020 erzeugt die Items:
tex2html_wrap_inline3022
tex2html_wrap_inline3024
tex2html_wrap_inline3026
tex2html_wrap_inline3028

tex2html_wrap_inline3030 erzeugt nur tex2html_wrap_inline3032

Ein Item zeigt also an, wieviel wir von einer Produktion während des Parsens schon gesehen haben.

tex2html_wrap_inline3024 heißt, wir haben eine Zeichenkette, die aus X ableitbar ist, erkannt und erwarten nun eine Zeichenkette, die man aus YZ ableiten kann.

Die Items werden nun in Mengen zusammengefaßt, die die Zustände eines LR-Parsers darstellen.

Die kanonische LR(0)-Kollektion ist die Basis für die Konstruktion von SLR-Parsern.

Für die Berechnung benötigen wir eine erweiterte Grammatik G' und zwei Funktionen CLOSURE   und GOTO  .

Die erweiterte Grammatik:
Sei G = (V,T,P,S) eine kfG  . Dann ist die erweiterte Grammatik tex2html_wrap_inline3044 . Es wird also lediglich ein neues Startsymbol eingeführt.

CLOSURE

Sei I eine Menge von Items für eine Grammatik G = (V,T,P,S). Dann berechnen wir CLOSURE(I) wie folgt:

  1. alle Items aus I sind in CLOSURE(I).
  2. tex2html_wrap_inline3070 CLOSURE(I) und tex2html_wrap_inline3074
    dann ist auch tex2html_wrap_inline3076 CLOSURE(I)

Der Grund für diese Berechnung ist, daß wir, wenn tex2html_wrap_inline3080 im Parsingprozeß auftritt, erwarten, als Nächstes eine Zeichenkette, die aus tex2html_wrap_inline3082 und damit aus tex2html_wrap_inline3084 abgeleitet werden kann, in der Eingabe zu finden.

  Beispiel:

tex2html_wrap_inline3086
tex2html_wrap_inline2339
tex2html_wrap_inline2341
tex2html_wrap_inline3092

tex2html_wrap_inline3094 , CLOSURE(I) ist dann:

tex2html_wrap_inline3098
tex2html_wrap_inline3100
tex2html_wrap_inline3102
tex2html_wrap_inline3104

Algorithmus:

ItemSet Closure(ItemSet I)
{
 do
 {
  für alle A -> a.Bb aus I und alle B -> g aus G':
   falls B -> .g nicht aus I
    setze I = I + {B -> .g}
 } while ``noch neue Items zu I hinzugekommen'';
 return I;
}

Gültige Items

       

Ein Item tex2html_wrap_inline3106 ist gültig für ein brauchbares Präfix   tex2html_wrap_inline3108 , wenn es eine Rechtsableitung S' tex2html_wrap_inline2976 tex2html_wrap_inline2978 tex2html_wrap_inline2980 tex2html_wrap_inline3118 gibt.

Normalerweise kann ein Item für mehrere Präfixe gültig sein.

Wenn tex2html_wrap_inline3106 gültig für tex2html_wrap_inline3108 ist, und tex2html_wrap_inline3124 , so heißt dies, das wir noch nicht das ganze Handle auf dem Keller haben und daher eine shift-Aktion   ausführen müssen.

Ist tex2html_wrap_inline3126 , so haben wir tex2html_wrap_inline3128 als Handle erkannt und müssen reduzieren  .

Nun kann es mehrere gültige Items für ein brauchbares Präfix geben, die unterschiedliche Aktionen erfordern. Einige dieser Konflikte lassen sich durch die Verwendung des Lookahead auflösen.

GOTO(I,X)

Wenn I eine Menge von Items ist und X ein Symbol der Grammatik, dann ist GOTO(I,X) als CLOSURE tex2html_wrap_inline3143 definiert.

Beispiel:

tex2html_wrap_inline3145

GOTO(I,+) ist dann
tex2html_wrap_inline3149
tex2html_wrap_inline3151
tex2html_wrap_inline3153
tex2html_wrap_inline3155
tex2html_wrap_inline3157

Wenn I eine Menge gültiger Items für ein brauchbares Präfix   tex2html_wrap_inline2962 ist, so ist GOTO(I,X) die Menge der gültigen Items für das brauchbare Präfix tex2html_wrap_inline3165 .

Konstruktion der kanonischen Kollektion

Algorithmus:

Collection Items(Grammar G')
{
 Collection C;
 
 C = {Closure({[S' -> .S]})};
 do
 {
  für alle Itemmengen I aus C und jedes Symbol X aus G':
   M = GOTO(I,X);
   if ((M != {}) && (M nicht aus C))
    C = C + {M};
 } while ``C hat sich noch verändert'';
 return C;
}

Beispiel:

I0:                I5 = GOTO(I0,id)
E'-> .E            F -> id. 
E -> .E+T
E -> .T            I6 = GOTO(I1,+)
T -> .T*F          E -> E+.T          
T -> .F            T -> .T*F
F -> .(E)          T -> .F
F -> .id           F -> .(E)
                   F -> .id
I1 = GOTO(I0,E)
E'-> E.            I7 = GOTO(I2,*)
E -> E.+T          T -> T*.F
                   F -> .(E)
I2 = GOTO(I0,T)    F -> .id
E -> T.
T -> T.*F          I8 = GOTO(I4,E)      GOTO(I4, T) = I2
                   F -> (E.)            GOTO(I4, F) = I3
I3 = GOTO(I0,F)    E -> E.+T
T -> F.
                   I9 = GOTO(I6,T)
I4 = GOTO(I0,()    E -> E+T.
F -> (.E)          T -> T.*F
E -> .E+T
E -> .T            I10 = GOTO(I7,F)
T -> .T*F          T -> T*F.
T -> .F
F -> .(E)          I11 = GOTO(I8,))
F -> .id           F -> (E).

Wenn wir die Itemmengen als Zustände eines DEA D ansehen, tex2html_wrap_inline3170 als Anfangszustand und alle Zustände als Endzustände, so können wir GOTO(I,X) als Zustandsübergangsfunktion interpretieren. Dann ist D ein endlicher Automat, der alle brauchbaren Präfixe der Grammatik G' akzeptiert.

tex2html_wrap3178

SLR-Parsing-Tabellen

Zu einer kontextfreien Grammatik G konstruieren wir die erweiterte Grammatik G' und die kanonische Kollektion von LR(0)-Items C.

Hieraus leiten wir jetzt die Funktionen ACTION und GOTO her:

Sei tex2html_wrap_inline3186

Die Zustände tex2html_wrap_inline3188 des Parsers werden aus den tex2html_wrap_inline3190 konstruiert.

Konstruktion von ACTION

  1. tex2html_wrap_inline3192 , GOTO tex2html_wrap_inline3194
    dann ist ACTION(i, a) = shift j für tex2html_wrap_inline3198
  2. tex2html_wrap_inline3200 dann ist ACTION(i, a) = reduce tex2html_wrap_inline3204 für tex2html_wrap_inline3206
  3. tex2html_wrap_inline3208 dann ist ACTION tex2html_wrap_inline3210 accept

Falls bei dieser Konstruktion Mehrfacheinträge auftreten, schlägt die Konstruktion fehl. Die Grammatik ist dann nicht SLR.

Konstruktion von GOTO

  1. GOTO tex2html_wrap_inline3212 :
    dann ist GOTO[i,A] = j
  2. alle anderen Einträge sind error

Anfangszustand ist der Zustand der zur Itemmenge gehört, die tex2html_wrap_inline3216 enthält.

Kanonische LR-Parsing-Tabellen

Die SLR-Methode erfordert im Zustand i eine Reduktion tex2html_wrap_inline3204 für das Eingabesymbol a, wenn tex2html_wrap_inline3224 ein Item in tex2html_wrap_inline3190 und tex2html_wrap_inline3206 ist.

Nun kann sich aber im Zustand i ein brauchbares Präfix tex2html_wrap_inline3232 auf dem Stack befinden, für das keine Rechtsableitung tex2html_wrap_inline3234 tex2html_wrap_inline2980 tex2html_wrap_inline3238 existiert. Dann wäre die Reduktion nicht zulässig. Wir betrachten ein

Beispiel:

  1. tex2html_wrap_inline3240
  2. tex2html_wrap_inline3242
  3. tex2html_wrap_inline3244
  4. tex2html_wrap_inline3246
  5. tex2html_wrap_inline3248

Die LR(0)-Item-Mengen

I0:                 I5 = GOTO(I0, id)
S' -> .S            L -> id.
S  -> .L = R
S  -> .R            I6 = GOTO(I2, =)
L  -> .*R           S -> L=.R
L  -> .id           R -> .L
R  -> .L            L -> .*R
                    L -> .id
I1 = GOTO(I0, S)
S' -> S.            I7 = GOTO(I4, R)
                    L -> *R.
I2 = GOTO(I0, L)
S -> L.=R           I8 = GOTO(I4, L)
R -> L.             R -> L.

I3 = GOTO(I0, R)    I9 = GOTO(I6, R)
S -> R.             S -> L=R.

I4 = GOTO(I0, *)
L -> *.R
R -> .L
L -> .*R
L -> .id

Die Parsertabelle:

Zustand ACTION GOTO
* id = $ S L R
0 s4 s5 1 2 3
1 acc
2 s6/r5 r5
3 r2
4 s4 s5 8 7
5 r4 r4
6 s4 s5 8 9
7 r3 r3
8 r5 r5
9 r1

Da tex2html_wrap_inline3260 und tex2html_wrap_inline3262 müßte eine Reduktion erfolgen. Es gibt aber keine Satzform, die mit 'R=' beginnt. Andererseits wird gefordert, daß das '=' geshiftet und in Zustand 6 gewechselt wird.

Um diesen Konflikt zu lösen, erweitern wir die Items um eine zusätzliche Komponente, den Lookahead  , und sprechen von LR(1)-Items  :

tex2html_wrap_inline3268

Wenn tex2html_wrap_inline3270 , ist der Lookahead ohne Wirkung.

Eine Reduktion wird aber nur noch dann durchgeführt, wenn das Item die Form tex2html_wrap_inline3272 hat und a das aktuelle Eingabesymbol ist.

Definition:
Ein LR(1)-Item tex2html_wrap_inline3268 heißt gültig für ein
brauchbares Präfix tex2html_wrap_inline2962 , wenn es eine Ableitung

S tex2html_wrap_inline2976 tex2html_wrap_inline3284 tex2html_wrap_inline2980 tex2html_wrap_inline3288 mit tex2html_wrap_inline3290

gibt, und a das erste Symbol von w oder tex2html_wrap_inline3296 und tex2html_wrap_inline3298 ist.

Wenn tex2html_wrap_inline3300 ein gültiges Item für ein brauchbares Präfix tex2html_wrap_inline2962 ist, so existiert nach unserer Definition eine Rechtsableitung S tex2html_wrap_inline2976 tex2html_wrap_inline3308 tex2html_wrap_inline2980 tex2html_wrap_inline3312 mit tex2html_wrap_inline3290 .

Angenommen aus tex2html_wrap_inline3316 kann man die terminale Zeichenkette by ableiten. Dann existiert für jede Produktion tex2html_wrap_inline3320 eine Ableitung
S tex2html_wrap_inline2976 tex2html_wrap_inline3326 tex2html_wrap_inline2980 tex2html_wrap_inline3330 .

Dann ist tex2html_wrap_inline3332 gültig für tex2html_wrap_inline2962 .

b kann das erste aus tex2html_wrap_inline2379 abgeleitete Terminal sein, oder aber auch a, wenn tex2html_wrap_inline2379 zu tex2html_wrap_inline1843 abgeleitet werden kann:

tex2html_wrap_inline3316 tex2html_wrap_inline2976 by

Zusammenfassend kann also b ein beliebiges Terminal aus tex2html_wrap_inline3354 sein.

Konstruktion der LR(1)-Item-Mengen

Wir benötigen wieder die Funktionen CLOSURE und GOTO.

ItemSet Closure(ItemSet I)
{
 do
 {
  für alle [A -> alpha.B beta, a] aus I 
      und alle B -> gamma aus G'
      und alle b aus FIRST(beta a):
   falls B -> .gamma nicht aus I
    setze I = I + {[B -> .gamma, b]}
 } while ``noch neue Items zu I hinzugekommen'';
 return I;
}

Collection GOTO(ItemSet I, Symbol X)
{
 Collection C;
 
 C = {[A -> alpha X. beta, a] | 
       [A -> alpha .X beta, a] aus I};
 return Closure(C);
}

int main()
{
 Collection C;
 
 C = {Closure({[S' -> .S, $]})};
 do
 {
  für ''alle Items I aus C 
        und alle Symbole X aus G', 
        für die GOTO(I,X) nicht leer
        und nicht in C''
   C = C + GOTO(I, X); 
 } while ''C hat sich verändert''
 return 0;
}

Beispiel

 

  1. tex2html_wrap_inline3358
  2. tex2html_wrap_inline3240
  3. tex2html_wrap_inline3242

4.
tex2html_wrap_inline3244
5.
tex2html_wrap_inline3246
6.
tex2html_wrap_inline3248

I0:               I4 = GOTO(I0, id)   GOTO(I5, *) = I5
S' -> .S, $       L -> id., =          
S  -> .L=R, $     L -> id., $         GOTO(I5, id) = I4 
S  -> .R, $                           
L  -> .*R, =      I5 = GOTO(I0, *)    
L  -> .id, =      L -> *.R, =         
R  -> .L, $       L -> *.R, $         I8 = GOTO(I5, L)
L  -> .*R, $      R -> .L, =          R -> L., =/$
L  -> .id, $      R -> .L, $
                  L -> .*R, =/$       I9 = GOTO(I6, L)
I1 = GOTO(I0, S)  L -> .id, =/$       R -> L., $
S' -> S., $                      
                  I6 = GOTO(I2, =)    I10 = GOTO(I6, *)
I2 = GOTO(I0, L)  S -> L=.R, $        L -> *.R, $
S -> L.=R, $      R -> .L, $          R -> .L, $
R -> L., $        L -> .*R, $         L -> .*R, $
                  L -> .id, $         L -> .id, $
I3 = GOTO(I0, R)                      
S -> R., $        I7 = GOTO(I5, R)    I11 = GOTO(I6, id)
                  L -> *R., =         L -> id., $
                  L -> *R., $         

I12 = GOTO(I6, R) I13 = GOTO(I10, R)
S -> L=R., $      L -> *R., $
 
GOTO(I10, L) = I9 GOTO(I10, *) = I10  GOTO(I10, id) = I11

Der LR(1)-Parser

Zu einer kontextfreien Grammatik G konstruieren wir die erweiterte Grammatik G' und die Mengen von LR(1)-Items C.

Hieraus leiten wir jetzt die Funktionen ACTION und GOTO her:

Sei tex2html_wrap_inline3186 die Kollektion der Mengen von LR(1)-Items.

Die Zustände tex2html_wrap_inline3188 des Parsers werden aus den tex2html_wrap_inline3190 konstruiert.

Konstruktion von ACTION

  1. tex2html_wrap_inline3382 , GOTO tex2html_wrap_inline3194
    dann ist ACTION(i, a) = shift j für tex2html_wrap_inline3198
  2. tex2html_wrap_inline3390 dann ist ACTION(i, a) = reduce tex2html_wrap_inline3204 .
    Dies ist der Unterschied zum SLR-Parser,
    bei dem für alle tex2html_wrap_inline3206 reduziert wurde.
  3. tex2html_wrap_inline3398 dann ist ACTION tex2html_wrap_inline3210 accept

Falls bei dieser Konstruktion Mehrfacheinträge auftreten, schlägt die Konstruktion fehl. Die Grammatik ist dann nicht LR(1).

Konstruktion von GOTO

  1. GOTO tex2html_wrap_inline3212 :
    dann ist GOTO[i,A] = j
  2. alle anderen Einträge sind error

Anfangszustand ist der Zustand der zur Itemmenge gehört,
die tex2html_wrap_inline3406 enthält.

Die Parser-Tabelle zum Beipiel:

Zustand ACTION GOTO
* id = $ S L R
0 s5 s4 1 2 3
1 acc
2 s6 r6
3 r3
4 r5 r5
5 s5 s4 8 7
6 s10 s11 9 12
7 r4 r4
8 r6 r6
9 r6
10 s10 s11 9 13
11 r5
12 r2
13 r4

LALR-Parsing-Tabellen

Abschließend konstruieren wir die LALR-Parser-Tabellen  . Die kanonische LR-Tabelle eines Parsers für eine Programmiersprache wie Pascal oder C ist mit mehreren tausend Zuständen etwa um das Zehnfache größer als die entsprechende SLR-Tabelle. Die LALR-Tabelle hat immer genauso viele Zustände wie die SLR-Tabelle und daher der LR-Tabelle vorzuziehen.

Die Idee beim LALR-Verfahren, das in der Praxis von Parsergeneratoren   wie yacc   verwendet wird, ist die folgende:

Bei den LR(1)-Itemmengen treten häufig Mengen auf, deren Elemente sich nur in der zweiten Komponente unterscheiden. Im Beispiel des letzten Abschnitts sind das z.B. die Mengen tex2html_wrap_inline3418
und tex2html_wrap_inline3420 oder auch tex2html_wrap_inline3422 und tex2html_wrap_inline3424 .

Man sagt, diese Zustände haben den gleichen Kern   oder core  .

Im Zustand 12 wird nur dann eine Reduktion durchgeführt, wenn das Endsymbol in der Eingabe erscheint, sonst liegt ein Fehler vor. Im Zustand 4 wird auch beim Symbol = reduziert. Wenn wir nun die beiden Mengen vereinigen, reduzieren wir immer, unabhängig vom aktuellen Eingabesymbol.

Bei einer fehlerhaften Zeichenkette wie id = id = id wird beim LR(1)-Parser das fehlerhafte zweite = im Zustand 11 bemerkt. Vereinigt man nun die Zustände 4 und 11, so wird noch einmal reduziert, aber dann wird der Fehler ebenfalls erkannt, und zwar bevor das nächste Symbol geshifted wird.

Da die GOTO-Funktion nur vom Kern des Zustandes abhängt, kann man auch die GOTO-Mengen von zusammengelegten Zuständen vereinigen.

Durch dieses Zusammenlegen können möglicherweise reduce/reduce-Konflikte   entstehen, nämlich wenn die Grammatik in zwei Mengen Items der Art tex2html_wrap_inline3430 und tex2html_wrap_inline3432 enthält.

shift/reduce-Konflikte   kann es nicht geben, da dann diese Konflikte bereits in der ursprünglichen Kollektion auftreten müßten.

Enthält die Vereinigung von zwei Mengen mit gleichem Kern die Items tex2html_wrap_inline3272 und tex2html_wrap_inline3436 , so müssen diese beiden Elemente schon vor der Vereinigung in einer Menge aufgetreten und daher die Grammatik nicht - wie vorausgesetzt - LR(1) gewesen sein.

Konstruktion von ACTION

  1. Sei tex2html_wrap_inline3438 die Kollektion der Mengen von LR(1)-Items.
  2. Vereinige alle Mengen mit gleichem Kern.
  3. Sei tex2html_wrap_inline3440 die resultierende Menge von LR(1)-Items. Berechne ACTION wie beim LR(1)-Verfahren.
  4. Berechnung von tex2html_wrap_inline3442 :
    Sei tex2html_wrap_inline3444 . Dann haben tex2html_wrap_inline3446 den gleichen Kern. Sei K die Vereinigung aller Itemmengen, die den gleichen Kern wie tex2html_wrap_inline3450 haben. Setze GOTO(J, X) = K

Beispiel:

Wir betrachten wieder die Tabelle aus dem Beispiel von Seite gif.

Es lassen sich die Mengen

  1. tex2html_wrap_inline3454
  2. tex2html_wrap_inline3456
  3. tex2html_wrap_inline3458
  4. tex2html_wrap_inline3460

identifizieren. Damit ergibt sich die Tabelle:

Zustand ACTION GOTO
* id = $ S L R
0 s5.10 s4.11 1 2 3
1 acc
2 s6 r6
3 r3
4.11 r5 r5
5.10 s5.10 s4.11 8.9 7.13
6 s5.10 s4.11 9 12
7.13 r4 r4
8.9 r6 r6
12 r2

Der Parsergenerator yacc

Nachdem wir im letzten Abschnitt die theoretischen Grundlagen der LR-Analyse kennengelernt haben, wollen wir nun die Praxis am Beispiel des Parsergenerators yacc   betrachten.   Die Abkürzung yacc steht für yet another compiler compiler. Die GNU-Werkzeuge   enthalten einen kompatiblen Generator mit dem Namen bison  .

Wir betrachten zunächst ein Beispiel. Danach sehen wir uns das Zusammenspiel von lex und yacc an und betrachten die Syntax für yacc-Spezifikationen.

Beispiel: Grammatik für arithmetische Ausdrücke

Als Eingabe erwartet der Generator eine kontextfreie Grammatik, aus der dann ein LALR-Parser erzeugt wird. Wir geben zunächst die lex-Spezifikation für die übliche Grammatik für arithmetische Ausdrücke an:

expr.l:

%{
#include "expr.h"
#include "expr.tab.h"
%}

idf	[A-Za-z][A-Za-z0-9_]*
nl	\n
blank	[ \t]+

%%

{blank}	    ;
nl          {linecnt++;}
\*          {return mult;}
\(          {return lpar;}
\)          {return rpar;}
\+          {return plus;}
{idf}       {return id;}

Die zugehörige yacc-Spezifikation ist

expr.y:

%{
#include "expr.h"
%}
%token plus mult lpar rpar id
%start E

%%
E : E plus T | T
T : T mult F | F
F : id | lpar E rpar
%%

Außerdem benötigen wir einige Deklarationen und ein Hauptprogramm:

expr.h:

#include <fstream.h>
#include <string.h>
#include <iomanip.h>

void yyerror(char *err);
int yylex();
extern int error;  
extern int linecnt;

exprmain.cc:

#include "expr.h"
#include <FlexLexer.h>

int linecnt = 1;
extern void yyparse();

void yyerror(char *err)
{ error = 1;
  cerr << "Line: " << linecnt << ": " << err;
}

FlexLexer* lexer = new yyFlexLexer;

int yylex(){return lexer->yylex();}
             
int error = 0;

int main()
{
 cout << "Auf gehts" << endl;
 yyparse();
 return 0;
}

Der erzeugte Kellerautomat

Der Aufruf bison -v expr.y liefert die Datei expr.output, die sehr nützliche Informationen über den generierten Parser enthält:

Grammar
rule 1    E -> E plus T
rule 2    E -> T
rule 3    T -> T mult F
rule 4    T -> F
rule 5    F -> id
rule 6    F -> lpar E rpar

state 0
    lpar	shift, and go to state 1
    id  	shift, and go to state 2

    E   	go to state 3
    T   	go to state 4
    F   	go to state 5

state 1
    F  ->  lpar . E rpar   (rule 6)

    lpar	shift, and go to state 1
    id  	shift, and go to state 2

    E   	go to state 6
    T   	go to state 4
    F   	go to state 5

state 2
    F  ->  id .   (rule 5)

    $default	reduce using rule 5 (F)

state 3
    E  ->  E . plus T   (rule 1)

    $   	go to state 12
    plus	shift, and go to state 7

state 4
    E  ->  T .   (rule 2)
    T  ->  T . mult F   (rule 3)

    mult	shift, and go to state 8

    $default	reduce using rule 2 (E)

state 5
    T  ->  F .   (rule 4)

    $default	reduce using rule 4 (T)

state 6
    E  ->  E . plus T   (rule 1)
    F  ->  lpar E . rpar   (rule 6)

    plus	shift, and go to state 7
    rpar	shift, and go to state 9

state 7
    E  ->  E plus . T   (rule 1)

    lpar	shift, and go to state 1
    id  	shift, and go to state 2

    T   	go to state 10
    F   	go to state 5

state 8
    T  ->  T mult . F   (rule 3)

    lpar	shift, and go to state 1
    id  	shift, and go to state 2

    F   	go to state 11

state 9
    F  ->  lpar E rpar .   (rule 6)

    $default	reduce using rule 6 (F)

state 10
    E  ->  E plus T .   (rule 1)
    T  ->  T . mult F   (rule 3)

    mult	shift, and go to state 8

    $default	reduce using rule 1 (E)

state 11
    T  ->  T mult F .   (rule 3)

    $default	reduce using rule 3 (T)

state 12
    $   	go to state 13

state 13
    $default	accept

Die Eingabe a + b * (c + d) liefert den folgenden Trace:

Auf gehts
yydebug = 1

Starting parse
Entering state 0

Reading a token: Next token is 262 (id)
Shifting token 262 (id), Entering state 2
Reducing via rule 5 (line 14), id  -> F
state stack now 0
Entering state 5
Reducing via rule 4 (line 12), F  -> T
state stack now 0
Entering state 4

Reading a token: Next token is 258 (plus)
Reducing via rule 2 (line 10), T  -> E
state stack now 0
Entering state 3
Next token is 258 (plus)
Shifting token 258 (plus), Entering state 7

Reading a token: Next token is 262 (id)
Shifting token 262 (id), Entering state 2
Reducing via rule 5 (line 14), id  -> F
state stack now 0 3 7
Entering state 5
Reducing via rule 4 (line 12), F  -> T
state stack now 0 3 7
Entering state 10

Reading a token: Next token is 259 (mult)
Shifting token 259 (mult), Entering state 8

Reading a token: Next token is 260 (lpar)
Shifting token 260 (lpar), Entering state 1

Reading a token: Next token is 262 (id)
Shifting token 262 (id), Entering state 2
Reducing via rule 5 (line 14), id  -> F
state stack now 0 3 7 10 8 1
Entering state 5
Reducing via rule 4 (line 12), F  -> T
state stack now 0 3 7 10 8 1
Entering state 4

Reading a token: Next token is 258 (plus)
Reducing via rule 2 (line 10), T  -> E
state stack now 0 3 7 10 8 1
Entering state 6
Next token is 258 (plus)
Shifting token 258 (plus), Entering state 7

Reading a token: Next token is 262 (id)
Shifting token 262 (id), Entering state 2
Reducing via rule 5 (line 14), id  -> F
state stack now 0 3 7 10 8 1 6 7
Entering state 5
Reducing via rule 4 (line 12), F  -> T
state stack now 0 3 7 10 8 1 6 7
Entering state 10

Reading a token: Next token is 261 (rpar)
Reducing via rule 1 (line 10), E plus T  -> E
state stack now 0 3 7 10 8 1
Entering state 6
Next token is 261 (rpar)
Shifting token 261 (rpar), Entering state 9
Reducing via rule 6 (line 14), lpar E rpar  -> F
state stack now 0 3 7 10 8
Entering state 11
Reducing via rule 3 (line 12), T mult F  -> T
state stack now 0 3 7
Entering state 10
Reading a token: 
Now at end of input.
Reducing via rule 1 (line 10), E plus T  -> E
state stack now 0
Entering state 3
Now at end of input.
Shifting token 0 ($), Entering state 12
Now at end of input.

Von der Spezifikation zum Programm

tex2html_wrap3483

Das folgende Makefile erzeugt einen Parser für arithmetische Ausdrücke:

FLAGS = -g -DUNIX -DYYDEBUG
BISONFLAGS = --debug
GCC   = g++
LEXY  = lex.yy
TABB  = expr.tab
CLEAN = rm -f *.o *.tab.* lex.yy.cc exprmain

exprmain   : $(LEXY).o exprmain.o $(TABB).o \
           ; $(GCC) $(FLAGS) -o exprmain *.o -lfl
$(LEXY).o  : $(LEXY).cc \
           ; $(GCC) $(FLAGS) -c $(LEXY).cc
$(TABB).o  : $(TABB).cc \
           ; $(GCC) $(FLAGS) -c $(TABB).cc
$(TABB).cc : $(TABB).c \
           ; cp $(TABB).c $(TABB).cc 
$(TABB).c  : expr.y \
           ; bison $(BISONFLAGS) expr.y
$(TABB).h  : expr.y \
           ; bison -d $(BISONFLAGS) expr.y
$(LEXY).cc : expr.l $(TABB).h expr.h \
           ; flex -+ expr.l
exprmain.o : exprmain.cc expr.h \
           ; $(GCC) $(FLAGS) -c exprmain.cc
clean      : \
           ; $(CLEAN)

Kontrollfluß zwischen lex und yacc

   

tex2html_wrap3485

yacc-Spezifikationen

Eine yacc-Spezifikation setzt sich aus drei Abschnitten zusammen:

Deklarationen
%%
Grammatik-Regeln
%%
C-Routinen

yacc-Deklarationen

%{
C-Deklarationen
%}

Die C-Deklarationen können enthalten:

#include

#define

Deklarationen von C-Variablen und C-Typen

Alles, was zwischen %{...%} steht, wird in das erzeugte C-Programm kopiert.

Weitere Deklarationen

 
%token ID NUMBER 		 /* Token-Deklarationen */

%start prog /* Definition des Startsymbols */

%union {typ typename; ...} /* Typdefinition für yylval */

%type<typename> nonterminal /* Typdefinition für Variable */

%token<typename> token /* Typdefinition für token */

Syntax für Produktionen der yacc-Grammatik

 Symbol : ĒDefinition

{

Semantische Aktion

}

;


Semantische Aktionen

Mit den einzelnen Produktionen der Grammatik kann man semantische Aktionen verknüpfen, z.B. das Berechnen von Werten, wenn der Parser Teil eines Interpretierers ist, oder die Ausgabe von Codesequenzen in einem Compiler:

%union {float real;
        int integer;}
%token PLUS 
%token <integer> INTEGER
%token <real> REAL
%type <real> rexpr
%type <integer> iexpr
%start expr1 
%%
expr1: |
       expr1 expr
expr : rexpr
       {printf("real sum = %f\n", $1);}
       | iexpr
         {printf("int sum = %d\n", $1);}
iexpr : INTEGER PLUS INTEGER 
       { $$ = $1 + $3;}
rexpr : REAL PLUS REAL
       { $$ = $1 + $3;}
%%
#include "lex.yy.c"

Semantische Werte

Mit jedem Element (Terminal oder Nonterminal) der rechten Seite kann ein semantischer Wert   verbunden werden, auf den über die Positionsnummer zugegriffen werden kann. Dazu ist es nötig, den Typ des zugeordneten Wertes im Deklarationsabschnitt zu vereinbaren. Das geschieht durch die Angabe des Typs im der Token- bzw. Nonterminaldeklaration und durch Anlegen einer Kompentente in der Struktur yylval  :

%union {Node *nodeptr;
	char strval[256]; /* Identifikatoren   */
	long intval;      /* Integerkonstanten */
        struct {BinNode *l, 
                    *up;} sel;
       }

%token <intval> multop divop modop andop addop subop orop
...
%token <intval> integer
%token <strval> ident
...

%type <intval> Sign Op1 Op2 Relop

%type <nodeptr> Factor Term SimpleExpression Expression

%type <sel> Selector
...

Konventionen für lesbare Spezifikationen

  1. Großbuchstaben für Tokens, Kleinbuchstaben für nichtterminale Symbole.
  2. Grammatikregeln und Aktionen auf verschiedene Zeilen verteilen.
  3. Linke Seiten zusammenfassen und das pipesymbol ''|'' verwenden.
  4. Rechte Seiten von Regeln ein TAB, Aktionen zwei TAB einrücken.
  5. Komplizierte Aktionen in Routinen in getrennten Dateien verstecken.

Konflike in yacc-Grammatiken

 

Das folgende Beispiel zeigt eine mehrdeutige Grammatik für primitive arithmetische Ausdrücke. Der vorhandene shift/reduce-Konflikt   wird dadurch aufgelöst, daß yacc im Zweifelsfall immer shiftet.

State 4 contains 1 shift/reduce conflict.

Grammar
rule 1    expr -> 'a'
rule 2    expr -> expr '+' expr

state 0
    'a' 	shift, and go to state 1
    expr	go to state 2

state 1
    expr  ->  'a' .   (rule 1)
    $default	reduce using rule 1 (expr)

state 2
    expr  ->  expr . '+' expr   (rule 2)
    $   	go to state 5
    '+' 	shift, and go to state 3

state 3
    expr  ->  expr '+' . expr   (rule 2)
    'a' 	shift, and go to state 1
    expr	go to state 4

state 4
    expr  ->  expr . '+' expr   (rule 2)
    expr  ->  expr '+' expr .   (rule 2)
    '+' 	shift, and go to state 3
    '+' 	[reduce using rule 2 (expr)]
    $default	reduce using rule 2 (expr)

state 5
    $   	go to state 6

state 6
    $default	accept

Das dangling-else-Problem

 

Ein weiteres bekanntes Problem ist die Zuordnung des else in geschachtelten bedingten Anweisungen, das von yacc aber wie erwartet gelöst wird:

State 5 contains 1 shift/reduce conflict.

Grammar
rule 1    S -> 'i' '5' 't' S
rule 2    S -> 'i' '5' 't' S 'e' S
rule 3    S -> 'a'

state 0
    'i' 	shift, and go to state 1
    'a' 	shift, and go to state 2
    S   	go to state 8
state 1
    S  ->  'i' . '5' 't' S   (rule 1)
    S  ->  'i' . '5' 't' S 'e' S   (rule 2)
    '5' 	shift, and go to state 3
state 2
    S  ->  'a' .   (rule 3)
    $default	reduce using rule 3 (S)
state 3
    S  ->  'i' '5' . 't' S   (rule 1)
    S  ->  'i' '5' . 't' S 'e' S   (rule 2)
    't' 	shift, and go to state 4
state 4
    S  ->  'i' '5' 't' . S   (rule 1)
    S  ->  'i' '5' 't' . S 'e' S   (rule 2)
    'i' 	shift, and go to state 1
    'a' 	shift, and go to state 2
    S   	go to state 5
state 5
    S  ->  'i' '5' 't' S .   (rule 1)
    S  ->  'i' '5' 't' S . 'e' S   (rule 2)
    'e' 	shift, and go to state 6
    'e' 	[reduce using rule 1 (S)]
    $default	reduce using rule 1 (S)
state 6
    S  ->  'i' '5' 't' S 'e' . S   (rule 2)
    'i' 	shift, and go to state 1
    'a' 	shift, and go to state 2
    S   	go to state 7
state 7
    S  ->  'i' '5' 't' S 'e' S .   (rule 2)
    $default	reduce using rule 2 (S)
state 8
    $   	go to state 9
state 9
    $   	go to state 10
state 10
    $default	accept

Fehlerbehandlung

 

Das spezielle Token error ist ein vordefiniertes terminales Symbol, das für die Fehlerbehandlung reserviert ist. Wenn ein Fehler auftritt, generiert der Parser dieses Token. Wenn eine Regel existiert, die dieses Token im aktuellen Kontext erkennt, kann der Parser weitermachen:

Beispiel:

     stmnt:  /* empty string */
             | stmnt '\n'
             | stmnt exp '\n'
             | stmnt error '\n'

Die vierte Regel besagt, daß das error-Token   gefolgt von einem Zeilenende ein gültiges Statement ist. Wenn nun ein Fehler z.B. in exp auftritt, wird yacc Zustände und Objekte vom Keller entfernen, bis ein Zustand erreicht wird, in dem error ein gültiges Eingabesymbol ist. Wenn dann der Lookahead ein Zeilenende ist, kann error auf den Keller geshiftet werden, ansonsten werden die Eingabesymbole überlesen, bis ein Zeilenende auftritt.

Es kann auch nützlich sein, im Fehlerfall zur schließenden Klammer eines geklammerten Ausdrucks vorzuspulen:

Factor: lpar Expression rpar
        |integer
        |ident Selector
        |notsy Factor
        |lpar error {yyerror(" error in factor ");} 
         rpar { $$ = NULL;}

Eingabe:

1 MODULE main;
2 BEGIN
3  WHILE (a + ) DO
4   IF a < b 
5   THEN a := b; c := d 
6    ELSIF c < a THEN a := c 
7     ELSIF b = (a+*b) 
8      THEN b = c
9      ELSE a := 2 *  
10  END;
11 END
12END main.

Ausgabe:

Syntaxfehler in Zeile 3: parse error
Syntaxfehler in Zeile 3:  error in factor 
Syntaxfehler in Zeile 7: parse error
Syntaxfehler in Zeile 7:  error in factor 
Syntaxfehler in Zeile 7:  error in factor 
Syntaxfehler in Zeile 7:  error in factor 
Syntaxfehler in Zeile 8: parse error

Trace:

Reducing via rule 44 (line 233), Term  -> SimpleExpression
state stack now 0 1 2 3 5 10 19 45 78 45 78 45 17 35
Entering state 40
Next token is 262 (addop)
Shifting token 262 (addop), Entering state 60
Reducing via rule 48 (line 248), addop  -> Op1
state stack now 0 1 2 3 5 10 19 45 78 45 78 45 17 35 40
Entering state 70
Reading a token: Next token is 274 (rpar)
Syntaxfehler in Zeile 9: parse error
Error: state stack now 0 1 2 3 5 10 19 45 78 45 78 45 17 35 40
Error: state stack now 0 1 2 3 5 10 19 45 78 45 78 45 17 35
Shifting error token, Entering state 55
Reducing via rule 61 (line 290),  -> @1
Syntaxfehler in Zeile 9:  error in factor
 state stack now 0 1 2 3 5 10 19 45 78 45 78 45 17 35 55
Entering state 87
Next token is 274 (rpar)
Shifting token 274 (rpar), Entering state 97
Reducing via rule 62 (line 290), lpar error @1 rpar  -> Factor




Next: Codeerzeugung Up: Syntaktische Analyse Previous: Top-down-Analyse

Prof. Dr. Reinhard Völler