• Nie Znaleziono Wyników

Gramatyki LL i SLR

N/A
N/A
Protected

Academic year: 2021

Share "Gramatyki LL i SLR"

Copied!
17
0
0

Pełen tekst

(1)

Gramatyki LL i SLR

Marcin Orchel

1 Wstęp

Przypomnienie: wyprowadzenie lewostronne to takie wyprowadzenie, w którym za każ- dym razem zastępujemy pierwszy od lewej nieterminal.

Przykład 1. Gramatyka

1. S → F 2. S → aSbF c 3. F → d

Wyprowadzenie lewostronne słowa: adbdc.

S =

2 aSbF c =

1 aFbF c =

3 adbFc =

3 adbdc . (1)

1.1 Algorytm LL(1)

Algorytm LL(1) służy do sprawdzenia czy dane słowo należy do języka generowanego przez gramatykę LL(1). Algorytm działa w czasie liniowym. Gramatyka LL to taka, w której czytamy słowo podczas sprawdzania od lewej do prawej, oraz taka, że możemy podjąć podczas wyprowadzenia lewostronnego jednoznaczną decyzję, którą produkcję wybrać. Gramatyka LL(1) to gramatyka LL taka, że podczas podejmowania decyzji, którą produkcję wybrać analizujemy dokładnie jeden symbol słowa. Algorytm LL(1) będzie brany pod uwagę tylko dla gramatyk bezkontekstowych, można zauważyć, że z tego względu, jeśli istnieje dowolne wyprowadzenie danego słowa, to zawsze istnieje wyprowadzenie lewostronne tego słowa. Przykład gramatyki, która nie jest typu LL(1).

1. S → bLc|a 2. L → LdS|S

Przykładowe wyprowadzenie dla słowa baadac

S =⇒ bLc . (2)

(2)

Po pierwszym kroku mamy zgodność symbolu pierwszego słowa, następnie mamy znaleźć taki ciąg produkcji, aby otrzymać zgodność dla drugiego symbolu słowa. Tutaj są dwie możliwości:

S =⇒ bLc =⇒ bSc =⇒ bac (3)

oraz

S =⇒ bLc =⇒ bLdSc =⇒ bSdSc =⇒ baSdSc . (4) Widzimy, że w obu wyprowadzeniach otrzymaliśmy zgodność drugiego symbolu słowa (a). Dlatego, że istnieje więcej niż jedno wyprowadzenie dla drugiego symbolu słowa, gra- matyka ta nie jest typu LL(1). Zauważmy ponadto, że słowo bac i tak byłoby odrzucone ze względu na brak zgodności dla trzeciego symbolu, ale gramatyka jest typu LL(1), a więc nie możemy korzystać w wyprowadzeniu z informacji o kolejnych symbolach słowa, a jedynie o aktualnie rozpatrywanym.

Przykład 2. Dla gramatyki 1. S → bLc|a

2. L → aM |S 3. M → ε

Przykładowe wyprowadzenia dla słowa bac:

S =⇒ bLc =⇒ baM c (5)

oraz

S =⇒ bLc =⇒ bSc =⇒ bac . (6)

Gramatyka nie jest typu LL(1), ponieważ otrzymaliśmy zgodność drugiego symbolu słowa dwoma różnymi lewostronnymi wyprowadzeniami.

1.2 Definicja zbiorów F IRST1 i F OLLOW1

Zbiory te będą zdefiniowane dla gramatyk bezkontekstowych. α jest pojedynczym sym- bolem terminalnym lub nieterminalnym. Zbiór F IRST1(α) jest zbiorem wszystkich ter- minalnych przedrostków długości 1 łańcuchów, które mogą być wyprowadzone z α oraz słowa pustego, jeśli może być wyprowadzone z α. Czyli dla dowolnego łańcucha w, ter- minal a jest w F IRST (w), gdy w =

av, gdzie v to ciąg terminali i nieterminali. Jeśli w =

ε, to ε ∈ F IRST (w). Zbiór F OLLOW1(α) jest zbiorem wszystkich terminalnych łańcuchów długości 1, które występują tuż za symbolem nieterminalnym A w dowolnych wyprowadzeniach. Inaczej, F OLLOW (A) jest to zbiór terminali a takich, że istnieje wyprowadzenie w postaci S =

αAaβ dla pewnych α i β. Dodatkowo, jeśli A jest skraj- nym prawym symbolem w pewnej formie zdaniowej, wówczas F OLLOW (A) zawiera także $. A więc F OLLOW jest zdefiniowany dla nieterminali. Wprowadzamy zasadę, że każdy łańcuch jest zakończony symbolem $. Algorytmy wyznaczenia zbiorów F IRST i F OLLOW . Wyznaczenie zbioru F IRST (α):

(3)

1. Jeśli α jest terminalem to F IRST1(α) = {α}.

2. Jeśli istnieje produkcja postaci: α → ε to F IRST1(α) = F IRST1(α) ∪ {ε}

3. Jeśli α jest nieterminalem to dla każdej produkcji postaci α → y1y2. . . yk:

F IRST1(α) = F IRST1(α) ∪ F IRST1(y1) (7) Od i = 2 do k, jeśli ε ∈ F IRST1(y1) ∧ . . . ε ∈ F IRST1(yi−1) to

F IRST1(α) = F IRST1(α) ∪ F IRST1(yi) . (8) Jeśli ε ∈ F IRST1(y1) ∧ . . . ε ∈ F IRST1(yk) to

F IRST1(α) = F IRST1(α) ∪ {ε} . (9) Wyznaczenie F IRST1(x1x2. . . xn), gdzie xisą terminalami lub nieterminalami przebiega następująco:

1. F IRST1(x1x2. . . xn) = F IRST1(x1)\{ε}.

2. Od i = 2 do n, jeśli ε ∈ F IRST1(x1) ∧ . . . ε ∈ F IRST1(xi−1) to

F IRST1(x1x2. . . xn) = F IRST1(x1x2. . . xn) ∪ F IRST1(xi)\{ε} . (10) Jeśli ε ∈ F IRST1(x1) ∧ . . . ε ∈ F IRST1(xn) to

F IRST1(x1x2. . . xn) = F IRST1(x1x2. . . xn) ∪ {ε} . (11) Alternatywna równoważna definicja

1. F IRST (ε) = {ε}.

2. Dla terminala a, F IRST (a) = {a}.

3. Dla nieterminala A, F IRST (A) jest unią wszystkich F IRST (w), gdzie w jest prawą stroną jakiejś produkcji z A.

4. Dla Xi będącym nieterminalem lub terminalem mamy

F IRST (X1X2. . . XN) = F IRST (X1), jeśli nie możemy wyprowadzić ε z X1 (12) F IRST (X1X2. . . XN) = F IRST (X1)\{ε} ∪ F IRST (X2X3. . . XN), jeśli X1=⇒

ε (13) Wyznaczenie zbioru F OLLOW1(B), gdzie B jest nieterminalem, α, β dowolne łań- cuchy terminali i nieterminali:

1. Jeśli B = S to F OLLOW1(B) = F OLLOW1(B) ∪ $

(4)

Tabela 1: Tabela F IRST i F OLLOW

FIRST FOLLOW A {ε, a} {b, c}

B {b, c} {b}

S {a} {$}

2. Dla każdej produkcji postaci A → αBβ:

F OLLOW1(B) = F OLLOW1(B) ∪ F IRST1(β)\{ε} (14)

3. Dla każdej produkcji postaci A → αB lub (A → αBβ ∧ ε ∈ F IRST1(β)):

F OLLOW1(B) = F OLLOW1(B) ∪ F OLLOW1(A) (15) Zauważmy, że zbiór F OLLOW nie zawiera symbolu ε.

Przykład 3. Dla gramatyki

1. S → aABb 2. A → aAc 3. A → ε 4. B → bB 5. B → c

Wyznaczmy przykładowo na podstawie bazowej definicji: F IRST1(A) - w związku z pro- dukcją 3 F IRST1(A) = {ε}, a w związku z produkcją 2 F IRST1(A) = a, ε. W związku z regułą 1 F IRST1(aAc) = a. W związku z produkcją 4 F IRST1(B) = b. W związku z produkcją 5 F IRST1(B) = {b, c}. W związku z produkcją 1 F OLLOW1(A) = b, c, produkcja 2 nie wnosi nic nowego. Dla potrzeb algorytmu tworzymy tabelkę ze zbiorami F IRST i F OLLOW dla każdego nieterminala.

1.3 Konstrukcja tabeli parsowania algorytmu LL(1)

Algorytm rozpoznawania czy dane słowo należy do języka i do wyznaczenia wyprowa- dzenia wykorzystuje następującą tabelę: wiersze odpowiadają nieterminalom, kolumny odpowiadają terminalom i symbolowi $, w komórkach tabeli znajdują się prawe strony odpowiednich produkcji. Algorytm wypełniania tabeli:

1. Dla każdej produkcji wyznacz F IRST (p), gdzie p to prawa strona produkcji. Zapisz prawą stronę produkcji w komórkach [A, a] dla każdego a, gdzie A to lewa strona produkcji, a to terminal znajdujący się w zbiorze F IRST (P ).

(5)

Tabela 2: Tabela parsowania.

a b c $

A aAc ε ε

B bB c

S aABb

2. Dla każdej produkcji, jeśli ε ∈ F IRST (p) to zapisz prawą stronę produkcji w komórkach [A, a] dla każdego a, gdzie A to lewa strona produkcji, a to terminal lub symbol $ znajdujący się w zbiorze F OLLOW (A). Jeśli w danej komórce należy wpisać prawą stronę więcej niż jednej produkcji, to gramatyka nie jest typu LL(1).

Przykład 4. Przykład dla gramatyki z poprzedniego zadania. Dla produkcji 1 F IRST (aABb) wynosi {a}, a więc do komórki [S, a] wpisujemy aABb. Dla produkcji 2 F IRST (aAc) wynosi {a}, a więc do komórki [A, a] wpisujemy aAc. Dla produkcji 3 F IRST (ε) wynosi ε. Dla produkcji 4 F IRST (bB) wynosi {b}, a więc do komórki [B, b] wpisujemy bB. Dla produkcji 5 F IRST (c) wynosi {c}, a więc do komórki [B, c] wpisujemy c. Dla produkcji 3 w F IRST (ε) znajduje się ε. A więc zapisujemy do komórek [A, b] i [A, c] ε.

1.4 Algorytm sprawdzania konkretnych słów

Mamy pewne słowo do sprawdzenia, czy należy do gramatyki. W każdym krok aktu- alizujemy bufor wejściowy, w którym znajduje się słowo oraz znak $ na końcu, oraz aktualizujemy stos, na którym na początku znajduje się symbol S. Algorytm sprawdze- nia polega na zdejmowaniu ze stosu kolejnych symboli. Jeśli jest to symbol terminalny, to sprawdzamy, czy jest identyczny z pierwszym symbolem z buforu wejściowego. Jeśli jest, to usuwamy go również z bufora wejściowego. Jeśli jest to symbol nieterminalny to zastępujemy go odpowiednią produkcją i umieszczamy prawą stronę produkcji na stosie.

Jeśli w buforze wejściowym pozostał tylko symbol $, wtedy możemy zastosować również produkcje z kolumny $. Algorytm zatrzymuje się w następujących stanach:

• w buforze wejściowym pozostał $, a stos jest pusty, wtedy i tylko wtedy, gdy słowo należy do danego języka

• terminal na stosie oraz w buforze wejściowym nie są zgodne, lub nie można już zastosować żadnych produkcji, wtedy i tylko wtedy, gdy słowo nie należy do danego języka

Przykład 5. Przykładowo dla podanej wcześniej gramatyki sprawdźmy słowo: aacbbcb.

1. Bufor wejściowy: aacbbcb$. Stos: S

2. Zdejmujemy S i zastępujemy łańcuchem znajdującym się w komórce [S, a]. Bufor wejściowy aacbbcb$. Stos aABb.

3. Zdejmujemy a. W słowie wejściowym również jest a, a więc bufor wejściowy acbbcb$.

Stos ABb.

(6)

4. Zdejmujemy A i zastępujemy komórką [A, a]. Bufor wejściowy acbbcb$. Stos aAcBb.

5. Zdejmujemy a. W słowie wejściowym również jest a, a więc: bufor wejściowy cbbcb$. Stos AcBb.

6. Zdejmujemy A i zastępujemy komórką [A, c]. Bufor wejściowy cbbcb$. Stos cBb.

7. Zdejmujemy c. Bufor wejściowy bbcb$. Stos Bb.

8. Zdejmujemy B i zastępujemy komórką [B, b]. Bufor wejściowy bbcb$. Stos bBb.

9. Zdejmujemy b. Bufor wejściowy bcb$. Stos Bb.

10. Zdejmujemy B i zastępujemy (B, b). Bufor wejściowy bcb$. Stos bBb.

11. Zdejmujemy b. Bufor wejściowy cb$. Stos Bb.

12. Zdejmujemy B i zastępujemy (B, c). Bufor wejściowy cb$. Stos cb.

13. Zdejmujemy c. Bufor wejściowy b$. Stos b.

14. Zdejmujemy b. Bufor wejściowy $. Stos pusty. Jako że stos jest pusty, a wejście równe $, słowo należy do języka. Wyprowadzenie tego słowa wygląda następująco:

S =⇒ aABb =⇒ aaAcBb =⇒ aacBb =⇒ aacbBb =⇒ aacbbBb =⇒ aacbbcb (16) Inne słowo: cbcbcb.

1. Bufor wejściowy cbcbcb$. Stos S.

2. Zdejmujemy S, zastępujemy łańcuchem znajdującym się w komórce [S, a]. Bufor wejściowy: cbcbcb$. Stos: aABb. Zdejmujemy ze stosu symbol a, pierwszy symbol na wejściu jest inny, a mianowicie c, a więc dane słowo nie należy do tego języka.

Inne słowo: aab.

1. Bufor wejściowy aba. Stos S.

2. Zdejmujemy S, zastępujemy łańcuchem znajdującym się w komórce [S, a]. Bufor wejściowy aba$. Stos aABb.

3. Zdejmujemy a. Bufor wejściowy ba$. Stos ABb.

4. Zdejmujemy A i zastępujemy b. Bufor wejściowy ba$. Stos bBb.

5. Zdejmujemy b. Bufor wejściowy a$. Stos Bb.

6. Zdejmujemy B, niestety nie jest możliwe zastąpienie B, ponieważ komórka [B, a]

jest pusta, a więc słowo nie należy do tego języka.

(7)

2 Algorytm SLR

Algorytm SLR(1) służy do sprawdzenia czy dane słowo należy do języka generowanego przez gramatykę SLR(1). Algorytm SLR(1) jest typu bottom-up. Będziemy rozpatry- wać wyłącznie gramatyki SLR(1) będące gramatykami bezkontekstowymi. Gramatyka SLR(1) jest szczególnym typem gramatyki LR(1). Gramatyka LR(1) jest podobna do gramatyki LL(1). Jedyną różnicą jest to, że stosujemy wyprowadzenie prawostronne, a więc za każdym razem zastępujemy pierwszy od prawej nieterminal. Gramatyka SLR(1) jest to taka gramatyka LR(1) dla której istnieje algorytm SLR(1) rozpoznający słowa tego języka. Porównanie wyprowadzenia lewostronnego i prawostronnego:

Przykład 6. Dla gramatyki

1. S → aABCb 2. A → a 3. B → b 4. C → c

wyprowadzamy słowo: aabcb.

1. S =⇒ aABCb =⇒ aaBCb =⇒ aabCb =⇒ aabcb 2. S =⇒ aABCb =⇒ aABcb =⇒ aAbcb =⇒ aabcb

Pierwsze wyprowadzenie jest lewostronne, drugie prawostronne.

Algorytm LL jest typu top-down, algorytm SLR jest typu bottom-up. Algorytm LL działa w ten sposób, że zaczynamy od symbolu startowego S i stosujemy kolejne produkcje tak aby uzyskiwać zgodność na kolejnych symbolach tego słowa począwszy od najbardziej lewego. Algorytm SLR stosuje wyprowadzenie prawostronne, a więc nie może być typu top-down, ponieważ wtenczas nie moglibyśmy walidować kolejnych symboli słowa począwszy od najbardziej lewego. W algorytmie SLR wyprowadzenie jest typu bottom-up, a więc wygląda następująco:

aabcb ⇐= aAbcb ⇐= aABcb ⇐= aABCb ⇐= S . (17) Jest to algorytm typu bottom-up, a więc w każdym kroku stosujemy tzw. redukcję, to znaczy zastępujemy prawe części odpowiednich produkcji. W związku z tym, że jest to wyprowadzenie prawostronne (jeśli rozpatrujemy je od symbolu startowego S), to jeśli w danym łańcuchu występuje więcej niż jeden nieterminal to musiały być one wypro- wadzone w algorytmie SLR w kolejności od lewej do prawej. W wyprowadzeniu (17) dla łańcucha aABcb najpierw wyprowadzone było A, a później B. Rozpatrzmy bliżej to wyprowadzenie: Jeśli wprowadzimy pojęcie stosu to wyprowadzenie powyższe można zamodelować następująco:

(8)

• Bufor wejściowy: aabcb$. Stos: pusty.

• Bufor wejściowy: abcb$. Stos: a.

• Bufor wejściowy: bcb$. Stos: aa.

• Bufor wejściowy: bcb$. Stos: aA.

• Bufor wejściowy: cb$. Stos: aAb.

• Bufor wejściowy: cb$. Stos: aAB.

• Bufor wejściowy: b$. Stos: aABc.

• Bufor wejściowy: b$. Stos: aABC.

• Bufor wejściowy: $. Stos: aABCb.

• Bufor wejściowy: $. Stos: S.

W każdym kroku aktualny łańcuch wyprowadzenia to stos + bufor wejściowy. Podział na stos i bufor wejściowy jest po to, aby ułatwić wyprowadzenie prawostronne. Powyższy proces można przedstawić również następująco:

aabcb ⇐= aabcb ⇐= aabcb ⇐= aAbcb ⇐= aAbcb ⇐= aABcb ⇐= aABcb (18)

= aABCb ⇐= aABCb= S. (19) Jak widzimy w wyprowadzeniu są dwie możliwe operacje:

• przemieszczenie terminala z bufora wejściowego na stos,

• redukcja na stosie.

Po usunięciu z powyższego wyprowadzenia operacji przemieszczania terminala otrzymu- jemy właściwe wyprowadzenie. Algorytm SLR(1) pomaga stwierdzić jakie operacje na- leży zastosować w poszczególnych krokach wyprowadzenia. Schemat algorytmu SLR(1) wygląda następująco:

1. Dodajemy produkcje S0→ S do zbioru produkcji, symbol startowy jest S0. 2. Konstruujemy zbiory F IRST i F OLLOW podobnie jak w algorytmie LL(1).

3. Tworzymy deterministyczny automat skończony dla podanej gramatyki.

4. Tworzymy na jego podstawie tabelę algorytmu SLR(1).

5. Sprawdzamy czy podane słowo należy do języka.

(9)

q0 start

q1 a

b

a b

Rysunek 1: Przykładowy automat.

2.1 Automaty

Automaty to jeden ze sposobów reprezentacji języka formalnego. Automat składa się z

• zbioru stanów Q,

• alfabetu Σ,

• δ funkcja tranzycji:

δ : Q × Σ → Q (20)

• q0 - stan startowy, q0 ∈ Q

• F – zbiór stanów zawarty w Q, zwanych stanami akceptującymi

W przypadku, gdy Q jest skończony, automat nazywa się automatem skończonym. Zbiór wszystkich rozpoznawanych języków przez automat skończony jest dokładnie zbiorem języków generowanych przez gramatyki regularne. Deterministyczny automat skończony (DFA) to taki automat skończony, że dla każdej pary (stan, symbol wejściowy) jest co najwyżej jedna tranzycja. Deterministyczny, zupełny automat skończony (DFA) to taki automat skończony, że dla każdej pary (stan, symbol wejściowy) jest dokładnie jedna tranzycja. Niedeterministyczny automat skończony (NFA) to taki automat skończony, że dla pewnej pary (stan, symbol wejściowy) jest więcej niż jedna tranzycja. Niedetermi- nistyczny automat skończony z ε-tranzycjami (NFA-ε) to niedeterministyczny automat skończony albo automat skończony zawierający co najmniej jedną ε-tranzycję, czyli taką przez którą przechodzimy nie pobierając symbolu z wejścia. Można pokazać, że DFA i NFA są równoważne, to znaczy dla każdego automatu DFA, można skontruować automat NFA i odwrotnie. A więc zarówno automaty NFA jak i DFA rozpoznają języki regularne.

Można również wykazać, że NFA-ε jest równoważny DFA i NFA. Sposób sprawdzania czy dane słowo należy do języka generowanego przez automat Rys. 1:

W podanym automacie DFA przykładowo mamy do sprawdzenia słowo aab. Zaczyna- my od stanu q0. Bierzemy z bufora wejściowego pierwszy symbol, czyli a, przechodzimy do stanu q1. Później bierzemy kolejny symbol, również a. Przechodzimy ze stanu q1

(10)

q0 start

q1 q2

q3 q4

ε

a b

a b

ε b

a

b a

Rysunek 2: Przykładowy automat z ε-tranzycjami.

q0

start q1

q2 b a

b b

Rysunek 3: Przykładowy automat dla języka formalnego (21).

do stanu q0. Następnie bierzemy symbol b i pozostajemy w stanie q0. Wyczerpaliśmy wszystkie symbole z wejścia i jesteśmy ostatecznie w stanie q0. Stan q0 jest stanem ak- ceptującym, a więc słowo należy do tego języka. Można zauważyć, że podany automat akceptuje wszystkie słowa o parzystej liczbie symboli a. Jeśli skończymy sprawdzanie na stanie, który nie jest akceptujący to słowo nie należy do języka. Jeśli na pewnym etapie nie będzie istniała tranzycja dla kolejnego symbolu z wejścia to słowo również nie należy do języka. Podczas sprawdzania słowa można przechodzić przez stan akceptujący, słowo jest akceptowane jedynie, gdy automat jest w stanie akceptującym po przeczytaniu całe- go słowa. Przykład niedeterministycznego automatu skończonego z ε-tranzycjami Rys.2.

Powyższy automat akceptuje wszystkie słowa, które zawierają parzystą liczbę symboli a lub parzystą liczbę symboli b. Ze stanu q0 możemy przejść zarówno do stanu q1 jak i q3. Przykład konstrukcji automatu z podanego języka formalnego w postaci zbioru Rys.3

L = {ambn: m ≥ 0, n > 0, n jest nieparzyste} . (21) Cechy automatów. Często automaty wykorzystuje się do modelowania różnych procesów, a w szczególności wykorzystuje się następujące koncepcje:

• Podział procesu na stany, w których może się on znajdować.

(11)

q0

start b q1

a

Rysunek 4: Przykładowy automat.

q0

start a q1 q2

b

a

Rysunek 5: Przykładowy automat.

• Zdefiniowanie informacji, która powoduje odpalenie tranzycji i wyznaczenie nowego stanu. Nowy stan jest wyznaczany na podstawie aktualnego stanu i otrzymanej informacji.

• Proces znajduje się na początku w zdefiniowanym stanie początkowym.

Łączenie automatów. Jeśli mamy dane dwa automaty i chcemy stworzyć język, który jest unią zbioru słów generowanych przez obydwa automaty, możemy to zrobić tworząc nowy stan, który będzie stanem startowym i prowadząc ε-tranzycje do poprzednich stanów startowych poszczególnych automatów. Przykładowo mamy dane dwa automaty Rys. 4, Rys.5. oraz Po ich połączeniu otrzymamy automat Rys.6.

2.2 Sposób tworzenia deterministycznego automatu skończonego Automat skończony to pewien model zachowania składający się ze skończonej liczby sta- nów, tranzycji pomiędzy stanami i z akcji. Deterministyczny automat skończony to taki automat skończony, że dla każdej pary (stan, symbol wejściowy) jest dokładnie jedna

q5

start

q0 q1

q2 q3 q4

ε

ε

b a

a

b

a

Rysunek 6: Przykładowy automat.

(12)

tranzycja do następnego stanu. Oznaczona produkcja to taka, w której pojawia się krop- ka po prawej stronie produkcji. Kropka ta oznacza, że wszystkie symbole po jej lewej zostały umieszczone na stosie, a po prawej jeszcze nie. W każdym zbiorze oznaczonych produkcji jest jedna wyróżniona główna produkcja oznaczona. Konstrukcja stanów. Za- czynamy od stanu q0, dla którego główną produkcją oznaczoną jest S0→ ·S. Od każdego stanu przechodzimy do kolejnego stanu lub tego samego wybierając jedną z produkcji oznaczonych, ale taką, że kropka nie jest na końcu produkcji. Tranzycji odpowiada sym- bol występujący tuż po kropce. W nowo powstałym stanie główną produkcją oznaczoną będzie ta, z której powstał ten stan, ale z przesuniętą kropką o jeden symbol w prawo.

Dla każdego stanu tuż po dodaniu głównej produkcji oznaczonej dodajemy pozostałe produkcje oznaczone postaci w → ·X. Są to wszystkie produkcje z w po lewej stronie wraz z dodaną kropką na początku, gdzie w to pierwszy po kropce nieterminal wystę- pujący w głównej produkcji oznaczonej. Stany, w których występuje co najmniej jedna oznaczona produkcja, taka, że kropka jest na końcu, są stanami końcowymi i są oznacza- ne podwójnym okręgiem. Ponieważ automat ma być deterministyczny, to w przypadku kilku produkcji z tym samym symbolem po kropce, tworzony jest jeden nowy stan.

2.3 Intuicyjne rozumienie konstrukcji automatu

Konstrukcja automatu to przejście przez możliwe wyprowadzenia, ale z drugiej strony, a więc zaczynając od ostatniej produkcji jaką będziemy musieli zastosować dla naszego słowa w wyprowadzeniu SLR(1). Po prawej stronie kropki produkcji oznaczonej znajduje się łańcuch, który chcemy wyprowadzić z aktualnego bufora wejściowego, a po lewej stronie znajduje się stos, w którym jest zapisany łańcuch, dla którego chcemy zastosować redukcje. Tranzycja z symbolem terminalnym oznacza umieszczenie na stosie symbolu terminalnego w celu zastosowania redukcji, ale równocześnie symbol terminalny jest zdejmowany z bufora wejściowego. Tranzycja z symbolem nieterminalnym odpowiada przetworzeniu symbolu nieterminalnego, który pojawił się wcześniej na stosie po redukcji.

Zaczynamy konstrukcję od stanu q0, dodajemy do niego główną produkcję oznaczoną:

S0 → ·S.

2.4 Tworzenie tabeli algorytmu SLR(1)

Na podstawie stworzonego automatu konstruujemy tabelę algorytmu SLR(1) Tabela4.

Tabela ma wiersze, które odpowiadają wszystkim stanom automatu, a kolumny od- powiadają wszystkim terminalom, nieterminalom oraz znakowi $. Tabelę wypełniamy począwszy od wiersza 1. Dla każdej tranzycji konstruujemy odpowiedni wpis w tabeli.

Jeśli w tranzycji jest nieterminal to wpisujemy: (w, N ) := i, gdzie i to stan do którego dochodzimy za pomocą tej tranzycji. Jeśli w tranzycji jest terminal to wpisujemy: (w, t) := si, gdzie s oznacza operację shif t, a i to stan, do którego dochodzimy za pomocą tej tranzycji. Jeśli analizując tranzycję trzeba będzie wypełnić komórkę [i, S] to wpisu- jemy do niej acc, co oznacza accepted. Jeśli analizowany stan jest stanem końcowym to wpisujemy do komórek [i, a] := rj, gdzie i to analizowany stan, a to każdy nieterminal ze zbioru F OLLOW (A), a j to numer produkcji. Jest to operacja redukcji.

(13)

Tabela 3: Tabela FIRST i FOLLOW dla SLR(1)

FIRST FOLLOW A {ε, a} {b, c}

B {b, c} {b}

S {a} {$}

2.5 Sprawdzanie czy dane słowo należy do języka gramatyki SLR(1) Sprawdzanie czy dane słowo należy do języka wygląda następująco: w każdym kroku mamy bufor wejściowy w którym na początku znajduje się analizowane słowo zakończo- ne $, oraz mamy stos na którym na początku znajduje się stan 0. Wybieramy produkcję z tabeli algorytmu SLR(1) z komórki [i, a], gdzie i to aktualny stan na stosie, a a to ter- minal na początku bufora wejściowego. Terminal ten jest usuwany z bufora wejściowego, a na stos jest dopisywany ten terminal wraz z nowym stanem. Gdy pojawi się w komórce redukcja, wtedy usuwamy ze stosu odpowiednią część zgodnie z redukcją i sprawdzamy następnie komórkę [i, A], gdzie i to aktualny stan a A to lewa strona produkcji związanej z redukcją. Jeśli otrzymamy na samym końcu accepted oraz w buforze wejściowym tylko

$ to dane słowo należy do rozpatrywanego języka.

Przykład 7. Dla gramatyki

• S → aABb

• A → aAc

• A → ε

• B → bB

• B → c

Na początku dodajemy produkcje S0 → S, od tej pory symbolem startowym jest S0.

• S0 → S

• S → aABb

• A → aAc

• A → ε

• B → bB

• B → c

Podobnie jak dla parsera LL(1) wyznaczamy tabelę zbiorów FIRST i FOLLOW Tabela3.

(14)

Rysunek 7: Automat dla algorytmu SLR.

Następnie budujemy deterministyczny automat skończony, który ma postać końcową jak na Rys. 7

Przykładowe kroki w konstrukcji automatu: zaczynamy od stanu q0. Należy do niego główna produkcja oznaczona S0 → ·S oraz wszystkie produkcje oznaczone, które po le- wej stronie mają symbol występujący po kropce czyli S i mają na początku kropkę. Jest taka jedna produkcja oznaczona: S → aABb. Rozważając produkcję oznaczoną S0 → ·S konstruujemy tranzycję dla S, przechodzimy do nowego stanu q1, do którego dołączamy tę produkcję tylko z przesuniętą kropką o jeden symbol S0 → S·. Kropka jest na końcu, więc jest to stan końcowy. Pójdźmy następnie inną drogą i wybierzmy w stanie q0 ter- minal a. Tworzymy stan q2, w którym zapisujemy produkcję oznaczoną S → a · ABb, oraz wszystkie produkcje oznaczone z A po lewej stronie i kropką na początku prawej strony: A → aAc i A → ·. Jest to stan końcowy, ponieważ znajduje się w nim produkcja oznaczona z kropką na końcu. W stanie q2 na stosie znajduje się symbol a. Następnie możemy wybrać terminal a z produkcji oznaczonej A → ·aAc, wtenczas otrzymamy nowy stan q4 z produkcjami A → a · Ac oraz A → ·aAc, A → ·, itd. Intuicyjnie zaczynamy od stanu q0 i głównej produkcji oznaczonej S0 → ·S. Oznacza ona, że aktualne słowo z bufora wejściowego chcemy wyprowadzić z terminala S, stos jest pusty. W związku z tym umieszczamy S na stosie i przechodzimy do stanu q1, w którym główną produkcją jest S0 → S·. W tym stanie mamy na stosie S, i nie ma potrzeby porównania z buforem wej- ściowym. Jeśli w tym stanie bufor wejściowy jest pusty, to otrzymaliśmy stan accepted, który kończy wyprowadzenie. Zamiast do stanu q1 możemy również przejść do stanu q2, ponieważ S może zostać zastąpione produkcją 2. Wtedy umieszczamy na stosie terminal

(15)

Tabela 4: Tabela parsowania algorytmu SLR(1).

a b c $ A B S

0 s2 1

1 acc

2 s4 r3 r3 3

3 s6 s7 5

4 s4 r3 r3 8

5 s9

6 s6 s7 10

7 r5

8 s11

9 r1

10 r4

11 r2 r2

a, równocześnie zdejmując go z bufora wejściowego, itd.

Następnie konstruujemy tabelę algorytmu SLR(1), która wygląda następująco:

Przykładowe wyprowadzenie. Zaczynamy od stanu q0. Gdy zastosujemy tranzycję S idziemy do stanu q1, a więc wpisujemy w [0, S] = 1. Gdy wybierzemy tranzycję a, idziemy do stanu q2, a więc wpisujemy s2. q1 to stan końcowy do którego prowadzi tranzycja S, a więc wpisujemy w nim acc. Następnie analizujemy stan q2. Jeśli w stanie q2 wybierzemy a, to idziemy do stanu q4, a więc [2, a] = s4. Jeśli natomiast wybierzemy A to idziemy do stanu 3, [2, A] = 3. Jest to stan końcowy ze względu na produkcję A → ·, a zatem sprawdzamy jaki jest zbiór F OLLOW (A), i wpisujemy [2, b] = r3, [2, c] = r4, itd.

Sprawdźmy słowo aacbbcb: Góra stosu znajduje się na początku. Bufor wejściowy aacbbcb$. Stos: 0. Sprawdzamy komórkę tabeli [0, a]. Jest w niej s2, a zatem, s ozna- cza, że a zdejmujemy z bufora wejściowego, kładziemy na stos, i kładziemy 2. Bufor wejściowy acbbcb$. Stos: 2a0. Sprawdzamy komórkę [2, a]. Jest w niej s4 a zatem bufor wejściowy cbbcb$. Stos 4a2a0. Sprawdzamy komórkę [4, c]. Jest w niej r3, a zatem stosu- jemy produkcję nr. 4 dla stosu począwszy od lewej, w produkcji mamy produkcję pustą, a więc bufor wejściowy cbbcb$. Stos A4a2a0. Następnie sprawdzamy komórkę [4, A] w której jest 8. A więc kładziemy 8 na stos, bufor wejściowy cbbcb$. Stos 8A4a2a0. I dalej analogicznie sprawdzamy komórkę [8, c] w której jest s11. A więc bufor wejściowy bbcb$.

Stos 11c8A4a2a0. Dalej sprawdzamy komórkę [11, b] w której jest r2, a więc redukujemy stos za pomocą produkcji nr. 2 bufor wejściowy bbcb$. Stos A2a0. Następnie sprawdzamy komórkę [2, A], w której jest 3, a więc kładziemy 3 na stos bufor wejściowy bbcb$. Stos 3A2a0, itd.

Odtworzenie stosowanych produkcji: zdjęcie ze stosu terminala oznacza przesunięcie w łańcuchu aktualnego wskaźnika. Wyprowadzenie pośrednie wygląda następująco:

aacbbcb ⇐= aacbbcb ⇐= aacbbcb ⇐= aaAcbbcb ⇐= aaAcbbcb ⇐= abbcb ⇐= . . . (22)

(16)

Po lewej stronie wskaźnika jest stos, a po prawej stronie wskaźnika jest bufor wejścio- wy. Końcowe wyprowadzenie otrzymamy usuwając z niego wyprowadzenia w których przesuwamy tylko wskaźnik:

aacbbcb ⇐= aaAcbbcb ⇐= abbcb ⇐= . . . . (23)

3 Zadania

3.1 Zadania podstawowe

1. Wykonać algorytm LL(1) dla gramatyki:

(a) S → aDS (b) S → b

(c) D → a (d) D → bSD

Sprawdzić słowa: aaab, oraz aab. Czy powyższa gramatyki jest typu LL(1)?

2. Wykonać algorytm LL(1) dla gramatyki:

(a) S → aB (b) A → bC (c) B → A (d) B → ε

(e) C → c (f) C → d

Sprawdzić słowo abc. Czy powyższa gramatyki jest typu LL(1)?

3. Sprawdzić na podstawie tabeli parsowania czy poniższa gramatyka jest typu LL(1).

(a) S → DaS (b) S → b

(c) D → a (d) D → bSD

4. Skontruować algorytm SLR(1) dla następującej gramatyki:

(a) S → SaT (b) S → T

(c) T → T bF (d) T → F

(17)

(e) F → cF d (f) F → e

Czy podana gramatyka jest typu SLR(1)? Wykonać parsowanie dla słowa: eaebe.

Na podstawie parsowania zapisać wyprowadzenie dla tego słowa.

5. Skontruować algorytm SLR(1) dla następującej gramatyki:

(a) S → SaS (b) S → SbS (c) S → cSd (d) S → e

Czy podana gramatyka jest typu SLR(1)? Wykonać parsowanie dla słowa: eaebe.

Na podstawie parsowania zapisać wyprowadzenie dla tego słowa.

Literatura

[1] U. J. D. Hopcroft John E., Rajeev Motwani, Wprowadzenie do teorii automatów, języków i obliczeń. Wydawnictwo Naukowe PWN, 2012.

Cytaty

Powiązane dokumenty

Żeby sprawdzić, czy słowo jest postaci ww R w można policzyć jego długość (musi to być liczba postaci 3k) a następnie użyć 3 liczników zmieniających się odpowiednio od 1 do

Powyższa punktacja zakłada, że wynik będzie podany w postaci uproszczonej - za po- danie wyniku w postaci rażąco nieuproszczonej, stracisz 0.2 punktu.. Przypominam, że N

Wiele osób jednak wykonuje dobrze pierwszy krok, a potem ginie w rachunkach. Dlatego to poćwiczymy prze kolejne

Proces Markowa jest ciągły, jeśli prawdopodobieństwo oddalenia się od punktu początkowego na skończoną odległość nie rośnie zbyt szybko w czasie (czyli w przebiegu czasowym

Der Tugendbegriff, der hier verwendet wird, steht nicht nur im Zusammenhang mit Kants Morallehre, sondern auch mit der christlichen Religiosität, in der die Moral, die Näch-

Therefore, this study shows that if straight instruments in a LESS configuration are used for a (surgical) task that require collaboration between two

Opracowanie, dotyczące charakterystyki kodu politycznego oraz jego wewnętrznego zróżnicowania, a także relacji między językiem ogólnym a językiem polityki,

Dla gramatyki bezkontekstowej w postaci normalnej Chomsky’ego algorytm CYK rozstrzyga, czy dane słowo należy do języka generowanego przez tę gramatykę.. Dla każdej