Generator analizatorów składniowych L L G E N Generator analizatorów składniowych
GENERATOR L LGEN Zadaniem generatora LLgen jest wygenerowanie analizatora składniowego, inaczej parsera (w języku C) metodą zejść rekurencyjnych, bez nawrotów; Kod źródłowy generowany jest przez LLgen’a w oparciu o plik zawierający specyfikację; W specyfikacji można korzystać z rozszerzonych specyfikacji gramatyk LL(1). Ponieważ LLgen zawiera wbudowane mechanizmy statycznego i dynamicznego rozstrzygania konfliktów, pozwala on na korzystanie z gramatyk niejednoznacznych; 2
GENERATOR LLGEN Schemat organizacji działania LLgen’a: scan.l gram.g L E X LLgen scan.c gram.c Lpars.c Lpars.h G C C scane.exe plik.txt 3 WYNIK
GENERATOR LLGEN flex –l scan.l (użycie generatora LEX) lex.yy.c LLgen gram.g (uzycie generatora LLgen dla pliku specyfikacji gram.g) Lpars.c i Lparse.h gcc lex.yy.c Lpars.c gram.c (kompilacja C++) ./a.out < plik.in (analiza pliku) 4 4
GENERATOR LLGEN Generator LLgen domyślnie korzysta z zewnętrznego analizatora leksykalnego, (wygenerowanego za pomocą LEX’a). W tym celu wywoływana jest funkcja yylex(); Plik Lpars.h, który powstaje podczas pracy generatora LLgen, zawiera definicje przypisujące stałe liczbowe nazwom zadeklarowanych token-ów; 5
GNENRATOR LLGEN Sposoby na wykorzystanie innego analizatora są następujące: umieścić implementację skanera bezpośrednio w specyfikacji gramatyki (w bloku ’{’ ’}’ lub w zewnętrznym pliku; W specyfikacji wskazać nazwę funkcji, którą Llgen ma wywołać; %lexical nazwa_funkcji; w razie potrzeby włączyć do analizatora leksykalnego plik Lpars.h; 6
GENERATOR LLGEN LLgen jest narzędziem wierszowym, do którego plik specyfikacji przygotowujemy w zwykłym pliku tekstowym, czasem w kilku plikach; Każdy wygenerowany kod źródłowy zawiera produkcje, dyrektywy generatora LLgen i deklaracje i kod w języku C 7
TWORZENIE SPECYFIKACJI Każdy produkcja ze specyfikacji dla programu LLgen składa się z: nieterminalu, znaku „ : ” i prawej strony produkcji. Zakończona jest średnikiem; Alternatywne prawe strony produkcji rozdzielane są znakiem „|”; Prawa strona produkcji może składać się z terminali, nieterminali i akcji semantycznych; nieterminal : prawa strona produkcji ; 8
TWORZENIE SPECYFIKACJI Reguły tworzenia specyfikacji: białe spacje są ignorowane, jednak nie mogą występować w obrębie nazwy; Komentarze wprowadzamy po znaku „/*” a zamykamy „*/”; Komentarze nie mogą być zagnieżdżone; Komentarze mogą wystąpić w każdym miejscu, gdzie dozwolone jest wystąpienie nazwy; 9
TWORZENIE SPECYFIKACJI Reguły tworzenia specyfikacji c.d. : Nazwy symboli terminalnych i nieterminalnych mogą być dowolnej długości. Maja one składnię taką, jak identyfikatory języka C; Nazwy symboli nie mogą kolidować ze słowami kluczowymi języka C; Wielkość liter w nazwie jest rozróżnialna; 10
TWORZENIE SPECYFIKACJI Reguły tworzenia specyfikacji c.d. : Nazwy symboli mogą być dowolnej długości, jednak w LLgen znaczących jest 50 pierwszych znaków; Wszystkie nazwy generowane i wykorzystywane przez LLgen rozpoczynają się prefiksem LL; 11
DEKLARACJA TERMINALI Terminale, które nie są literałami deklarujemy: %token ken; Jeśli mamy kilka terminali do deklaracji możemy to zrobić tak: %token nazwa1, nazwa2, nazwa3; Każde użycie terminala musi być poprzedzone jego deklaracją; 12
DEKLRACJE TERMINALI Terminale, które są literałami są ujmowane w apostrofy; LLgen rozpoznaje także (podobnie jak C) zestaw literałów specjalnych, tzn.: nowa linia ‘\n’ tabulator ‘\t’ powrót karetki ‘\r’ apostrof ‘\’’ wycofanie znaku ‘\b’ odwrotny ukośnik ‘\\’ liczba oktalna ‘\xxx’ 13
DEKLRACJE TERMINALI ZAPAMIETAJ!!! Napotkana w pliku specyfikacji nazwa, która nie była zadeklarowana jako token, będzie traktowana przez LLgen jako symbol nieterminalny; 14
DEKLRACJE TERMINALI Nieterminal jest implementowany jako funkcja języka C; W LLgenie możemy korzystać ze zmiennych lokalnych funkcji. Generator pozwala je deklarować, w nawiasach klamrowych, jedynie po lewej stronie produkcji za symbolem nieterminalnym, np.: A {int zmienna;} : S ken T ; 15
DEKLRACJE TERMINALI Przez akcję semantyczną rozumiemy dowolną pojedynczą instrukcję (grupę instrukcji) napisaną w języku C, które są ujęte w nawiasy klamrowe; W LLgenie akcje semantyczne możemy wstawić jedynie po prawej stronie produkcji, np.: A {int zmienna} : S ken {licznik=1;} T ; 16
NIETERMINAL STARTOWY Analizatory generowane przez LLgen mogą posiadać wiele nieterminali startowych; Deklaracja nieterminalu startowego inaczej aksjomatu wygląda następująco: %start funkcja , nazwa_nieterminala; np.: %start parse, S; 17
KOMPILACJA Polecenie, które służy do uruchomienia generatora to LLgen. Polecenie to jest wywoływane dla pilku specyfikacji (rozszerzenie g), np.: LLgen gram.g Generator Llgen na wyjściu produkuje trzy pliki: gram.c – plik w C zwierający implementację parsera; Lpars.h – plik zawierający interfejs analizatora leksykalnego; Lpars.c – szkielet parsera i tablica sterująca; 18
OPCJA -V Czasem warto jest przy uruchamianiu i testowaniu parsera korzystać z opcji –v; Dzięki opcji –v wygenerowany zostanie plik LL.output, który będzie zwierał informacje o nierozwiązanych konfliktach, które pojawiły się w gramatyce; 19
ROZSZERZENIE SKŁADNI GRAMATRYK Rozszerzenia standardowej składni gramatyk bezkontekstowych: * (*liczba) – domknięcie zwrotne + (+liczba) – domknięcie dodatnie ; ? – operator opcjonalności; [...] – możliwość grupowania symboli; 20
Przykład Niech ={a,b}. Rozważmy język regularny L=L(b*a). Wówczas: S : B A ; B : ‘b’ * A : ‘a’ S : B A ; B : | ‘b’ B A : ‘a’ 21
Przykład Niech ={a,b}. Rozważmy język L={b, ab, aab, aaab}. Wówczas: S : A B ; A : | ‘a’ C C : | ‘a’ B : ‘b’ S : A B ; A : ‘a’ *3 B : ‘b’ 22
Przykład Niech ={a,b}. Rozważmy język L={ab, aab, aaab}. Wówczas: S : A B ; A : ‘a’ C C : | ‘a’ B : ‘b’ S : A B ; A : ‘a’ +3 B : ‘b’ 23
Przykład Niech ={a,b}. Rozważmy język L={b, ab}. Wówczas: S : A B ; A : | ‘a’ B : ‘b’ S : A B ; A : ‘a’ ? B : ‘b’ 24
Przykład Niech ={a,b}. Rozważmy język L={A * : |A|=2}. Wówczas: S : ‘a’ B | ‘b’ B ; B : ‘a’ | ‘b’ S : [ ‘a’ | ‘b’ ] +2 ; 25
PORÓWNANIE Rozważmy gramatykę, która nie jest gramatyką LL(1). Porównajmy pracochłonność procedury dostosowania gramatyki a bezpośrednią implemantacją gramatyki w generatorze LLgen’; Niech =[a,b}. Napiszmy program akceptujący język bezkontekstowy L={A* : A=an bn ; n }; 26
PORÓWNANIE { int ilosc_a, ilosc_b; } %start parse , S; S : A B { if (ilosc_a= = ilosc_b) puts(’’OK.’’); else puts(’’Blad’’); } ; 27
PORÓWNANIE Usuwamy lewostronną rekurencję; A : ’a’ { ilosc_a=1; } B : ‘b’ { ilosc_b=1; } | B ‘b’ { ilosc_b++; } A : ’a’ { ilosc_a=1; } | ’a’ A { ilosc_a++; } ; B : ‘b’ { ilosc_b=1; } | ‘b’ B { ilosc_b++; } 28
PORÓWNANIE A : ’a’ C { ilosc_a++; } ; C : { ilosc_a=0; } B : ’b’ D { ilosc_b++; } D : { ilosc_b=0; } | ’b’ D { ilosc_b++; } 29
PORÓWNANIE S : {ilosc_a=ilosc_b=0} A B { if (ilosc_a= = ilosc_b) puts(’’OK.’’); else puts(’’Blad’’); } ; A : [ ’a’ {ilosc_a++} ] + B : [ ’b’ {ilosc_b++} ] + 30
LLSYMB LLsymb jest globalną zmienną całkowitą, która może przyjmować różne wartości. To jaka wartość będzie przyjęta, zależy od położenia głowicy czytającej po prawej stronie produkcji: Możliwe wartości: Jeśli przeczytany został token, to LLsymb przechowuje token; Po grupowaniu i alternatywie w zmiennej znajduje się podglądany token; 31
TWORZENIE SPECYFIKACJI W pliku ze specyfikacją do generatora LLgen obowiązkowo powinna znaleźć się implementacja funkcji main; %start parse, S ; int main(){ parse(); return 0; } 32
TWORZENIE SPECYFIKACJI W pliku ze specyfikacją do generatora LLgen powinna także znaleźć się implementacja funkcji LLmessage; Funkcja ta jest automatycznie wywoływana przez parser, gdy wystąpi bład składniowy; void LLmessage ( int tk ); Nie zwraca żadnej wartości Ma jeden parametr typu całkowitego 33
TWORZENIE SPECYFIKACJI Zmienna tk przyjmuje następujące wartości: gdy oczekiwany był token „tk” – tk > 0; gdy wczytany został nieoczekiwany token i został on usunięty – tk = 0; gdy nie został napotkany oczekiwany koniec pliku i pozostałe wejście będzie pominięte – tk = - 1; 34
Przykład Działanie generatora LLgen najlepiej zobaczyć na przykładzie. Na wejściu znajduje się ciąg słów złożonych z alfabetu naturalnego, słowa kończą się znakiem dwukropka i są rozdzielane przecinkiem. Dany na wejście ciąg zawiera co najmniej jedno słowo.... 35
KONFLIKTY W trakcie pracy generatora składniowego może dojść do konfliktu polegającego na: nie jesteśmy w stanie określić, którą z prawych stron należy rozwijać – konflikt alternatyw; Konstrukcja która jest aktualnie przetwarzana zawiera domkniecie i trudno określić, czy wejście jest jej dalszym ciągiem, czy też rozpoczyna inną konstrukcję – konflikt powtórzeń; 36
KONFLIKTY Konflikt alternatyw można rozstrzygnąć na dwa sposoby: dynamiczne rozstrzyganie konfliktu alternatywy: %if (warunek) statyczne rozstrzyganie konfliktu alternatyw: %prefer %if(1) %aviod %if(0) 37
Przykład Rozważmy zadanie badania parzystości liczby binarnej; Analizator leksykalny rozpoznaje i zwraca liczby binarne; %% [01] { return yytext[0]; } 38
Przykład { int wczytcyfra; } %start parse, S; S : ’0’ { wczytcyfra = 0; } R | ’1’ { wczytcyfra = 1; } R ; R : %if (wczytcyfra ==0 ) {puts(”parzysta”);} | {puts(”nieparzysta”);} | S 39
ROZWIĄZYWANIE KONFLIKTÓW Przykład użycia mechanizmu statycznego rozwiązywania konfliktów alternatyw jest problemem tzw. „wiszącego else”; Problem ten omówimy szczegółowo na wykładzie poświęconym generatorowi YACC; 40
ROZWIĄZYWANIE KONFLIKTÓW Konflikt powtórzeń jest rozstrzygany przy pomocy słowa kluczowego %while; %while ( warunek ) W ewaluacji warunku bardzo przydatne może być makro języka C, które jest generowane przez Llgen na podstawie słowa kluczowego %first; %first fmac, nonterm ; 41
KONIEC KONIEC WYKŁADU SZÓSTEGO