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 ©

Podobne dokumenty