Download as pdf or txt
Download as pdf or txt
You are on page 1of 20

3 Izrada leksikog analizatora

U prethodnom poglavlju je pokazano kako se moe napisati funkcija getToken() koja obavlja
leksiku analizu. Sada e biti pokazano kako se, na temelju specifikacije leksema pomou regularne
gramatike ili regularnih izraza, moe, generirati programski kd leksikog analizatora.

3.1 Specifikacija leksema


Upoznat emo tri naina specifikacije leksema: konane automate, regularnu gramatiku i regularne
izraze.

3.1.1 Konani automat


Pregledom prethodno opisanog leksikog analizatora, vidimo da se analiza vri izborom
postupka ili stanja koji ovisi o trenutnom ulaznom znaku. Taj postupak se moe formalizirati
definiranjem konanih automata za prepoznavanje leksema. to je to konani automat?
Konani automat je matematiki model odreen sa 5-znaajki (S, , T, I, F)
S

I
F

- skup stanja automata,


- skup ulaznih znakova - alfabet,
- poetno stanje
- skup zavrnih ili konanih (finalnih) stanja.

T - relacija prijelaza S x S,
(stanje S i znak odreuju prijelaz u stanja S)
Konani automat se najee prikazuje kao tranzitivni dijagram. To je usmjereni graf koji se
sastoji od vorova i usmjerenih veza. vorovi oznaavaju stanje automata, a usmjerene veze, koje se
oznaavaju znakovima ulaznog alfabeta, oznaavaju prijelaz u stanje. vorovi su oznaeni krunicom.
vorovi koji predstavljaju konana stanja oznaeni su dvostrukom krunicom.
Primjerice, slijedei automat sa 4 stanja A, B, C i D, prepoznaje literalni zapis realnog broj u
jeziku Fortran, primjerice oblika: 7.89, .98., .8 i 8. Ulazni alfabet se sastoji od znamenki i decimalne
toke .
.

S2

S3

S1

S = { S1, S2, S3, S4 }


d

S4
d

= { . , d } - d je znamenka
T opisan dijagram prijelaza
I = S1
F = { S3, S4 }

Rad automata poinje u stanju S1. Zatim se dobavlja ulazni znak. Ako je taj znak toka, prelazi se u
stanje S2, a ako je znamenka, prelazi sa u stanje S3. Ako je to neki drugi znak, onda je greka. Iz stanja
3. Leksika analiza

S2 prelazi se u stanje S3 ukoliko je ulazni znak znamenka, ili nastupa greka. Stanje S3 je konano
stanje u kojem se automat zadrava sve dok je u ulazni znak znamenka. Kada ulazni znak prestane biti
znamenka znai da je dosegnut kraj zapisa numerike konstante. Stanje S4, takoer, moe biti
konano stanje, ali ako je ulazni znak toka, prelazi se u stanje S3.
Ovakvi automat se moe jednostavno programski realizirati tako da se funkcija prijelaza T, kojom se
odreuje prijelaz iz jednog stanja u drugo stanje, a na temelju trenutnog ulaznog znaka, opie
dvodimenzionalnom tablicom prijelaza:

Klasa ulaznog znaka


stanje
S1
S2
S3
S4

Klasa
znakova
vrijednost

znamenka
(digit)
S4
S3
S3
S4

toka
(dot')
S2
error
error
S3

Zavrno
stanje

ne
ne
da
da

[0-9]

[.]

ostali

digit

dot

ostali

Rad konanog automata realizira funkcija getToken(), koja vraa Token ili greku. Najprije se
automat postavlja u poetno stanje, a zatim se vri petlja koja se ponavlja dok se automat ne postavi u
zavrno (konano) stanja ili stanje greke.
int Transition[MAXSTATE][MAXSIMBOL]; /* stanje klasa-znaka
char currentChar
/*globalna varijabla - sadri ulazni znak
char *lexeme;
/* leksem koji dobavljamo
void Concat(char *s, char ch);
/* dopunja string znakom ch

*/
*/
*/
*/

int getToken()
{
int state = INITIALSTATE; lexeme[0] = '\0'
while (currentChar != EOF)
{
state = Transition[state][CharClass(currentChar)];
if (state == ERROR) // or final state
break;
Concat(lexeme, currentChar);
currentChar = nextChar();
}
if (isFinalState(state))
return(token(state));
else
return(ERROR_TOKEN);
}

3. Leksika analiza

Prethodno opisani automat naziva se deterministiki konani automat (DKA) jer je kod tog
automata uvijek jednoznano odreen prijelaz u neko stanje. Ukoliko bi imali automat u kojem
sljedei ulazni znak vodi u vie razliitih stanja takovi automat se naziva nedeterministiki konani
automat (NKA). On se ne moe realizirati pomou tablice prijelaza.
Kasnije emo pokazati kako se nedeterministiki konani automat moe transformirati u
deterministiki konani automat.
Deterministiki konani automat se moe realizirati i bez definiranja tablice prijelaza. Prijelazi se tada
mogu realizirati pomou goto naredbe i odreditem skoka (labelom) koja odgovaraju stanjima
automata, kao u slijedeem primjeru:
char currentChar
char *lexeme;

/*globalna varijabla - sadri ulazni znak


/* leksem koji dobavljamo

*/
*/

void Concat(char *s, char ch);


/* dopunja string znakom ch */
int AdvanceInput()
{
int klas = charClass(currentChar);
Concat(lexeme, currentChar);
currentChar = nextChar();
return klas;
}

int getToken()
{
lexeme[0] = '\0';

/* prazni string */

int klas;
S1:

klas = AdvanceInput();
if(klas == dot) goto S2;
if(klas == digit) goto S3;
return ERROR_TOKEN;

S2:

klas = AdvanceInput();
if(klas == digit) goto S3;
return ERROR_TOKEN;

S3:

klas = AdvanceInput();
if(klas == digit) goto S3;
return NUM_TOKEN;

S4:

klas = AdvanceInput();
if(klas == dot) goto S3;
if(klas == digit) goto S4;
return NUM_TOKEN;

Verzija s goto naredbom se esto koristi u "runo kodiranim" automatima, a verzije s tablicom
prijelaza se koriste programski generiranim leksikim analizatorima.

3. Leksika analiza

3.1.2 Regularna gramatika


Specifikacija leksema moe se izvriti i pomou regularne gramatike. Ona se zapisuje s dva tipa
produkcija:
A -> b C
C -> d

gdje su A i C neterminalni simboli, a b i d su terminalni simboli.


Primjerice, prethodni se realni broji moe gramatiki definirati s:
A
B
D
C

->
->
->
->

digit
digit
digit
digit

D | dot B
C
| digit D | dot C
C | digit

Regularna gramatika se lako moe predstaviti ekvivalentnim konanim automatom. Pregledom


prethodnog konanog automata vidi se da neterminali regularne gramatike A,B,C,D predstavljaju
stanja automata, a terminali digit i dot oznaavaju prijelaze izmeu stanja automata.

3.1.3 Regularni izrazi


Regularna gramatika nije ba najbolji nain za zapis leksema, jer su njene specifikacije duge i
nepregledne Mnogo bolji nain specifikacije leksema je pomou regularnih izraza.
Regularni izrazi su izrazi kojima se na jednostavan nain zapisuju leksike specifikacije. Regularni
izrazi se sastoje od lanova i operatora. Najprostiji regularni izraz je lan, nazovimo ga a, koji
predstavlja jedan znak ili klasu znakova. Ovakvi lanovi se mogu koristiti u izrazima koji sadre
operatore |,+ i *, prema sljedeim pravilima:
a

je regularni izraz koji predstavlja znak alfabeta (ili klasu znakova)

( epsilon ) je prazni regularni izraz

ab

je regularni izraz koji se formira dopunjavanjem izraza a sa izrazom b.

a|b

je regularni izraz koji se formira izborom izraza a ili b.

a*

je regularni izraz koji se formira od nula ili vie uzastopnih izraza a.

a+

je regularni izraz koji se formira od jednog ili vie uzastopnih izraza a.

(izraz)

U regularnim izrazima se mogu koristiti zagrade za grupiranje izraza i za eksplicitno


odreenje djelovanja prioriteta i asocijativnosti operatora. Primjerice, izraz (a|b)c je
isti kao izraz ac|bc.

Primjer: Regularni izraz za literalni zapis realnog broja u oblicima: 78.8, .98, 78 i 78., je
(d*.d+) | (d+(|.))

Prva alternativa je specifikacija za zapis koji ima decimalni dio, a druga specifikacija je za zapis koji
nama decimalni dio, ali moe sadravati decimalnu toku.

3. Leksika analiza

Bitno je napomenuti da su s teorijskog stajalita sva tri oblika specifikacije leksema (konani
automat, regularna gramatika i regularni izrazi) ekvivalentna i mogu se transformirati jedan u drugog.
U praksi se za specifikaciju najee koriste regularni izrazi, a za programsku implementaciju
leksikih analizatora se koriste konani automati.

3. 2 Konstrukcija leksikog analizatora iz specifikacije


Automatsko generiranje leksikog analizatora sastoji se od sljedeih koraka:
1. Prvo se vri pretvorba specifikacije regularnih izraza (ili regularne gramatike) u ekvivalentne
konane automate.
2. Zatim se vri spajanje ovih automata u jedan automat. Najee se dobije nedeterministiki
automat.
3. Ukoliko se dobije nedeterministiki automat vri se pretvorba u deterministiki automat.
4. Konano se vri izgradnja tablice prijelaza automata. Obino se dobiju velike tablice, s
velikim brojem stanja, pa se u ovom koraku koriste i posebni postupci transformacije
automata u ekvivalentni automat s minimiziranim brojem stanja i prijelaza.
5. Kada je poznata tablica prijelaza zapravo je zavren proces izgradnje leksikog analizatora,
jer jedino jo treba umetnuti tablicu prijelaza u prethodno definirani program koji simulira
rad konanog automata.
Dalje e biti detaljno opisani navedeni koraci izgradnje leksikog analizatora.

3.3 Konstrukcija konanog automata iz regularne


gramatike
Polazi se od injenice da se regularna gramatika zapisuje neterminalnim simbolima, terminalnim
simbolima, i skupom pravila produkcija - oblika:
A xB
A y

Svako pravilo moe imati samo jedan neterminal na lijevoj strani i samo jedan ili nijedan neterminal
na desnoj strani. Neterminale se moe tretirati i kao stanja u procesu prepoznavanja leksema ili
reenice jezika.
Proces pretvorbe u konani automat vri se u dva koraka. U prvom koraku se odreuju stanja
konanog automata, prema pravilu:
Svaki neterminalni simbol postaje oznaka stanja konanog automata.

Startni simbol gramatike oznaava poetno stanje automata

Automatu se dodaje jo jedno, konano stanje, u kojem e biti automat kada se prepozna
sekvenca koja odgovara leksikoj specifikaciji.

3. Leksika analiza

Dakle, regularna gramatika sa n neterminalnih simbola se moe pretvoriti u konani automat sa n+1
stanjem. Skup terminalnih simbola koje prepoznaje automat isti je kao i skup terminalnih simbola
gramatike.
U drugom koraku se oznaavaju prijelazi, prema pravilu:

Svakoj produkciji oblika A t B odgovara prijelaz iz stanja A u stanje B, a oznaava se


terminalom t.
Svakoj produkciji oblika A t odgovara prijelaz oznake t iz stanja A u konano stanje .

Primjer: Formirat emo konani automat prema regularnoj gramatici za zapis realnog numerike
konstante:
R
R
A
A
B
C

dA
.B
dA
.C
dC
dC

| d
|
|
|
|

d
.
d
d

Automat se konstruira sa stanjima R, A, B , C i konanim stanjem Z. Poetno stanje je R, jer je s R


oznaen startni neterminalni simbol. U 6 navedenih pravila 5 imaju alternativne produkcije, tako da
ukupno ima 11 produkcija koje se preslikavaju u 11 prijelaza automata.

Konani automat koji


prepoznaje realnu konstantu
B

.
R

d
d

d
d

d
A

S = { R, A, B, C, Z }
= { . , d } d je znamenka
T odreen dijagramom
I=R
F={Z}

3.4 Konstrukcija konanog automata iz regularnih izraza


Proces konstrukcije konanog automata iz regularnih izraza temelji se na zapaanju da se svaki
regularni izraz sastoji od vie temeljnih regularnih izraza. U tablici su najprije definirani konani
automati koji odgovaraju temeljnim regularnim izrazima sa simbolima a i b, a zatim automati koji
nastaju zbog primjene operatora dopune, ponavljanja i alternative na sloenim regularnim izrazima A
i B (na taj nain moe se formirati automate kompleksnih regularnih izraza).

3. Leksika analiza

regularni izraz

ekvivalentni konani automat


a

a+

a*
ab

a|b

Najjednostavniji regularni izraz sadri jedan terminalni simbol a. Odgovarajui automat ima dva
stanja izmeu kojih je prijelaz oznaen simbolom a. Operatori ponavljanja (+ ili *) se oznaavaju
prijelazom koji poinje i zavrava u istom stanju.
AB

A|B

A*

Sloeni izrazi se transformiraju koritenjem -prijelaza. Za te prijelaze vrijedi:

ako su stanja X i Y spojena -prijelazom oni predstavljaju isto stanje

ako je stanje X spojeno sa stanjima Y i W pomou -prijelazom takovi automat je


nedeterministiki

3. Leksika analiza

Primjer: Ilustrirajmo prethodni proces izgradnje automata za slijedei regularni izraz


(d+(|.)) | (d*.d+)

Najprije se definiraju dva automata koji predstavljaju alternativne regularne izraze (d+(|.)) i
(d*.d+):
(d*.d+)

(d+(|.))

d
W

Kombiniranjem ova dva automata dobije se nedeterministiki konani automat:

d
d

A
d

d
.

d
F

Tablica prijelaza, koja jednoznano opisuje konani automat, moe se definirati samo za
deterministiki konani automat, stoga je nuno provesti pretvorbu NKA u DKA, ako se eli
programski realizirati konani automat pomou tablice prijelaza.
Kod DKA svakom prijelazu odgovara samo jedno stanje. Tu injenicu u tablici prijelaza
opisujemo s parom (simbol, stanje). Kod NKA nekom prijelazu pripada vie stanja, to znai da bi
tablica prijelaza za NKA trebala sadravati parove (simbol, skup stanja).

3.5 Pretvorba NKA u DKA


Pretvorba NKA u DKA se provodi pomou algoritma koji se naziva konstrukcija podskupa
(subset construction) time se dobije deterministiki automat kojem stanja predstavljaju podskup
stanja originalnog nedeterministikog automata.
Prije definiranja tog algoritma uvedimo slijedee operacije i oznake za nedeterministiki automat.
Neka s predstavlja stanje NKA, a T skup stanja NKA. Takoer, definirajmo operacije kojima se
formiraju sljedei skupovi:

3. Leksika analiza

operacija
-closure(s)

opis
je operacija koja daje skup NKA stanja u koja se moe doi iz stanja s
koristei -prijelaze, tj.

-closure(s)={s} {t | s je spojen s t pomou -prijelaza }


-closure(T)

je operacija koja daje skup NKA stanja u koja se moe doi iz svih
stanja sT koristei -prijelaze (ukljuuje T), tj.

-closure(T)= -closure(s) za sva stanja s T


je operacija koja daje skup NKA stanja u koja se moe doi ako je
trenutni ulazni simbol a, a automat se nalazi u jednom od stanja sT.

move(T, a)

Koristei ove operacije (skupove) definira se sljedei algoritam (Aho, Sethi, Ullman):

Algoritam: Konstrukcija podskupa (subset construction)


Ulaz: NKA koji ima skup stanja NStanja (s0 je poetno stanje od NKA)
Izlaz: DKA koji ima skup stanja DStanja i tablicu prijelaza Dtablica
Poetno, prije nego je dobavljen ulazni simbol, NKA moe biti u bilo kojem od stanja iz skupa closure(s0), gdje je s0 poetno stanje NKA. Pretpostavimo da neki skup T sadri stanja u koja se
moe doi iz s0, za danu ulazni niz, i neka a predstavlja sljedei ulazni simbol. Zbog njega se NKA
moe postaviti u bilo koje stanje iz skupa move(T, a), ali ako u tim stanjima postoje -prijelazi NKA
moe biti u jednom od stanja iz skupa -closure(move(T,a)), nakon primitka simbola a.
DStanja i DTablica se konstruiraju na sljedei nain:
1. Neka je -closure(s0) jedino stanje u DStanja,
Oznai to stanje kao nekompletno stanje
2. Dok postoji nekompletno stanje T u DStanja ponavljaj
oznai T = kompletno;
za svaki ulazni simbol a ponavljaj
U = -closure(move(T, a));
ako U nije element DStanja tada
dodaj U kao nekompletno stanje u DStanja
DTablica[T, a]= U
}
}
3. Oznai stanje DKA kao konano stanje, ako ono predstavlja skup NKA
stanja koji sadri bar jedno konano stanje.

3. Leksika analiza

Koristei ovaj algoritam pokazat emo kako se vri pretvorba u DKA automata iz prethodnog
primjera.
Akcija
poetno stanje

simbol

move(T,a)
A start

-prijelaz
W,E

Y,C

C
C

kompletiraj S

WxdX
ExdE
Ex. F
XxdX
ExdE
Xx. Y
Ex. F
FxdG

kompletiraj V
kompletiraj U

d
d

GxdG
FxdG

kompletiraj R
.
d
kompletiraj T
.

Dstate
dodaj R =
{A,W,E}
dodaj T =
{X,E,Y,C}

Dtable

dodaj S = {F}
T

Dtable[R,.]=S
Dtable[T,d]=T

dodaj U =
{F,Y,C}

Dtable[T,.]=U
konano stanje

dodaj V =
{G,C}
V
V

Dtable[S,d]=V
konano stanje
Dtable[V,d]=V
Dtable[U,d]=V

Y,C

Dtable[R,d]=T
konano stanje

Dobili smo DKA sa stanjima R, S, T, U, V i tablicom prijelaza:

{F}

{A,W,E}
R
{X,E,Y,C}
d

V {G,C}

{F,Y,C}
U

d
d

Ulazni simboli
ostali
.
S
error

stanje
R

d
T

error

error

prihvati

error

prihvati

error

prihvati

Oito je da rezultirajui automat sadri prijelaze izmeu skupova stanja (ili podskupova NKA).

3. Leksika analiza

10

3. 6 Optimiranje konanog automata


Moe se pokazati da nedeterministiki automat koji ima n stanja moe rezultirati sa deterministikim
automatom koji ima 2n-1 stanja. Na sreu, kod realnih programskih jezika dobiva se znatno manji broj
stanja. Poeljno je izvriti dodatno optimiranje stanja automata u cilju smanjenja broja moguih
stanja.
Optimiranje se moe provesti sljedeim postupkom:
1. Najprije se formiraju dva skupa (particije). Prva particija neka sadri sva konana stanja, a
druga particija neka sadri sva ne-konana stanja (Optimizacijska procedura poinje s
optimistikim oekivanjem da se mogu spojiti sva konana i sva ne-konana stanja).
2. Zatim se vri test konsistentnosti particije. Kaemo da je patricija konzistentni skup ako se svi
mogui prijelazi iz njenih sastavnih stanja vre u stanja koja se nalaze u istoj particiji, inae,
patricija je nekonzistentna.
3. Ukoliko postoji nekonzistentna particija, vri se podjela te particije i ponavlja korak 2, a ako
ne postoji nekonzistentna particija postupak optimizacije je zavren.
Ovaj postupak se za NKA iz prolog primjera moe prikazati tabelarno:

Partiticije
[R,S] [T,U,V]

[R] [S] [T,U,V]

[R] [S] [T] [U,V]

Provjera
konzistentnosti
Rxd->T
Sxd->V
Rx.->S
Sx.->error
Txd->T
Uxd->V
Vxd->V
Tx.->U
Ux.->error
Vx.->error
Uxd->V
Vxd->V
Ux.->error
Vx.->error

Zakljuak

Fragmentacija

[R,S]
nekonzistentan

[R,S] ->[R] [S]

[T,U,V]
nekonzistentan

[T,U,V] ->[T] [U,V]

[U,V]
konzistentan

nije potrebna

Konano se dobije optimalni automat:


.

V
d

T
d

3. Leksika analiza

11

Mogue je daljnje komprimiranje tablice tako da se ona ne implementira kao prosti dvodimenzionalni
niz, ve kao rijetka matrica (sparse array).

3.7 Specifikacija za generiranje leksikog analizatora


Automatsko generiranje leksikog analizatora vri se na temelju specifikacije regularnih izraza
leksema i pripadnih terminalnih simbola. Uobiajeno je da se ta specifikacija zapisuje u tekstualnoj
datoteci u kojoj se iza svakog regularnog zapisuje proizvoljni kod u C jeziku unutar kojeg se
odreuje vrijednost (token) koju vraa leksiki analizator kada prepozna neki leksem. Primjerice,
[A-Za-z_][A-Za-z0-9]* return(IDENTIFIER);
while
return(WHILE_TOKEN);

je oblik specifikacije koji se koristi u programu Lex.

3.7.1 Zapis regularnih izraza u programu Lex


U tablici 3.10.1 definirani su regularni izrazi koji se koriste u programu Lex . Koritenje tog programa
e biti opisano u sljedeem poglavlju.
.
r1 | r2
r*
r+
r{n,m}
(r)
[abc]
[a-d]

^r
r$
\c
r?
"..."
r1 / r2

Tablica 3.7.1 Lex metaznakovi za formiranje regularnih izraza


bilo koji znak osim nove linije (\n)
izraz r1 ili r2
nula ili vie ponovljenih izraza r
jedan ili vie ponovljenih izraza r, pr. [0-9]+ generira "1", "12", "943"itd.
izraz r se ponavlje minimalno n ili maksimalno m puta..
grupiranje izraza
jedan od znakova, tj. ekvivalentno je znaenje (a|b|c). Ako je prvi znak ^, na
tom mjest moe biti bilo koji znak osim a ili b ili c.
jedan od znakova od a do d, tj. ekvivalentno znaenje je (a|b|c|d). Ako je
prvi znak unutar zagrada ^, (tj, [^a-d])tada na tom mjest moe biti bilo
koji znak osim a-d.
Znak oznaava interval.
Ako je ^ napisan izvan uglatih zagrada tada r mora biti na poetku linije.
Ako je $ napisan iza r, tada r mora biti na kraju retka. Tj. $ je znak kraj linije
\ se koristi za unos standardnih kontrolnih znakova (\n) i za unos metaznakova.
Primjerice, ako elimo u izrazu zapisati zvjezdicu tada piemo \*.
? oznaava da je prethodni izraz opcioni, tj. r? je ekvivalentno s (|r)
sve unutar navodnika se uzima literalno. Izraz se najee zapisuje unutar
zagrada kada je potrebno da sadri znakove razmaka.
/ oznaava da se prepoznaje izraz r1, ali samo ako iza njega slijedi r2.
Primjerice 0/1 prepoznaje "0" u stringu "01", ali ne "0" u stringu "02".

Nekoliko primjera regularnih izraza i njihovo znaenje.


integer
a
(a)
"a"

3. Leksika analiza

prepoznaje znakovni niz integer


a
a
a
12

\a
ab|cd
ab?c
-?[0-9]+
[0-9]*\.[0-9]+
[^a-zA-Z]
[A-Za-z][A-Za-z0-9]*

a
ab ili cd
ac ili abc
cijeli broj s opcionim predznakom
decimalni broj koji mora imati bar jednu decimalu
bilo koji znak osim slova
identifikator koji zapoima slovom

Ako dva regularna izraza poinju s istim podnizom znakova tada se uvijek odabire dulji niz znakova,
ako postoji. Ako su oba niza iste duljine tada se uvijek daje prednost prvom izrazu.

3.8 Program Lex (Flex)


Lex je program, originalno razvijen kao dio UNIX operativnog sustava, koji kao ulaz prima
specifikaciju leksikog analizatora, a kao izlaz generira datoteku imena lex.yy.c. U toj datoteci je
definirana funkcija int yylex() koja pri svakom pozivu vraa token, tj. cijeli broj koji je oznaka
terminalnog simbola trenutno prepoznatog leksema iz ulaznog niza. Tu funkciju najee koristi u
parser u postupku sintaktike analize za dobavu slijedeeg tokena. To je samo jedna od primjena
programa Lex, jer se u ulaznoj specifikaciji lex-a mogu predvidjeti i druge radnje za leksiku obradu
teksta. Postupno emo upoznati mogunosti programa Lex. Koristit emo GNU verzija Lex-a koja se
naziva Flex.

3.8.1 Opi oblik specifikacije za program Lex


Lex ulazna datoteka ima 3 sekcije odijeljene s %%:

definicije
%%
pravila
%%
korisnike funkcije
Ne moraju biti zastupljene sekcije definicija i korisnikih funkcija. Pravila se zapisuju nakon oznake
%%. Ako nema definiranih korisnikih funkcija ne mora se zadati druga oznaka %%.
Evo kako izgleda specifikacija za leksiki analizator Mikro jezika, koji je opisan u prethodnom
poglavlju:
%{
#include <stdlib.h>
#include <string.h>
#include "global.h"
int linenum = 1;
tValue tokenval;
int lineno=1;

3. Leksika analiza

13

%}
%%
[0-9]+

{ tokenval.i = atoi(yytext); return T_NUM_INT;}

[0-9]+\.[0-9]*

{ tokenval.f = atof(yytext); return T_NUM_FLOAT ;}

print
int
float

return
return
return

T_PRINT;
T_INT ;
T_FLOAT;

[a-zA-Z_][a-zA-Z0-9_]* return
[ \t\r]+
[\n]
.
%%

T_ID;

{;}
linenum++;
return tokenval.op = yytext[0];

int yywrap(void)
{
return 1;
}
void syn_error(char *str)
/* error handling */
{
printf("Line %d,: %s\n",lineno,str);
exit(1);
}

Prije upoznavanja s pravilima za definiranje ovih sekcija potrebno je upoznati regularne izraze koji se
koriste u programu Lex.
3.8.2 Upoznavanje programa Lex kroz primjere
Primjer 1. Trivijalni sluaj primjene Lex-a je da prepozna ulazni niz znakova i da ga proslijedi na
izlaz. Specifikacijska datoteka za takvu "analizu" izgleda ovako:
/* File : ex1.lex */
%%
. | \n
ECHO;
%%
int yywrap(void)
{
return 1;
}
int main(void)
{
yylex();
return 0;
}

Da bi dobili i testirali izvrni program, koriste se sljedee komande:


c:>flex ex1.lex
c:>cl lex.yy.c /Fe ex1.exe
c:>ex1 < test.txt

3. Leksika analiza

(lex stvara datoteku lex.yy.c)


(stvara se izvrmi program ex1.exe)
(test.txt se kopira standardni izlaz)

14

c:>ex1 < test.txt > test.out

(test.txt se kopira u test.out)

U prethodnoj specifikaciji, u sekciji "pravila", koja je omeena s %%...%%, napisano je samo jedno
pravilo
. | \n

ECHO;

Ovo pravilo znai da ako se prepozna bilo koji znak osim nove linije (.) ili znak nove linije (\n) izvrit
e se akcija ECHO.
ECHO je makro naredba koja je definirana u Lex-u, tipino s
#define ECHO fwrite(yytext, yyleng, 1, yyout)

to znai da se iz stringa yytext, proslijedi yyleng znakova u izlazni tok yyout. Ove tri varijable
su globalne varijable koje Lex generira u lex.yy.c. Znaenje ime je dano u tablici 3.8.1.
Varijabla yytext sadri prepoznati leksem, a yyleng sadri duljinu prepoznatog leksema.
Isti uinak bi bio postignut i s pravilom:
. | \n

printf("yytext");

jer je yytext pokaziva na znakovni niz koji je uvijek zakljuen s '\0', dakle predstavlja string.
Uoite: Pravilo se sastoji od regularnog izraza i "akcije", koja sadri naredbu C jezika.
Ako ima vie naredbi, mora ih se pisati unutar vitiastih zagrada.
Nakon sekcije "pravila", definirane su dvije korisnike funkcije yywrap() i main(). U ovoj sekciji
se moe definirati proizvoljan programski kod u C jeziku, ali obvezatno se mora definirati funkcija
yywrap(), koja mora vratiti cjelobrojnu vrijednost 0 ili 1. Ako ova funkcija vrati vrijednost 1, to
znai da s leksika analiza prekida kada Lex dostigne kraj datoteke. Ako vrati vrijednost 0, analiza se
nastavlja datotekom, koju mora otvoriti korisnik (obino unutar ove funkcije).
Neke verzije Lex-a sadre biblioteke s prethodno definiranim "default" oblikom yywrap() funkcije.
Ime

Funkcija
poziv leksikog analizatora (obino se realizira da
int yylex(void)
vraa vrijednost tokena)
char *yytext
pokaziva na prepoznati leksem
int yyleng
duljina orepoznatog leksema
yylval
vrijednost tokena (korisnik definira tip od yylval)
wrapup, vraa 1 ili 0, ovvisno o tome da li se eli
int yywrap(void)
zavriti procesiranje ulaza
FILE *yyout
izlazna datoteka (default stdout)
FILE *yyin
ulazna datoteka (default stdin)
INITIAL
inicijaliziraj startne uvjete
BEGIN condition zaponi od startnih uvjeta
ECHO
ispii prepoznati leksem na yyout
Tablica 3.8.1 Lex - predefinirane varijable, funkcije i makroi

Primjer 2. U sljedeem programu se ispisuje da li je neki leksem: kljuna rije, operator, separator ili
broj.
/* File ex2.lex:

3. Leksika analiza

ovaj program prepoznaje neke kljucne rijeci

15

identifikatore, operator i separatore


*/
%{
#include<stdio.h>
%}
%%
[\t\r ]+
;
while |
if |
do |
for |
switch |
case |
else |
break |
continue |
static
[0-9]+
[a-zA-Z]+
[\*+-=<>]
[\(\)\[\];]
.|\n
%%

/* preskoci bijele znakove */;

{printf("%s: kljucna rijec\n", yytext);}


{printf("%s: broj\n", yytext);}
{printf("%s: identifikator \n", yytext);}
{printf("%s: operator\n", yytext);}
{printf("%s: separator\n", yytext);}
{ ECHO; /*default */}

int yywrap(void) { return 1; }


int main ()
{
yylex();
return 0;
}

Ako otkucamo:
c:>ex2
do a=3+c; while(x>7);

dobit emo ispis:


do: kljucna rijec
a: identifikator
=: operator
3: broj
+: operator
c: identifikator
;: operator
while: kljucna rijec
(: separator
a: identifikator
>: operator
7: broj
): separator
;: operator

3. Leksika analiza

16

U poetnoj sekciji "definicija" unutar %{ ... %} zapisuje se proizvoljni kd u C jeziku, koji e


kasnije biti ukljuen u program leksikog analizatora.
U sekciji "pravila", unutar %%....%% , kao i u prethodnom primjeru, zapisana se pravila koja se
sastoje od regularnog izraza i pridruene akcije koja je zapisana naredbom C jezika.
U ovom primjeru postoji 6 pravila:
Pravilo 1 odreuje da se preskoi bijele znakove (nema akcije )
[\t\r ]+

/* preskoci bijele znakove */;

Pravilo 2 sadri listu kljunih rijei (odvojeno vertikalnom crtom to oznaava alternativne
regularne izraze) . Ako je prepoznata kljuna rije ispisuje se poruka. Ovu listu se moglo zapisati u
jednom retku:
(while)|(if)|(do)|(for)|(...)|(else)| {printf("%s: kljucna rijec\n", yytext);}

ali tada svaku izraz treba napisati unutar zagrada.


Pravilo 3 - [0-9]+ prepoznaje cijeli broj i ispisuje poruku.
Pravilo 4 - [\*+-=<>] prepoznaje operatore: *+-=<> i ispisuje poruku.
Pravilo 5 - [\(\)\[\];] prepoznaje separatore : ()[]; i ispisuje poruku.
Pravilo 6 - . | \n prepoznaje sve znakove i znak nove linije, ali koristi ovo pravilo samo ako,
prema prethodnim pravilima, nije pronaen lekcem vee duljine.

Zapamti: ako vie sekvenci zapoima istim znakovima Lex izvrava akciju za najdulju
sekvencu.

Pravilo 6 (.) se gotovu uvijek zadaje, je bez njega Lex ne bi mogao razlikovati podsekvence,
primjerice, bez ovog "default" pravila, Lex bi prepoznao leksem "do" i u sekvencama "undo" i
"done".

U zavrnoj sekciji je ponovo se moe pisati program u C jeziku.

Primjer 3. Rad s proizvoljnom ulaznom datotekom


U ovom primjeru izvrena je specifikacija kojom se svakoj liniji teksta na izlazu dodaje broj linije.
Broj linije se biljei u globalnoj varijabli yylineno. (neke varzije lex-a imaju ugraenuovu
varijablu). Ulazna datoteka se mora registrirati u globalnoj varijabli lex-a yyin, (tipa FILE *).
Ukoliko se ne zada vrijednost yyin lex je postavlja na stdin.
/*File ex3.lex*/
%{
int lineno=0;
%}
%%
3. Leksika analiza

/* the row counter */

17

^(.*)\n

printf("%4d\t%s", ++lineno, yytext);

%%
int yywrap(void) {

return 1; }

int main(int argc, char *argv[])


{
yyin = fopen(argv[1], "r");
if(yyin == NULL) exit(1);
yylex();
fclose(yyin);
}

Primjer 4. Supstitucije regularnih izraza


Za esto koritene regularne izraze mogu se definirati makroi. U sljedeem primjeru, u sekciji
definicija, definirani su makroi digit i letter. U sekciji specifikacije oni se zapisuju unutar vitiastih
zagrada, to oznaava da se na tom mjestu supstituira prethodno definirani reg. izraz.
/*File ex4.lex: macro substitution */
digit
letter

[0-9]
[A-Za-z]

%{
int count;
%}
%%
/* match identifier */
(-|{letter})({letter}|{digit})*
%%
int yywrap(void) {

count++;

return 1; }

int main(int argc, char *argv[])


{
yyin = fopen(argv[1], "r");
if(yyin == NULL) exit(1);
yylex();
printf("number of identifiers = %d\n", count);
fclose(yyin);
return 0;
}

Napomena: u definiciji izmeu imena i definicije mora biti razmak.


Primjer 5. Sljedei analizator ispisuje broj znakova, rijei i linija u nekoj datoteci (slinu funkciju
ima Unix program wc):
%{
int nchar, nword, nline;
%}
%%

3. Leksika analiza

18

\n
{ nline++; nchar++; }
[^ \t\n]+ { nword++, nchar += yyleng; }
.
{ nchar++; }
%%
int main(void) {
yylex();
printf("%d\t%d\t%d\n", nchar, nword, nline);
return 0;
}

Primjer 6. Sljedei analizator prepoznaje decimalne brojeve u obinom i eksponencijalnom formatu,


ignorira bijele znakove i novu liniju, te ispisuje na izlaz sve ostale znakove koji ne pripadaju
decimalnom broju (ECHO).
EXP [eE][-+]?[0-9]+
DOT \.
DIG [0-9]
%%
[\n\t ]
;
({DIG}+{DOT}?)|({DIG}*{DOT}{DIG}+{EXP}?) {printf("number\n");}
. ECHO;
%%
int main()
{
yylex();
return 0;
}

Primjer 7. Ovom primjeru, (a) velika slova se pretvaraju u mala slova, (b) odstranjuju se znakovi
razmaka na kraju linije, (c) viestruki znak razmaka se zamjenjuje jednim znakom.
%%
[A-Z]
[ ]+$
[ ]+

putchar(yytext[0]-A+a);
;
putchar( );

Primjer 8: U ovom primjeru je specifikacija leksikog analizatora za komandni jezik koji prepoznaje
brojeve, stringove, znak nove linije, identifikator komande, te ignorira bijele znakove i komentar koji
zapoima znakovima // i traje do kraja linije.
%{
#define NUMBER 400
#define COMMENT 401
#define STRING 402
#define COMMAND 403
%}
%%
[ \t]+;
[0-9]+|[0-9]+\.[0-9]+| \.[0-9]{ return NUMBER; }
\"[^\"\n]*\"
{ return STRING; }
[a-zA-Z][a-zA-Z0-9]+
{ return COMMAND; }

//.*
\n

;
{ return \n; }

%%
#include <stdio.h>

3. Leksika analiza

19

main(int argc, char *argv[])


{
int val;
while(val = yylex())
printf("value is %d\n",val);
return 0;
}

Primjer 10: Demostrirat emo upotrebu makro naredbi input() i unput(), u detekciji komentara u C
jeziku, koji je napisan unutar znakova /* .. */.
Predefinirane makro naredbe su:
(i)

input() dobavlja ulazni znak

(ii)

unput(c) vraa dobavljeni znak u ulazni niz,

(iii)

output(c) postavlja znak na izlaz.

D [0-9]
%%
if
[a-z]+
0{D}+
{D}+
"++"
"+"
"/*"

printf("IF statement\n");
printf("ID %s\n",yytext);
printf("octal number %s\n",yytext);
printf("decimal number %s\n",yytext);
printf("unary op\n");
printf("binary op\n");
{ loop:
while (input() != *)
continue;
switch (input())
{
case /: break;
case *: unput(*);
default: go to loop;
}
}

3. Leksika analiza

20

You might also like