Spis treści:
Flex jest generatorem analizatorów leksykalnych (skanerów). Usiłując opisać flex-a w kilku zdaniach można powiedzieć, że:
Działanie flex-a jest schematycznie przedstawione na rysunku:
Poniżej zaprezentowany jest zbiór wyrażeń regularnych udostępniach przez flex-a.
| Wyrażenie | Obejmuje | Przykład |
|---|---|---|
| c | dowolny znak nie będący operatorem | a |
| \c | zwykłe znaczenia znaku | \* |
| "s" | ciąg znaków | "**" |
| . | dowolny znak z wyjątkiem znaku nowej linii | a.b |
| ^ | początej linii | ^początek |
| $ | koniec linii | koniec$ |
| [s] | dowolny znak ze zbioru | [0123abc] |
| [^s] | dowolny znak nie będący w zbiorze | [^0123abc] |
| [s1-s2] | dowolny znak ze zbioru s1-s2 | [0-9] |
| r* | zero badź więcej wystąpień wyrażenia r | a* |
| r+ | jedno badź więcej wystąpień wyrażenia r | a+ |
| r? | zero bądź jedno wystąpienie wyrażenia r | a? |
| r{n,m} | od n do m wystąpień wyrażenia r | a{1,5} |
| r{m} | dokładnie m wystąpień wyrażenia r | a{5} |
| r1r2 | wyrażenie r1 a następnie r2 | ab |
| r1|r2 | wyrażenie r1 lub r2 | a|b |
| (r) | wyrażenie r | (a|b) |
| r1/r2 | wyrażenie r1 gdy następuje po nim r2 | a/b |
| <x> r | wyrażenie r pod warunkiem przebywania w stanie x | <x>abc |
Przykłady:
| Wyrażenie regularne | Akceptowane ciągi znaków |
|---|---|
| flex | flex |
| [0-9][^0-9] | 2a, 3%, 5m |
| L(R[0-9]|L[0-9]) | LR1, LL0 |
| "-"?1 | -1, 1 |
| 0x[0-9A-F]+ | 0xFF |
| DD" "[0-9]{7} | DD 3452378 |
Specyfikacja (wejście) dla flex-a to najczęściej plik z rozszerzeniem .l, np. example.l. Specyfikacja ma następujący układ:
definicje pomocnicze %% reguły %% podprogramy pomocnicze
Sekcja definicje pomocnicze umożliwiaja zdefinowanie pomocniczych nazw dla złożonych wyrażeń regularnych oraz podanie kodu, który będzie bezpośrdnio kopiowany na początek kodu skanera.
Sekcja reguły zawiera wyrażenia regularnym wraz z przypisanymi im akcjami w języku C (lub C++).
Sekcja podprogramy pomocnicze umożliwia podanie kodu, który ma być skopiowany bezpośrednio na koniec pliku skanera.
Poniżej przedstawiony jest specyfikacja flexa dla skanera, który wyszukuje ciąg znaków lex i zamienia go na ciąg znaków flex.
%{
/* Program wyszukuje ciag znakow "lex" i zamienia go na ciag znakow "flex" */
%}
%%
lex printf("flex");
%%
main() {
printf("Zamiana lex na flex:\n");
yylex();
return 0;
}
Źródła: zamien.l
Aby wygenerować kod skanera wpisujemy:
flex zamien.l
Otrzymany w ten sposób kod należy skompilować:
gcc lex.yy.c -o
zamien -lfl
Skaner można uruchomić z przykladowym plikiem testowym test_zamien:
./zamien
<test_zamien
Przykładowy Makefile:
PROG = example
all : ${PROG}
lex.yy.c: ${PROG}.l
flex ${PROG}.l
${PROG}: lex.yy.c
gcc -o ${PROG} lex.yy.c -lfl
Źródła: Makefile
Więcej informacji:
http://developers.sun.com/solaris/articles/make_utility.html
http://www.gnu.org/software/make/manual/html_chapter/make.html#SEC_Top
Gdy więcej niż jedna reguła pasuje do ciągu wejściowego flex stosuje kolejno dwie zasady:
%%
begin printf("%s slowo kluczowe\n", yytext); // regula 1
[a-z]+ printf("%s identyfikator\n", yytext); // regula 2
.|\n /* empty */
Jeżeli na wejściu pojawi się ciąg znaków beginner, to zadziała
reguła 2 i flex potraktuje ciąg znaków jako identyfikator, bo reguła
begin dopasuje 5 znaków, a reguła [a-z]+ dopasuje 8
znaków.
Jeżeli na wejściu pojawi się ciąg znaków begin, to zadziała
reguła 1 i flex potraktuje go jako słowo kluczowe, bo obie reguły dopasowują po
5 znaków.
Źródła: begin.l
Zdarza się, że w różnych momentach analizy dla tego samego wzorca mają być zastosowane różne akcje. Taka sytuacja wymaga rozpoznawania kontekstu przez skaner.
Flex oferuje następujące metody rozpoznawania kontekstu:
^ - początek linii, $ - koniec linii, / - prawy kontekst.
%{
/* passwd.l
Analiza zawartosci pliku passwd */
%}
%%
^ewa printf("uzytkownik ewa ma konto w systemie\n");
:$ printf("uzytkownik nie ma zdefiniowanego shell-a\n");
.|\n ;
Źródła: passwd.l | test_passwd
%{
/* shutdown.l
dopasowuje ciag shut, jezeli wystapi po nim ciag down */
%}
%%
shut/down printf("%s\n", yytext);
.|\n /* empty */
Źródła: shudown.l
Do rozpoznawania lewego kontekstu można wykorzystać również stany startowe. Zilustrowano to poniżej.
Przykład użycia stanów startowych
%{
/* jaguar1.l
Rozpoznawanie znaczenia slowa jaguar w zaleznosci od lewego kontekstu
przy uzyciu stanow */
%}
%s A B
%%
^a BEGIN(A);
^b BEGIN(B);
<A>jaguar printf("Duzy kot\n");
<B>jaguar printf("Sportowy samochod\n");
.|\n ;
Źródła: jaguar1.l
Ten sam przykład z użyciem flagi
%{
/* jaguar2.l
Rozpoznawanie znaczenia slowa jaguar w zaleznosci od kontekstu
z wykorzystaniem flagi */
char flag;
%}
%%
^a flag = 'a';
^b flag = 'b';
jaguar switch(flag){
case 'a': printf("Duzy kot\n");
break;
case 'b': printf("Samochod sportowy\n");
break;
}
.|\n ;
Źródła: jaguar2.l
Flex udostępnia również stany wyłączne. W wyłącznym stanie startowym nie działają żadne reguły poza posiadającymi ten stan w prefixie.
%{
/* exclusive.l
demonstruje dzialanie wylacznych stanow startowych */
%}
%x LITERAL
%%
raz ECHO;
dwa ECHO;
# BEGIN(LITERAL);
<LITERAL># BEGIN(INITIAL);
. /* empty */
Źródła: exclusive.l | test_exclusive
Predefiniowane zmienne i funkcje
| Obiekt predefiniowany | Znaczenie |
|---|---|
ECHO |
przekopiowuje dopasowany ciąg znaków na wyjście |
yytext |
dopasowany ciąg znaków |
yyleng |
długość dopasowanego ciągu znaków |
yywrap() |
zwraca 1, gdy jest jeszcze coś na wejściu do przetworzenia; w przeciwnym razie zwraca 0 |
yymore() |
powoduje, że następny rozpoznany ciąg zostanie dopisany do aktualnego,
w yytext otrzymamy ich konkatenację |
yyless(n) |
pozostawia w yytext tylko n początkowych znaków,
pozostałe zwraca z powrotem na wejście |
REJECT |
oznacza: "przejdź do innego wariantu"; porzuca aktualne dopasowanie i powoduje przejście do alternatywnej reguły; aktualnie dopasowany ciąg zostaje zwrócony na wejście |
yyin |
wejście |
yyout |
wyjście |
input |
pobiera następny znak z wejścia |
unput(c) |
wypisuje znak c na wyjście |
yylval |
zmienna globalna wykorzystywana do przekazania atrybutu tokenu przekazywanego do parsera |
Przykłady:
%{
/* meta-dane.l
demonstruje dzialanie funkcji yymore */
%}
%%
meta- ECHO; yymore();
dane ECHO;
Źródła: meta-dane.l
%{
/* very_easy.l
demonstruje wykorzystanie dyrektywy REJECT */
%}
%%
very printf("%s ",yytext); REJECT;
[a-z]+ ECHO;
Źródła: very_easy.l
REJECT = yyless(0);
%{
/* block.l
Zliczanie wystapien ciagow znakow block i lock */
int block_c, lock_c;
%}
%%
block {
block_c++;
/* Po dopasowaniu ciagu block wycofaj
wszystkie znaki poza pierwszym do ponownej analizy */
yyless(1);
}
lock lock_c++;
\n|. /* empty */
%%
main(){
yyout = fopen("scanner_output", "a+");
yylex();
fprintf(yyout, "block - %d, lock - %d.\n", block_c, lock_c);
fclose(yyout);
}
Źródła: block.l | test_block
%{
/* pary.l
Tworzy statystyke wystapien wszystkich par malych liter */
int digram [26][26];
int i, j;
%}
%%
[a-z][a-z] {
digram[yytext[0]-97][yytext[1]-97]++;
REJECT;
}
.|\n /* empty */
%%
main(){
/* wyzeruj tablice */
for(i=0; i<26; i++)
for(j=0; j<26; j++)
digram[i][j] = 0;
yylex();
/* wypisz tablice */
/* Drukowanie podsumowań często umieszcza się także w funkcji yywrap */
for(i=0; i<26; i++)
for(j=0; j<26; j++)
if (digram[i][j] != 0)
printf("%c,%c: %d\n", i+97, j+97, digram[i][j]);
}
Źródła: pary.l
Lex - A Lexical
Analyzer Generator, M. E. Lesk,
Flex - manual po polsku,
Flex, version
2.5 - A fast scanner generator, Vern Paxson.