You are on page 1of 12

ELEKTROTEHNIKI FAKULTET BANJA LUKA TEORETSKE OSNOVE RAUNARSTVA A405

(f)lex
Uvod flex (The Fast Lexical Analyzer) je alat za generisanje leksikih analizatora. flex-om se generiu skeneri programi koji prepoznaju leksike uzorke u tekstu. flex ita specifikovane ulazne fajlove (ili standardni ulaz ukoliko nije specifikovan nijedan ulazni fajl) za opis skenera koji se generie. Opis je u formi parova regularnih izraza i C koda, zvanih pravila. flex generie C fajl kao izlaz, podrazumevano lex.yy.c, u kojem je definisana funkcija yylex(). Ovaj fajl kompajliran i linkovan sa flex bibliotekom daje izvrni program. Kada se izvrni program pokrene, on analizira ulaz na pojavljivanja regularnih izraza. Format ulaznog fajla Ulaz flex-a se sastoji od tri dela, meusobno razdvojena linijom koja sadri samo %%.
... definicije ... %% ... pravila ... %% ... korisniki kod ...

Deo definicija sadri deklaracije definicija imena koje omoguavaju jednostavnije definisanje specifikacije skenera, i deklaracije poetnih uslova. Opti oblik definicije imena je:
ime definicija

Ime je re koja poinje slovom ili znakom _ iza kojeg sledi nula ili vie slova, brojeva ili znakova _ i -. Kao definicija uzima se sve od prvog non-whitespace znaka nakon imena, pa do kraja linije. Definicija se moe pozvati koritenjem konstrukcije {ime}, koja e biti zamenjena sa (definicija). Na primer,
CIFRA JMB [0-9] [0-9]{13}

definie ime CIFRA koje predstavlja regularni izraz koji opisuje jednu cifru, i ime JMB koje definie regularni izraz koji opisuje niz od 13 cifara. Konstrukcija
{CIFRA}+"."{CIFRA}*

odgovara
([0-9])+"."([0-9])*

U delu definicija, svaki uvueni tekst ili tekst okruen sekvencama %{ i %} kopira se na izlaz (pri emu su uklonjene sekvence %{ i %}). Sekvence %{ i %} moraju se pojaviti same u liniji i ne smeju biti uvuene. Takoe, neuvueni komentar (linija koja poinje sekvencom /*) kopira se na izlaz sve do sekvence */. Primer:
%{ #include <primer.h> int broj; %} 1

Deo pravila sadri seriju pravila iji je opti oblik:


uzorak akcija

gde uzorak ne sme biti uvuen, a akcija mora da pone u istoj liniji. U delu pravila, svaki uvueni tekst ili tekst okruen sekvencama %{ i %} koji se pojavljuje pre prvog pravila moe se iskoristiti za deklarisanje lokalnih varijabli funkcije za skeniranje i (nakon deklaracija) koda koji se izvrava svaki put kada se pozove funkcija za skeniranje. Uvueni tekst i tekst okruen sekvencama %{ i %} kopira se na izlaz (pri emu su uklonjene sekvence %{ i %}). Sekvence %{ i %} moraju se pojaviti neuvuene i same u liniji. Deo korisnikog koda se jednostavno kopira na izlaz. Obino se koristi za definisanje funkcija koje pozivaju ili koje poziva skener. Ovaj deo je opcion, a ako se izostavi tada se moe izostaviti druga %% sekvenca. Primer:
int main() { yylex(); /* ... */ return 0; } int max(int a, int b) { return (a>b) ? a : b; }

Kako radi skener? Kada se pokrene generisani skener, on analizira ulaz i trai stringove koji se poklapaju sa nekim od specifikovanih uzoraka. Ako pronae vie poklapanj a, bira se poklapanje sa najvie teksta (raunajui trailing context). Ukoliko postoji vie poklapanja iste duine, bira se ono koje je definisano prvo. Kada se odredi poklapanje, izvrava se akcija koja odgovara uzoraku koji se poklapa sa pronaenim tekstom, a nakon toga skenira preostali ulaz i trai se novo poklapanje. Pronaeni tekst koji se poklapa sa uzorkom naziva se token. Dostupan je preko globalne varijable yytext, a njegova duina preko yyleng. Ukoliko se ne nae nijedno poklapanje, primenjuje se podrazumevano pravilo: sledei znak se posmatra kao poklapanje i kopira se na izlaz. Prema tome, najjednostavniji validni ulaz flex-a je:
%%

koji generie skener koji jednostavno kopira ulaz na izlaz (znak po znak). Akcije Svaki uzorak u pravilu ima odgovarajuu akciju, koja moe biti bilo koji C iskaz. Kraj uzorka predstavlja prvi non-escaped whitespace znak; ostatak linije je akcija. Ukoliko akcija nije specifikovana, tada se pronaeni token jednostavno brie. Na primer, specifikacija skenera koji brie sa ulaza sva poklapanja sa nizom znakova TeOR je:
%% "TeOR"

Ovaj skener e sve ostale znakove sa ulaza kopirani na izlaz jer se poklapaju sa podrazumevanim pravilom.

Skener koji e viestruke beline (space-ovi i tab-ovi) zameniti jednom, i koji e obrisati beline s kraja linije, je:
%% [ \t]+ [ \t]+$ putchar(' ');

Ako akcija sadri otvarajuu srednju zagradu ({), tada se akcija protee do odgovarajue zatvarajue srednje zagrade (}), tako da akcija moe biti i vielinijska. Akcije mogu poeti i sekvencom %{ i u tom sluaju akcijom se smatra itav tekst do sekvence %}. Akcija koja se sastoji samo od znaka | ima znaenje isto kao i akcija sledeeg pravila. Kako akcije mogu sadravati C kod, tako mogu sadravati i naredbu return, koja vraa vrednost funkciji koja je pozvala funkciju yylex(). Svaki put kada se pozove funkcija yylex(), ona nastavlja sa skeniranjem tamo gde je zadnji put stala, dok ne doe d o kraja fajla ili return naredbe. Pri definisanju skenera mogu se koristiti brojne predefinisane varijable, makroi i funkcije. U tabeli 1 dat je pregled osnovnih predefinisanih varijabli, makroa i funkcija.
Tabela 1. Pregled osnovnih predefinisanih varijabli, makroa i funkcija
int yylex() char *yytext int yyleng int yylval FILE* yyin FILE* yyout int yywrap()

Pozivom ove funkcije vri se leksika analiza. Pronaeni tekst (token) koji se poklapa sa uzorkom u odgovarajuem pravilu. Duina tokena. Vrednost pridruena tokenu. Ulazni fajl. Izlazni fajl. Poziva se kada skener stigne do kraja ulaznog fajla. Vraa 0 ili 1. Ukoliko funkcija vrati 1, analiza se zavrava i program se vraa iz funkcije yylex(). U sluaju vraene 0, analiza se nastavlja. Ovo je pogodno kada treba obraditi vie fajlova zaredom u funkciji yywrap() redefinie se yyin tako da pokazuje na sledei fajl koji treba obraditi i vrati se 0 kao rezultat funkcije. Kada se zavri obrada poslednjeg fajla, vrati se 1 i time se zavrava leksika analiza. Postoji unapred definisana verzija funkcije yywrap() u flex biblioteci koja uvek vraa 1. Da bi se mogla koristiti ova predefinisana funkcija, potrebno je ukljuiti flex biblioteku pri linkovanju programa, opcijom lfl kod poziva gcc-a. U istoj biblioteci nalazi se i predefinisana verzija funkcije main(), koja se sastoji samo iz poziva funkcije yylex(). Kopira yytext na izlaz skenera. ECHO je makro definisan kao:
#define ECHO fwrite(yytext, yyleng, 1, yyout)

ECHO

REJECT BEGIN NAZIV_KONTEKST

Nakon izvrene akcije nad tokenom, nastavlja nad njim ispitivanje preostalih pravila. Stavlja analizator u odgovarajue stanje, odnosno kontekst. Stanja se definiu u delu definicija navoenjem:
%s NAZIV_KONTEKSTA

Na poetku analize, analizator se nalazi u inicijalnom kontekstu koji je oznaen kao INITIAL. Poreenje sa uzorcima se moe ograniiti uslovom da se u datom trenutku skener nalazi u odgovarajuem kontekstu

sa:
<NAZIV_KONTEKSTA>uzorak akcija

INITIAL int yyless(int n)

int yymore()

int unput(int c) int input() yyterminate()

Sada odgovarajui string moe biti uparen sa uzorkom samo onda kad se skener nalazi u kontekstu NAZIV_KONTEKSTA. Naziv inicijalnog konteksta. Vraa sve osim prvih n znakova tokena na ulazni stream tako da e oni biti ponovo skenirani pri traenju sledeeg poklapanja. Govori skeneru da kada nae sledee poklapanje, odgovarajui token doda na kraj trenutne vrednosti promenljive yytext umesto toga da je zameni. Vraa znak c na ulazni stream. Znak c e biti sledei skenirati znak. ita sledei znak sa ulaznog stream-a. Prekida skeniranje i vraa 0 pozivaocu skenera, oznaavajui da je sve zavreno. Poziva se podrazumevano kada se doe do kraja fajla.

Trailing context Ukoliko je potrebno ograniiti poklapanje stringa sa jednim uzorkom samo na sluajeve kada se iza tog stringa nalazi string koji se poklapa sa drugim uzorkom, koristi se konstrukcija:
uzorak1/uzorak2 akcija

Koritenje flex-a Opti oblik flex naredbe je:


flex [opcije] [naziv_ulaznog_fajla]

Neke od osnovnih opcija su: -o, --outfile=FILE Omoguava specifikovanje naziva izlaznog fajla. -t, --stdout Generie skener na stdout umesto u fajl lex.yy.c. -P, --prefix=STRING Koristi STRING kao prefiks umesto yy. --stdinit Inicijalizuje yyin/yyout tako da pokazuju na stdin/stdout. Kompajliranje generisanog fajla i linkovanje sa flex bibliotekom se obavlja naredbom gcc na sledei nain:
gcc naziv_generisanog_fajla.c lfl o naziv_izvrsnog_programa

Skener se pokree sa:


./naziv_izvrsnog_programa [< ulazni_fajl.txt]

Podrazumevano, yyin pokazuje na stdin, pa ukoliko se eli skenirati neki fajl potrebno je inicijalizovati yyin tako da pokazuje na dati fajl, ili se moe izvriti redirekcija. Primeri generisanja, kompajliranja i pokretanja skenera dati su na sl. 1.

Slika 1. Primeri generisanja, kompajliranja i pokretanja skenera

Primeri
Tabela 2. Primeri leksikih analizatora

(f)lex program
%% %% . %% [aA] ECHO; . %% . [aA] ECHO; %% \n %% . \n %% "TeOR" %% TeOR %% [ \t]+ putchar(' '); [ \t]+$ int br_reci = 0; %% TeOR REJECT; [^ \t\n]+ ++br_reci; %% int main() { yylex(); printf("Br. reci: %d\n", br_reci); return 0; }

Ulaz
Teoretske osnove racunarstva - TeOR A405 Teoretske osnove racunarstva - TeOR A405 Teoretske osnove racunarstva - TeOR A405 Teoretske osnove racunarstva - TeOR A405 Teoretske osnove racunarstva - TeOR A405 Teoretske osnove racunarstva - TeOR A405 Teoretske osnove racunarstva - TeOR A405 Teoretske osnove racunarstva - TeOR A405 Jedan Dva Tri Cetiri Pet Teoretske osnove racunarstva - TeOR A405 Br. reci: 6

Izlaz
Teoretske osnove racunarstva - TeOR A405

aaa A

Teoretske osnove racunarstva - TeORA405

Teoretske osnove racunarstva A405 Teoretske osnove racunarstva A405 Jedan Dva Tri Cetiri Pet

%% a ab abc abcd .|\n %% a ab abc abcd

| | | ECHO; REJECT; | | | ECHO; REJECT;

%% megaECHO; yymore(); test ECHO; %% mega-test ECHO; yyless(4); [a-z]+ ECHO; %% username printf("%s", getlogin()); int br_linija = 0, br_znakova = 0; %% \n ++br_linija; ++br_znakova; . ++br_znakova; %% int main() { yylex(); printf( "# linije = %d, # znakovi = %d\n", br_linija, br_znakova); return 0; } cifra [0-9] broj {cifra}+ %% {broj} printf("%d", 2*atoi(yytext)); cifra [0-9] broj {cifra}+ %% {broj} printf("%s\n", yytext); .|\n

a ab abc abcd abcde a ab abc abcd abcde This is mega-test This is mega-test My name is username. Teoretske osnove racunarstva - TeOR A405

aabaabcabaabcdabcabaabcdabcaba

aa abaab abcabaabc abcdabcabaabcd abcdabcabaabcde This is mega-mega-test This is mega-test-test My name is goran. # linije = 2, # znakovi = 41

Dva puta 12 je 24. 2*12=24 Dva puta 12 je 24. 2*12=24

Dva puta 24 je 48. 4*24=48 12 24 2 12 24

%% super/man

%% [a-zA-Z]+/[,;:".?!] { printf("Pronadjeno: %s \n",yytext); } .|\n ; %% %% [a-zA-Z][a-zA-Z0-9]* { printf("REC"); } "<"[^\<]+">" { printf("TAG %s ", yytext); } [0-9]{1,3}[/]0[0-8] { printf("Br. indeksa: %s.\n", yytext); }

superman je superheroj super/man je superheroj Januar ima 31 dan, a april 30 dana. U koliko sati je ispit? "Danas imam kolokvijum", rece Marko.

%{ int c=0, w=0, l=0; %} rec [^ \t\n]+ eol \n %% {rec} { w++; c+=yyleng; } {eol} { l++; c++; } . { c++; } %% int main() { yylex(); printf("%d ** %d ** %d\n", l, w, c); return 0; } %% [^ \t\n] ECHO; . { } [\n] { printf("<>"); ECHO; } %% abcd ECHO; unput(yytext[0]); unput(yytext[1]); unput('A'); Aba printf("+"); %% char c; abcd c = input(); if (c == 'A') ECHO;

Januar ima 31 dan, a april 30 dana. 234 U koliko sati je ispit? 9907/33 <title>Hello world!</title> 81/03 Januar ima 31 dan, a april 30 dana. 234 U koliko sati je ispit?

man je superheroj super/man je superheroj Pronadjeno: dan Pronadjeno: dana Pronadjeno: ispit Pronadjeno: kolokvijum Pronadjeno: Marko REC REC 31 REC, REC REC 30 REC. 234 REC REC REC REC REC? 9907/33 TAG <title> REC REC!TAG </title> Br. indeksa: 81/03. 3 ** 14 ** 65

Januar ima 31 dan, a april 30 dana. 234 U koliko sati je ispit? abcd abcdAabcdB

Januarima31dan,aapril30dana.<> 234<> Ukolikosatijeispit?<> abcd+ abcd

%s expect %% expect-floats <expect>[0-9]+"."[0-9]+ <expect>\n [0-9]+ "." [ ] %s expect %% expect-floats <expect>[0-9]+"."[0-9]+ <expect>\n <expect>[0-9]+ <expect>"." <expect>[ ] %s state1 %s state2 %% float int <state1>[0-9]+"."[0-9]+ <state2>[0-9]+ <state1,state2>abc

BEGIN(expect); { printf("dec(%f)", atof(yytext)); } { BEGIN(INITIAL); } { printf("int(%d)", atoi(yytext)); } printf("tacka");

1.25 3 21 expect-floats 2.3 1 4.5 2 2.45

int(1)tackaint(25)int(3)int(21) dec(2.300000)int(1)dec(4.500000)int(2) int(2)tackaint(45)

BEGIN(expect); { printf("dec(%f)", atof(yytext)); } { BEGIN(INITIAL); } { printf("int(%d)", atoi(yytext)); } printf("tacka");

1.25 3 21 expect-floats 2.3 1 4.5 2 2.45

1.25 3 21 dec(2.300000)int(1)dec(4.500000)2 2.45

BEGIN(state1); BEGIN(state2); { printf("f"); } { printf("i"); } { printf("reset"); BEGIN(INITIAL); }

1.25 abc 3 cba 21 int 3 1.25 cba 1 abc 5 float 3 1.25 cba 1 abc 5.21 1.25 abc 3 cba 21

Trazenje... 1.25 abc 3 21 i i.i i reset 5 3 f 1 reset 5.21 1.25 abc 3 21

cba %% int main() { printf("Trazenje...\n"); yylex(); return 0; } %s L LI "*" %% <INITIAL>^{LI} { printf("<ul><li>"); BEGIN L; } <L>^{LI} { printf("<li>"); } <L>^[^*] { printf("</ul>"); BEGIN INITIAL; REJECT; } <L>\n { printf("</li>\n"); }

*1 *2 3 4

<ul><li>1</li> <li>2</li> </ul>3 4

Kao to je ranije pomenuto, funkciju yywrap() je pogodno iskoristiti u situacijama kada je potrebno obraditi vie fajlova zaredom. Na primer, specifikacija skenera koji omoguava obradu fajlova test1.txt i test2.txt je:
%{ char *filenames[] = {"test1.txt", "test2.txt"}; int curr_file, sum; %} cifra [0-9] broj {cifra}+ %% {broj} ECHO; sum += atoi(yytext); %% int yywrap(void) { if (++curr_file < 2) { fclose(yyin); yyin = fopen(filenames[curr_file], "r"); return 0; } else return 1; } int main() { yyin = fopen(filenames[curr_file], "r"); yylex(); printf("\nsum=%d\n", sum); return 0; }

Neka se specifikacija datog skenera nalazi u fajlu suma.l. Neka je sadraj fajla test1.txt:
Januar ima 31 dan, a april 30 dana. 234

Neka je sadraj fajla test2.txt:


TeOR A405

Generisanje, kompajliranje i izvravanje datog skenera ilustrovano je na sl. 2.

10

Slika 2. Generisanje, kompajliranje i pokretanje skenera ija se specifikacija nalazi u fajlu suma.l, a koji omoguava obradu fajlova test1.txt i test2.txt

Specifikacija skenera koji omoguava obradu fajlova koji se navode kao argumenti komandne linije prilikom pokretanja, je:
%{ int sum; %} cifra [0-9] broj {cifra}+ %% {broj} ECHO; sum += atoi(yytext); %% int main(int argc, char *argv[]) { int i; for (i = 1; i < argc; i++) { yyin = fopen(argv[i], "r"); yylex(); fclose(yyin); } printf("\nsum=%d\n", sum); return 0; }

Generisanje, kompajliranje i izvravanje datog skenera (specifikacija se nalazi u fajlu suma.l) ilustrovano je na sl. 3. Sadraj fajlova test1.txt i test2.txt je isti kao i u prethodnom primeru.

11

Slika 3. Generisanje, kompajliranje i pokretanje skenera ija se specifikacija nalazi u fajlu suma.l, a koji omoguava obradu fajlova iji se nazivi specifikuju kao argumenti komandne linije prilikom pokretanja skenera

12

You might also like