Flex jest generatorem analizatorów leksykalnych.
Działanie flex-a jest przedstawione na rysunku:
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 dla programu flex (plik z rozszerzeniem .l) 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
Inny przykład - skaner dla pseudo-Pascala (źródło: http://www.gnu.org/software/flex/manual/html_mono/flex.html).
/* scanner for a toy Pascal-like language */ %{ /* need this for the call to atof() below */ #include <math.h> %} DIGIT [0-9] ID [a-z][a-z0-9]* %% {DIGIT}+ { printf( "An integer: %s (%d)\n", yytext, atoi( yytext ) ); } {DIGIT}+"."{DIGIT}* { printf( "A float: %s (%g)\n", yytext, atof( yytext ) ); } if|then|begin|end|procedure|function { printf( "A keyword: %s\n", yytext ); } {ID} printf( "An identifier: %s\n", yytext ); "+"|"-"|"*"|"/" printf( "An operator: %s\n", yytext ); "{"[^}\n]*"}" /* eat up one-line comments */ [ \t\n]+ /* eat up whitespace */ . printf( "Unrecognized character: %s\n", yytext ); %% main( argc, argv ) int argc; char **argv; { ++argv, --argc; /* skip over program name */ if ( argc > 0 ) yyin = fopen( argv[0], "r" ); else yyin = stdin; yylex(); }
Źródła: pseudo-pascal.l
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
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
make
jest narzędziem, które pozwala na zautomatyzowanie procesu tworzenia plików wykonywalnych na programów zawierający wiele plików Ľródłowych. Program make
odczytuje konfiguracje z pliku o nazwie Makefile
. Plik Makefile
jest podzielony na sekcje. Każda sekcja ma następującą strukturę:
plik_docelowy: lista_wymaganych_plikow <TAB> polecenie_1 <TAB> polecenie_n
Rozważmy przykład. Mamy stworzyć działający skaner na podstawie specyfikacji flex-a. Zależności pomiędzy plikami ilustruje rysunek.
Przykładowy plik Makefile
może wyglądać następująco:
example: lex.yy.c gcc -o example lex.yy.c -lfl lex.yy.c: example.l flex example.l
Źródła: Makefile1
Po wpisaniu make
z linii komand polecenia wyspecyfikowane w pliku Makefile
zostaną wykonane i zostanie utworzony plik wykonywalny. Poniżej przedstawiony jest przykład wykorzystujacy makra:
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: Makefile2
Makra są używane do zastępowania wielokrotnie powtarzających się ciągów znaków. Definicje mark pojawiają się na początku pliku Makefile
w postaci: nazwa_makra = zastepowany_ciag_znakow
. Odwołania do makr w dalszej częsci pliku Makefile
mają postać: ${nazwa_makra}
.
Więcej informacji: http://www.mtsu.edu/~csdept/FacilitiesAndResources/make.htm, http://developers.sun.com/solaris/articles/make_utility.html
Generator skanerów flex - manual po polsku.
Flex, version 2.5 - A fast scanner generator, Vern Paxson
Lex - A Lexical Analyzer Generator, M. E. Lesk
Poniżej znajdują się specyfikacje flex-a dla skanerów, które w ciągu znaków pojawiających się na wejściu wyszukują daty w formacie dd.mm.rrrr i wypisują ją na standardowe wyjście. Proszę obejrzeć specyfikację, wygenerować skaner i sprawdzić jego działanie. Następnie proszę obejrzeć wygenerowny kod skanera i odnaleźć w nim fragmenty zawarte w pliku specyfikacji.
Źródła: data1.l | data2.l | test_data
Poniżej znajduje się specyfikacja flex-a dla skanera, który rozpoznaje identyfikatory, liczby całkowite i liczby rzeczywiste oraz wypisuje na wyjście informacje, w której linii pliku wejściowego się znajdują. Proszę obejrzeć specyfikację, wygenerować skaner i sprawdzić jego działanie przy pomocy pliku testowego.
Źródła: identyfikatory.l | test_identyfikatory
Proszę wygenerować skaner, który będzie wyrywać ciągi znaków będące rzeczywistymi datami w formacie dd.mm.rrrr (np. eliminowane są ciągi typu 99.99.9999, poprawna data 01.01.2000 wygląda następująco 1.01.2000).
Można spróbować napisać wyrażenia regularne do rozpoznawania dat w innych formatach (np. daty typu 2/3/2000, 2 Mar 2000, 2 III 2000, itd.).
Proszę wygenerować skaner, który rozpoznaje poprawne identyfikatory, liczby całkowite i liczby rzeczywiste (także w formacie naukowym).
Źródła: test_identyfikatory_2
Proszę przygotować skaner akceptujący zawartość pliku /etc/services
, który będzie rozpoznawał poszczególne elementy pliku oraz wyświetlał ich "typ" i "zawartość" (patrz poniżej). Przykładowo:
telnet 23/tcp http 80/tcp www www-http # WorldWideWeb HTTP
Źródła: test_services
Przykładowy wynik działania programu:
nazwa serwisu: telnet numer portu: 23 nazwa protokolu: tcp brak opisu nazwa serwisu: http numer portu: 80 nazwa protokolu: tcp nazwa aliasu: www nazwa aliasu: www-http opis: WorldWideWeb HTTP
W razie wystąpienia błędów składni program powinien wypisać komunikat o błędzie.
Zadanie polega na stworzeniu skanera do analizy logów w formacie tcpdump (tcpdump manual).
Skaner po wczytaniu prawidłowego logu ma wypisywać informację o godzinie, adresie hostów między którymi przesyłane są pakiety oraz o liczbie ewentualnie przesłanych danych, czyli dla wejścia:
11:57:13.923781 IP 149.156.99.111.1791 > 66.102.9.99.80: P 1560712269:1560712573(304) ack... 11:57:13.980726 IP 66.102.9.99.80 > 149.156.99.111.1791: . ack 304 win 7886 11:57:13.981908 IP 66.102.9.99.80 > 149.156.99.111.1791: . ack 304 win 6432 11:57:13.986793 IP 66.102.9.99.80 > 149.156.99.111.1791: P 1431:1636(205) ack 304 win 6432 11:57:13.987073 IP 149.156.99.111.1791 > 66.102.9.99.80: . ack 1 win 65535
ma dawać na wyjściu:
11:57:13 from 149.156.99.111 to 66.102.9.99 304 bytes 11:57:13 from 66.102.9.99 to 149.156.99.111 11:57:13 from 66.102.9.99 to 149.156.99.111 11:57:13 from 66.102.9.99 to 149.156.99.111 205 bytes 11:57:13 from 149.156.99.111 to 66.102.9.99
Linie w nieprawidłowym formacie powinny być pominięte. Proszę sprawdzać poprawność części logu znajdującej się przed :, w tym poprawność godziny i adresu IP (adres może być zbudowany z liczb z zakresu 0-255).
Przyładowy log tcpdump: test-log.