Analizator syntaktyczny program źródłowy analizator leksykalny token daj nast. token analizator syntaktyczny drzewo rozbioru syntaktycznego analizator semantyczny kod pośredni tablica symboli Analizator Syntaktyczny (parser) ma za zadanie dokonać rozbioru syntaktycznego tekstu źródłowego analizowanego programu. Analizator leksykalny (skaner) dokonuje zamiany tekstu wejściowego na ciąg tokenów. Nie zostaje przy tym zmieniona struktura syntaktyczna programu wejściowego. Parser pracuje na podstawie gramatyki syntaktycznej G s, która na ogół jest gramatyką bezkontekstową. Od parsera oczekujemy odtworzenia wywodu i jakiegoś zanotowania tego wywodu. Najczęściej stosowanym sposobem notowania wyprowadzenia jest podanie ciągu numerów produkcji. Niekiedy równolegle z analizą syntaktyczną parser wykonuje pewne akcje semantyczne jak np. generowanie kodu pośredniego w postaci ONP. tetrad (n-tek), itd. Działanie parsera: * We: ω S (ω jest słowem nad alfabetem S, S jest zbiorem tokenów) Wy: π=p 1...p i...p n (p i numer produkcji) lub err
Przykład: G S =<{,,},{id,+,*,(,)},p,> P: (1) + (2) (3) * (4) (5) () (6) id * Analizowane słowo: (id+id)*id Do zbudowania drzewa rozbioru syntakt. (podstawa konstrukcji kodu wynikowego) niezbędna jest wiadomość, że ciąg numerów produkcji związany jest z wywodem (tutaj) lewostronnym ( id + ) id (2) (3) (4) (5) (1) (2) (4) (6) (4) (6) (6) * * ()* (+)* (+)* (+)* (id+)* (id+)* (id+id)* (id+id)* id acc ciąg numerów produkcji jest wyjściem z parsera
Definicja zbiorów IRS i OLLOW G = < N,, P, Z > G BK IRS K (α) = {x * : (α*xβ x =k) (α*x x <k); α,β (N ) * } Zbiór IRS K (α) jest zbiorem wszystkich terminalnych przedrostków długości k (lub mniejszej, jeżeli z α wyprowadza się łańcuch terminalny krótszy niż k) łańcuchów, które mogą być wyprowadzalne z α OLLOW K (β) = {x * : (Z*αβγ x IRS K (γ); α,β,γ (N ) * } Zbiór OLLOW K (β) zawiera terminalne łańcuchy o długości k (lub mniejszej patrz powyżej), które mogą pojawić się w wyprowadzeniach jako następniki β. Uwaga: jeśli OLLOW K (β) zawiera x: x <k, to wówczas zastępujemy x przez x$, gdzie $ prawy ogranicznik słowa (dla wygody). Wyznaczanie IRS 1 dla gramatyki G=<N,, P, Z> G BK Wyznaczanie IRS 1 (x) dla x (N ) (1) if x then IRS 1 (x):={x}; (2) if (x ε) P then IRS 1 (x):= IRS 1 (x) {ε}; (3) if x N and (x y 1 y 2...y k ) P then begin IRS 1 (x):=irs 1 (x) (IRS 1 (y 1 )\{ε}) for i:=2 to k do if (ε IRS 1 (y 1 ))and...and(ε IRS 1 (y i-1 )) then IRS 1 (x):=irs 1 (x) (IRS 1 (y i )\{ε}); if (ε IRS 1 (y 1 ))and...and(ε IRS 1 (y k )) then IRS 1 (x):=irs 1 (x) {ε} end; Aby wyznaczyć IRS 1 (x) dla wszystkich X (N ) należy stosować reguły (1), (2) i (3) tak długo, aż nic nowego nie da się dołączyć do któregokolwiek zbioru IRS 1 (x). Wyznaczanie IRS 1 (x 1 x 2...x n ) IRS 1 (x 1 x 2 x n ):=IRS 1 (x 1 )\{ε} for i:=2 to k do if (ε IRS 1 (x 1 ))and and(ε IRS 1 (x i-1 ))then IRS 1 (x 1 x n ):= IRS 1 (x 1 x n ) (IRS 1 (x i )\{ε}); if (ε IRS 1 (x 1 ))and and(ε IRS 1 (x n )) then IRS 1 (x 1 x n ):= IRS 1 (x 1 x n ) {ε};
Wyznaczenie OLLOW 1 dla wszystkich A N Stosować poniższe reguły (1), (2), (3) dopóty nic nowego nie może już zostać dodane do żadnego zbioru OLLOW 1. (1) OLLOW 1 (Z):=OLLOW 1 (Z) {$} (2) if (A αbβ) P then OLLOW 1 (B):=OLLOW 1 (B) (IRS 1 (β)\{ε}) (3) if (A αb) P or ((A αbβ) P and (ε IRS 1 (β))) then OLLOW 1 (B):=OLLOW 1 (B) OLLOW 1 (A); Przykład: (1) ` (2) +` ε (3) ` (4) ` *` ε (5) () id IRS 1 [+]={+} IRS 1 [*]={*} IRS 1 [(]={(} IRS 1 [)]={)} IRS 1 [id]={id} IRS 1 ()={(, id} IRS 1 (`)={ε,+} IRS 1 ()={(, id } IRS 1 (`)={ε,*} IRS 1 ()={(, id} OLLOW 1 ()={$,)} OLLOW 1 (`)={$,)} OLLOW 1 ()={+,),$} OLLOW 1 (`)={+,),$} OLLOW 1 ()={+,*,),$}
Generacja parsera: We: gramatyka syntaktyczna G S =<N S, S,P S,Z S > (oznaczana dalej G=<N,,P,Z>) Wy: algorytm parsera Algorytm parsera musi być efektywny. Uniwersalny algorytm arley a ma dla gramatyki bezkontekstowej (bez ograniczeń na jednoznaczność) złożoność obliczeniową: czasową O(n 3 ), pamięciową O(n 2 ), zaś dla gramatyk jednoznacznych złożoność czasowa wynosi O(n 2 ). Są to złożoności niedopuszczalne w praktyce. Musimy się posługiwać algorytmami prostszymi, ale wiąże się to z zawężeniem klasy gramatyk. Dla gramatyk LL(k) i LR(k) złożoność obliczeniowa algorytmu parsera wynosi: czasowa O(n) oraz pamięciowa O(n). Dalej rozpatrywane będą algorytmy parsingu dla gramatyk LL(k) i LR(k) przeglądanie wejścia od lewej do prawej LR(k) odtwarzanie wywodu prawostronnego metodą bottom-up LL(k) liczba symboli, które trzeba podglądać na wejściu, aby jednoznacznie określić numer produkcji, którą należy zastosować przeglądanie wejścia od lewej do prawej odtwarzanie wywodu lewostronnego metodą top-down liczba symboli, które trzeba podglądać na wejściu, aby jednoznacznie określić numer produkcji, którą należy zastosować
Na ogół stosowane są gramatyki: LL(1) LR(1) oraz jej podklasy SLR(1) oraz LALR(1), dające możliwość znacznego uproszczenia algorytmu parsingu przy niewielkim ograniczeniu możliwości gramatyki.