Analiza Składniowa Wstępująca Automat ze stosem Przykład gramatyki Automat rozpoznający Gramatyki LR(k) 8 marca 2004 Wykład 5 Andrzej Salwicki
Automat ze stosem Definicja Automat ze stosem jest uporządkowaną siódemką A= (T, Q, R, q0 , F, S, s0) Q jest zbiorem stanów automatu, q0 jest stanem początkowym, F Q jest akceptującym podzbiorem stanów. Zbiory T i Q są rozłączne. T jest zbiorem symboli wejściowych. S jest zbiorem symboli wpisywanych na stos, s0 jest symbolem - początkową wartością stosu. Kazdy element zbioru R ma postać qa 'q' gdzie i ' są elementami S* , q i q' elementami ze zbioru Q, a jest elementem T lub a = , a jest elementem T*. 1
Automat ze stosem c.d. Automat ze stosem akceptuje napis T , jeśli s0q0q dla pewnego q ze zbioru F. Jeśli kazde zdanie jest zakończone znakiem $, to automat A definiuje język L(A) = { | s0q0$q$, q F, } Automat jest maszyną ze skończonym zbiorem Q stanów wewnętrznych i ze stosem, który jest skończony ale nie ma ograniczenia jego długości. Konfiguracja s1 ... snq oznacza, ze automat jest w stanie q, jest nieprzeczytaną jeszcze częścią analizowanego tekstu wejściowego, s1 ... sn jest zawartościąstosu, (sn jest na wierzchołku stosu, s1 na dnie stosu). Twierdzenie Dla kazdej gramatyki bezkontekstowej G istnieje automat ze stosem A, taki, ze L(A) = L(G). 1
Budujemy automat dla gramatyki G Niech G = (T, N, P, S) będzie gramatyką bezkontekstową. Definiujemy automat A=(T, {q}, R, q, {q}, V, ) gdzie V=T N, R jest zbiorem zdefiniowanym następująco: R = {x1...xnq X | X x1...xnP}{qttq | tT}{Sqq} Ten automat akceptuje dany napis z L(G) wytwarzając odwrócone prawostronne wyprowadzenie takiego napisu z symbolu początkowego S. 1
Wybrana gramatyka Gramatyka G3 T = {+, * , (, ), i} symbole terminalne N = {E, T, F} symbole nieterminalne P= { E ::=T, E ::= E + T, produkcje T ::= F, T ::= T * F, F ::= i, F ::= ( E ) } S = E symbol startowy Zbudujemy odpowiedni dla niej automat ze stosem. 1 13 marca 2002
Gramatyka i automat Gramatyka G T = {+, * , (, ), i} N = {E, T, F} P= { E ::=T, E ::= E + T, T ::= F, T ::= T * F, F ::= i, F ::= ( E ) } Start = E Automat AG T = {+, * , (, ), i} S = {+, * , (, ), i, E, T, F} R= { Tq Eq, E+Tq Eq, Fq Tq, T * Fq Tq, iq Fq, ( E )q Fq, q+ +q, q* *q, q( (q, q) )q, qi iq, Eq q } Q = {q} Cel = E 1
Stos i F T E E+ E+i E+F E+T E+T* E+T*i E+T*F E+T E Przebieg analizy Stos i F T E E+ E+i E+F E+T E+T* E+T*i E+T*F E+T E Stan Wejście q i+i*i q +i*i q +i*i q +i*i q +i*i q i*i q *i q *i q *i q i q q q q q Wyprowadzenie prawostronne i+i*i shift F+i*i reduce T+i*i reduce E+i*i reduce shift shift E+F*i reduce E+T*i reduce shift shift E+T*F reduce E+T reduce E reduce 1
W tym przykładzie wszystko przebiegło gładko W tym przykładzie wszystko przebiegło gładko. Skąd mamy czerpać gwarancję, że tak będzie zawsze, dla każdej gramatyki i dla każdego napisu wejściowego. Zauważmy, że możliwe są konflikty, gdy w danej konfiguracji automat może dokonać wyboru spomiędzy kilku przejść. Prawie zawsze automat ma do wyboru przejście przesuwające z wejścia na stos postaci qt tq, lub przejście redukujące. Ten konflikt możemy rozstrzygnąć w ogólny sposób dając pierwszeństwo przesunięciom nad redukcjami. Nadal jednak mogą się zdarzyć gramatyki o takich własnościach, które spowodują nieefektywność procesu analizy składniowej. 1
Nieco teorii
Notacja k-głowa napisu =df pierwsze min(k, ||+1) symboli napisu , przyjmujemy oznaczenie k: FIRSTk() =df zbiór wszystkich terminalnych k-głów napisów dających się wyprowadzić z napisu EFFk(w) =df zbiór wzystkich napisów z FIRSTk() dla których w prawostronnym wyprowadzeniu , w ostatnim kroku nie zastosowano produkcji anulującej A FOLLOWk() =df zbiór wszystkich terminalnych k-głów, które mogą wystąpić po Prawostronne wyprowadzenie X * Y oznaczamy X R Y Indeks k pomijamy gdy k=1
Formalne definicje FIRSTk() =df { takie, ze k} EFFk() =df FIRSTk() FIRSTki takie, że R FOLLOWk() =df { V* takie, ze S k: }
Tablica Wejście S t o s W y j ś c i e actions goto Program automatu LR 1
Konfiguracje Analiza wstępująca przeprowadzona przez ten automat konstruuje wyprowadzenie od symbolu startowego do ciągu źródłowego, w odwróconej kolejności. Konfiguracja -istotna tu jest informacja nie o stanie wewnętrznym automatu, lecz para () gdzie S* oznacza zawartość stosu, a * oznacza nieprzeczytaną jeszcze część tekstu wejściowego. Pary opisujące konfiguracje automatu można podzielić na klasy równowazności: 1
Klasy redukcji Definicja Dla p = 1, ...,n niech Xp -> p będzie p-tą produkcją gramatyki bezkontekstowej G=(T, N, P, S). Klasy redukcji Rj, j=0,...,n są zdefiniowane następująco: R0 = {() | , , takie, ze S A, A R', } Rp = {() | p, SR p} gdzie A R'oznacza relację “A Roraz ostatni krok w tym wyprowadzeniu nie jest anulujący: B ” 1
Przejścia automatu wyznaczone przez klasy redukcji Klasy redukcji zawierają wszystkie pary napisów, kóre mogą pojawić się podczas analizy wstępującej wykonywanej przez opisany automat ze stosem. Ponadto, klasa redukcji, do której nalezy para charakteryzuje przejście automatu wykonywane wtedy gdy para pojawia się jako konfiguracja bieząca automatu. Istnieją trzy mozliwości: (1) ()R0 , nie ma na stosie pojedynczej frazy p; stosuje się przejście qt -> tq dla t= pierwszy symbol z (jest to przejście z przesunięciem, ang. shift), (2) ()Rp , 1pn . Pojedyncza fraza jest całkowicie na stosie i wykonuje się przejście redukujące. Dla p=1 jest wykonywane jest przejście akceptujące Sq -> q. (3) ()Rp , 0jn . Dalsze przejścia nie są mozliwe. Błąd! 1
Gramatyki LR(k) Definicja Dla pewnego k >0, zbiory Rj,k, dla j=0,...,n nazywamy klasami k- stosowymi gramatyki G jeśli Rj,k = {() | Rj takie, ze = k:} k: to początek , k symboli Gramatyka bezkontekstowa G jest typu LR(k) dla danego k > 0, jeśli dla dowolnych wyprowadzeń: SR A V*T*, A-> P SR 'B''''V*'T*, B-> P (||+k): = (|'|):'' implikuje, ze ', A =B, Twierdzenie Gramatyka bezkontekstowa jest typu LR(k) wtedy i tylko wtedy gdy jej klasy k-stosowe są parami rozłączne. 1
Twierdzenie powyzsze można wykorzystać do testowania własności LR(k) poprzez obliczanie przecięć klas k-stosowych. Kłopot polega na tym, że klasy te mogą być nieskończone, mogą zawierać nieskończenie wiele par (). Długość zawartości stosu jest przecież nieograniczona. Okazuje się jednak, że dla każdej klasy k-stosowej Rj,k mozemy podać gramatykę regularną Gj taką, ze L(Gj) = {() | () Rj,k}. Pozostaje tylko skorzystać z algorytmu sprawdzającego czy dane dwa języki regularne są rozłączne. Twierdzenie Niech G będzie gramatyką bezkontekstową i niech k>0. Zakładamy, ze & T N . Istnieje zbiór gramatyk regularnych Gj, dla j=0,...,n taki, ze L(Gj) = {() | () Rj,k}. 1
Kazdy stan automatu ze stosem można reprezentować trójką (p, j ) gdzie p jest numerem zastosowanej produkcji, 0<j<np jest liczbą juz przeanalizowanych symboli prawej strony produkcji Xp -> xp,1, ... ,xp,np a jest zbiorem k-głów napisów, które mogą wystąpić za napisem wyprowadzonym z Xp. Taką trójkę nazywamy sytuacją i będziemy stosować następującą notację [X->] gdzie = xp1, ... , xp,j, = xp,j+1, ... ,xpnp Kropka zaznacza pozycję analizy wewnątrz prawej strony. W większości przypadków zawiera pojedynczy napis - piszemy go bez nawiasów. 1
W = {[X ; ] X P , FOLLOWk(X)} Gramatyki Gj regularne generujące klasy k-stosowe, są oparte na sytuacjach W = {[X ; ] X P , FOLLOWk(X)} te sytuacje są symbolami nieterminalnymi gramatyk regularnych (stanami odpowiedniego automatu). Najpierw określimy zbiór gramatyk, które generują klasy k-stosowe, ale nie muszą być regularne Gj' = (V {&, $}, W, P' P” Pj, [S -> . s;$]) alfabet końcowy nieterminale produkcje symbol startowy Produkcje z P' P” budują składnik klasy k-stosowej. Stanowią one skończony opis napisów nieskończonej długości. Produkcje z Pj dołączają składnik , kończąc klasę k-stosową. P' = {[ X ; ] -> [X ; ] V} 1
P”= {[ X ; ] [B ; ] B P, EFFk()} P0 = {[X ; ] & EFFk()} Pp = {[Xp ˝p. ;] &} p=1, ..., n Przypomnijmy, ze długości napisów t i w są ograniczone do k symboli. Zatem liczba mozliwych napisów &t i &w jest skończona. Jeśli napisy te potraktować jako pojedyncze symbole terminalne, to produkcje z P' i z Pj , j=0, 1, ... ,n są dopuszczalne w gramatyce regularnej. Produkcje z P” nie są dopuszczalne w gramatyce regularnej poniewaz mają postać A -> B, A, B N. Gramatyka Gj' nie jest więc regularna. Można ją jednak przepisać tak by nie zawierała produkcji typu P”. Zastosujemy domknięcie symbolu nieterminalnego H(A) = {A} {B | C->B P, C H(A)} 1
Procedura przepisywania gramatyki jest następująca: 1. Wybierz A N, dla którego H(A) {A}. 2. Niech P:= P - {A B : BN} 3. Niech P := P {A : B P, BH(A), N } Algorytm kończy się gdy nie mozna dokonać wyboru w kroku 1. Twierdzenie Dla każdej gramatyki G typu LR(k) istnieje deterministyczny automat ze stosem A taki, że L(A) = L(G) 1
Gramatyka P={SX, XY, XbYa, Yc, Yca} Przejścia R={ q0bc q0q3c, q0c$ q0q4$, q3ca q3q6a, q4a$ q4q7$, q5a$ q5q8$, q6aa q6q9a, q0q2$ q0q1$, q0q4$ q0q2$, q3q6a$ q3q5a$, q0q4q7$ q0q2$, q0q3q5q8$ q0q1$, q3q6q9a$ q3q5a$ } Stany q0: [SX;$] q4: [Yc.;$] [X.Y;$] [Yc.;$] [X.bYa;$] [Y.c;$] q5: [XbY.a;$] [Y.ca;$] q6: [Yc.;a$] q1: [SX.;$] [Yc.a;a$] q2: [XY.;$] q7:[Yca.;$] q3: [Xb.Ya;$] q8:[XbYa.;$] [Y.c;a$] q9: [Yca.;$] [Y.ca;a$] 1
Tworzenie parsera LR(0) Gramatyka 0 S' ::= S$ 1 S ::= ( L ) 2 S ::= x 3 L ::= S 4 L ::= L , S Początkowy stan automatu: stos pusty, na wejściu: zdanie i $. Oznaczamy ten stan przez S'-> . S$ (kropka wskazuje pozycję ) W tym stanie gdy wejście rozpoczyna się od S , tzn. od jakiejkolwiek prawej strony produkcji dla S S' . S $ S . x S . ( L ) 1 To jest stan 1. Stan ten utozsamiamy z pewnym zbiorem sytuacji.
Tworzenie parsera LR(0) Przesunięcie symbolu Co się stanie gdy w stanie 1 przesuniemy x. W nowym stanie 2 znajdzie się sytuacja S ->x . , Reguły S-> S$ i S -> ( L ) nie mają znaczenia gdy przeczytano x. A więc stan 2 to jedna tylko sytuacja A jeśli w stanie 1 przeczytamy ( lewy nawias? Nowa sytuacja to S ( . L ) , wiemy, ze na stosie jest lewy nawias, a na wejście jest ciąg wyprowadzony z L a potem prawy nawias. Jakie sytuacje odpowiadają temu nowemu stanowi 2 S x . 3 L ( . L ) L . L , S L . S S . ( L ) S . x
Tworzenie parsera LR(0) Akcje przejścia Co się stanie gdy z S wyprowadzimy pewien ciąg symboli? Cała prawa strona produkcji będzie zdjęta ze stosu i parser wykona akcję przejścia dla symbolu S i stanu 1 wchodząc do stanu 4 Redukcje W stanie 2 kropka jest na końcu pewnej sytuacji. Oznacza to, ze na wierzchołku stosu jest cała prawa strona odpowiedniej produkcji S x gotowa do redukcji. W takim stanie parser powinien wykonać akcję redukcji r2 tzn. zastosować drugą produkcję. W tym przypadku x zostanie zastąpione przez S. 4 S' S . $
Tworzenie parsera LR(0) Dwie operacje są nam potrzebne by wyznaczać stany, a to: Domknięcie i Przejście. Domknięcie(I)= repeat for each item A . X in I for each production X I := I {X .} until I does not change return I Przejście(I,X)= J := ; for each item A . Xin I J := J X . return Domknięcie(J)
Algorytm konstrukcji parsera LR(0) Powiększ gramatykę dodając produkcję S' S $. T będzie zbiorem stanów, E zbiorem krawędzi (shift albo przejścia) T := Domknięcie({S' . S $}); E := ; repeat for each state I in T for each item A . X in I J := Przejście(I, X); T := T {J}; E := E {I X J }; until E oraz T nie zwiększają się w tej iteracji; Dla symbolu $ nie obliczamy Przejście(I, $); zamiast tego tworzymy stan akceptuj.
Algorytm konstrukcji parsera LR(0) c.d. Teraz wyznaczamy redukcje. R := {}; for each state I in T for each item A in I R := R
Przykład S' . S $ S . x S . ( L ) S x . 1 2 8 9 S' . S $ S . x S . ( L ) S x . x x L L , . S S . ( L ) S . x S L L , S . 3 ( S ( . L ) L . S L . L , S S . ( L ) S . x ( , ( S 5 S ( L . ) L L . , S L 4 S' S . $ ) 6 S S ( L ) . 7 L S .
Przykład c.d. Tablica przejść automatu LR(0) dla przykładowej gramatyki 0 S' ::= S$ 1 S ::= ( L ) 2 S ::= x 3 L ::= S 4 L ::= L , S actions goto
Tworzenie parsera SLR (str. 1/3) Przykład: gramatyka S E$, E E + T, E T, T -> x. Graf przesunięc i przejśćutworzony wg LR(0) 1 2 S . E $ E . T + E E . T T . x E E T . $ 3 T E T . + E E T . 4 , E T = . E E . T + E E . T T . x T x 5 T x . E 6 E T + E .
Tworzenie parsera SLR (str. 2/3) Tablica parsera, zauważ, że jest konflikt nowy algorytm SLR : stany obliczamy j.w. a redukcje tak: R := {}; for each state I in T for each item A in I for each token X in FOLLOW(A) R := R {(I, X, A )};
Tworzenie parsera SLR (str. 3/3) Po zastosowaniu algorytmu SLR otrzymujemy tablicę:
Co by tu jeszcze? Nie zdążyłem opowiedzieć: O metodzie pierwszeństwa operatorów, O analizatorach LALR, O pożytkach płynących z zastosowania gramatyk niejednoznacznych , O wydobywaniu się z błędów.