J.Nawrocki, M. Antczak, A. Hoffa, S. Wąsik Plik źródłowy: 08cw10-jfig.doc; Data: 2008-10-22 13:29:00 Ćwiczenie nr 10 Języki formalne i gramatyki Wprowadzenie 1. Napisz analizator leksykalny (LEX) i analizator składniowy (YACC), który będzie wykorzystywany przez akceptor łańcuchów o postaci: c 1 c 2 gdzie c 1 jest ciągiem cyfr oktalnych ([0-7]), a c 2 lustrzanym odbiciem c 1. Ciągi c 1 i c 2 mogą być puste, zatem łańcuch zawierający jedynie znak jest poprawny. Łańcuch poprawny (np.: 123 321) winien być zaakceptowany komunikatem Syntax OK, natomiast niepoprawny (np.: 1234 3321) - odrzucony komunikatem Syntax error!. W celu wykonania powyższego zadania należy: a. pobrać pakiet, który zawiera narzędzia, które pozwolą na skonfigurowanie środowiska uruchomieniowego, które będzie wykorzystywane podczas tworzenia akceptora, b. stworzenie analizatora leksykalnego (z wykorzystaniem narzędzia LEX) oraz analizatora składniowego (za pomocą narzędzia YACC), c. kompilacja i uruchomienie napisanych w poprzednim kroku analizatorów; wynik powinien zawierać ocenę poprawności poszczególnych wierszu znajdujących się w pliku wejściowym. 1. Instalacja i konfiguracja narzędzi Pobierz i rozpakuj pakiet, w dowolnej lokalizacji nie zawierającej spacji, korzystając z adresu: http://www.cs.put.poznan.pl/mantczak/teaching/itc/lex&yacc.zip. Po pomyślnym rozpakowaniu (np.: na dysku E:\) powinien się pojawić katalog Temp (np.: E:\Temp). Katalog Temp zawiera następujące katalogi: MKS pakiet MKS Lex and Yacc for DOS w wersji 3.2. Ukazał się na rynku w 1993 roku. Pomimo swojego wieku zawiera w pełni użyteczne wersje LEXa i YACCa. W późniejszych latach ukazały się poprawki do pakietu, które umożliwiły przetwarzanie większych specyfikacji i poprawiły kompatybilność narzędzi (teraz używamy 3.2a). Strona producenta pakietu: http://www.mks.com. W celu instalacji narzędzi dla innego systemu operacyjnego proszę skorzystać z informacji zawartych pod adresem http://www.cs.put.poznan.pl/wcomplak/jf-tools.htm. TC w celu korzystania z pakietu potrzebny jest także kompilator języka C. Wypróbowaliśmy z powodzeniem (co nie oznacza, że pierwsza kompilacja zakończy się powodzeniem) dobrze znany nam już z ćwiczeń dotyczących programowania imperatywnego kompilator Turbo C. Temp katalog wykorzystywany przez Lex-a i Yacc-a w celu przechowywania plików tymczasowych, które pojawiają się podczas korzystania z powyższych narzędzi. Plik init.bat, w którym zawarte są ustawienia zmiennych środowiskowych systemu Windows, które są wymagane podczas korzystania z powyższych narzędzi. SET ROOTDIR=%1\MKS // ścieżka do głównego katalogu z SET TMPDIR=%1\Temp // narzędziami // ścieżka do katalogu, w którym tworzone są // pliki tymczasowe przez wykorzystywane // narzędzia SET PATH=%PATH%;%1\MKS\BIN // dodanie ścieżki dostępu do narzędzi do //zmiennej systemowej PATH w celu //możliwości odwoływania się do narzędzi z //konsoli uruchomionej w dowolnym katalogu.
Przejście do wnętrza katalogu Temp i uruchomienie w nim konsoli systemowej za pomocą polecenia cmd. Uruchomienie w tej nowo utworzonej konsoli systemowej pliku init.bat z przekazaną jako parametr ścieżką do katalogu Temp (np.: init.bat E:\Temp). W tym momencie w tej konkretnej konsoli systemowej jest zdefiniowane środowisko uruchomieniowe, które jest wystarczające w celu realizacji powyższego zadania. Ważną kwestią jest, że jeżeli zamknięte zostanie ta konsola systemowa i uruchomiona kolejna, wtedy w tej i każdej następnej musi zostać uruchomione polecenie z punktu 1.5 przed rozpoczęciem pracy z narzędziami Lex i Yacc. 2. Konstrukcja analizatora leksykalnego i składniowego Celem analizatora leksykalnego jest sprawdzenie czy dane znajdujące się w wejściowym pliku są poprawnego typu (rodzaju). Analizując treść powyższego zadania wiemy, że przykładem poprawnych danych wejściowych jest np.: 123 321. W takim razie wiemy, że na wejściu mogą się pojawić tylko cyfry oraz znak. Dodatkowo wiemy, że mogą to być tylko cyfry tworzące liczby oktalne, a więc ograniczamy zakres naszych cyfr od 0-7. Wynik analizy leksykalnej zakończy się powodzeniem tylko wtedy, gdy w pliku wejściowym znajdować się będą tylko cyfry w zakresie od 0-7 oraz znak. Na tym etapie niebadana jest ilość znaków, tzn. czy w pliku wejściowym znajduje się tylko jeden znak. Taka analiza dokonywana jest podczas analizy składniowej w kolejnym etapie, do którego można przejść tylko wtedy, gdy etap analizy leksykalnej zakończy się sukcesem. Jeżeli na wejściu zostanie wyszczególniony, co najmniej jeden znak, który nie jest cyfrą z zakresu od 0-7 lub znakiem wtedy analiza leksykalna kończy się niepowodzeniem. Poniżej znajduje się zawartość pliku scan.l, który zawiera implementację prostego analizatora leksykalnego zapisanego w języku Lex: %% [0-7] {return yytext[0];} // gdy na wejściu znajduje się // cyfra [0-7] przekaż ją do // dalszego etapu analizy " " {return ' ';} // gdy na wejściu znajduje się // znak przekaż go do dalszego // etapu analizy. {YY_FATAL("ERR!");} // jeżeli na wejściu pojawi się // dowolny znak (. ), który nie // został obsłużony przez żadną z // powyższych reguł bazujących na // wzorcach to przerwij analizę // leksykalną z błędem // sygnalizujących błędnie // przygotowaną zawartość pliku // wejściowego Celem analizatora składniowego jest sprawdzenie czy dane wejściowe poprawnego typu są zgodne z przyjmowaną przez nas jako poprawną składnię języka. W przypadku powyższego zadania przykładem poprawnych danych wejściowych jest np.: 123 321. Zauważmy w takim razie, że 123 321 = 1 S 1 => S = 23 32 = 2 S 2 => S = 3 3 => 3 S 3 => S =, gdzie S to rodzaj podwyrażenia. Dla powyższego języka można zdefiniować następującą gramatykę, która może zostać zapisana z wykorzystaniem narzędzie Yacc (poniżej przedstawiona zostaje zawartość pliku scang.y):
%{ #include <stdio.h> // pliki nagłówkowe zawierające #include <dos.h> // funkcje, które będą używane #include <stdlib.h> // w dalszych sekcjach kodu #include <process.h> // źródłowego %} %% // rozpoczyna się sekcja gramatyki E : S {printf("syntax OK\n");} // potwierdź na wyjściu popr. skł. ; S : '0' S '0' // kolejne reguły, które definiują '1' S '1' // poprawną składnię języka '2' S '2' // zdefiniowanego w treści zadania '3' S '3' '4' S '4' '5' S '5' '6' S '6' '7' S '7' ' ' ; %% // impl. pokrywanych funkcji yacca void yyerror(char* msg) // kod funkcji, który zostanie { // wywołany w momencie, gdy printf("syntax error!\n"); // wyrażenia w pliku wejściowym exit(1); // nie będą się zgadzać składniowo } // z językiem z treści zadania 3. Kompilacja i uruchomienie i testowanie Powyższe pliki wraz z plikiem zawierającym dane wejściowe in.i znajdują się w katalogu projektu scan w katalogu kompilatora TC (np.: E:\Temp\TC\Projects\jfk\scan). W celu uruchomienia powyższego projektu został napisany plik makejf.bat, który znajduję się w katalogu głównym kompilatora (np.: E:\Temp\TC). Przechodzimy do katalogu kompilatora TurboC za pomocą polecenia cd TC. Kompilacja i uruchamianie projektu scan znajdującego się w katalogu Projects\jfk, w którym powinny się znajdować katalogi kolejnych tworzonych projektów (np.: E:\Temp\TC\Projects\jfk\scan) za pomocą polecenia makejf.bat nazwa_projektu (np.: makejf.bat scan).
Dodatkowe materiały Zakładka materiały dla studentów dla przedmiotu języki formalne i kompilatory na stronie domowej dr inż. W. Complaka http://www.cs.put.poznan.pl/wcomplak. Zadania Zad. 1. Jakie elementy składają się na pełną definicję formalną gramatyki opisywanego języka? a)...(t), b)...(n), c)...(s), d)...(p). Zad. 2. Załóżmy, że chcemy zdefiniować gramatykę dla języka opisanego przez 1+ = {1,11,111,...}. W rezultacie otrzymujemy następującą gramatykę: {1;S;S;S->1,S->S1}, gdzie 1 -..., S -..., S -..., S->1,S->S1 -... Zad. 3. Zaznacz wszystkie poprawne odpowiedzi. Forma zdaniowa jest to skończony ciąg wyrazów, który musi zawierać przynajmniej jeden wyraz. Wyrazy wchodzące w skład formy zdaniowej składają się z: a) symboli nieterminalnych (np.:...->ss->...), b) symboli terminalnych (np.:...->11->...), c) symboli terminalnych i nieterminalnych (np.:...->s1->...). Zad. 4. Zaznacz wszystkie poprawne odpowiedzi. Zdanie jest to skończony ciąg wyrazów, który musi zawierać przynajmniej jeden wyraz. Wyrazy wchodzące w skład zdania składają się z: a) symboli nieterminalnych (np.:...->ss), b) symboli terminalnych (np.:...->11), c) symboli terminalnych i nieterminalnych (np.:...->s1). Zad. 5. Zaznacz wszystkie poprawne odpowiedzi. a) formą początkową wywodu zdania jest forma zdaniowa, b) formą końcową wywodu zdania jest symbol początkowy, c) formą przejściową wywodu są zdania, d) formą końcową wywodu zdania jest zdanie, e) formą początkową wywodu zdania jest symbol początkowy, f) formą przejściową wywodu zdania są formy zdaniowe, g) wywód zdania rozpoczynany jest od symbolu początkowego, który następnie jest przekształcany w kolejne formy zdaniowe (wykorzystanie produkcji), aż do momentu osiągnięcia zdania, które chcieliśmy wywieść, h) wywód zdania rozpoczynany jest od zdania, które chcemy wywieść i następnie jest ono przekształcane w kolejne zdania (wykorzystanie produkcji), aż do momentu osiągnięcia symbolu początkowego wykorzystywanej gramatyki, i) każde zdanie jest formą zdaniową, ale nie na odwrót, j) symbol początkowy nie jest formą zdaniową. Zad. 6. Weryfikacja poprawności gramatyki opisującej dany język jest dokonywana poprzez... poprawnych i niepoprawnych zdań.
Zad. 7. Załóżmy, że mamy następujące dwie gramatyki liniowe zdefiniowane poniżej. Należy określić, która z nich jest lewoliniowa i prawoliniowa oraz czym się charakteryzuje każda z nich. S A B A A a A a B B b B b S A B A a A A a B b B B b...... Dla każdego... istnieje gramatyka lewoliniowa (prawoliniowa) opisująca ten sam język. Zad. 8. Załóżmy, że mamy następujące dwie gramatyki zdefiniowane poniżej. Należy określić, która z nich jest bezkontekstowa i kontekstowa oraz czym się charakteryzuje każda z nich. W S W W + S S C S S * C C L C ( W ) L 1 L 2 L 3 S a X Y S a S X Y a X a b b X b b c X c c b Y b c c Y c c...... (*) Dla każdego zadania z zakresu od 9 do 13 sprawdzić poniższe zdania z wykorzystaniem zaproponowanych gramatyk zaimplementowanych z wykorzystaniem pakietu zawierającego narzędzia Lex i Yacc. Zad. 9(*). Należy zaproponować gramatykę dla języka opisanego za pomocą następującego wyrażenia regularnego 1 n 2 m 1 + 2 m 1 n, gdzie m,n>0 i wywieść następujące zdania: a) 112221122211..., b) 1211 -..., c) 1212 -..., d) 1221221 -... Zad. 10. Należy zaproponować gramatykę dla języka opisanego za pomocą następującego wyrażenia regularnego 1+2*1+ i wywieść następujące zdania: a) 111221..., b) 221 -..., c) 122 -..., d) 122111 -... Zad. 11. Należy zaproponować gramatykę dla języka opisanego za pomocą następującego wyrażenia regularnego a n b+c+a n, gdzie n>0 i wywieść następujące zdania: a) aabcaa..., b) aabca -..., c) aaabaaa -..., d) aaabbcccaaa -... Zad. 12. Należy zaproponować gramatykę dla języka opisanego za pomocą następującego wyrażenia regularnego a n b n+m a m, gdzie n,m>0 i wywieść następujące zdania: a) aabbbbbaaa..., b) abbba -..., c) aaaa -..., d) aaabbbba -...
Zad. 13(*). Należy zaproponować gramatykę dla języka opisanego za pomocą następującego wyrażenia regularnego a n b * a n, gdzie n>0 i wywieść następujące zdania: a) aabbbbaa..., b) abbba -..., c) aaaa -..., d) aaabbbbbbaaa -... Zad. 14. Notacja Backusa-Naura (BNF) jest sposobem zapisu., czyli sposobem opisu.. Notacja BNF jest zestawem o następującej postaci: <symbol> ::= <wyrażenie zawierające symbole> Zad. 15. Wykorzystując rozszerzoną notację Backusa-Naura (EBNF) opisz składnię polskiego adresu pocztowego. Sz.P. Jan Kowalski, ul. Tajna 1 m2/3, 12-345 Duże, Mały Zad. 16(*).Wykorzystując rozszerzoną notację Backusa-Naura (EBNF) zdefiniuj notację EBNF.