Wykład 5

Transkrypt

Wykład 5
Wykład 5
Jan Pustelnik
Konstruowanie parsera
●
●
Istnieje kilka podstawowych metod
konstrukcji parsera bez nawracania
Ze względów wydajnościowych parser
bez nawracania jest jedynym
sensownym rozwiązaniem (prawo
Moore'a jest w stanie przyspieszyć
znacząco wyłącznie algorytmy O(n))
Konstruowanie c.d.
●
Podstawowymi metodami
konstruowania parserów bez
nawracania są:
–
top-down (metoda zejścia rekursywnego,
oznaczana często LL, szczególnie dobra dla
ręcznego budowania parsera dla prostych
języków, zasadna zwłaszcza tam, gdzie nie
mamy możliwości skorzystania z yacca/bisona, tak np. zbudowany jest
freepascal, gdzie source jest w pascalu)
Konstruowanie c.d.
–
bottom-up – metoda wstępująca; zwykle
używana jest któryś z wariantów ogólnej
metody zwanej LR (L – czytaj od lewej do
prawej, R – wywódź prawostronnie – czyli
od terminali do symbolu głównego):
●
●
●
SLR – simple LR (LR(0))
LALR – lookahead LR (LR(k), k>0, zwykle LR(1))
LR(k) – to ogólny symbol, k oznacza tutaj liczbę
znaków lookahead, które są rozważane w
czasie analizowania jaką ścieżką podążyć; k=0
bierze pod uwagę wyłącznie bieżący symbol,
k=1 patrzy jeden znak naprzód
Konstruowanie c.d.
●
Wszystkie wspomniane metody należą
do rodziny L – czytających tekst od
lewej strony, w związku z czym
konieczne w ich wypadku jest
wykonanie dwóch operacji: usunięcia
lewostronnej rekursji i wykonanie
lewostronnej faktoryzacji (wyjęcia
największego wspólnego czynnika
przed nawias)
Konstruowanie c.d.
●
W metodzie zejścia rekursywnego
naturalną postacią parsera jest
program w postaci ciągu
rekurencyjnych procedur, które
wywołują same siebie lub inne
procedury; schemat wzajemnych
zależności odpowiada gramatycje
języka
Konstruowanie c.d.
●
W wypadku metod LR (wstępujących)
mamy do czynienia z tablicami ACTION
i GOTO oraz akcjami SHIFT i REDUCE
–
–
–
–
akcja SHIFT wkłada symbol na stos
akcja REDUCE implementuje dokonanie
“zwinięcia” kilku symboli prostszych w
jeden symbol bardziej ogólny zgodnie z
którąś z reguł gramatyki
tablica ACTION decyduje, czy mamy
wykonać SHIFT czy REDUCE
tablica GOTO mówi do którego stanu
mamy się udać
Konstruowanie c.d.
–
tablice ACTION i GOTO konstruowane są
zgodnie z określonymi regułami;
najbardziej zgrubną regułą jest: preferuj
REDUCE względem SHIFT (reguła
zdroworozsądkowa – nie odkładaj rzeczy
na później) – niestety reguła ta zawodzi w
szczególnych przypadkach, dlatego dla
rzeczywistego parsera potrzebne są
jeszcze trzy narzędzia:
●
●
●
zbiory FIRST
zbiory FOLLOW
zbiory konfiguracji
Konstruowanie c.d.
●
Dlatego na wstępie powiemy sobie w
ogólności o metodach eliminacji
rekursji i faktoryzacji a także o zbiorach
FIRST, FOLLOW i zbiorach konfiguracji
tak, by rzeczy nudne, ale potrzebne
zmieściły się w ramach niniejszego
wykładu; potem będziemy już tylko
mówić o konkretnych metodach analizy
składniowej
Rekurencja lewostronna
●
●
●
Gramatyka jest lewostronnie
rekurencyjna, jeśli ma terminal A taki,
że istnieje wyprowadzenie A⇒A dla
pewnego napisu .
Powoduje to następujący problem:
jeżeli analizujemy tekst od lewej strony,
wówczas nigdy nie dotrzemy do ,
ponieważ “ugrzęźniemy” w A
Ceterum censeo rekurencja
lewostronna delendam esse
i jej eliminacja
Przeanalizujmy przypadek szczególny
(nieterminal bezpośrednio
rekurencyjny):
Zastępujemy produkcję o postaci:
AA|
Następującymi produkcjami:
AA'
A'A'|
Zwróćmy uwagę na pojawienie się
epsilon-produkcji!
●
eliminacji c.d.
Przeanalizujmy teraz następujący
przykład gramatyki:
SAa|b
AAc|Sd|
Nieterminal S jest teraz lewostronnie
rekurencyjny, ale nie jest bezpośrednio
lewostronnie rekurencyjny. Zatem
musimy zastosować pewien algorytm,
polegający na sukcesywnym
rozwinięciu wszystkich produkcji tak, by
●
eliminacji c.d.
dowolna produkcja zawierała po prawej
stronie wyłącznie terminale,
nieterminale w pozycjach
prawostronnie rekurencyjnych lub
nieterminal znajdujący się po lewej
stronie w pozycji lewostronnie
rekurencyjnej, a następnie wykonaniu
eliminacji bezpośredniej lewostronnej
rekurencji z wszystkich produkcji
eliminacji c.d.
●
●
Warunkiem powodzenia algorytmu
eliminacji jest brak pętli oraz brak
epsilon-produkcji w startowej
gramatyce; efektem ubocznym jest
stworzenie epsilon produkcji.
Zwykle algorytm ten omijamy po prostu
pisząc gramatykę w taki sposób, by nie
było lewostronnej rekurencji
Faktoryzacja lewostronna
Weźmy następujące dwie produkcje:
instr  if wyr then instr else instr
| if wyr then instr
Jeśli na wejściu widzimy symbol if,
wówczas nie wiemy, którą produkcję
wybrać, należy wyciągnąć pierwszą
część “przed nawias”, otrzymując
następujące produkcje:
instr  if wyr then instr część-else
część-else  else instr | 
●
faktoryzacja c.d.
●
●
Standardowo postępujemy pisząc
gramatykę tak, by od razu uwzględnić
lewostronną faktoryzację
Należy zauważyć, że algorytm
eliminacji lewostronnej faktoryzacji
może zostać łatwo zintegrowany z
algorytmem tworzenia analizatora
składniowego typu LR
Zbiory FIRST i FOLLOW
●
●
Zbiory FIRST i FOLLOW budowane są w
trakcie konstrukcji algorytmu
analizatora składniowego metodą LR
Rozumienie ich konstrukcji jest
konieczne dla analizy błędów w
gramatyce raportowanych przez
generator analizatorów składniowych
yacc/bison
FIRST
●
Zbiór FIRST dla dowolnego ciągu
symboli z gramatyki X jest zbiorem
terminali, od których zaczynają się
ciągi wyprowadzalne z X; jeżeli z X
można wyprowadzić epsilon, to epsilon
jest także w FIRST(X); należy zauważyć,
że wszystkie zbiory FIRST są
konstruowane jednocześnie dla całej
gramatyki, dzięki czemu czas
obliczania ich jest proporcjonalny do
długości najdłuższego wyprowadzenia
FIRST c.d.
FIRST – ALGORYTM:
1. Jeśli X jest terminalem, to FIRST(X) jest równe {X}
2. Jeśli mamy epsilon produkcję dla X, to dodajemy
epsilon do FIRST(X)
3. Jeśli X jest nieterminalem, wówczas dla wszystkich
produkcji postaci:
X  Y1Y2Y3...
Należy wykonać następujący algorytm:
a) dodaj do FIRST(X) zbiór FIRST(Y1)
b) jeśli FIRST(Y1) zawiera epsilon, wówczas dodaj do
FIRST(X) zbiór FIRST(Y2)
c) jeśli FIRST(Y2) zawiera epsilon... itd.
FIRST c.d.
d) jeśli wszystkie FIRST(Yj) zawierają epsilon, wówczas
FIRST(X) też będzie zawierał epsilon
FOLLOW
●
Zbiory FOLLOW obliczamy zgodnie z
następującym algorytmem:
1) dla symbolu startowego S w FOLLOW(S)
umieszczamy $
2) dla każdej produkcji A B, gdy FIRST() nie
zawiera , wszystkie symbole z FIRST()
umieszczamy w FOLLOW(B)
3) jeżeli FIRST() zawiera , lub mamy do czynienia z
produkcją A B, wówczas do FOLLOW(B) dodajemy
FOLLOW(A)
Prosty przykład
analizatora LL
●
Teraz podamy prosty przykład
analizatora LL, żeby już dalej nie
zajmować się tym tematem; dla nas
ważne są analizatory LR, które są
generowane automatycznie przez
odpowiedni generator analizatorów
składniowych
Prosty przykład c.d.
●
Analizatory LL zasadniczo nadają się
wyłącznie do generowania ręcznego i
jako takie sprawdzają się w wypadkach
prostych języków (kalkulator) lub gdy
do dyspozycji nie mamy odpowiednich
narzędzi (Pascal), a dodatkowo zależy
nam na małym kodzie wykonywalnym
(tablice LR potrafią być duże, dla
ośmiobitowców LL było jedynym
wyjściem)

Podobne dokumenty