Zakładamy że symbolami gramatyki są pojedyńcze znaki. Uzupełnić poniższą gramatykę regułami tak by rozpoznawała zwykłe liczby całkowite w notacji dziesiętnej, liczby całkowite w notacji szesnastkowej, tak jak w C: `0x1ab' i liczby zmiennopozycyjne (te muszą zawierać kropkę dziesiętną, np: `.001', `100.' `3.1415').
liczba : liczba_zmiennopozycyjna
| liczba_dziesietna
| liczba_szesnastkowa
;
Uwaga: Zakładamy że liczby dziesiętne i liczby zmiennopozycyjne
nie zawierają zer na początku.
Symbolami rachunku zdań są zmienne (wszystkie zmienne są reprezentowane przez pojedynczy symbol), negacja, iloczyn, suma, implikacja i nawiasy. Negacja ma najwyższy priorytet, następnie iloczyn, potem suma. Implikacja ma najniższy priorytet. Iloczyn i suma są łaczne. Dla uniknięcia nieporozumień przyjmiemy że implikacja jest niełączliwa (tzn. wymaga jawnych nawiasów). Napisać gramatykę akceptującą poprawne składniowo zdania rachunku zdań (nie interesuje nas w tej chwili ich wartość logiczna).
Automat z stosem ma taśmę wejściową na której w danym momencie widzi jeden znak i po której przesuwa się tylko do przodu. Ma też stos na którym zapamiętuje stany i symbole. W danym momencie automat może wykonać jedną z trzech rodzajów operacji, mianowicie przesuniecie, redukcję lub stop. Przesunięcie zapamiętuje bieżący stan i bieżący symbol na stosie i przesuwa się do przodu po taśmie wejsciowej. Redukcje są związane z regułami gramatyki: logicznie redukcja zastępuje prawą stronę reguły przez lewą, tzn. jesli prawa stron redukcji ma długość k to odczytuje się ze stosu k par symboli i stanów. Ostatni odczytany stan staje się bieżącym stanem. Następnie automat przechodzi do nowego stanu na zależnie od bieżącego stanu (przed chwilą odczytanego ze stosu) i symbolu z lewej strony produkcji. Jeśli operacją jest stop to automat zatrzymuje się albo akceptując albo zgłaszając błąd. Poniższy pseudo kod ilustruje dzialanie automatu:
stan = poczatkowy;
while (1) {
znak = daj_znak();
switch(akcja(stan, znak)) {
case przesuniecie:
push(stan, znak);
stan = przejscie(stan, znak);
znak = daj_znak();
break;
redukcja_1:
for(i=0; i < dlugosc1; i++) {
(stan, _) = pop();
}
push(stan, nonterminal1);
stan = przejscie(stan, nonterminal1);
break;
redukcja_2:
...
stop_błąd:
...
stop_akceptuj:
goto koniec;
}
}
koniec:
Dla wyrażeń arytmetycznych tabelę przejść można zbudować w
następujący sposób. Ciąg znaków (symboli)
tworzących wyrażenie uzupełniamy dodatkowym symbolam
znacznika końca. Stany kodują
informację o tym czy ostatnio widziane podwyrażenie jest
kompletne i o operatorze. Jeśli na wejściu jest zmienna
a stan odpowiada niekompletnemu podwyrażeniu to w tabeli
'akcja' mamy przesunięcie, zaś stan zmienia się na
kompletne podwyrażenie (z tym samym operatorem). Jeśli
na wejściu jest operator zaś stan odpowiada kompletnemu
podwyrażeniu to porównujemy priorytety operatorów, tego
ze stanu i tego widzianego na wejściu. Jeśli operator
ze stanu ma wyższy lub równy priorytet to wykonujemy
redukcję (pobieramy trzy pozycje ze stosu),
jeśli niższy to przesunięcie i przechodzimy do stanu
odpowiadającego przesuniętemu operatorowi i niekompletnemu wyrażeniu.
Jeśli na wejściu jest nawias otwierający, zaś stan odpowiada
niekompletnemu podwyrażeniu to przesuwamy, a odpowiadający mu
stan traktujemy jako niekompletny i mający niższy priorytet od
prawdziwych operatorów. Jeśli na wejściu mamy nawias zamykający
zaś stan odpowiada kompletnemu prawdziwemu operatorowi to
wykonujemy redukcję (pobieramy trzy pozycje ze stosu). Jeśli
zaś stan odpowada "kompletnemu" nawiasowi to wykonujemy
przesunięcie i przechodzimy do specjalnego stanu
w którym niezależnie od wejścia wykonujemy redukcję.
Jeśli na wejściu jest znacznik końca zaś stan to "kompletny"
stan początkowy to akceptujemy, jeśli inny "kompletny" stan
to wykonujemy redukcję. Redukcje zwykle zachowują stan
podniesiony ze stosu, ale jeśli podniesiony stan to stan
początkowy (traktujemy go jako niekompletny) to zmieniamy
go na kompletny. Podobnie redukcja w stanie po nawiasie
zamykającym zmienia niekompletny stan podniesiony ze stosu
na kompletny.
Nieopisane pozycje tabel albo są niemożliwe albo oznaczają
błąd składni w wejściowym wyrażeniu.
Zgodnie z powyższym opisem jawnie wypisać tabele 'akcja'
i 'przejscie' tak by analizować wyrażenia arytmetyczne
z operatorami '+', '*' i nawiasami. Odpowiada to
gramatyce:
W0 : W EOF ;
W : T | W '+' T ;
T : P | T '*' P ;
P : 'x' | '(' W ')' ;
Gdzie 'x', '+', '*', '(', ')' i EOF są symbolami końcowymi, zaś
W0, W, T i P są
symbolami pomocniczymi, przy czym W0 jest symbolem początkowym.
Komentarz1: W powyższym automacie zapisywanie na stosie symboli może się wydawać zupełnie niepotrzebne. Ale łatwo zmodyfikować automant tak by produkował (być może spłaszczone) drzewo rozbioru i do tego symbole na stosie są niezbędne. Komentarz2: Metoda analizy syntaktycznej za pomocą automatów jest szeroko stosowana. Detale automatów mogą się różnić, różnią się też sposoby budowy tabel (zwylke tabele są generowane mechanicznie). Komatarz3: Dla wyrażeń metodę można trochę uprościć, bo nietrywialne zmiany stanu zależą tylko od operatorów i nawiasów a nie zależą od zmiennych.
Dla poniższej gramatyki wyznaczyć symbole wyprowadzające słowo puste. Symbolami końcowymi są ARRAY, CONST, ID, LICZBA, OF, TYPE i znaki pojawiające się w gramatyce.
deklaracje : stale typy ;
stale : stale ';' stala
| /* puste */
;
stala : CONST ID '=' LICZBA ;
typy : typy typ
| /* puste */
;
typ : TYPE ID '=' typ1 ;
typ1 : ID
| array '[' LICZBA ']' OF typ1
;
Wyznaczyć zbiory `FIRST' i 'FOLLOW' dla reguł poniższej gramatyki (symbolem początkowym jest `S').
S : S 'x' | /* puste */Czy ta gramatyka jest LL(1). Dlaczego analizator rekursywny ma problem z tą gramatyką?
Wyznaczyć zbiory `FIRST' i 'FOLLOW' dla reguł poniższej gramatyki (symbolem początkowym jest `S').
S : T 'x' ; T : A | B ; A : 'a' | /* puste */ ; B : 'b' | /* puste */ ;Czy ta gramatyka jest LL(1)?
Zapisać w postaci diagramów syntaktycznych gramatykę zadaną przy pomocy poniższch reguł (symbolem początkowym jest `S').
S : I | B ; B : 'b' I2 'e' ';' ; I2 : /* puste */ | I2 I ';' I : 't' X | 'v' X | 'x' '=' E ';' | 'i' C 'd' I2 'e' | 'i' C 'd' I2 'o' I2 'e' | 'w' C 'd' I2 'e' ; X : 'x' ( ',' 'x') * ';' ; E : 'x' ( '+' E )* ; C : E '=' EUwaga: reguły dla X i E to reguły EBNF. Komentarz: Dla skrócenia rachunków używam tylko jednoznakowych symboli końcowych, lecz można sobie wyobrażać że 'x' to dowolny identyfikator, 'b', 'd', 'e', 'i', 'o', 't', 'v' i 'w' to słowa kluczowe ("begin", "do", "end", "if", "otherwise", "type", "var" i "while" odpowiednio). Przerobienie tego na porządny język programowania pozostawiam Państwa wyobraźni...
Wyznaczyć zbiory `FIRST' i 'FOLLOW' dla gramatyki z zadania 7. Czy jest ona LL(1)?