Instrukcja Lex 2012
Transkrypt
Instrukcja Lex 2012
Laboratorium z użyciem analizatora leksykalnego FLEX Analizator leksykalny FLEX jest narzędziem służącym do tworzenia programów rozpoznających wzorce. FLEX na podstawie pliku wejściowego, za pomocą reguł i powiązanych z nimi akcji, tworzy program analizatora leksykalnego, który opiera się o maszynę stanów. Wynik działania tego programu jest zapisany w postaci kodu języka C, gdzie główna metoda rozpoczynająca działanie analizatora leksykalnego została nazwana yylex(). Analizator po pobraniu danych z wejścia dopasowuje wyrażenia regularne do podanych wzorców i wykonuje zdefiniowane dla nich akcje. Struktura pliku Plik podawany na wejście analizatora leksykalnego FLEX ma następującą postać: SEKCJA DEFINICJI %% SEKCJA REGUŁ %% SEKCJA KODU UŻYTKOWNIKA Przykłady najkrótszego programu, który przepisuje dane z wejścia na wyjście: %option noyywrap %% %% int main() { yylex(); return 0; } Sekcja definicji Sekcja definicji zawiera deklaracje prostych nazwanych definicji, co ma za zadanie uprościć pisanie analizatora leksykalnego. Dodatkowo znajdują się tutaj definicję stanów analizatora. Składnia nazwanej definicji jest następująca: NAZWA DEFINICJA gdzie NAZWA jest dowolnym słowem, natomiast DEFINICJA jest zapisem wyrażenia regularnego. W sekcji reguł odwołujemy się do nazwanych definicji w następujący sposób: | Karolina Nurzyńska © 1 {NAZWA} Przykłady nazwanych definicji: MAŁE [a-z]* CYFRY [0-9]? Jakikolwiek tekst nierozpoczynający się od pierwszego wiersza jak i wzięty w %{ %} jest kopiowany bezpośrednio do pliku wyjściowego. Można również za pomocą bloku %top{ } określić część tekstu, która ma zostać przekopiowana bezpośrednio do pliku wyjściowego, ale na samą górę tego pliku. Sekcja reguł Sekcja reguł składa się z serii reguł o następującym formacie: WZORZEC AKCJA, gdzie WZORZEC nie może być wcięty (nie może rozpoczynać go spacja) oraz AKCJA musi zaczynać się w tej samej linii. Przykłady reguł: [A-Z]* {CYFRA} printf(„Rozpoznano duże litery %s”, yytext); printf(„Rozpoznano cyfrę”); Sekcja kodu użytkownika Sekcja kodu użytkownika jest opcjonalną częścią pliku, która jest bezpośrednio kopiowana do pliku wyjściowego. Zazwyczaj są tutaj umieszczane definicje funkcji napisanych w języku C, które wywoływane są w akcjach. Komentarze FLEX wspiera komentarze zgodnie ze składnią języka C i wszystko co znajduje się między znakami „/*” i „*/” kopiuje bezpośrednio do pliku wyjściowego. Jednak żeby komentarz nie został pomylony przez analizator z regułą, sugeruje się, aby nigdy nie rozpoczynał się on w pierwszej kolumnie. Wyrażenia regularne Wzorce zapisuje się za pomocą rozszerzonego zestawu regularnych. Poniżej wyrażenia regularne zgodnie z zapisem we FLEX Wzorzec „X” . \n [abc] [ab-fz] [^A-C] [a-z]* [a-z]+ wyrażeń Opis Dopasuj znak X. Dopasuj dowolny znak poza znakiem końca linii. Dopasuj koniec linii. (klasa znaków) Dopasuj znak a lub b lub c. (klasa znaków z wykorzystaniem operatora zakresu) Dopasuj znak a lub od b do f lub z. (zanegowana klasa znaków) Dopasuj dowolny znak poza A, B i C. (* - zero lub wiele powtórzeń wyrażenia regularnego). Dopasuj ciąg składający się z zera lub wielu małych liter. (+ - jedno lub wiele powtórzeń wyrażenia regularnego). Dopasuj | Karolina Nurzyńska © 2 ciąg składający się z jednej lub wielu małych liter. (? – zero lub jedno powtórzenie). Dopasuj ciąg składający się z żadnej lub jednej małej litery. [a-z]{2,5} ({n,m} – powtarzaj od n do m razy). Dopasuj ciąg skłdający się z od 2 do 5 małych liter. [a-z]{2,} ({n,m}). Dopasuj ciąg skłdający się z co najmniej 2 małych liter. [a-z]{2} ({n} – powtarzaj n razy). Dopasuj ciąg składający się z dokładnie 2 małych liter. {CYFRY} Odwołanie się do nazwanej definicji. „Karolina” Dopasuj ciąg do napisu Karolina. ([a-z]) (operator () do nadawania łączności regułom). [a-z]|[A-Z] (| - operator wyboru) Dopasuj małą lub dużą literę. [a-z][A-Z] (regularne wyrażenia następujące po sobie). Dopasuj ciąg znaków, gdzie pierwsza litera jest mała, a druga duża. (dopasowanie warunkowe). Dopasuj A, ale tylko jeżeli występuje „A”/[a-z] po nim mała litera. ^[a-z] (początek linii). Dopasuj małą literę, ale tylko na początku linii. [a-z]$ (koniec linii). Dopasuj małą literę ale tylko, jeżeli jest na końcu linii. <<EOF>> Koniec pliku. Dodatkowo zostały zdefiniowane pewne klasy wyrażeń znakowych, których nazwa jest podawana między znakami „[:” i „:]” i dodatkowo przy tworzeniu wyrażenia regularnego musi być wzięta w nawiasy kwadratowe. Klasa wyrażeń Znaczenie [[:lower:]] małe litery [[:upper:]] duże litery [[:digit:]] cyfry [[:space:]] spacja [a-z]? Działanie analizatora Po uruchomieniu analizator stara sie dopasować zadany ciąg znaków do wzorców znajdujących się w sekcji reguł w następujący sposób: W przypadku, gdy można dopasować kilka wzorców zawsze wybierany jest ten, który dopasowuje najwięcej znaków. Jeżeli istnieją dwa wzorce dopasowujące tą samą liczbę znaków, zawsze wybrany zostanie ten, który został zdefiniowany jako pierwszy. Po dopasowaniu do wzorca w akcji można odczytać dopasowany ciąg znaków poprzez odwołanie się do zmiennej yytext, natomiast długość bieżącego dopasowania jest przechowywana w zmiennej yyleng. Domyślnie zmienna yytext jest tablicą znakową o długości 256 znaków, jednak istnieje możliwość zdefiniowania jej jako wskaźnik poprzez zastosowanie przełącznika %pointer. Po dopasowaniu wzorca wywoływana jest akcja z nim związana, a następnie pozostała część ciągu znaków na wejściu jest dopasowywana do wzorców. | Karolina Nurzyńska © 3 Przykłady zastosowania reguł: %option noyywrap LETTERS INTEGER REAL [a-z]* [1-9][0-9]*|0 {INTEGER}"."[0-9]+ DZIEN MIESIAC ROK DATA %% [^0-9] ^[a-z] [a-z]$ {REAL} yytext); {INTEGER} {DATA} [1-9]|1[0-9]|2[0-9]|30 [1-9]|1[0-2] [1-9][0-9]{0,2}|1[0-9]{0,3}|200[1-9]|20[1-4][0-9]|2050 {DZIEN}"-"{MIESIAC}"-"{ROK} printf("To nie jest liczba %s\n", yytext); printf("Znak na poczatku linii %s\n", yytext); printf("Znak na koncu linii %s\n", yytext); printf("Rozpoznano liczbe zmiennoprzecinkowa %s\n", printf("Rozpoznano liczbe calkowita %s\n", yytext); printf("Rozpoznano date %s\n", yytext); {LETTERS}/{INTEGER} yytext); printf("Rozpoznano litery przed liczbami %s\n", \n printf("Koniec linii"); . printf("Nierozpoznany znak"); %% int main() { yylex(); return 0; } Akcje Z każdym wzorcem połączona jest akcja, która może być dowolną instrukcją napisaną w języku C. Domyślnie akcja powinna zostać zapisana w jednej linii zaraz po wzorcu. W przypadku, gdy składa się ona z wielu instrukcji można zapisać instrukcje kolejno w jednej linii lub zapisać je w osobnych liniach; wtedy jednak należy zamknąć blok instrukcji w nawiasy „{„ i „}”, dodatkowo takie rozwiązanie sprawia, że kod jest czytelniejszy. W akcjach można modyfikować zawartość zmiennej yytext, jednak nie można zmieniać jej długości, czy dopisywać znaków na końcu, gdyż spowoduje to nadpisanie znaków pobranych na wejściu analizatora; tych które nie zostały jeszcze przetworzone. W akcjach można również zmieniać wartość zmiennej yyleng, jednak jest to zabronione w przypadku gdy w tej samej akcji korzysta się z funkcji yymore(). Predefiniowane dyrektywy, z których można korzystać w akcjach: Dyrektywa Znaczenie Kopiuje zawartość zmiennej yytext na wyjście analizatora. ECHO BEGIN() Pozwala przechodzić między stanami w analizatorze leksykalnym. | Karolina Nurzyńska © 4 REJECT yymore() yyless(int n) unput(char c) input() Wymusza na analizatorze znalezienie następnej pasującej reguły i wykonanie dla niej akcji. Wymusza na analizatorze pozostawienie w zmiennej yytext dopasowanego tokena, tak aby był on widoczny po dopasowaniu następnej reguły. Przesyła na wejście analizatora wszystkie poza n pierwszymi znakami z właśnie dopasowanego tokena, by umożliwić ich kolejne przetworzenie. Przesyła znak na wejście analizatora. Ten znak będzie jako pierwszy wzięty do dopasowania do wzorca. Pobiera kolejny znak z wejścia analizatora. Program do zliczania linii i znaków w pliku: %option noyywrap %{ #include <stdlib.h> int liczbaZnakow = 0; int liczbaLinii = 0; %} %% \n . %% liczbaZnakow++;liczbaLinii++; liczbaZnakow++; int main(int argc, char** argv) { if(argc > 1) { yyin = fopen(argv[1], „r”); yylex(); printf("Liczba znaków wynosi %d natomiast liczba linii to %d", liczbaZnakow, liczbaLinii); } fclose(yyin); return 0; } Zadania: 1. Napisz program, który będzie zliczał liczbę liter oraz cyfr w linii tekstu. Po wciśnięciu entera powinien wypisać wynik. 2. Napisz program, który będzie liczył liczbę słów w podanej linii tekstu i po wciśnięciu entera wypisze wynik na ekranie. Wygenerowany analizator Wynikiem działania programu FLEX jest program zapisany w pliku lex.yy.c, który zawiera główną metodę yylex() oraz wygenerowane tablice służące do rozpoznawania tokenów oraz listę wygenerowanych makr oraz funkcji. | Karolina Nurzyńska © 5 Metoda yylex() pobiera zawsze dane ze strumienia, do którego można mieć dostęp poprzez globalną zmienną plikową yyin (domyślnie jest to stdin), i działa tak długo, aż dotrze do końca pliku lub jedna z jej akcji będzie zawierała wyrażenie return. W przypadku gdy analizator dojdzie do końca pliku, kolejne odwołania do niego są niezdefiniowane, chyba że zmiennej yyin zostanie przypisany nowy plik z danymi do przetworzenia. W przypadku zakończenia pracy analizatora po wykonaniu wyrażenia return, kolejne wywołanie funkcji yylex() spowoduje wznowienie pracy analizatora w miejscu, gdzie ostatnio skończył. Gdy zostanie rozpoznany stan końca pliku, analizator automatycznie przechodzi do funkcji yywrap(). Jeżeli nie jest ona zdefiniowana, należy włączyć opcję %noyywrap, by została ona wygenerowana automatycznie. Funkcja yywrap domyślnie zwraca wartość prawda. Jeżeli jednak chcielibyśmy kontynuować pracę analizatora, powinniśmy zastąpić domyślną definicję tej funkcji w części kodu użytkownika i przypisać zmiennej yyin nowy plik, który będzie przetwarzany. Domyślnie strumień wyjściowy analizatora jest połączony ze standardowym wyjściem na ekran (stdout). Jednak istnieje możliwość przekierowania go na dowolne inne wyjście poprzez odpowiednie przedefiniowanie globalnej zmiennej plikowej yyout. Obsługa końca pliku Istnieje specjalny wzorzec <<EOF>>, który umożliwia wykonanie akcji w momencie, gdy został rozpoznany koniec pliku i jednocześnie funkcja yywrap() zwróciła prawdę. Akcje wykonane w przypadku dopasowania tego wzorca mogą być następujące: Przypisać zmiennej yyin nowy strumień. Wykonać instrukcję return. Wykonać funkcję yyterminate(), która informuje skaner o tym, że wszystko z wejścia zostało przetworzone i można zakończyć pracę. Przełączyć się między buforami za pomocą funkcji yy_switch_to_buffer. Wzorzec <<EOF>> nie może być łączony z innymi wzorcami. Jeżeli istnieje wzorzec <<EOF>> nieprzypisany do żadnego stanu, to będzie on dopasowywany w każdym ze stanów, jeżeli dla danego stanu nie został taki wzorzec zdefiniowany. Stany analizatora FLEX umożliwia warunkowe rozpoznawanie wzorców. Wykorzystywane są w tym celu stany analizatora. Stany analizatora definiuje się w sekcji definicji poprzez rozpoczęcie linii przełącznikiem %s lub %x i następnie podanie listy nazw stanów oddzielonych przecinkami. %s definiuje stany łączne, czyli takie, które rozpatrują również wzorce nie przypisane do żadnego stanu. Natomiast %x definiuje stany wyłączne i w tym przypadku rozpatrywane są wzorce należące tylko do tego stanu. W sekcji reguł analizatora każdy wzorzec może zostać przypisany do wybranego stanu lub grupy stanów w następujący sposób: | Karolina Nurzyńska © 6 <STAN>WZORZEC %s – definiuje stan inclusive (łączny) INITIAL %x – definiuje stan exlusive (wyłączny) INITIAL gdzie STAN jest nazwą stanu zdefiniowaną w sekcji definicji. W przypadku większej liczby STANów należy rozdzielić je spacją. Dodatkowo, jeżeli do jednego stanu chcemy przypisać więcej wzorców, można zastosować następującą konstrukcję: <STAN>{ WZORZEC_1 ... WZORZEC_2 } Po rozpoczęciu pracy analizator znajduje się w stanie domyślnym (INITIAL). Do zmiany stanu pracy analizatora służy makro BEGIN, któremu jako parametr podaje się nazwę stanu, do jakiego ma przejść. Należy pamiętać, że analizator sam nigdy nie zmieni stanu analizatora. Program rozpoznający napisy oraz komentarze: %option noyywrap %x STRING COMMENT %% \" BEGIN(STRING); "/*" BEGIN(COMMENT); <STRING>[a-z]* <STRING>\" printf("Zawartosc stringa %s \n", yytext); BEGIN(INITIAL); <COMMENT>{ [a-z]* "*/" } printf("W komentarzu %s \n", yytext); BEGIN(INITIAL); %% int main() { yylex(); return 0; } Globalne zmienne YYSTATE oraz YY_START zwracają wartość bieżącego stanu analizatora. Istnieje również możliwość zagnieżdżania odwołań do stanów. W tym celu został stworzony stos, który umożliwia zapamiętywanie kolejnych stanów, przez które przechodzi analizator. Pamięć stosu przydzielana jest dynamicznie. W celu wykorzystania tego stosu należy posługiwać się następującymi funkcjami: Funkcja Działanie yy_push_state(nowy_stan) Przełącza bieżący stan pracy analizatora na nowy i odkłada jego wartość na stosie. yy_pop_state() Pobiera stan znajdujący się na góre stosu i przy | Karolina Nurzyńska © 7 yy_top_state() wykorzystaniu funkcji BEGIN przechodzi do pracy w nim. Pobiera stan z góry stosu, bez zmiany zawartości. Zadania: 1. Napisz program, który będzie rozpoznawał zawartość komentarzy jednoliniowych w C (rozpoczynających się od znaków „//” i kończących wraz z końcem linii. Przetwarzanie wielu plików Niektóre analizatory wymagają współpracy z wieloma plikami. W tym celu został stworzony specjalny mechanizm buforów umożliwiający w prosty sposób przełączanie się między plikami. YY_BUFFER_STATE yy_create_buffer(FILE* file, int size) YY_BUFFER_STATE yy_new_buffer(FILE* file, int size) Pobiera zmienną plikową oraz rozmiar pliku i tworzy powiązany z nimi bufor danych. W przypadku gdy nie jest znany potrzebny rozmiar bufora, należy jako drugi parametr wywołania podać YY_BUF_SIZE. YY_BUFFER_STATE jest predefiniowaną strukturą: void yy_switch_to_buffer(YY_BUFFER_STATE nowy bufor); Funkcja podmienia plik na wejściu analizatora na ten powiązany z buforem podanym jako parametr. Funkcja ta często jest wykorzystywana w ciele funkcji yywrap(), by kontynuować skanowanie z kolejnego pliku. void yy_delete_buffer(YY_BUFFER_STATE bufor); Funkcja zwalnia zasoby związane z buforem. YY_CURRENT_BUFFER Makro zwracające YY_BUFFER_STATE dla bieżącego bufora. Współpraca z YACC Jednym z głównych zastosowań analizatora leksykalnego jest połączenie go wraz z analizatorem składniowym wygenerowanym przez program YACC. Interakcja między tymi programami przebiega w następujący sposób: Analizator składniowy wywołuje metodę yylex(), w wyniku której zostaje do niego przekazany token opisany przez typ oraz wartość (zapisywaną do zmiennej yylval). Nazwy tokenów ustalane są w ciele programu napisanego dla YACC’a, jednak wywołanie kompilacji tego programu z opcją „-d” generuje dodatkowy plik nagłówkowy y.tab.h, w którym znajdują się definicje tokenów. Plik ten należy dołączyć do pliku napisanego w analizatorze leksykalnym. Umożliwia to odwołanie się do tych samych zmiennych. 8 | Karolina Nurzyńska © Program do dołączania plików po słowie #include: %{ #define MAX 100 YY_BUFFER_STATE stos[MAX]; int nBufor = 0; %} %x INCLUDE %% "include" BEGIN(INCLUDE); [a-zA-Z]+ ECHO; /*Wypisujemy zawartość pliku*/ [0-9]+ ECHO; <INCLUDE>{ [\t]* ; /*Omijaj tabulacje*/ [a-z]+"."[a-z]{3}{ stos[nBufor++] = YY_CURRENT_BUFFER; yyin = fopen(yytext, "r"); yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE)); BEGIN(INITIAL); } } <<EOF>> { if(nBufor == 0) { yyterminate(); } else { yy_delete_buffer(YY_CURRENT_BUFFER); yy_switch_to_buffer(stos[--nBufor]); } } %% int yywrap() { printf("Wykryto koniec pliku\n"); return 1; } int main(int argc, char** argv) { yylex(); return 0; } 9 | Karolina Nurzyńska ©