You are on page 1of 200

Principi programiranja Prevodioci i interpretatori

SVEUILITE/UNIVERZITET VITEZ U TRAVNIKU


FAKULTET POSLOVNE INFORMATIKE

S K R I P T A
Predmet: PRINCIPI PROGRAMIRANJA

Samo za internu upotrebu

Prof. dr Sinia Mini


Mr sci Hadib Salki

Travnik, 2011.

Principi programiranja Prevodioci i interpretatori

A. UVOD
A1. Upotreba jezika u raunarstvu
Jezici se kao tip komunikacije izmeu ovjeka i raunara primjenjuju od druge
polovine pedesetih godina, kada je definisana prva verzija programskog jezika
FORTRAN i kada je napravljen prevodilac za njega. Osnovni motiv za uvoenje jezika je
potreba da se premosti razlika u nivou razmiljanja ovjeka i raunara. Dok ovjek ima
mogunost da razmilja na razliitim, a ponekad i visokim, nivoima apstrakcije, raunar
je ogranien na mogunosti koje su definisane u trenutku njegove izgradnje i u toku
izgradnje njegovog operativnog sistema. Jezici omoguavaju ovjeku da formulie
poruku na nain koji je za njega relativno jednostavan i prirodan. S druge strane,
perecizna definicija doputenih oblika i njihovog znaenja omoguava izgradnju jezikih
procesora pomou kojih raunar tumai jezik.
U upotrebi su razliite vrste jezika.
Mainski jezici su jezici koje raunar (zajedno sa svojim operacionim sistemom)
razumije bez druge programske opreme. Podaci i instrukcije mainskog jezika su binarni.
Oblik podataka i instrukcija definisan je u trenutku izgradnje raunara i njegovog
operacionog sistema. Programiranje na mainskom jeziku omoguavalo bi maksimalno
iskoritenje mogunosti raunara, ali njihova binarna forma ini ove jezike praktino
neupotrebljivim za ovjeka.
Simboliki jezici ili asembleri uvedeni su da bi se pojednostavilo programiranje
za ovjeka. Simboliki jezik je uvijek vezan za neki mainski jezik. On je, po nivou
instrukcija, veoma blizak mainskom jeziku za koji je vezan, ali je forma pogodnija za
ovjeka operacije se predstavljaju mnemonikim skraenicama, a podaci na nain koji
je blizak notacijama koje ljudi i obino koriste. U mnogim simbolikim jezicima postoji
mogunost uvoenja makroinstrukcija koje dalje pojednostavljuju programiranje.
Jezici visokog nivoa oblikuju se na nain koji prije svega vodi rauna o
problematici za koju su namijenjeni. Struktura ovih jezika u principu omoguava njihovo
koritenje na svim raunarima. Vanu klasu tih jezika ine proceduralni ili algoritamski
jezici, kao to su Pascal, FORTRAN, C itd. Standardizacija algoritamskih jezika
omoguava prenosivost programske opreme s jednog raunara na drugi.
Problem orijentisani ili specijalizovani jezici namijenjeni su rjeavanju problema
u specifinim oblastima primjene. Osnovne klase podataka i operacije u takvim jezicima
obino su elementarne samo u okviru problematike za koju su namijenjeni, a njihova
realizacija je obino relativno komplikovana. Postojanje problem orijentisanog jezika
omoguava strunjacima iz te oblasti da pri koritenju raunara koncentriu napore na
ono to je karakteristino za oblast primjene, bez ulaenja u detalje raunarske realizacije.
Time se znatno poveava efikasnost rada, a od korisnika se trai samo elementarno
poznavanje raunara koji koriste.

Principi programiranja Prevodioci i interpretatori

A2. Sintaksa, Semantika, Pragmatika


Za komunikaciju izmeu ovjeka i raunara pomou nekog jezika koristi se jedan
konaan skup simbola, koji se zove azbuka. Nizanjem slova dobijaju se rijei ili niske
nad odgovarajuim azbukama.
Sintaksa jezika je skup pravila kojima se opisuju sve niske (rijei koje su
dozvoljene u jeziku).
Azbuka mainskih jezika je binarna, tj. ima samo dva slova. Ta slova obino
oznaavamo nulom i jedinicom. Dozvoljene niske su, dakle, binarne niske ija duina je
vezana za duinu registra raunara obino je duina niske umnoak duine registra.
Naravno, nisu sve binarne niske dio mainskog jezika dozvoljene niske zapravo su
mainske instrukcije raunara. Ve kod simbolikih jezika koristi se bogatija azbuka
azbuka koja ukljuuje simbole iz ASCII koda. Dozvoljene niske se dobijaju nizanjem
pojedinanih instrukcija, koje se, po pravilu, sastoje iz tri dijela: obiljeja, mnemonikog
koda operacije i adresnog dijela, kojim se predstavljaju adrese operanada. Za sva tri dijela
postoje pravila obiljeje je obino niska koja poinje slovom, a nastavlja se slovima i
ciframa, mnemoniki kod je neka skraenica iz konanog skupa skraenica, a adresni dio
ima neto sloeniju formu. Kod viih jezika, azbuka je opet skup simbola iz ASCII koda,
ali su pravila za pisanje dozvoljenih niski kompleksnija (dozvoljene niske su programi,
koji se sastoje iz instrukcija, a za svaki tip instrukcije postoje posebna pravila).
Semantika jezika je skup pravila kojima se definie znaenje (ispravnih)
konstrukcija jezika. Formalno, ako je L neki jezik i D skup moguih znaenja niski iz L,
onda je semantika neko preslikavanje iz L u D.
Zadavanje semantike programskog jezika zasniva se na njegovoj sintaksi. Niska
nekog jezika moe se posmatrati kao konstrukcija sastavljena od jednostavnijih
konstrukcija, koje se opet mogu posmatrati kao konstrukcije sastavljene od jo
jednostavnijih itd. sve do konstrukcija koje se dalje ne mogu rastavljati. Semantika
pridruena nekoj niski izvodi se iz semantike konstrukcija od kojih je ona sastavljena.
Semantika svojstva jednostavnijih komponenti takoe se izvode iz svojstava
konstrukcija od kojih su sastavljene (to su tzv. izvedena ili sintetizovana svojstva ili
atributi), kao i od svojstava konstrukcije iji su dio (to su tzv. nasljeena svojstva).
Svojstva najjednostavnijih konstrukcija izvode se iz njihove forme i svojstava
konstrukcije iji su dio. I opis sintakse i opis semantike u osnovi su rekurzivni.
U nekih programskim jezicima postoje konstrukcije koje nemaju uticaja na
semantiku programa, ve utiu na rad samog jezikog procesora. Efekti tih instrukcija
ine pragmatiku jezika. Na primjer, u mnogim jezicima postoji instrukcija INCLUDE dat
i sl., kojom se jeziki procesor upuuje da na mjesto instrukcije ukljui sadraj
imenovane datoteke, kao da je sastavni dio programa. Analogiju za to moemo nai kod
govornih jezika na interpretaciju neke reenice moe da utie kontekst u kom je reena.

Principi programiranja Prevodioci i interpretatori

A3. Tipovi jezikih procesora


Precizne formulacije sintakse i semantike omoguavaju izradu programa za
obradu jezika, odnosno jezikih procesora. Postoje dvije osnovne vrste jezikih
prevodilaca, a to su interpretatori i prevodioci.
Unutranji
oblik
programa P

Program P na
jeziku L

Radni podaci
programa P

Interpretator
Izbor prve instrukcije
Podaci
D

Analiza instrukcije
Izvravanje (obrada)
Izbor naredne instrukcije

Tebele
interpretatora

Rezultat
R

Kod interpretatora, jezika


analiza (koja se, na kraju, svodi na
izvravanje programa) ostvaruje se
na sljedei nain: uzima se
instrukcija programa, vri se sintaksna analiza te instrukcije, nakon
toga se vri semantika obrada te
instrukcije, a zatim se prelazi na
sljedeu instrukciju, za koju se
postupak ponavlja, sve dok se ne
naie na instrukciju koja trai
zavretak rada. U ovom sluaju se
semantika
obrada
izvrnih
instrukcija svodi na izvravanje
instrukcija. Izvorni kod (program) P
je jedan od ulaznih podataka
interpretatora.

Kako u programu mogu postojati ciklusi, interpretator uva izvorni program u


nekom unutranjem obliku. Unutranji oblik je veoma blizak izvornom obliku u tom
smislu da se iz unutranjeg moe rekonstruisati izvorni oblik. Ulazni podaci D programa
P su takoe ulazni podaci interpretatora. On ih uzima kada interpretira instrukcije koje to
zahtijevaju. Rezultat rada interpretatora su izlazni podaci programa koji se interpretira.
S druge strane, prevodioci su jeziki procesori koji prevode program s jednog
jezika na drugi. Jezik sa kojeg se prevodi je izvorni, a jezik na koji se prevodi je izlazni ili
ciljni jezik prevodioca. Izvravanje nekog programa P napisanog na izvornom programu
L odvija se u dva koraka. U prvom koraku P se prevodi sa izvornog na izlazni jezik L'. U
drugom koraku dobijeni program se interpretira. Posebno znaajna vrsta prevodilaca su
prevodioci iji izlazni jezik je mainski jezik raunara. U tom sluaju interpretaciju
izlaznog programa (odnosno, izvravanje tog programa) obavlja sam raunar, bez
dodatnog jezikog procesora.
Program P na jeziku L
Prevodilac

Podaci D
Program P' na jeziku L'
Rezultat R

Principi programiranja Prevodioci i interpretatori

Prevoenje i interpretacija su pristupi ije su


komplementarni.

prednosti i nedostaci

Kako interpretator analizira i izvrava jednu po jednu instrukciju, u sluaju da u


programu postoje ciklusi, iste instrukcije e se analizirati vie puta, ime se gubi u brzini
izvravanja. Meutim, kako su izvorni program i njegovi podaci prisutni u toku
izvravanja, interpretatori po pravilu omoguavaju neposredan pristup korisnika procesu
raunanja, izvornom programu i podacima tog programa. Koncept interpretatora posebno
je pogodan za programe koji imaju samo jednu instrukciju, ili koji nemaju cikluse to je
sluaj kod jezika u sastavu tekst editora ili jezika za kontolu paketnih obrada, kao i u
sluaju da je vrijeme analize zanemarljivo u odnosu na vrijeme izvravanja, to se deava
kod jezika za rad s bazama podataka i s problem orijentisanim jezicima.
Prevodilac (u sluaju da je izlazni jezik mainski) daje kao rezultat programe ije
izvravanje se odvija vrlo brzo. Iako je proces prevoenja relativno spor, ako izlazni
program treba da se izvrava vie puta, troak prevoenja e se isplatiti. S druge strane,
prevedeni program gubi skoro svaku vezu s izvornim. Ako neto u programu treba da se
izmjeni, cio postupak mora da se ponovi. Koncept prevoenja na mainski jezik
nezamjenljiv je u sluaju programa s intenzivnom numerikom obradom, kada je vano
da se operacije izvravaju maksimalnom brzinom koju raunar omoguava.
A4. Struktura jezikih procesora
Ovdje emo govoriti o prevodiocima iji izlazni jezik je mainski. Opta struktura
takvih prevodilaca prikazana je na slici:
Izvorni program P
na jeziku L
Leksika analiza
Sintaksna analiza
Semantika obrada
Generisanje meukoda
Globalna optimizacija
Generisanje koda
Lokalna optimizacija
Izlazni program P'
na jeziku L'

Tabele

Leksiki analizator. Ve smo naveli da


se program na nekom jeziku moe razloiti
na jednostavnije komponente, a one na jo
jednostavnije itd. Obrada jednostavnih
konstrukcija, koje se nazivaju leksikim
klasama, moe se obaviti efikasnim
algoritmima koji se zasnivaju na konceptu
konanih automata. Leksiki analizator ita
izvorni program, izdvaja u njemu leksike
klase i zamjenjuje ih leksemama. Svaka
leksema se sastoji iz dva dijela: jedan dio
predstavlja vrstu leksike klase, a drugi
sadri dodatne podatke o konstrukciji na
koju se odnosi. Obino se ti podaci uvaju
u nekim tabelama i tada je druga
komponenta lekseme pokaziva na tabelu
koja odgovara leksikoj klasi. Primjeri
leksikih klasa u programskim jezicima su
konstante, identifikatori, kljune rijei,
simboli operatora, nizovi blankova itd.

Principi programiranja Prevodioci i interpretatori

Sintaksni analizator. Zadatak sintaksnog analizatora je da konstruie sintaksnu


strukturu izvornog programa. Ulazni podatak sintaksnog analizatora je niz leksema koji
se dobija iz leksikog analizatora. U toku sintaksne analize odreuje se sintaksna
struktura izvornog programa. Ako tu strukturu nije mogue odrediti, to znai da izvorni
program nije korektan. Tada sintaksni analizator daje poruku o sintaksnoj greci. Izlaz iz
sintaksnog analizatora obino je struktura izvornog programa, predstavljena pomou
drveta ili na neki slian nain. Listovi drveta su lekseme, a unutranji vorovi odgovaraju
sintaksnim klasama.
Semantika obrada. Na osnovu sintaksne strukture mogu se izvriti semantike
obrade. Cilj semantikih obrada je da se uz svaki vor drveta sintakse vezuju odreeni
podaci koje smo nazvali atributima. Za izraunavanje nekog izvedenog atributa koriste se
atributi donjih vorova. Slino, za izraunavanje nasljeenih atributa potrebni su atributi
gornjih vorova. Na prvi pogled moe izgledati dovoljno prei po drvetu dva puta
jednom odozdo navie da bi se izraunali izvedeni atributi i drugi put odozgo nanie da bi
se izraunali nasljeeni atributi. To je tano u mnogim sluajevima, ali u optem sluaju
ne. Naime, neki izvedeni atributi mogu zavisiti od atributa donjih vorova koji su
nasljeeni itd. U tom sluaju kretanje po drvetu navie i nanie moe da se obavlja vie
puta.
Generisanje meukoda. Moe se rei da je rezultat semantike obrade drvo
sintakse okieno atributima na drvetu se moe nai sve to je potrebno da se generie
program na nekom meujeziku. Oblik meujezika uvijek zavisi od mainskog jezika i
bira se tako da dvije faze prevoenja koje slijede mogu da se izvre na najefikasniji nain.
Globalna optimizacija. Cilj globalne optimizacije je da se program na
meujeziku, dobijen iz prethodne faze, transfornie u efukasniji program na istom jeziku.
Za tu transformaciju potrebna je detaljna analiza toka izvravanja programa i
meuzavisnosti podataka. Nakon takve analize moe se utvrditi da se neka izraunavanja
mogu izvui ispred ciklusa ili da neki izrazi imaju zajednike podizraze.
Generisanje koda. U ovoj fazi, program na meujeziku transformie se u
program na izlaznom jeziku.
Lokalna optimizacija. Mada je zavretkom prethodne faze dobijen program na
izlaznom jeziku, on se obino moe podvrgnuti jo jednoj transformaciji. Ona zavisi od
izlaznog jezika, tj. od mainskog jezika raunara. Sastoji se od zamjene nekih instrukcija
efikasnijim, eventualnog izbacivanja suvinih instrukcija itd. Za svaku izmjenu analizira
se jedna ili vie uzastopnih instrukcija, po emu je ova vrsta optimizacije dobila ime.
to se tie strukture interpretatora, njihova struktura se moe naslutiti iz slike
kojom su prikazani. Analiza instrukcije zapravo sadri i leksiku i sintaksnu analizu.
Semantika obrada kod interpretatora svodi se na izvravanje pojedinanih instrukcija.
Jasno, potreba za optimizacijom ne postoji, jer bi uteda u brzini izvravanja bila
zanemarljiva u odnosu na vrijeme koje je potrebno da se izvri optimizacija.

Principi programiranja Prevodioci i interpretatori

A5. Analogija s prevoenjem govornih jezika


Faze rada jezikog analizatora, koje smo upravo razmatrali, mogu se usporediti sa
fazama prevoenja jedne reenice s jednog govornog jezika na drugi, recimo sa srpskog
na engleski. Naravno da veine ovih faza u toku prevoenja nismo svjesni one se
odvijaju automatski.
Pretpostavimo da imamo neku reenicu na srpskom jeziku. Ona se sastoji iz rijei,
pa moemo rei da su u tom sluaju rijei njeni sastavni dijelovi, njene jednostavnije
komponente, koje mogu da pripadaju razliitim leksikim klasama (u govornom jeziku te
klase bi bile imenice, glagoli, prijedlozi, veznici, zamjenice...).
Na poetku, u okviru leksike analize, analiziramo rije po rije, pa rijeima,
dodjeljujemo lekseme, koje se sastoje iz dva dijela prvi nas obavjetava o leksikoj
klasi kojoj rije pripada, a drugi dio prenosi samu rije. U ovoj fazi takoe kontroliemo
da li svaka od tih rijei odgovara pravopisnim pravilima srpskog jezika tj. da li su to sve
rijei koje pripadaju srpskom jeziku - ako neka nije, dajemo poruku o leksikoj greci i
prekidamo analizu/prevoenje.
Sada je na redu semantika obrada. Analiziramo reenicu, koja je sada pretvorena
u skup leksema. Prvi dio lekseme obavjetava nas o vrsti leksike klase, a drugi dio nas
obavjetava o rijei. Najprije moramo izanalizirati da li ta reenica prati sva gramatika
pravila srpskog jezika, odnosno, da li je raspored rijei u reenici odgovarajui. Ako nije,
izdajemo poruku o sintaksnoj greci i prekidamo analizu/prevoenje. Nakon toga, ako
je reenica korektna, njenim leksemama kaimo atribute, koji u osnovi predstavljaju
misaone pojmove koji se vezuju s datim rijeima.
Generisanje meukoda svodilo bi se na formiranje misaone predstave o znaenju
cijele reenice, odnosno niza pojmova koji odgovaraju rijeima iz polazne reenice.
Sada vrimo globalnu optimizaciju, odnosno prilagoavamo taj niz pojmova
uobiajenoj strukturi reenice na izlaznom jeziku, odnosno, u ovom sluaju, engleskom.
Generisanje koda svodi se na nalaenje engleskih rijei koje odgovaraju, redom,
svakom od pojmova u nizu.
Posljednja faza je globalna optimizacija reenicu na engleskom uljepavamo,
traimo dijelove koji bi se mogli izraziti nekim idiomom, biramo ljepe sinonime, itd.
A6. Regularni izrazi
Def1. Azbuka je konaan neprazan skup. Elementi azbuke su slova.
Def2. Rije ili niska nad azbukom je konaan skup slova iz . Prazna rije ili prazna
niska je rije bez slova, koja se obiljeava simbolom .

Principi programiranja Prevodioci i interpretatori

Duina rijei w je broj slova rijei w i oznaava se sa |w|.


Definiemo ={ }.
Skup rijei nad azbukom je * = , gdje n=0....
Jezik nad azbukom je svaki skup L *.
Def3. Neka su u i v rijei nad azbukom i neka je |u| = n i |v| = m. Proizvod,
konkatenacija ili dopisivanje rijei u i v, u oznaci uv ili uv, je rije w takva da |w| = n+m
i i-to slovo rijei w jednako je i-tom slovu rijei rijei u, kad je in, odnosno (i-n)-tom
slovu rijei v kad je i>n.
Iz gornje definicije slijedi da je za svaku rije u*, u = u =u.Specijalno, = .
Proizvod rijei nad azbukom je takoe rije nad azbukom . Dopisivanje je
asocijativno.
Kako su jezici definisani kao skupovi, nad njima su definisane sve skupovne operacije i
relacije. Osim njih emo definisati operacije dopisivanja i iteracije.
Def4. Neka su L, L1 i L2 jezici nad azbukom .
1. Proizvod ili konkatenacija jezika L1 i L2 u oznaci L1L2 ili L1L2 je jezik
L1L2= {uv | uL1 i vL2}.
2. Iteracija jezika L je jezik
L* = L, gdje n=0....
3. Pozitivna iteracija jezika L je jezik
L = L, gdje n=1....
Def5. Neka je azbuka i neka je N pomona azbuka (N=), ijim slovima su
pridrueni neki jezici nad azbukom . Izraz nad azbukama i N i jezik predstavljen
izrazom uvode se rekurzivno na sljedei nain:
(a) je izraz koji predstavlja jezik ;
(b) je izraz koji predstavlja jezik {}.
(c) Za svako a, a je izraz koji predstavlja jezik {a};
(d) Za svako AN, A je izraz koji predstavlja odgovarajui jezik;
(e) Ako su E1 i E2 izrazi koji predstavljaju redom jezike L1 i L2, onda je
(f1)(E1)(E2) izraz koji predstavlja jezik L1L2.
(f2)E1+E2 je izraz koji predstavlja jezik L1L2.
(f) Ako je E izraz koji predstavlja jezik L, tada je
(f1)E* izraz koji predstavlja jezik L*, i
(f2)E izraz koji predstavlja jezik L.
Def6. Izraz nad azbukama i N koji sadri samo slova iz je regularni izraz nad
azbukom .
Primjeri:
Posmatraemo azbuku {a,b,c}
a + b je regularni izraz koji odgovara jeziku {a}{b}, tj. jeziku koji sadri rijei a i b

Principi programiranja Prevodioci i interpretatori

a b ili ab odgovara jeziku {ab}, to je jezik koji sadri samo rije ab. Regularni operator
(koji se izostavlja u pisanju) naziva se konkatenacija (nadovezivanje).
(a+b)b je regularni izraz koji odgovara jeziku koji sadri dvije rijei, ab i bb.
(a+b)(a+b) je regularni izraz koji odgovara jeziku {aa,ab,ba,bb}
a* je regularni izraz koji odgovara jeziku ije rijei se sastoje od slova a, ponovljenog
proizvoljan broj puta, ukljuujui i nulu, tj. jeziku {,a,aa,aaa,aaaa,aaaaa, ...}
(a + b)* odgovara jeziku {,a,b,aa,ab,ba,bb,aaa, ...}- skup svih rijei nad azbukom {a,b}
ako je regularni izraz, * = + + + +... tj. rije pripada * ako se moe
sastaviti od rijei iz , tako
ako je ={aa,bb}, onda aabbaa*, a aabaa*.
= { + + +...} (* bez prazne rijei, odnosno ponavljanje proizvoljan broj
puta, ne ukljuujui nulu). - plusi
1. Napisati regularni izraz koji opisuje jezik ije sve rijei poinju sa a, a iza toga sadre
proizvoljno mnogo slova bi c u proizvoljnom redoslijedu, npr. abbc, a, acb, ...
a (b + c)*
2. Napisati regularni izraz koji opisuje jezik ije sve rijei poinju proizvoljno dugim
nizom slova a i b u proizvoljnom redoslijedu, a zavravaju se slovom c, npr. abbc, c,
bc...
(a + b)* c
3. ta predstavlja regularni izraz (a* + b*) c?
To je regularni izraz koji opisuje jezik ije rijei poinju nizom slova a ili nizom
slova b, a zavravaju se slovom c. Npr. aaaac (a* + b*) c, bbbbc (a* + b*) c, ali
abc (a* + b*) c.
4. Napisati regularni izraz koji opisuje jezik ije sve rijei sadre proizvoljne
kombinacije slova a, b i c proizvoljne duine, ali uz uslov da slovo a uvijek dolazi u
paru, npr bcaacaa pripada jeziku, ali abc ne pripada.
(aa + b + c)*
5. Neka je ={,a,b} azbuka, a jezik je jezik literala, odnosno niski slova iz azbuke
ogranienih navodnicima, pri emu:
a) unutar rijei nema apostrofa, (a + b)*
b) unutar rijei apostrofi moraju biti udvojeni. (a + b + )*
6. Uvodimo promjenljive, tj. Oznake koje mogu da predstavljaju vie razliitih slova
azbuke, recimo neka X predstavlja sva slova engleskog alfabeta, onda moemo pisati
samo X umjesto da piemo a+b+c+d+e+f+...+x+y+z.
Na primjer, ako u prethodnom primjeru kaemo da Y predstavlja sva slova azbuke
osim navodnika, onda regularni izraz piemo kao (Y+)*.
Takoe, ako elimo opisati sve rijei oblika P......I, pisaemo PX*I.

Principi programiranja Prevodioci i interpretatori

10

Osobine regularnih izraza


Neka su , i regularni izrazi. Tada vai:
1. + = + , + predstavlja uniju
2. * = , je prazna rije
3. + ( + ) = ( + ) +
4. ( )= ( )
5. ( + )= +
6. ( + ) = +
7. = =
8. = =
9. * = * =
10. (*)*= *
11. + =
12. + =
13. + = , pa + = *
Primjeri
1. Dokazati ekvivalentnost sljedeih izraza: a + a + , a*.
a + a + = a + , prema pravilu 1, a + = a , prema pravilu 14 (jer je a a , pa
a + a= a)
2.

Dokazati ekvivalentnost sljedeih izraza: a* (a + b) b* , a b* + a* b


a* (a + b) b*=(a* a + a* b) b*=(a + a* b) b*=a b* + a* b b* = a b* + a* b

3.

Dokazati ekvivalentnost sljedeih izraza: (a + b)* a*, (a + b)*


Ovdje ne moemo koristiti distributivnost, pa, da bismo dokazali da ta dva regularna
izraza definiu isti jezik, pokazaemo najprije da je jezik koji definie izraz s lijeve
strane podskup jezika koji definie izraz s desne strane, a zatim i suprotno, da je jezik
koji definie izraz s desne strane podskup jezika koji definie izraz s lijeve strane.
Najprije primijetimo da ako vai , vai i , * *, i + +
(tj. relacija se slae sa svim regularnim operacijama).
() a* (a + b)* (a + b)* a* (a + b)* (a + b)* a*
() (a + b)* je skup svih rijei nad azbukom a,b, a (a + b)* a* je skup nekih rijei
nad tom istom azbukom, pa mora biti podskup skupa svih rijei.

4.

Dokazati ekvivalentnost sljedeih izraza: (a + b)*, (a* + b)*


() kao u prethodnom primjeru
() a a* a + b a* + b (a + b)* (a* + b)*

5.

Dokazati ekvivalentnost sljedeih izraza: (a + b), (a + b)


() a a a + b a + b (a + b) (a + b)
() Skup svih rijei izuzev je nadskup skupa nekih rijei koji takoe ne ukljuuje .

6.

Dokazati da je (abc)+=a(bca)*bc
Dokazuje se indukcijom.

Principi programiranja Prevodioci i interpretatori

11

B. GENERATOR LEKSIKIH ANALIZATORA FLEX


FLEX je alat za generisanje skenera (leksikih analizatora), tj. programa koji
prepoznaju niske koje odgovaraju odreenim leksikim klasama u tekstu. FLEX oitava
date input datoteke (ulazne datoteke, specifikacije, opise) ili standardni ulaz, ako se pri
pozivu ne navede ime datoteke, i odatle uzima opis skenera kojeg treba da generie. Opis
je u obliku parova koje ine regularni izrazi i C kod, pri emu se ti parovi nazivaju
pravila. FLEX kao output generie C datoteku LEXYY.C koja definie rutinu yylex(). Ta
datoteka se kompajlira i linkuje, ime se dobija izvrni kod. Kad se izvrna datoteka
pokrene ona analizira input koji joj se daje i trai pojave niski koje su primjerci leksikih
klasa opisanih pomou regularnih izraza. Kad god se naie na nisku koja odgovara
regularnom izrazu izvrava se odgovarajui C kod.
B1. Format ulazne datoteke
Ulazna datoteka za FLEX sastoji se od tri sekcije, koje su odvojene redom koji
sadrti samo %%.
definicije
%%
pravila
%%
korisnicki kod
Sekcija definicija sadri deklaracije jednostavnih definicija imena koja slue za
pojednostavljivanje specifikacije skenera i deklaracije poetnih uslova (neemo ih
obraivati na vjebama, pogledati Flex User Manual). Definicije imena imaju sljedei
oblik:
ime definicija
Ime je rije koja poinje slovom ili podcrtom, to prati 0 ili vie slova, cifara,
podcrta ili crtica. Definicija poinje na prvom karakteru koji nije blanko nakon imena i
zavrava se krajem linije. Definiciju kasnije moemo pozvati piui samo ime, to se
onda automatski proiruje na (definicija). Na primjer,
cifra [0-9]
identifikator [a-z] [a-z0-9]*
definie leksiku klasu s imenom cifra kao regularni izraz kojem odgovara jedna cifra,
koja moe biti u intervalu od 0 do 9, a identifikator je leksika klasa kojoj odgovara
regularni izraz koji poinje slovom iza koga slijedi nula ili vie slova ili cifara. Vidjeti
primjer L3.
Ako kasnije napiemo
{cifra}+.{cifra}*

Principi programiranja Prevodioci i interpretatori

12

to je isto kao
[0-9]+.([0-9])*
i odgovara jednoj ili vie cifara iza koje slijedi taka, iza koje slijedi nula ili vie cifara
tj. to predstavlja dfefiniciju realnih brojeva, napravljenu pomou jednostavnijih
definicija.
Sekcija pravila inputa za FLEX sadri niz pravila oblika
uzorak akcija
gdje uzorak ne smije biti uvuen, a akcija mora poeti na istoj liniji. U daljem tekstu
emo detaljno opisati uzorke i pravila.
Sekcija korisnikog koda se jednostavno doslovno prekopira u LEXYY.C. Ona
se koristi za pratee rutine koje pozivaju skener ili koje poziva skener. Ova sekcija je
opciona ako nije ukljuena u opis ne mora se pisati ni drugi red %%.
U sekciji definicija, svaki uvueni tekst ili tekst okruen sa %{ i %} takoe se
doslovno kopira u rezultat, odnosno u LEXYY.C, pri emu se izostave znakovi %{ i %}.
Ti znakovi se moraju pojaviti u zasebnim linijama i ne smiju biti uvueni.
U sekciji pravila, svaki uvueni tekst ili tekst okruen sa %{ i %} koji se
pojavljuje prije prvog pravila moe se koristiti za deklaraciju promjenljivih koje su
lokalne za skenersku rutinu i, nakon toga, za unos koda koji e se izvravati svaki put kad
se pokrene skenerska rutina.
U sekciji definicija, neuvueni komentari (ogranieni sa /* i */) takoe se doslovno
kopiraju u rezultujuu C datoteku.
B2. Uzorci
Uzorci u inputu piu se uz koritenje proirenog skupa regularnih izraza. FLEX
notacija se razlikuje od regularnih izraza iz dva osnovna razloga:
1. Omoguava efikasniju reprezentaciju nekih tipova simbola, u smislu broja
karaktera koji se koriste;
2. Proiruje mo notacije regularnih izraza u odreenim, relativno ogranienim,
sluajevima.
Na primjer, regularni izraz ne moe predstaviti pojam svaki karakter azbuke sem
jednog, bez pisanja svih drugih karaktera azbuke. Kod FLEX notacije, meutim,
napisaemo samo taj karakter, uz znak ^, koji oznaava komplement.

Principi programiranja Prevodioci i interpretatori

13

U FLEX notaciji koriste se sljedei uzorci:


x
.
[xyz]

odgovara karakteru/znaku x
svaki karakter osim oznake za novi red
klasa karaktera; u ovom sluaju odgovara jednom karakteru, koji moe
biti x ili y ili z
[abj-oZ]
klasa karaktera sa opsegom; u ovom sluaju odgovara jednom karakteru,
koji moe biti a ili b ili bilo koje slovo izmeu j i o, ili Z
[^A-Z]
negirana klasa karaktera tj. svaki karakter sem onih u klasi; u ovom
sluaju svaki karakter sem velikog slova
[^A-Z\n]
svaki karakter sem velikog slova ili oznake za novi red
r*
nula ili vie r, pri emu je r neki regularni izraz u FLEX notaciji
r+
jedno ili vie r
r?
nula ili jedno r (opciono r)
r{2,5}
od 2 do 5 r
r{2,}
dva ili vie r
r{4}
tano etiri r
{ime}
proirenje definicije imena
[xyz]\foo doslovno, niska [xyz]\foo
\X
ako je X a, b, f, n, r, t, ili v, onda C interpretacija \X
\123
karakter s oktalnom vrijednou 123
\x2a
karakter s heksadecimalnom vrijednou 2a
(r)
odgovara r; zagrade se koriste za eksplicitno odreenje prioriteta
rs
regularni izraz r praen regularnim izrazom s, naziva se konkatenacija
r|s
ili r ili s (ekskluzivno ili)
^r
regularni izraz r, ali samo na poetku linije
r$
regularni izraz r, ali samo na kraju linije.
Regularni izrazi koji su gore navedeni grupiu se prema prioritetu i navedeni su
prema stepenu prioriteta oni na vrhu liste imaju najvii prioritet, oni na dnu imaju
najnii. Tako, npr
foo|bar*
je isto to i
(foo)|(ba(r*))
jer operator * ima vii prioritet od konkatenacije, a konkatenacija ima vii prioritet od
alternacije |. Prema tome, taj uzorak odgovara ili niski foo ili niski ba koju prati nula
ili vie r. Ako elimo uzorak koji odgovara niski foo ili nula ili vie niski bar,
koristiemo
foo|(bar)*
a ako elimo uzorak koji odgovara nula ili vie niski foo ili niski bar, koristiemo

Principi programiranja Prevodioci i interpretatori

14

(foo|bar)*
B3. Kako se procesira input
Kad se pokrene generisani skener, on analizira svoj input traei niske koje
odgovaraju nekom od uzoraka koji se nalaze u njegovom opisu. Ako nae vie od jednog
uzorka koji odgovaraju nisci, odluuje se za onaj uzorak kojemu odgovara najdui tekst.
Ako nae dva ili vie uzoraka koji odgovaraju tekstu iste duine, odluuje se za onaj
uzorak koji se nalazi prvi na listi uzoraka input datoteke (opisa) za FLEX. Kada se odredi
koji uzorak odgovara ulaznoj nisci, tekst koji odgovara uzorku (koji nazivamo token ili
leksema) postaje dostupan u globalnoj promjenljivoj yytext, koja je po tipu niz karaktera,
odnosno pokaziva na karakter. Onda se izvrava akcija koja je dodijeljena uzorku koji je
prepoznat (detaljnije o akcijama u nastavku teksta), a zatim se preostali dio inputa skenira
u potrazi za novim poklapanjem dijela inputa i nekog uzorka. Ako se ne nae nijedno
takvo poklapanje, izvrava se default pravilo: naredni karakter inputa smatra se
prepoznatim i kopira se na standardni izlaz. Prema tome, najjednostavniji input za FLEX
ima sljedei oblik:
%%
a to generie skener koji jednostavno kopira svoj input na standardni izlaz, karakter po
karakter.
B4. Akcije
Svaki uzorak u pravilu ima odgovarajuu akciju, koja moe biti bilo koji izraz u
C-u. Uzorak se zavrava na prvoj bjelini a ostatak pravila predstavlja akciju. Ako je
akcija prazna, onda kad se odgovarajui uzorak prepozna, ulazni token jednostavno
odbacuje.
PrimjerL1. Na primjer, evo specifikacije za program koji brie sve pojave niske brisi
me iz svog inputa, a sve ostale karaktere kopira sa inputa na izlaz poto e oni biti
prepoznati po default pravilu.
%%
brisi me
Ova specifikacija nalazi se u datoteci PR1L.L. Sve specifikacije za FLEX imaju
ekstenziju .L. Da bismo je mogli izvriti, dodaemo joj poziv funkcije main() u skeciju
korisnikog koda. U ovom, najjednostavnijem, sluaju funkcija main e samo pozivati
skenersku rutinu yylex(). Dakle, u datoteci imamo sljedee
%%
brisi me
%%
main()
{ yylex();

Principi programiranja Prevodioci i interpretatori

15

}
Skener (C-ovski program za skeniranje inputa koji se unosi sa standardnog ulaza)
emo dobiti tako to emo fleksovati ovu datoteku, odnosno ovaj opis za FLEX. U
direktorijumu FLEX, u kojem se nalazi program FLEX, izvriemo sljedeu naredbu:
C:\FLEX> flex pr1l.l
Ako je opis za FLEX u redu, ne bismo trebali dobiti nikakvu poruku. Time smo u
istom direktorijumu formirali C-ovsku datoteku LEXYY.C. Sada emo pokrenuti skener
koji se u njoj nalazi. Najprije emo prei u direktorijum tc, i u njemu pokrenuti kompajler
za turbo C.
C:\FLEX> cd\tc
C:\tc> tc
Kad smo otvorili kompajler za tc, sa Alt+F izaberemo opciju File, a u opadajuem
meniju koji nam se otvori izabraemo Load, a zatim upisati
C:\FLEX\LEXYY.C
Nakon to pritisnemo Enter, otvara se datoteka koja sadri na skener. Nju emo
kompajlirati sa Alt+C. Pri kompajliranju se pojavljuje izvjetaj, koji bi, ako je sve do
sada korektno uraeno, trebao sadravati samo upozorenja (warnings). Ona se uglavnom
odnose na dijelove koda koji nee biti upotrebljeni jasno je da su nepotrebni dijelovi
koda neto to se ne moe izbjei kad se kod generie automatski. Sada pokrenemo
skener sa Alt+R. Unesemo nisku, recimo
treuuqetruztqerztbrisi mesfgjgjfhbrisi mez
i pritisnemo Enter. Ispie se naredni red
treuuqetruztqerztsfgjgjfhz
dakle, sve pojave niske brisi me su eliminisane. Ako elimo ponovo isprobati skener,
pritisnemo enter i opet ukucavamo nisku. Ako pritisnemo Ctrl+Z pa enter, izvravanje
programa bie prekinuto.
PrimjerL2. Evo programa koji kompresuje viestruke blankove i tabulatore na jedan
blanko, i odbacuje bjeline koje se nalaze na kraju linije:
%%
[ \t]+
[ \t]+$

putchar( );
/* ignorisi ovaj token*/

Principi programiranja Prevodioci i interpretatori

16

Program se izvrava analogno prethodnom.


Ako akcija sadri {, onda traje do se ne nae odgovarajue }. Akcija moe da
se protegne preko vie redova. Akcija koja se sastoji samo od vertikalne crte | znai isto
kao i akcija za sljedee pravilo. Akcije mogu da sadre razliite C izraze, ukljuujui
return izraze za vraanje vrijednosti rutini koja pozove yylex(). Svaki put kad se pozove
yylex(), skener nastavlja da procesira tokene na mjestu gdje je posljednji put prekinuo,
sve dok ne doe do kraja datoteke ili sve dok ne izvri return izraz. Akcijama nije
dozvoljeno da modifikuju yytext.
PrimjerL3. Input za FLEX koji prepoznaje identifikatore nalazi se u datoteci PR3L.L:
slovo [a-z]
cifra [0-9]
ident {slovo}({slovo}|{cifra})*
%%
{ident} {printf("Identifikator je prepoznat\n");}
%%
main()
{yylex();
}
Ako ukucate tekst koji sadri neke identifikatore i neke niske koje nisu
identifikatori (brojeve, interpunkcijske znake), a zatim enter, skener e umjesto
identifikatora ispisati poruku identifikator je prepoznat, a niske koje nisu identifikatori
e prekopirati.
PrimjerL4. Neto interesantniji analizator bio bi proizveden iz sljedeeg opisa za FLEX,
koji se nalazi u datoteci PR4L.L:
slovo [a-z]
cifra [0-9]
identifikator {slovo}({slovo}|{cifra})*
%%
{identifikator} {printf(" identifikator %s duzine %d\n",
yytext, yyleng);}
%%
main()
{yylex();
}
jer koristi FLEX promjenljive yytext, ija vrijednost je tekstualna reprezentacija
posljednjeg simbola (tokena, lekseme) koji je prepoznat i yyleng, koja uva duinu tog
posljedenjeg oitanog simbola. Ako ukucate tekst koji sadri neke identifikatore i neke
niske koje nisu identifikatori (brojeve, interpunkcijske znake), a zatim Enter, skener e

Principi programiranja Prevodioci i interpretatori

17

umjesto identifikatora ispisati poruku identifikator (taj i taj) duzine (te i te), a niske
koje nisu identifikatori e prekopirati. Npr. za ulaz
qrwzetr6726hjdhd..z
izlaz e biti
identifikator qrwzetr duzine 7
6726 identifikator hjdhd duzine 5
.. identifikator z duzine 1
PrimjerL5. FLEX input za proizvodnju analizatora koji identifikuje i tampa pojave
realnih brojeva definisanih regularnim izrazom (+|-| ) cifra* . cifra cifra* nalazi se u
datoteci PR5L.L:
cifra [0-9]
realni [+\-]?{cifra}*\.{cifra}+
%%
{realni} {printf("realni broj %s\n", yytext);}
%%
main()
{yylex();
}
Upitnik iza karakterske klase koja sadri + i znai da je znak opcion. U
karakterskoj klasi se ispred minusa nalazi kosa crta, da bi se ukazalo da se misli na
minus, doslovno, a ne na znaenje koje minus inae ima u opisima za FLEX interval
karaktera. Iz istog razloga imamo kosu crtu i ispred take dakle, misli se na taku,
doslovno, a ne na svaki karakter osim oznake za novi red. Svi karakteri inputa koji se
takoe koriste i kao dio notacije moraju se pisati sa \ karakterom ispred sebe (ili izmeu
dvostrukih navodnika) svugdje gdje moe doi do dvosmislenosti. Npr, kod prve pojave
+ to ne mora da se radi, jer se plus ne moe protumaiti drugaije nema dvosmislenosti.
Realni brojevi se, dakle, definiu tako da na poetku imaju opcioni znak, iza kojeg
slijedi nula ili vie cifara, pa taka, pa jedna ili vie cifara. Za vjebu, pokuajte da
ubacite i eksponencijalni dio slovo e ili E, iza kojeg slijedi opcioni znak, pa jedna ili
vie cifara.
PrimjerL6. Proizvodi analizator za prepoznavanje (pojednostavljenih) konstanti,
identifikatora, niski (stringova) i odreenih kljunih (rezervisanih) rijei jezika u nekom
programu na programskom jeziku Pascal. Druge rijei bie prepoznate kao identifikatori.
Primjer se nalazi u datoteci PR6L.L
cifra
intconst
realconst
slovo

[0-9]
[+\-]?{cifra}+
[+\-]?{cifra}+\.{cifra}+(e[+\-]?{cifra}+)?
[a-zA-Z]

Principi programiranja Prevodioci i interpretatori

18

identifikator {slovo}({slovo}|{cifra})*
bjelina
[\t\n]
stringk
[^']
string
'{stringk}+'
ostalik
[^0-9a-zA-Z+\-'\t\n]
ostalisimb
{ostalik}+
%%
program
printf ("prepoznat program
\n");
var
printf ("prepoznat var \n");
begin
printf ("prepoznat begin \n");
for
printf ("prepoznat for \n");
to
printf ("prepoznat to \n");
do
printf ("prepoznat do \n");
end
printf ("prepoznat end \n");
{intconst}
printf ("integer %s\n
", yytext);
{realconst}
printf ("realni broj %s\n
", yytext);
{string}
printf ("string %s\n ", yytext);
{identifikator} printf ("identifikator %s\n ", yytext);
{bjelina}
; /* bez akcije */
{ostalisimb}
; /* bez akcije */
%%
main()
{yylex();
}
Primijetite da bjelina moe biti bilo kakva sekvenca blankova, znakova za novi
red i tabulatora i da se \n i \t koriste da otznae karaktere koji oznaavaju novi red i
tabulator na slian nain na koji se koriste u printf izrazima u C-u. Ipak, simbol \ se
koristi u suprotnom smislu od onog kojeg smo vidjeli u prathodnom primjeru da oznai
notacijsko koritenje t i n, a ne da predstavlja same karaktere. U praksi, nekonzistentno
koritenje karaktera \ ne dovodi do konfuzije.
String se definie kao svaka sekvenca karaktera koja ne ukljuuje navodnik, a
ograniena je navodnicima. Ostalisimb je sekvenca sastavljena od karaktera koji nisu ve
pomenuti. Primijetite da je prazna akcija povezana sa bjelinama i ostalim simbolima. To
je zato to, ako ti simboli ne budu prepoznati u ostatku opisa za FLEX, bie tampani na
kanal standardnog izlaza, tj. meu ostalim outputom, koji nas obavjetava o prepoznatim
leksemama, to bi dalo neuredan rezultat.
Pretpostavimo da analizatoru koji proizvede FLEX damo sljedei Pascal program
kao input
program double (input, output);
var i: 1..10;
begin
writeln ('number':10, 'timestwo':10);
for i:= 1 to 10 do

Principi programiranja Prevodioci i interpretatori

19

writeln (i:10, i*i:10);


writeln
end.
Output bi bio sljedei
prepoznat program
identifikator double
identifikator input
identifikator output
prepoznat var
identifikator i
integer 1
integer 10
prepoznat begin
identifikator writeln
string 'number'
integer 10
itd.
Primijetite da su kljune rijei prepoznate kao kljune rijei, a ne kao
identifikatori. To je zato to FLEX prihvata prvo poklapanje u sekciji pravila i prema
tome je vano da su kljune rijei definisane na poetku sekcije pravila. Takoe
primijetite da je double pravilno prepoznat kao identifikator, a prva dva slova do nisu
prepoznata kao kljuna rije do. To je zato to FLEX uvijek trai najdue poklapanje i
samo ako su oba poklapanja iste duine uzima prvo.
PrimjerL7. Ovaj primjer ilustruje kako C kod moe biti integrisan u analizator koji
proizvodi FLEX. Primjer broji linije i karaktere inputa i tampa izvjetaj o tome. Nalazi
se u datoteci PR7L.L:
%{
int brojlinija=0, brojkaraktera=0;
%}
%%
\n ++brojlinija; ++brojkaraktera;
. ++brojkaraktera;
%%
main()
{yylex();
printf("broj linija je %d, broj karaktera je %d\n",
brojlinija, brojkaraktera);
}
Kada ga budete izvravali, ovaj primjer nee dati rezultat nakon to pritisnete
enter, ve input moete unositi u vie redova, a zavravate ga kad unesete Ctrl+Z pa

Principi programiranja Prevodioci i interpretatori

20

Enter. Nakon toga, sa Alt+F i OS Shell vidite rezultat izvjetaj o tome koliko linija i
koliko karaktera je bilo u inputu.
Kao i funkcije koje se pojavljuju u treem dijelu inputa, deklaracije ili drugi kod
mogu se pojavljivati u prvom dijelu, sve dok su uvuene ili ograniene znacima %{ i %}.
Svaki takav kod se samo prekopira u C program koji FLEX proizvede. Vjerovatno je
bolje koristiti % i % za ograniavanje takvog koda umjesto oslanjanja na uvlaenje, ija
svrha moda nee biti u toj mjeri jasna. Ako se ti simboli koriste, oni se moraju pojaviti
na poetku linije. Mora se zapamtiti da e FLEX ignorisati sve uvuene linije i da e one
biti prekopirane u C program bez izmjena.
Primijetite regularni izraz koji prihvata sve (oznaen takom) koji e, u ovom
sluaju, biti izjednaen sa svakim karakterom osim oznake za novi red. Uopte gledano,
on e biti izjednaen sa svakim prethodno definisanim simbolom koji ve nije prepoznat.
PrimjerL8. Opis za FLEX dat u datoteci PR8L.L slui za izraunavanje maksimalne i
prosjene duine rijei u datom tekstu (inputu). Zasniva se na analizatoru iz prethodnog
primjera:
%{
int slova=0, rijeci=0, duz=0, duzina;
double prosjek;
%}
rijec
[a-zA-Z]+
blanko [ \n]
bjelina {blanko}+
%%
{rijec} {++rijeci; duzina=yyleng;
slova=slova+duzina;
if (duzina>duz) duz=duzina;}
{bjelina} ;
.
;
%%
main()
{yylex();
printf("maximalna duzina rijeci=%d", duz);
prosjek=slova/rijeci;
printf("prosjecna duzina rijeci je %f\n", prosjek);
}
Primijetite koritenje promjenljive yyleng koja daje duinu posljednjeg oitanog
simbola.
PrimjerL9. Da se od analizatora iz prethodnog primjera trailo da zavri s radom na
kraju prve reenice, pri emu se reenicom smatra niz karaktera koji se zavrava takom,
uzvinikom ili upitnikom, input za FLEX mogao je biti sljedei, dat u datoteci PR9L.L:
%{

Principi programiranja Prevodioci i interpretatori

21

int slova=0, rijeci=0, duz=0, duzina;


double prosjek;
%}
rijec
[a-zA-Z]+
blanko [ \n]
bjelina {blanko}+
eor
[!?.]
%%
{rijec} {++rijeci; duzina=yyleng;
slova=slova+duzina;
if (duzina>duz) duz=duzina;}
{eor}
yyterminate();
bjelina ;
.
;
%%
main()
{yylex();
prosjek=slova/rijeci;
printf("maximalna duzina rijeci=%d, prosjecna duzina rijeci=
%f\n", duz, prosjek);
}
Efekt poziva rutine yyterminate() je zaustavljanje analize.
PrimjerL10. Jo jedan jednostavan primjer koritenja FLEX-a je proizvodnja alata za
dodavanje broja linije izvrnom kodu. Rezmotrite sljedei opis za FLEX, dat u datoteci
PR10L.L:
%{
int brojlinije=0;
%}
linija [^\n]*\n
%%
{linija} {printf("%d %s", brojlinije++, yytext);}
%%
main()
{yylex();
}
Output e biti izvorni kod oitamn sa svakom linijom, ukljuujui prazne linije, sa
prefiksom i obliku broja linije.
Moda iznenaujue, prepoznavanje komentara u jeziku (to je uvijek zadatak
leksike analize) nije, u optem sluaju, jednostavno. Problem proistie iz karaktera koji
se koriste za ograniavanje koemntara i koji se, prema tome, ne smiju pojavljivati unutar
komantara. Naravno, mogue je definisati regularni izraz za komentar (u C-u, npr.) ali je

Principi programiranja Prevodioci i interpretatori

22

to teko, kao to emo vidjeti i, prema tome, podlono grekama. Bolja solucija je
jednostavno napisati dio koda za prepoznavanje poetka komentara i dalje, za skidanje
svih karaktera unutar komentara, dok se ne doe do kraja komentara. Naizad, sadraj
komentara nije uopte zanimljiv sa gledita kompajlera. Sljedei fragment opisa za FLEX
prikazuje jedan metod obrade komentara u C-u.
%%
/*

{ char in;
for ( ; ; )
{
while ((in
/*preskace
while ((in
/*preskace
if ((in ==

= getchar())!=*);
sve karaktere koji nisu zvjezdice*/
= getchar()) ==*);
sve zvjezdice*/
/) break; /*kraj komentara*/

}
}
Pazi se da se ignoriu zvjezdice koje nisu praene sa / i znakovi / ispred kojih se
ne nalaze zvjezdice. Kod se moe poboljati tako da otkrije pojavu EOF (kraja datoteke)
unutar komentara.
Komentar se takoe moe definisati kao regularni izraz, prema sljedeem
"/*""/"*([^*/]|[^*]"/"|"*"[^/])*"*"*"*/"
"/*" na poetku i "*/" na kraju jednostavno oznaavaju parove karaktera koji
su neophodni na poetku i na kraju komentara, to ostavlja
"/"*([^*/]|[^*]"/"|"*"[^/])*"*"*
to predstavlja ono to se moe pojaviti unutar komentara. "/"* na lijevoj strani
predstavlja injenicu da unutar komentara moe postojati proizvoljan broj (ukljuujui i
nulu) kosih crta na poetku komentara, a "*"* na desnoj strani predstavlja injenicu da
unutar komentara moe postojati proizvoljan broj (ukljuujui i nulu) zvjezdica na kraju
komentara.
Srednji dio, ([^*/]|[^*]"/"|"*"[^/])*, predstavlja nula ili vie
(proizvoljan broj) segmenata, od kojih svaki
- ne sadri pojavu "/" ili "*"
- sadri samo"/", ispred kojeg nema "*"
- sadri samo "*", iza kojeg nema "/".
Kao to smo vidjeli iz prethodnih primjera, tipovi analize koji se odnose na
leksiku strukturu inputa ukljuuju prepoznavanje simbola, brisanje komentara,
upisivanje broja linije, identifikaciju i evaluaciju konstanti, identifikaciju kljunih

Principi programiranja Prevodioci i interpretatori

23

(rezervisanih) rijei, identifikaciju svih razliitih identifikatora u programu, brojanje


linija komentara u programu, raunanje broja i prosjene duine literala itd. Kompleksniji
tipovi analize, koji se odnose na sintaktiku strukturu programa, a ne na leksiku, u
optem se sluaju rade uz pomo alata za parsiranje (sintaksnu analizu). Tu spadaju
koritenje izraza, strukture s ugnjeavanjem, uporeivanje promjenljivih i provjera da li
su promjenljive dobro definisane.
PrimjerL11. Izbrojati linije koda koje ne ukljuuju samo komentare brojanje linija
koda, pri emu se ne raunaju blanko linije i linije koje ukljuuju samo komentare. Opis
je dat u datoteci PR11L.L:
%{
int linbezkom=0, broj=0;
%}
komentar
"/*""/"*([^*/]|[^*]"/"|"*"[^/])*"*"*"*/"
bjelina
[ \t]
novalinija \n
%%
{komentar}
;
{bjelina}
;
{novalinija} {if (broj>0) linbezkom=linbezkom+1; broj=0;}
.
broj=broj+1;
%%
main()
{yylex();
printf("broj linija koda koje ne sadrze samo komentare je
%d", linbezkom);
}
PrimjerL12. Prosjean broj karaktera po liniji je dobra metrika (alat za ocjenjivanje
veliine ili efikasnosti nekog koda). To, zajedno s prethodnim, moe dati bolji uvid u
veliinu programa. Brojaemo prosjean broj karaktera po liniji koda, pri emu uzimamo
u obzir samo one linije koje ne sadre samo komentare. Prazne linije emo ignorisati.
Opis se nalazi u datoteci PR12L.L:
%{
int brojkar=0, linbezkom=0, broj=0;
double prosjek;
%}
komentar
"/*""/"*([^*/]|[^*]"/"|"*"[^/])*"*"*"*/"
bjelina
[ \t]
novalinija \n
%%
{komentar}
;
{bjelina}
;
{novalinija} {if (broj>0) {linbezkom=linbezkom+1; broj=0;}}
.
{broj=broj+1;brojkar=brojkar+1;}

Principi programiranja Prevodioci i interpretatori

24

%%
main()
{yylex();
printf("prosjecan broj karaktera po linijama koda koje\n");
prosjek=brojkar/linbezkom;
printf ("ne sadrze samo komentare je %f",prosjek);
}
Ostale mjere veliine programa mogu se odrediti na isti nain, npr. ukupan broj
linija koda, broj linija koda s komentarima, ukupan broj karaktera itd i sve su
odgovarajue za skupljanje kroz leksiku analizu. Druge mjere veliine, kao to je broj
funkcija, broj izraza, broj izraza po liniji bolje odgovaraju skupljanju u toku sintaksne
analize i mogu se zasnivati na YACC-u.
Primjer 13. Mogue je traiti i odlike koda koje su iz nekog razloga nepoeljne kao to
su predstavljaju defekte, npr. vrlo dugi ili vrlo kratki identifikatori. Skener koji se dobija
iz sljedeeg opisa za FLEX, koji se nalazi u datoteci PR13L.L izvjetava o konstantama
koje su preduge ili prekratke.
slovo [a-zA-Z]
cifra [0-9]
identifikator {slovo}({slovo}|{cifra})*
%%
{identifikator} {if (yyleng==1)
printf("identifikator %s je dug samo jedan
karakter\n", yytext);
if (yyleng>8)
printf("identifikator %s je duzi od osam
karaktera\n", yytext);
}
%%
main()
{yylex();
}
Duina konstanti takoe moe biti provjeravana na slian nain, kako bi se vidjelo
da one ne prelaze ogranienja implementacije. Meutim, to treba ostaviti za kasnije, jer
prve faze analize trebaju biti to nezavisnije od maine.
Na izvornom kodu moe se uraditi veliki broj provjera, kao to su neodgovarajue
koritenje goto naredbe, visoka kompleksnost kontrole toka, neodgovarajua dubina
ugnjeavanja, neoznaene konstante u izrazima itd.

Principi programiranja Prevodioci i interpretatori

25

PrimjerL14. Ovaj primjer u datoteci PR14L.L daje leksiki analizator za rimske brojeve.
Najprije primijetimo da nadskup rimskih brojeva moe da bude definisan regularnim
izrazom kao to je
M*(CM|CD|DC*|C*)(XC|XL|LX*|X*)(IX|IV|VI*|I*)
kojeg je lako dobiti ako razmotrimo nain na koji se hiljade, stotine, desetice i jedinice
mogu napisati. Taj izraz generie sve rimske brojeve, ali i neke niske koje nisu rimski
brojevi, kao to je npr VIIIIIIIIIIII, koji ima previe I. Takve niske bismo mogli izbjei
time to bismo posljednji dio regularnog izraza zamijenili sa
IX|V|VI|VII|VIII|IV|I|II|III|
Ostalim dijelovima izraza bi trebalo pruiti slian tretman, tako da bi bilo mogue
napisati (prilino kompleksan) regularni izraz koji bi predstavljao tano sve rimske cifre.
Meutim, neke od provjera koje su neophodne za eliminaciju neodgovarajuih niski
mogu se kodirati u akcije. Sljedei input za FLEX, koji se nalazi u datoteci PR13L.L,
dovoljan je za evaluaciju rimskih brojeva:
%{
int vr=0;
%}
hiljade M*
stotine CM|CD|DC*|C*
desetine XC|XL|LX*|X*
jedinice IX|IV|VI*|I*
%%
{hiljade}
{vr=vr+yyleng*1000;}
{stotine}
{if (!strcmp(yytext,"CM"))
vr=vr+900;
else if (!strcmp(yytext,"CD"))
vr=vr+400;
else if (yytext[0]=='D')
if (yyleng>4)
printf("previse C\n");
else vr=vr+500+(yyleng-1)*100;
else if(yyleng>4)printf("previse C\n");
else vr=vr+yyleng*100;}
{desetine} {if (!strcmp(yytext,"XC"))
vr=vr+90;
else if (!strcmp(yytext,"XL"))
vr=vr+40;
else if (yytext[0]=='L')
if (yyleng>4)
printf("too many X's\n");
else vr=vr+50+(yyleng-1)*10;
else if (yyleng>4)printf("previse X\n");

Principi programiranja Prevodioci i interpretatori

26

else vr=vr+yyleng*10;}
{jedinice} {if (!strcmp(yytext,"IX"))
vr=vr+9;
else if (!strcmp(yytext,"IV"))
vr=vr+4;
else if (yytext[0]=='V')
if (yyleng>4)
printf("previse I\n");
else vr=vr+5+(yyleng-1);
else if (yyleng>4)printf("previse
I\n");
else vr=vr+yyleng;}
%%
main()
{yylex();
printf("vrijednost rimskog broja je %d\n", vr);
}
Ovaj program se dalje moe doraivati, da bi ukljuio provjere za previe C, X ili
L. Funkcija strcmp, koja uporeuje niske, definisana je tako da daje 0 ako su njena dva
argumenta, koji su niske, identini. To bi u C-u bilo ekvivalentno s false, pa ta vrijednost
mora biti invertovana koritenjem operatora !.

PrimjerL15. Napisati opis ya FLEX koji e dati leksiki analizator koji prepoznaje
vaee automobilske tablice u BiH (tri cifre, crtica, slovo koje se jednako pie i latinicom
i irilicom, crtica, tri cifre). Analizator takoe treba da prepoznaje automobilske tablice
vozila politiara, ako politiari visokog ranga voze automobile s tablicama na kojima su
sve cifre jednake, politiari srednjeg ranga voze automobile s tablicama na kojima su
desna i lijeva strana jednake, a politiari niskog ranga voze automobile s tablicama na
kojima su desna i lijeva strana simetrine. Opis se nalazi u datoteci PR15L.L:
cifra [0-9]
slovo [AEOJKMT]
tablica {cifra}{cifra}{cifra}"-"{slovo}""{cifra}{cifra}{cifra}
%%
{tablica} {printf("vazeca registarska tablica %s\n",yytext);
if (yytext[0]==yytext[1] && yytext[1]==yytext[2]
&& yytext[2]==yytext[6] && yytext[6]==yytext[7] &&
yytext[7]==yytext[8])
printf("visoko politicka. sve cifre jednake\n");
else if (yytext[0]==yytext[6] &&
yytext[1]==yytext[7] && yytext[2]==yytext[8])

Principi programiranja Prevodioci i interpretatori

27

printf("srednje politicka, lijeva i desna strana


jednake\n");
else if (yytext[0]==yytext[8] &&
yytext[1]==yytext[7] && yytext[2]==yytext[6])
printf("nize politicka. lijeva i desna strana
simetricne\n");
}
%%
main()
{yylex();}

PrimjerL16. Skener za paskaloliki jezik, datoteka PR16L.L:


%{
#include <math.h>
%}
cifra [0-9]
ident [a-z][a-z0-9]*
%%
{cifra}* {printf("cijeli broj: %s (%d)\n", yytext,
atoi(yytext));}
{cifra}+"."{cifra}* {printf("realni broj: %s (%g)\n",
yytext, atof(yytext));}
if|then|begin|end|procedure|function {printf("kljucna rijec:
%s\n", yytext);}
{ident} {printf("identifikator: %s\n", yytext);}
"+"|"-"|"*"|"/" {printf("operator: %s\n", yytext);}
"{"[^}\n]*"}" ; /*eliminisemo komentare*/
[ \t\n]+

; /*eliminisemo bjeline*/

printf("neprepoznat karakter: %s\n",yytext);

%%
main(argc, argv)
int argc;
char **argv;
{

Principi programiranja Prevodioci i interpretatori

28

++argv, --argc; /*preskacemo ime programa*/


if (argc>0)
yyin = fopen (argv[0],"r");
else
yyin = stdin;
yylex();
}
Kod ovog primjera zanimljiv je mehanizam poziva programa sa komandne linije.
Ovdje moemo program izvriti tako to (nakon kompajliranja) iz komandne linije
pozovemo izvrnu verziju programa s argumentom (imenom datoteke u kojoj se nalazi
input, tako da ga ne moramo unositi s standardnog ulaza - tastature). Meutim, program
moemo izvravati i na uobiajen nain.

Principi programiranja Prevodioci i interpretatori

29

C. GRAMATIKE
C1. Definicija gramatike
Primijetimo da postoje jezici za koje se jednostavno ne moe napisati
odgovarajui regularni izraz. Najjednostavniji primjer za to je jezik ije se rijei sastoje
od jednakog broja x-eva i y-na, tj, jezik xy, n>0. To to x-ova i y-na ima proizvoljan
broj moemo izraziti regularnim operatorima * i +, ali nam oni ne pruaju mogunost da
zapamtimo koliko je x-ova generisano prije nego to ponemo generisati y-ne. Ako x
izjednaimo s otvorenom zagradom (ili poetkom bloka u programu, recimo, kljunom
rijeju BEGIN), a y sa zatvorenom zagradom (ili krajem bloka, tj. END), jasno je da je se
taj problem vrlo esto javlja.
Prema tome, jasno je da e nam za definisanje takvih jezika trebati jai
mehanizam od regularnih izraza, odnosno, gramatike. Da bismo uveli gramatike
razmotriemo jezik aritmetikih izraza s cjelobrojnim nenegativnim argumentima i
operatorima sabiranja i mnoenja.
Azbuka nad kojom se formiraju izrazi je = {0,1,2,3,4,5,6,7,8,9,+,*, (,)}. Da
bismo definisali skup izraza I, moramo se posluiti i nekim jednostavnijim
konstrukcijama sintaksnim klasama. To su cifre, brojevi, faktori i sabirci. Uvodimo i
oznake I za izraz, S za sabirak, F za faktor, B za broj i C za cifru. Smjestiemo te
pomone simbole u azbuku N = {I,S,F,B,C}. Izraze i potrebne sintaksne klase uvodimo
sljedeim pravilima:
1. Jednoslovne niske 0, 1, 2, 3, 4, 5, 6, 7, 8, i 9 su cifre.
2. Ako je C cifra onda je C i broj.
3. Ako je B broj i C cifra, onda je BC broj.
4. Ako je B broj, onda je B i faktor.
5. Ako je I izraz onda je (I) faktor
6. Ako je F faktor, onda je F i sabirak.
7. Ako je S sabirak i F faktor, onda je S*F sabirak.
8. Ako je S sabirak, onda je S i izraz.
9. Ako je I izraz i S sabirak onda je I+S izraz
10. Izrazi se dobijaju konanim brojem primjena pravila 1-9.
Koristei se slovima iz azbuke N mogu se pisati rijei kao to je npr. S + F * F.
ako se u takvoj rijei S zamijeni nekim sabirkom, a svaki F nekim faktorom (ne obavezno
istim) dobie se izraz koji pripada jeziku. Ideja proizvodnje izraza sastoji se upravo u
zamjenama slova iz N.
U namjeri da proizvedemo izraz poi emo od jednoslovne rijei i zatim emo
vriti zamjene slova iz n dok ne dobijemo rije nad azbukom . Za formulisanje pravila
zamjene oslanjaemo se na pravila 1-9.
Tako, na primjer, moemo rei da u bilo kojoj rijei I moe da se zamijeni sa I+S.
To, u stvari, znai da gdje moe da se pojavi I (izraz) umjesto njega moe da stoji I+S.

Principi programiranja Prevodioci i interpretatori

30

Postupajui na taj nain sa svim pravilima 1-9 dobijamo sljedea pravila zamjene (ona
odgovaraju redom pravilima od 9 do 1 iz prethodnog dijela teksta):
1.
2.
3.
4.
5.
6.
7.
8.
9.

I se moe zamijeniti sa I + S
I se moe zamijeniti sa S
S se moe zamijeniti sa S * F
S se moe zamijeniti sa F
F se moe zamijeniti sa ( I )
F se moe zamijeniti sa B
B se moe zamijeniti sa BC
B se moe zamijeniti sa C
C se moe zamijeniti bilo kojom cifrom.

Rijei se moe zamijeniti sa zamjenjujemo simbolom .


Tako imamo:
I S * F F * F ( I ) * F ( I + S) * F (S + S) * F (F + S) * F (F + F) * F
(B + F) * F (B + B) * F (B + B) * B (BC + B) * B (CC + B) * B
(CC + C) * B (CC + C) * C (2C + C) * C (23 + C) * C (23 + 5) * 2
Polazei od I mogli smo vriti zamjene i na drugi nain, pri emu vjerovatno ne
bismo dobili istu rije na kraju. Meutim, rije koju bismo dobili uvijek bi bila ispravan
izraz u jeziku koji smo definisali u primjeru 1.1. I obratno, svaki izraz jezika iz primjera
1.1 moe se dobiti polazei od I primjenom pravila 1-9. Zakljuak moemo uoptiti na
sljedei nain:
Da bismo generisali rijei nekog jezika nad azbukom uveli smo pomonu
azbuku N, skup pravila zamjene i jedan polazni simbol iz N. Po pravilima zamjene u
naem sluaju uvijek se zamjenjuje po jedno slovo, a uopteno uzevi moe da se
zamjenjuje neka podrije koja ima bar jedno slovo iz N, tj. podrije iz skupa (N U )* N
(N U )*. Konano, pravila zamjene moemo formalizovati kao ureeni par: prvi
element para je rije koja se zamjenjuje, a drugi rije kojom se ona zamjenjuje. Tako smo
zapravo doli do definicije gramatike.
Def. Gramatika je ureena etvorna G=(N, , S, P), gdje je
N nezavrna azbuka. Slova te azbuke su nezavrna slova ili neterminali.
zavrna azbuka. Slova te azbuke su zavrna slova ili terminali.
P konaan skup pravila zamjene P (N U )* N (N U )* x (N U )*.
S N je poetni simbol.
Umjesto (,) P piemo i itamo se zamjenjuje sa u gramatici G.
Nezavrna slova ponekad se nazivaju i sintaksne klase. Pravila koja na lijevoj
strani imaju nezavrno slovo A zvaemo A pravila. Pravila koja na lijevoj strani imaju
zvaemo pravila.

Principi programiranja Prevodioci i interpretatori

31

Poslije ove definicije moemo rei da jeziku iz primjera 1.1 odgovara gramatika
G=(N, , P, S), gdje je
N={I,S,F,B.C},
={0,1,2,3,4,5,6,7,8,9,+,*,(,)},
P={I I + S, I S, S S * F, S F, F ( I ), F B, B B C, B C, C 0,
C 1, C 2, C 3, C 4, C 5, C 6, C 7, C 8, C 9}
S =I
Vie pravila s istom lijevom stranom mogu da se piu u obliku jednog produenog
pravila uz upotrebu simbola |. Npr. I I + S | S.
Gramatika se esto zadaje tako to se samo ispiu njena pravila. Tada se
podrazumijeva da su nezavrna slova ona koja se nalaze s lijeve strane pravila, da su
zavrna slova sva ostala, a da je startni simbol na lijevoj strani prvog pravila.
C2. Jezik definisan gramatikom
Relacija igra glavnu ulogu u postupku generisanja rijei. Meutim, iako u
prethodnoj gramatici vai pravilo I I + S, to ne povlai da vai i ptavilo ( I ) * F (I
+ S) * F, odnosno, relacija P nije saglasna s dopisivanjem slijeva i zdesna. Zbog toga
emo uvesti relaciju koja ukljuuje i koja je saglasna s dopisivanjem slijeva i zdesna.
Pored te relacije, od interesa su i njen k-ti stepen, tranzitivno zatvorenje i refleksivno i
tranzitivno zatvorenje te relacije.
Def. Ako u gramatici G=(N, , P, S) postoji pravilo onda za bilo koje dvije rijei
, (N U )* vai
, i kae se da se rije neposredno izvodi iz rijei .
K-ti stepen relacije oznaava se sa k. Ako vai k onda se kae da se izvodi
iz u k koraka.
Tranzitivno zatvorenje rekacije oznaava se sa +. Ako vai + onda se kae da
se u pozitivnom broju koraka netrivijalno izvodi iz .
Tranzitivno i refleksivno zatvorenje relacije oznaava se sa *. Ako vai *
onda se kae da se izvodi iz .
Def. Jezik definisan gramatikom G=(N, , P, S) je L(G)={w | w * i S * w}.
Jezik definisan gramatikom ine sve rijei nad zavrnom azbukom koje se mogu izvesti
iz poetnog simbola.
Def. Gramatike G1=(N1, 1, P1, S1) i G2=(N2, 2, P2, S2) su ekvivalentne ako je
L(G1)=L(G2).

Principi programiranja Prevodioci i interpretatori

32

Za izvoenje neke rijei jezika zadatog nekom gramatikom obino je potrebno


vie koraka neposrednog izvoenja. Rijei nad azbukom N U koje se dobijaju kao
meurezultati takvog izvoenja su takoe od interesa.
Def. Jezik gramatikih formi gramatike G=(N, , P, S) je
GF(G)={ | (N U )* i S * }.
Gramatike forme nazivaju se jo i reeninim formama ili sentencijalnim formama.
Def. Izvoenje u gramatici G=(N, , P, S) je niz gramatikih formi 0, 1, ... n, gdje je
0 = S, i i-1 i za svako i, 0<=i<=n.
Ponekad je pogodno pri zapisivanju izvoenja umjesto 0, 1, ... n pisati
0 1 ... n.
Primjer 2. neka je G=({S,A,B},{a,b},P,S), gdje je P = {S A B, A a A | a, B b}.
Dajemo tri razliita izvoenja rijei aab:
S AB aAB aaB aab
S AB aAB aAb aab
S AB Ab aAb aab
Nije teko utvrditi da se ta izvoenja u sutini ne razlikuju jer se nezavrna slova
uvijek zamjenjuju na isti nain.
Prikazaemo i tzv. drvo izvoenja za tu rije:
S
A
a

B
A

a
Rije dobijamo spajanjem listova (krajeva grana). Primjetite da dato drvo
izvoenja odgovara svim izvoenjima koja smo gore naveli, tj. sva izvoenja imaju isto
drvo izvoenja, odnosno, u ovoj gramatici, svaka rije ima jedinstveno drvo izvoenja,
bez obzira to se moe dobiti razliitim izvoenjima. Gramatike koje imaju to svojstvo su
nedvosmislene gramatike. Kod dvosmislenih gramatika moe se desiti da postoji vie od
jednog drveta izvoenja za jednu rije, npr. razmotrimo gramatiku
SS+S
Sa
i rije a + a + a. Za nju postoje dva razliita drveta izvoenja.

Principi programiranja Prevodioci i interpretatori

S + S

S + S

S+S
a

33

S+S
a

Primjer3. Data je gramatika


SSpAq
S
ASpA
AAqS
A
Iz tih pravila mogu se izvesti i pravila A p i A q pa emo i njih koristiti, radi
skraivanja izvoenja.
Napisati izvoenja i drvo izvoenja za rije ppqpqq.
Uvijek kreemo od startnog simbola:
S
S p A q
Mogli bismo poi i suprotnim smjerom dok ne dobijemo S
Lijevo izvoenje:
S SpAq pAq pSpAq ppAq ppAqS ppqS ppqSpAq ppqpAq
ppqpAqSq ppqpqSq ppqpqq.
Drvo izvoenja:
S
S

S p A q

Primjer4. Napisati gramatiku za dati regularni izraz (a + b).


S SE | E
Ea|b
Primjer5. Dat je jezik aritmetikih izraza kod kojih su operandi a i b a operatori + i -.
Napisati gramatiku za taj jezik.

Principi programiranja Prevodioci i interpretatori

34

Treba napraviti gramatiku za jezik izraza sljedeeg oblika a + b + a a b ...


Pokuamo kod razliitih izraza koji pripadaju jeziku nai njihovu zajedniku strukturu:
Prvi nain: primijetiemo da svaki izraz poinje operandom (promjenljivom, a ili b), iza
kojeg slijedi operator, koji moe biti + ili -, iza kojeg opet slijedi izraz, koji moe biti
skoro toliko komplikovan kao i polazni izraz, ili se moe svoditi samo na promjenljivu,
pa je gramatika
SPZS|P
Z+|Pa|b
Pri emu P oznaava promjenljivu, Z znak, a S neki izraz.
Drugi nain: S P + S | P S | P
Trei nain:
S PN
N + PN | - PN |
etvrti nain:
SS+S|SS|P
(nezgodno je jer ova gramatika nije jednoznana/nedvosmislena).
Primjer6. Dat je jezik koji je podskup prethodnog ukljuuje samo one izraze kod kojih
znaci + i alterniraju. Napisati gramatiku za taj jezik.
Izrazi koji pripadaju tom jeziku su oblika a + b a + a b + b .....
Dakle, poinju promjenljivom, iza koje slijedi zank, pa neki nastavak. Ako je taj znak +
imaemo nastavak u kojem je prvi znak -, i obratno, ako je taj znak -, imaemo nastavak
u kojem je prvi znak +. Dakle, imamo dvije vrste nastavaka, Np kao plus nastavak i Nm
kao minus nastavak:
S P Np
Np + P Nm |
Nm - P Np |
Pa|b
Primjer7. Dat je jezik slian jeziku iz primjera 5, samo to osim navedenog sadri jo i
zagrade.
U osnovi, na mjestu svakog izraza moe stajati izraz u zagradama. Gramatike se mogu
predstavljati i Backus-Naurovom formom. U tom sluaju terminali se piu izmeu
dvostrukih navodnika, a na kraju niza slijedi taka zarez. Ovu gramatiku emo predstaviti
tako:
<S>::= <F> ''+'' <S> | <F> ''-'' <S> | <F> ;
<F>::= <P> | ''('' <S> '')'' ;
<P>::= ''a'' | ''b'' ;

Principi programiranja Prevodioci i interpretatori

35

Primjer8. Prethodnom primjeru dodati jo i mnoenje i dijeljenje.


<izraz>::= <sabirak> + <izraz>
a + b * a
| <sabirak> - <izraz>
| <sabirak> ;
P
P
P
<sabirak>::= <faktor> * <sabirak>
| <faktor>/<sabirak>
F
F
F
| <faktor> ;
<faktor>::=<promjenljiva> | ( <izraz> );
S
S
<promjenljiva>::= a | b ;
S
Jedino mogue (jedinstveno) drvo izvoenja
I
I
Zadatak. Dodati operaciju stepenovanja i unarne operatore plus i minus. Unarni
operatori imaju najvii stepen prioriteta, nakon toga stepenovanje, pa mnoenje i
dijeljenje itd.
Primjer8. Napisati gramatiku za rimske brojeve.
Razdvajamo sintaksne klase za jedinice, desetice, stotice i hiljade: skup neterminala N =
{P,Q,R,S,T}. Skup terminala je ={I,V,X,L,C,D,M}. Gramatika pravila su:
STQRP
P I | II | III | IV | V | VI | VII | VIII | IX |
Q X | XX | XXX | XL | L | LX | LXX | LXXX | XC |
R C | CC | CCC | CD | D | DC | DCC | DCCC | DM |
T M | MM | MMM |
C3. Hijerarhija omskog
Hijerarhija omskog predstavlja klasifikaciju jezika, odnosno gramatika koje ih
generiu. Kreemo od najire klase, i svaka klasa obuhvata u sebi i sljedeu, uu klasu.
Najira klasa gramatika naziva se klasa Tip 0 gramatika, odnosno klasa
rekurzivno nabrojivih gramatika. Kod tih gramatika ne postoje restrikcije vezane za tip
produkcija (pravila). One su ekvivaltntne Turingovim mainama.
Prvo ogranienje vezano za oblik pravila koje se moe pojaviti u gramatici je da
se kae da za sva pravila oblika vai da je duina niske manja ili jednaka duini
niske , tj. da je | | <= | |. Takve gramatike su Tip 1 ili kontekst osjetljive, i one su
ekvivalentne linearnim konanim automatima.

Principi programiranja Prevodioci i interpretatori

36

Klasa gramatika koje zadovoljavaju naredno ogranienje, da se samo jedan


neterminal moe pojaviti s lijeve strane pravila, nazivaju se Tip 2 ili kontekst slobodne
gramatike.
Moe se dopustiti da pravilo S bude ukljueno ak i u kontekst sobodnoj
gramatici (ne samo u kontekst osjetljivoj, iako kri pravilo za kontekst osjetljive
gramatike), kako bi se prazna niska ukljuila u jezik, jer to ne poveava ekspresivnost.
Tip 2 gramatike su ekvivalentne potisnim automatima.
Posljednju, najuu, klasu ine Tip 3 ili regularne gramatike. Prvo definiemo da
je desno linearna gramatika ona kod koje je svako pravilo jednog od dva sljedea oblika
Aa
AbC
Za gramatike koje su desno linearne kae se da su regularne. Analogno i za
gramatike koje su lijevo linearne, ali gramatika kod koje ima mijeanja desno linearnih i
lijevo linearnih pravila nije regularna.
Jezik definisan regularnom gramatikom je regularan. Takav jezik se moe
definisati i regularnim izrazom i takve gramatike su ekvivalentne konanim automatima.
Kao to je ve reeno, radi se o inkluzivnoj gramatici ako je gramatika tipa 3,
ona e biti i tipa 2 i tipa 1 i tipa 0. Meutim, ako je jezik tipa 2, ne mora znaiti da nije i
tipa 3, jer moda postoji gramatika tipa 3 koja definie isti jezik. Na primjer, jezik x*y*
definiu obje doljenavedene gramatike, pri emu je lijeva tipa 2, a desna tipa 3.
SXY
XxX
X
YyY
Y

SxS
S y B
Sx
Sy
ByB
By
S

Regularne gramatike i jezici su podskup kontekst slobodnih gramatika i kontekst


slobodnih jezika. U analizi, gdje god je mogue, treba koristiti regularne gramatike i
jezike. Postoji jednostavna odlika gramatike koja se moe koristiti da se odredi da li je
jezik koji se generie regularan. Radi se o rekurziji. Rekurzije mogu biti lijeve (A A
b), desne (B c B) i srednje (C d C e). Tkoe, mogu biti i neposredne (kao ove koje
smo do sada naveli) i posredne (A B c, B C d, C A c). Meutim, postoji
jednostavan algoritam za prevoenje posredne rekurzije u neposrednu (vidjeti naredno
poglavlje).
Da bismo odredili da li neka gramatika generie regularan jezik, razmotrimo da li
sadri rekurzije. Ako ih ne sadri, to je malo vjerovatno, onda je jezik regularan jer je
konaan. Meutim, ako gramatika sadri rekurzije, ali ne sadri srednje rekurzije, onda je
jezik koji generie opet regularan, a regularnu gramatiku emo dobiti metodom

Principi programiranja Prevodioci i interpretatori

37

oslobaanja od lijeve rekurzije (vidjeti naredno poglavlje). Dakle, jezik generisan


gramatikom
SXY
XxX
Xx
YyY
Yy
Sy
S
je regularan, jer pravila ne sadre srednju rekurziju.
Jednostavan jezik koji nije regularan je, kao to smo ve pomenuli, jezik ije se
rijei sastoje od jednakog broja x-eva i y-na, tj, jezik xy, n>0.
Leksiki aspekti veine jezika imena promjenljivih, brojevi, konstante isl. Mogu
se skoro uvijek definisati regularnim izrazima. Meutim, kod aritmetikih izraza ili
blokova (gdje imamo uparivanje zagrada), to nije mogue izraziti regularnim izrazima ni
regularnim gramatikama, jer gramatika koja se sastoji od niski uparenih zagrada
ukljuuje pravila tipa S ( S ), odnosno pravila s unutranjom rekurzijom. Prema tome,
za leksiku analizu koristiemo gramatike tipa 3, a za sintaksnu analizu gramatike tipa 2.
C4. Transformacije kontekst slobodnih gramatika
C4.1. Oslobaanje od suvinih slova (neterminala)
Def1. Slovo je neproduktivno ako se iz njega ne moe izvesti nijedna rije iz zavrne
azbuke (azbuke terminala).
Def2. Slovo je nedostino ako se iz poetnog simbola ne moe izvesti ni jedna rije koja
sadri to slovo.
Slovo moe biti i dostino i produktivno pa da ipak bude suvino (npr. dostino je preko
nedostinog).
Primjer 1. Osloboditi se suvinih slova u gramatici
Sa|A
AAB
Bb
Prvo se oslobaamo neproduktivnih pa nedostinih slova. Pitanje produktivnosti se ne
postavlja za terminale.
Pravimo niz skupova P1, P2, ... pri emu je P1 skup svih neterminala iz kojih se izvode
rijei sastavljene od terminala, P2 je skup svih neterminala iz kojih se izvode rijei

Principi programiranja Prevodioci i interpretatori

38

sastavljene od terminala i slova iz P1, P3 je skup svih neterminala iz kojih se izvode rijei
sastavljene od terminala i slova iz P2... i tako dalje, a postupak se prekida kad doemo u
situaciju da je Pi-1=Pi. Tada je Pi skup svih produktivnih terminala date gramatike.
P1={S,B}, P2={S,B} itd.
Dakle, A nije produktivno slovo jer njim dobijamo rije; koja poinje sa A.
to se tie dostinih slova, opet pravimo niz skupova, D1, D2, ... pri emu skup D1 sadri
poetni simbol, skup D2 simbole koji se mogu nai u reeninim formama koje se mogu
izvesti iz poetnog simbola, skup D3 simbole koji se mogu nai u reeninim formama
koje koje se mogu izvesti iz simbola koji pripadaju skupu D2 itd... i tako dalje, a postupak
se prekida kad doemo u situaciju da je Di-1=Di. Tada je Di skup svih dostinih terminala
date gramatike.
D1={S}, D2={S,A,a}, D3={S,A,a,B,b}, D4=D3.
Dakle, u poetnoj gramatici nema nedostinih slova. Meutim, kad smo otkrili da je
slovo A neproduktivno, moramo ga se osloboditi, a rezultujua gramatika
Sa
Bb
Sadri nedostino slovo B. prema tome, rezultujua gramatika, koja nema suvinih
slova, je
Sa
Prema tome, postupak tee tako da se najprije oslobodimo neproduktivnih, a onda
nedostinih slova. Slovo moe biti dostino zahvaljujui neproduktivnom, ali ne moe
biti neproduktivno zahvaljujui nedostinom.
Primjer 2. Osloboditi se od suvinih slova u sljedeoj gramatici:
SA|B
AaB|bS|b
BAB|Ba
CAS|b
P1={A,C}, P2={A,C,S}, P3={A,C,S}=P2, pa slovo B nije produktivno
SA
AbS|b
CAS|b
D1={S}, D2={S,A}, D3={S,A,b}, D4={S,A,b}, dakle, C nije dostino
SA
AbS|b

Principi programiranja Prevodioci i interpretatori

39

C4.2. Oslobaanje od pravila


Def. Gramatika je slobodna ukoliko nema pravila ili (u sluaju da jezik sadri rije)
ima samo jedno pravilo, S (a inae nije nigdje s desne strane pravila).
Primjer 1. Osloboditi se pravila u datoj gramatici
SaSbS|bSaS|
Postupak je sljedei: U sluaju da jezik sadri rije (a u ovom sluaju je tako), najprije
dodajemo novi neterminal, S koji e imati ulogu startnog simbola, i sljedea pravila za
taj neterminal S' | S, koja e dovesti do toga da ostatak bude bez .Zatim pravimo
skup niz skupova Ni, pri emu je Ni,
i
Ni = {A | A } ( se moe izvesti iz neterminala A u i koraka, i=0,1,...)
Postupak prekidamo kad dva uzastopna skupa N budu jednaka. U ovom sluaju, N ={S}.
Nakon toga, gdje god se u pravilima pojavljuju neterminali iz skupa N, ta pravila
dupliramo, jednom s tim neterminalom, drugi put bez. Dakle, rezultujua gramatika je
S' | S
SaSbS|abS|aSb|ab|bSaS|baS|bSa|ba
Primjer 2. Osloboditi se pravila u datoj gramatici
SABC
ABB|
BCC|a
CAA|b
N1={A}, N2={A,C}, N3={A,C,B}, N4={A,C,B,S}, N5={A,C,B,S}=N4
Pa je gramatika bez pravila:
S' | S
SABC|AB|AC|BC|A|B|C
ABB|B
BCC|C|a
CAA|A|b
C4.3. Oslobaanje od jednostrukih projekcija
Primjer1. Osloboditi se jednostrukih projekcija (pravila) u gramatici
SA|B
AaB|bS|b
BAB|Ba
CAS|b
Jednostruka pravila su S A i S B. Pravilo S A zamjenjujemo pravilima

Principi programiranja Prevodioci i interpretatori

40

S a B | b S | b, a pravilo S B pravilima S A B | B a.
Uopte, za neterminale koji su na desnoj strani jednostrukih pravila prave se skupovi
Ns ={X | S * X} ( znai da i S pripada N). Zahtjeva se da je gramatika slobodna.
U ovom sluaju, NS ={S, A, B}. Zatim se desne strane pravila kojima su s lijeve strane
terminali iz NS koji nisu S stave s desne strane pravila kojima je S s lijeve strane, kao
gore. Dakle, rezultujua gramatika je:
SaB|bS|b|AB|Ba
AaB|bS|b
BAB|Ba
CAS|b
Primjer2. Osloboditi se jednostrukih projekcija (pravila) u gramatici
EE+T|T
TT*F|F
F(E)|a
NE = {E,T,F}, NT = {T,F}, NF = {F}
EE+T|T*F|(E)|a
TT*F|(E)|a
F(E)|a
Primjer3. Osloboditi se jednostrukih projekcija (pravila) u gramatici
SB+S|A|B
AA+B|S|B
Ba|SA
Ovo je gramatika s ciklusima!
NS = {S,A,B}, NA = {A,S,B}, NB={B}
SB+S|A+B|a|SA
AA+B|B+S|a|SA
Ba|SA
Sad su neterminali S i A ekvivalentni, pa se ova gramatika moe i dalje transformisati.
C4.4. Oslobaanje od lijeve rekurzije
Def. Gramatika je lijevo rekurzivna ako A + A i A N (ako se iz nekog neterminala
moe u pozitivnom broju kortaka izvesti reenina forma koja poinje tim istim
neterminalom. U sluaju da je taj broj koraka jednak jedan, radi se o neposrednoj lijevoj
rekurziji, a ako je broj koraka vei od jedan, radi se o posrednoj lijevoj rekurziji).

Principi programiranja Prevodioci i interpretatori

41

Def. Pravilna gramatika je gramatika koja nema suvinih slova, nema pravila i nema
cikluse. Moe imati jednostruka pravila, ali oslobaanje od jednostrukih pravila e
eliminisati i ckluse, koji su nepoeljni.
U postupak oslobaanja od lijeve rekurzije ulazi pravilna gramatika.
Primjer lijeve rekurzije: A A a | b. To je primjer neposredne lijeve rekurzije. Jezik te
gramatike je ba* (A Aa Aaa baa isl.). Kako za taj jezik napisati gramatiku koja
nije lijevo rekurzivna? Uveemo novi neterminal, A i njegova pravila
A b A'
A' a A' |
Sada nam smeta pravilo, N ={A'}, pa je gramatika
A B A' | b
A' a A' | a
Ovaj postupak s uvoenjem novog neterminala uoptavamo, pa za sve gramatike
koje ukljuuju pravila oblika:
A A 1 | A 2 | ... | A n | 1 | 2 | ... | m
Ta pravila zamjenjujemo pravilima
A 1 A' | 2 A' | ... | m A' | 1 | 2 | ... | m
A' 1 A' | 2 A' | ... | n A' | 1 | 2 | ... | n
Ako lijeva rekurzija nije neposredna, pravimo takvu transformaciju da za svako pravilo
vai da ako ono ima neterminale s desne strane, to su neterminali koji su nie u
redoslijedu (njima poinju pravila koja su ispod).
Primjer1. Osloboditi se lijeve rekurzije u sljedeoj gramatici
A BC|a
BCA|Ab
CAB|CC|a
Najprije ispitamo da li je gramatika pravilna ima li suvinih slova, pravila ili
jednostrukih projekcija. U ovom sluaju gramatika jeste pravilna. Nakon toga uvodimo
red meu neterminalima, ali tako da se poredak to manje mijenja. Dalje, za svaki
neterminal po redu:
- vidimo da li neterminal gleda unazad (da li mu se s desne strane pojavljuju
neterminali koji su ispred njega u redoslijedu),
- ako gleda unazad, to eliminiemo,
- vidimo da li sada u tom praviolu postoji neposredna lijeva rekurzija (da li taj
neterminal gleda u sebe),
- oslobodimo se neposrednih lijevih rekurzija prema gore navedenom pravilu.

Principi programiranja Prevodioci i interpretatori

42

A B C | a ovo pravilo je u redu


B C A | A b ovo pravilo gleda unazad, pa ga zamjenjujemo sa
B C A | B C b | a b ovdje imamo neposrednu lijevu rekurziju
B C A B' | a b B' | C A | a b
B' C b B' | C b
B' ne pravi novu rekurziju, pa je u redu
C A B | C C | a ovo pravilo gleda unazad, pa ga zamjenjujemo sa
C B C B | a B | C C | a ovo pravilo gleda unazad, pa ga zamjenjujemo sa
C C A B' A B | a b B' C B | C A C B | a B C B | a B | C C | a, ovdje imamo neposrednu
lijevu rekurziju
C a b C B C' | a b B' C b C' | a B C' | a C' | a b C B | a b B' C B | a B | a |
C' A C B C' | A B' C B C' | C C' | A C B | A B' C B | C
Primjer2. Osloboditi se lijeve rekurzije u sljedeoj gramatici
SA|B|C|D|E
AC|x|y|z
BAx|y|z|
CBx|Ay|z
DCx|By|Az
EDx|Cy|Bz
1) Gramatika nije pravilna, ima pravilo. Oslobaamo se od pravila N = {B,S}
S S |
SA|B|C|D|E
AC|x|y|z
BAx|y|z
CBx|Ay|z
DCx|By|Az
EDx|Cy|Bz
2) Sada treba napraviti adekvatniji redoslijed
Vidimo da se D i E ne pojavljuju u pravilima za A, B i C i treba ih, dakle, premjestiti
naprijed. Redoslijed kod kog tek u estom pravilu mijenjamo je:
S S |
SA|B|C|D|E
EDx|Cy|Bz
DCx|By|Az
AC|x|y|z
BAx|y|z
CBx|Ay|z

Principi programiranja Prevodioci i interpretatori

43

3) Oslobaanje od lijevih rekurzija


S S |
SA|B|C|D|E
EDx|Cy|Bz
DCx|By|Az
A C | x | y | z ova pravila su sva u redu
B A x | y | z ovo pravilo gleda unazad, pa ga zamjenjujemo sa
B C x | x x | y x | z x | y | z ovdje nema neposredne lijeve rekurzije
C B x | A y | z ovo pravilo gleda unazad, pa ga zamjenjujemo sa
CCxx|xxx|yxx|zxx|yx|zx|x|Cy|xy|yy|zy|z
ovdje imamo neposrednu lijevu rekurziju
Cxxx|yxx|yxx|yx|zx|x|xy|yy|zy|z
| x x x C' | y x x C' | y x x C' | y x C' | z x C' | x C' | x y C' | y y C' | z y C' | z C'
C' x x | y | x x C' | y C'

Principi programiranja Prevodioci i interpretatori

44

D. PARSIRANJE ODOZGO NADOLE (TOP DOWN)


Problem parsiranja se sastoji iz nalaenja izvoenja (ako postoji) odreene
reenice uz koritenje date gramatike. U sluaju parsiranja odozgo nadole (top down),
trai se lijevo izvoenje, dok se u sluaju parsiranja odozdo navie (bottom up) trai
desno izvoenje. Kod parsiranja odozgo nadole poinjemo s reeninim simbolom i
generiemo reenicu, dok kod parsiranja odozdo nagore polauzimo od reenice i
redukujemo je do reeninog simbola. Pretpostaviemo da e reenice koje treba
generisati, odnosno redukovati, biti oitane slijeva nadesno, iako su teorijski mogua
vraanja koja reenice oitavaju zdesna nalijevo.
Razmatramo jezik x*y* koji je generisan sljedeim pravilima izvoenja
SXY
XxX
Xx
YyY
Yy
kako bismo pokazali da reenica xxxyy moe biti generisana desnim izvoenjem, tj.
S XY xXY xxXY xxxY xxxyY xxxyy
Prvi korak izvoenja je direktan, jer se reenini simbol S pojavljuje na lijevoj
strani samo jednog pravila izvoenja, tako da je jednostavno napisati S XY. Drugi
korak, meutim, nije tako jednostavan, jer se najljevlji nezavrni simbol reenine forme
(X) pojavljuje na lijevoj strani vie od jednog pravila izvoenja (dva, u ovom sluaju).
Ipak, treba imati na umu da je, kada se izvrava parsiranje, rezultat, odnosno generisana
reenica, unaprijed poznata. U ovom sluaju, trai se izvoenje reenice xxxyy, a kako
ona ima vie od jednog x, sljedee pravilo izvoenja koje treba koristiti je X x X.
Slino, u treem koraku izvoenja treba primijeniti isto pravilo izvoenja, to daje xxXY.
etvrti korak je xxXY xxxY i prikazuje prvu upotrebu pravila X x, jer je to
posljednja x koje treba biti generisano. Peti korak, xxxY xxxyY, prikazuje primjenu
pravila Y y Y, koje se primjenjuje zato to treba biti generisano jo y-na. Zavrni
korak u izvoenju, xxxyY xxxyy, prikazuje primjenu pravila Y y, poto nee vie
biti generisan ni jedan y.
Drugi koristan nain prikazivanja koraka izvoenja je sljedei:
Input
xxxyy
xxxyy
xxxyy
xxxyy
xxxyy
xxxyy
xxxyy

Pravilo zamjene
SXY
XxX
XxX
Xx
YyY
Yy

Reenina forma
XY
xXY
xxXY
xxxY
xxxyY
xxxyy

Principi programiranja Prevodioci i interpretatori

45

Karakteri reenice se razmatraju jedan po jedan i koriste se za voenje procesa


parsiranja. Kada se karakter reenice generie, u input nisci se prikazuje kao prekrien. U
svakoj fazi parsiranja prikazuju se tri stavke, input reenica s vodeim karakterinma
precrtanim, pravilo zamjene koje se u tom trenutku primjenjuje u reenina forma u
trenutnom obliku. Kada je parsiranje zavreno, svi karakteri u input reenici su prekrieni
i reenina forma je jednaka poetnoj reenici koja je unijeta.
U svakoj fazi, prvi simbol input niske koji nije prekrien definie se kao nailazei
simbol i koristi se za voenje parsiranja. Ide se simbol po simbol, sve dok se ne generie
zavrni simbol u reeninoj formi. U toku parsiranja, nailazei simbol dobija vrijednost
tekueg ulaznog simbola ili markera kraja. Marker kraja je zaseban simbol za koji se
pretpostavlja da lei na kraju svake reenice, i koji se obino predstavlja simbolom .
Pri parsiranju odozgo nadole, odluke se obino zasnivaju na nailazeem simbolu,
ili sekvenci nailazeih simbola, iako postoje i optiji metodi u kojima se takoe uzima u
obzir i dosadanji tok parsiranja.
U primjeru se izvoenje lako nalazi iz toga to znamo koja reenica treba biti
generisana, iako je u najveem dijelu izvoenja potrebno poznavanje jo dva simbola,
nakon onih koji su do sada generisani. Za neke gramatike se i vie od dva simbola (ak,
ponekad i proizvoljan broj simbola!) trai da bi se identifikovalo odgovarajue izvoenje.
Na vjebama emo se skoncentrisati na gramatike koje zahtijevaju poznavanje
najvie jednog nailazeeg simbola u svakoj fazi izvoenja da bi se identifikovalo
odgovarajue pravilo zamjene koje treba iskoristiti. Takve gramatike nazivaju se LL(1)
gramatike (itanje s lijeva na desno, koriste se lijeva izvoenja, s jednim nailazeim
simbolom). LL(1) gramatike su osnova metoda parsiranja odozgo nadole, jer se jezici
opisani njima uvijek mogu deterministiki parsirati, bez potrebe za vraanjem
(backtrackingom). LL(a) gramatike nisu najvei skup gramatika koje to omoguavaju, ali
se najee koriste. Nedeterministiko parsiranje odozgo nanie, kao i LL(k) gramatike,
koje za identifikaciju odgovarajuih pravila zamjene zahtijevaju k nailazeih simbola,
vie se ne smatraju praktinim.
Postoji algoritam kojim se utvruje da li je gramatika LL(1). Meutim, ne
postoji algoritam koji provjerava da li je jezik LL(1), tj. da li neka gramatika koja nije
LL(1) ima ekvivalentnu gramatiku (koja definie isti jezik) koja jeste LL(1). Odrediti da
li je neki jezik LL(1) je nerjeiv problem. esto se gramatike moraju transformisati da bi
se mogle koristiti za parsiranje odozgo nanie, poto su gramatike koje se obino koriste
za definisanje jezika rijetko LL(1). Kako ne postoji algoritam koji bi nam rekao da li za
neku gramatiku postoji ekvivalentna gramatika koja je LL(1), tako, prirodno, ne postoji
ni algoritam kojim bismo mogli da proizvoljnu gramatiku transformiemo u LL(1)
gramatiku. Ipak, postoje algoritmi koji rade u najveem broju sluajeva, i koji nikad nee
dati pogrean odgovor problem je u tome to oni mogu ui u neprekidnu petlju.
Vano je primijetiti da postoji odlika koja, ako je prisutna, sprjeava da gramatika
bude LL(1). To je lijeva rekurzija. Ve smo razmatrali algoritam za uklanjanje lijeve

Principi programiranja Prevodioci i interpretatori

46

rekurzije. Bez obzira da li se obavljaju automatski ili ne, transformacije gramatika su


neophodan dio LL(1) parsiranja i predstavljaju jedno od njegovih ogranienja. Meutim,
s odgovarajuom gramatikom, pisanje LL(1) parsera je jednostavno, poto se koristi tzv.
metoda rekurzivnog spusta.
D1. Algoritam za odreivanje da li je data gramatika LL(1) raunanje skupa izbora
Neka je, na primjer, data sljedea gramatika:
SAyB
Sp
ACAy
AB
BzSp
B
Cx
Cqp
Definisaemo dvije funkcije, prvi i sled (sljedei). Prvi je funkcija nad terminalima,
neterminalima i rijeima, Dom(prvi) = {terminali, neterminali, rijei}, a sled je funkcija
nad neterminalima, Dom(sled) = neterminali. Vrijednosti ovih funkcija su skupovi
terminala.
a prvi () ako * a
Tj. a pripada skupu prvi () ako se iz u proizvoljnom broju koraka moe izvesti rije
koja poinje sa a. Specijalno, prvi () ako * .
a sled (A) ako S * A a
tj, definiemo sled(A) za neki neterminal A kao skup terminala a koji se mogu pojaviti
neposredno desno od A u nekom koraku izvoenja. Primijetimo da u nekom koraku
izvoenja moe biti simbola izmeu A i a, ali ako je tako, oni proizvode i postaju
providni. Ako je A krajnji desni simbol neke rijei onda tj. marker kraja pripada
skupu sled(A).
Pravila za pravljenje skupa prvi(x):
1) Ako je x terminal onda je prvi(x)={x}.
2) Ako postoji pravilo x , onda dodamo u prvi(x).
3) Ako je x neterminal i x y1 y2 ... yk je pravilo, onda smjestimo a u prvi(x)
ako, za neko i, a je u skupu prvi(yi) i je u svim skupovima prvi(y1), prvi(y2),
..., prvi(yi-1), tj ako y1 y2 ... yi-1 * . Ako je u svim prvi(yj), za j=1,2,..., k,
onda ne dodajemo nita vie u prvi(x), ali ako y1 * , onda dodajemo
prvi(y2) i tako redom.
Napomene:
Ako postoji pravilo x y1 y2 ... yk, onda
a prvi(y1) a prvi (x)
a prvi (y2) i y1* a prvi(x)
....
a prvi>(yi+1) i y1 y2 ... yi * a prvi(x)

Principi programiranja Prevodioci i interpretatori

47

prvi(AB) = prvi(B) U prvi(A) ako B *


prvi(y1 y2 ... yk) = prvi(y1) U prvi(y2) U ... U prvi(yi+1) \ { }, ako y1 y2 ... yi * , ali ne
vai i yi+1 * .
Pokuajmo da odredimo skupove prvi (x) pri emu x uzima vrijednosti neterminala i
terminala iz date gramatike.
prvi(S)={p,y,x,q,z}
prvi(A)={,z,x,q}
prvi(B)={,z}
prvi(C)={x,q}
prvi(p)={p}
prvi(q)={q}
prvi(x)={x}
prvi(y)={y}
prvi(z)={z}
(u sled(S), p ulazi zbog pravila S p; y ulazi zbog pravila S A y B, uzimajui da je A
providno - A B, B ; x i q ulaze zbog pravila S A y B, A C a y, dakle: ako
reenina forma poinje sa S, kasnije u izvoenju e poinjati s A, a jo kasnije s C a
reenine forme koje poinju s C mogu, zbog pravila kojima je C s lijeve strane, poinjati
sa x ili q; a z ulazi u prvi(S) zbog pravila S A y B, A B, B z S p. Slino za ostale
neterminale, a kod terminala se jednostavno primijeni pravilo br.1))
Pravila za pravljenje skupa sled(x):
1) Smjestimo tj. marker desnog kraja u sled(S), gdje je S startni simbol.
2) Ako postoji pravilo A B s, onda sve iz prvi(s) osim smjestimo u sled(B).
3) Ako postoji pravilo A B ili A B s, gdje prvi(s) sadri (tj. s* )
onda sve iz sled(A) preslikamo u sled(B) (tj. kako je A B, ono to je
poslije A to ostaje poslije B).
Pokuajmo da odredimo skupove sled(X) pri emu X uzima vrijednosti neterminala iz
date gramatike:
sled(S)={ , p}
sled(A)={y}
sled(B)={ ,p,y}
sled(C)={x,q,z,y}
(Najprije smjestimo marker kraja u sled(S). Onda gledamo gramatiku i traimo pravila u
kojima se s desne strane nalazi neterminal praen terminalom, i za ta pravila smjestimo
taj terminal u skup sled za taj neterminal konkretno, u pravilu B z S p, p slijedi iza S
i zato se stavlja u sled(S). Iz istog razloga, y ulazi u sled(A). Kod pravila A C A y
primjenjujemo isti princip, primijetivi da je A prozirno, pa y ulazi u sled(C). Zatim
traimo pravila ija se lijeva strana zavrava neterminalom, recimo S A y B , i onda
sve iz sled(S) preslikamo u sled(B). Iz tog razloga su i p uli u sled(B). Imamo i pravilo

Principi programiranja Prevodioci i interpretatori

48

A B , zbog kojeg sve iz sled(A) tj. y, ulazi u sled(B). Onda traimo pravila u kojima s
desne strane nalazi neterminal praen neterminalom, pa onda sve iz skupa prvi za drugi
neterminal u tom paru stavljamo u skup sled za drugi neterminal u tom paru konkretno,
u pravilu A C A y, primijetimo par C A, i sve iz prvi(A) sem , tj. z, x, q prebacimo u
sled(C).)
Sada definiemo funkciju izbor, nad gramatikim pravilima
prvi(), ako ne vai da prvi()
Izbor(A ) =
prvi () U sled(A) \ {}, ako prvi()
LL(1)gramatike su one gramatike kod kojih su skupovi izbora za gramatika
pravila s istom desnom stranom disjunktni.
Pokuajmo da odredimo skupove izbora za pravila iz date gramatike
izbor(S A y B)= prvi(AyB)={z,x,q,y}
izbor(S p)= prvi(p)={p}
izbor(A C A y)= prvi(CAy)= prvi(C)={x,q}
izbor(A B)= prvi(B) U sled(A) \ {}={z,y}
izbor(B z S p)= prvi(zSp)= prvi(z)={z}
izbor(B )= sled(B) \ {}={ ,p,y}
izbor(C x)= prvi(x)={x}
izbor(C q p)= prvi(qp)= prvi(q)={q}
Vidimo da je data gramatika LL(1) jer su skupovi izbora za pravila koja s desne strane
imaju isti neterminal disjunktni.
Primjer 1. Odrediti skupove izbora pravila u sljedeoj gramatici:
SAB|rB
ApSq|
BqA
prvi(S)={r,p,q}
prvi(A)={,p}
prvi(B)={q}

sled(S)={,q}
sled(A)={q,}
sled(B)={,q}

izbor(S A B)= prvi(AB)= prvi(A) \ {}U prvi(B)={p,q}


izbor(S r B)= prvi(rB)= prvi(r)={r}
izbor(A p S q)= prvi(p)={p}
izbor(A )= sled(A)={q,}
Ova gramatika jeste LL(1).

Principi programiranja Prevodioci i interpretatori

49

Primjer 2. Odrediti skupove izbora pravila u sljedeoj gramatici:


SPN
PxQ|(S)
Qr|q
N+PN|-PN|
prvi(S)={x,(}
prvi(P)={x,(}
prvi(Q)={r,q}
prvi(N)={+,-,}

sled(S)={ ,)}
sled(P)={+,-, ,)}
sled(Q)={+,-, ,)}
sled(N)={ ,)}

izbor(P x Q)={x,(}
izbor(P ( S ))={x}
izbor(Q r)={(}
izbor(Q q)={q}
izbor(N + P N)={+}
izbor(N - P N)={-}
izbor(N )= sled(N)={ ,)}
Primjer 3. Sastavljanje gramatike tako da bude LL(1)
(1) Gramatika s lijevom rekurzijom ne moe biti LL(1) recimo da gramatika sadri
pravilo A A | , i pretpostavimo da a prvi(), tj. a izbor(A ), a s druge
strane, kako iz a prvi() slijedi i a prvi(A), tako a izbor(A A ), pa je oigledno
da ti skupovi izbora nisu disjunktni, pa nam zato ne odgovara lijeva rekurzija.
ak i kad prvi(), b prvi(), pa b sled(A), pa b izbor(A ), ali kako
b prvi(), b prvi(A), pa b izbor(A A ), i opet imamo konflikt.
(2) Lijeva faktorizacija
pravila A x P | x Q mogu se zamijeniti pravilima A x E, E P | Q
ali i tu moe da postoji konflikt
Dakle, treba sastaviti LL(1) gramatiku od sljedee
SS+S|SS|x|(S)
Ova gramatika nije LL(1) zbog lijeve rekurzije. Oslobaamo se neposredne lijeve
rekurzije.
S x S | ( S ) S | x | ( S )
S + S S | - S S | + S | - S
Lijeva faktorizacija
SxP|(S)P
P S |
S' + S P | - S P
Sad raunamo skupove izbora

Principi programiranja Prevodioci i interpretatori

prvi(S)={x,(}
prvi(P)={,,-}
prvi(S)={+,-}

50

sled(S)={ ,),+,-}
sled(P)={ ,),+,-}
sled(S)={ ,),+,-}

izbor(S x P)={x}
izbor(S ( S ) P)={(}
izbor(P S)= prvi(S)={+,-}
izbor(P )= sled(P)={ ,),+,-}
Posljednja dva skupa izbora nisu disjunktni i dalje imamo konflikt.
Probajmo ponovo s faktorizacijom:
SxP|(S)P
P S |
S' + S Q | - S Q
Q S |
Meutim, ispostavi se da ni ovo nee pomoi, pa ni ta gramatika nije LL(1).
Probajmo na drugi nain. Odnosno, probajmo da sagledamo koji je to jezik. Radi se o
jeziku aritmetikih izraza sa sabiranjem, oduzimanjem i zagradama. Za taj jezik imamo i
gramatiku:
SBN
N+BN|-BN|
Bx|(S)
prvi(S)={x,(}
prvi(N)={,+,-}
prvi(B)={x,(}

sled(S)={ ,)}
sled(N)={ ,)}
sled(B)={ ,+,-}

izbor(N + B N)={+}
izbor(N - B N)={-}
izbor(N )= sled(N)={ ,)}

Ovdje nema konflikata i gramatika jeste LL(1).


Primjer 4. Odrediti da li je sljedea gramatika LL(1)
SZcY|a
Xe|bSa
YdSa|
ZXZc|Y

S
X
Y
Z

Prvi
a,c,e,b,d
e,b
d,
e,b,d,

Sled
,a
e,b,d,c
,c,a
c

Principi programiranja Prevodioci i interpretatori

Pravilo
SZcY
Sa
Xe
XbSa
YdSa
Y
ZXZc
ZY

Izbor
e,b,d,c
a
e
b
d
,c,a
e,b
d,c

Skupovi izbora za pravila s istim desnim stranama su disjunktni, pa je gramatika LL(1).


Primjer 5. Odrediti da li je sljedea gramatika LL(1)
SABD|
ApS|q
BnSCyA|rAn
CrB|
Dx|Cm

S
A
B
C
D

Prvi
p,q,
p,q
n,r
r,
x,r,m

Pravilo
SABD
S
ApS
Aq
BnSCyA
BrAn
CrB
C
Dx
DCm

Sled
,r,n,m,y,x
n,r,m,y,x
m,y,x,r
m,y
n,r,m,y,x,
Izbor
p,q
,r,n,m,y,x
p
q
n
r
r
y,m
x
r,m

Skupovi izbora za pravila s istim desnim stranama su disjunktni, pa je gramatika LL(1).

51

Principi programiranja Prevodioci i interpretatori

52

D2. Rekurzivni spust


Rekurzivni spust je metod parsiranja koji ukljuuje korienje rekurzivnih poziva
procedura i radi odozgo nanie. Moe se primijeniti samo na LL(1) gramatike.
Uopteno govorei, za svaki neterminal gramatike piemo funkciju, koja
korienjem simbola iz skupova izbora pravila koja s desne strane imaju taj neterminal
odabire pravila koja e se koristiti, te za neterminale koji se nalaze s desne strane tih
odgovarajuih pravila poziva njihove funkcije, a terminale preko leksikog analizatora
uporeuje s onim to je na redu u ulaznom toku.
Primjer RS1
Napisaemo rekurzivni spust za sljedeu gramatiku, za koju smo dokazali da je LL(1).
SAB|rB
ApSq|
BqA
#include <stdio.h>
#include <stdlib.h>
int nailazeci;
void greska()
{ fprintf(stderr, "greska \n");
exit (1);
}
void uporedi(int x)
{ if(x==nailazeci) nailazeci=getchar();
else greska();
}
void s(), a(), b();
void s()
{ if (nailazeci=='r')
{ uporedi('r');
b();
}
else
{ a();
b();
}
}
void a()
{ if (nailazeci=='p')

Principi programiranja Prevodioci i interpretatori

53

{ uporedi('p');
s();
uporedi('q');
}
else ;
}
void b()
{ if (nailazeci=='q')
{ uporedi('q');
a();
}
else greska();
}
void analiza()
{ nailazeci=getchar();
s();
if (nailazeci!='\n')
greska();
}
main()
{ printf("unesite nisku \n");
analiza();
printf("niska je sintaksno ispravna \n");
}
Najprije imamo par direktiva za ukljuenje C biblioteka u kojima se nalaze
definicije funkcija kao to su getchar(), koja uzima jedan po jedan karakter iz ulaznog
toka. Nakon toga, imamo deklaraciju cjelobrojne promjenljive nailazeci, koja uzima
vrijednosti iz skupa izbora za pravila s istom lijevom stranom. Nakon toga imamo
funkciju za obradu greaka i funkciju uporedi, koja uporeuje svoj cjelobrojni argument s
trenutnom vrijednou promjenljive nailazei, i poziva leksiki analizator (koji se ovdje
svodi na funkciju getchar(), jer sve leksike klase u stvari predstavljaju samo po jedan
karakter).
Nakon toga dolaze deklaracije funkcija za neterminale. Kako u C-u funkcija mora
biti makar deklarisana ako se poziva, a ove funkcije se uzajamno pozivaju, staviemo
deklaracije tih funkcija prije definicije prve od njih. Iza deklaracija slijede definicije
funkcija za neterminale. Primijetite kako se obrauje pravilo A (else ;), kao i mjesto
na koje se stavlja poziv funkcije za obradu greaka (kod neterminala B, kako imamo
samo pravilo B q A, nailazeci mora imati vrijednost q, ili se radi o greci takav
determinizam nemamo ni kod neterminala S ni kod neterminala A).
Na kraju imamo dvije funkcije, analiza() i main(), koje su, opet, ablonske.
Funkcija analiza() uzima jedan karakter s ulaza, poziva funkciju koja odgovara najiroj

Principi programiranja Prevodioci i interpretatori

54

strukturi, polaznom simbolu S, a nakon izlaska iz te funkcije funkcija analiza provjerava


da li se dolo do kraja unosa ovdje kraj unosa oznaavamo sa enter, pa se tu trenutna
vrijednost promjenljive nailazeci usporeuje sa /n. Funkcija main() komunicira s
korisnikom, poziva funkciju analiza() i obavjetava korisnika o rezultatu.
Ovaj program se nalazi u datoteci PR1RS.C i izvrava tako to se otvori u turbo C
editoru, kompajlira (Alt+C) i pokrene (Alt+R). Zatim se na standardni ulaz unose niske
koje pripadaju jeziku (kao, npr. rq, koja se dobija izvoenjem S rB rqA rq, ili
rqprqq, koja se dobija izvoenjem S rB rqA rqpSq rqprBq rqprqAq
rqprqq), i niske koje ne pripadaju jeziku (npr. p, qq itd.). Unosi se zavravaju sa Enter, a
rezultat se oitava sa Alt+F, OS Shell. Pokuajte da napiete programe za rekurzivni
spust za ostale gramatike za koje smo ranije dokazali da su LL(1).
Primjer RS2
Sada emo razmotriti kompleksniji primjer. Uzmimo jezik aritmetikih izraza, koji kao
operacije ukljuuju sabiranje, odutzimanje, mnoenje, dijeljenje, kao i unarni plus i
unarni minus, a kao argumente ukljuuju cijele brojeve i identifikatore (koji se sastoje iz
slova i cifara, a poinju slovom). Npr. 2+3*a+(4/3). Gramatika za taj jezik je:
<izraz>::=<sabirak><nastavakI> ;
<nastavakI>::= +<sabirak><nastavakI>
| -<sabirak><nastavakI>
| ;
<sabirak>::=<faktor><nastavakS>
<nastavakS>::= *<sabirak><nastavakS>
| /<sabirak><nastavakS>
| ;
<faktor>::= +<faktor>
| -<faktor>
| <prostfaktor> ;
<prostfaktor>::= NCEO | IDENT | ( <izraz> ) ;
Za dvije leksike klase, cijele brojeve i identifikatore, trebae nam leksiki
analizator. Njega emo napraviti uz pomo FLEX-a. Specifikacija za FLEX pomou koje
se dobija leksiki analizator koji prepoznaje identifikatore i cijele brojeve nalazi se u
datoteci PR2RS.L:
%{
#include "tab.h"
%}
slovo [A-Za-z]
cifra [0-9]
bjel [ \t\n]
ident {slovo}({slovo}|{cifra})*
nceo {cifra}+
praz {bjel}+

Principi programiranja Prevodioci i interpretatori

%%
{ident}
{nceo}
{praz}
.

55

{return IDENT;}
{return NCEO;}
;
{return yytext[0];}

Obratite panju da je trea sekcija, sekcija programa, u ovom opisu prazna (tj.
nema je!), za razliku od opisa za FLEX koje smo pisali u Poglavlju 1. To je zato to e
funkcije main() i yylex(), koje smo ablonski ukljuivali u tu sekciju, biti pozvane iz
parsera, tako da nema potrebe da ih tu navodimo.
U zaglavlju tab.h, koje izgleda ovako:
#define NCEO 257
#define IDENT 258
definisane su dvije leksike klase, NCEO i IDENT, i prikljueni su im brojevi 257 i 258.
To je zbog toga to leksiki analizator mora parseru vraati cjelobrojan rezultat po
defaultu, kad prepozna karaktere iz ASCII skupa, leksiki analizator vraa brojeve koji
im odgovaraju u ACSII skupu, tj. brojeve od 1 do 256, pa emo novim leksikim klasama
dodjeljivati brojeve od 257, i kad leksiki analizator vrati taj broj, parser e dobiti
informaciju da je prepoznata ta leksika klasa.
Parser e oekivati da leksiki analizator nae u datoteci LEXYY.C, pa zbog toga
prvo moramo da fleksujemo gore navedeni opis za FLEX. Dakle, izvrimo sljedeu
naredbu:
C:\FLEX> flex pr2rs.l
Tako smo dobili leksiki analizator. Naravno, on sada ne moe da se koristi sam
za sebe (sjetite se, nema main()!), ve treba napraviti i parser koji e ga pozivati. Evo
parsera napisanog metodom rekurzivnog spusta, koji se nalazi u datoteci PR2RS.C:
#include "c:\flex\lexyy.c"
void greska()
{ fprintf(stderr, "greska \n");
exit(1);
}
int nailazeci;
void uporedi(int x)
{ if (x==nailazeci) nailazeci=yylex();
else greska();

Principi programiranja Prevodioci i interpretatori

56

}
void izraz(), sabirak(), nastavaki(), faktor(), nastavaks(),
prostf();
void izraz()
{ sabirak();
nastavaki();
}
void nastavaki()
{ if (nailazeci=='+')
{ uporedi('+');
sabirak();
nastavaki();
}
else if (nailazeci=='-')
{ uporedi('-');
sabirak();
nastavaki();
}
else;
}
void sabirak()
{ faktor();
nastavaks();
}
void nastavaks()
{ if (nailazeci=='*')
{ uporedi('*');
faktor();
nastavaks();
}
else if (nailazeci=='/')
{ uporedi('/');
faktor();
nastavaks();
}
else;
}
void faktor()
{ if (nailazeci=='+')
{ uporedi('+');
faktor();

Principi programiranja Prevodioci i interpretatori

}
else if (nailazeci=='-')
{ uporedi('-');
faktor();
}
else prostf();
}
void prostf()
{ if (nailazeci==NCEO) uporedi(NCEO);
else if (nailazeci==IDENT) uporedi(IDENT);
else if (nailazeci=='(')
{ uporedi('(');
izraz();
uporedi(')');
}
else greska();
}
void analiza()
{ nailazeci=yylex();
izraz();
if(nailazeci!=0) greska();
}
main()
{ printf("unesite izraz \n");
analiza();
printf("izraz je sintaksno ispravan \n");
}

57

Principi programiranja Prevodioci i interpretatori

58

Primijetimo da su razlike u odnosu na prethodni primjer minimalne najprije,


moramo ukljuiti datoteku u kojoj se nalazi leksiki analizator (#include
"c:\flex\lexyy.c"), a u funkcijama uporedi i analiza umjesto funkcije getchar() koristi se
funkcija yylex().
Ovaj program se nalazi u datoteci PR2RS.C i izvrava tako to se otvori u turbo C
editoru, kompajlira (Alt+C) i pokrene (Alt+R). Zatim se na standardni ulaz unose niske
koje pripadaju jeziku aritmetiki izrazi. Unosi se zavravaju sa Enter, a rezultat se
oitava sa Alt+F, OS Shell.
Primjer RS3.
Napisati program koji metodom rekurzivnog spusta proverava sintaksnu ispravnost
unetog skupa naredbi. Sintaksa naredbe iju ispravnost treba proveriti je:
<naredba>

::= <while_naredba>
|<begin_end_naredba>|
<naredba_dodele>
<while_naredba>
::= while <iskaz> do <naredba>;
<begin_end_naredba> ::= begin <naredba>;[<naredba>;]* end
<naredba_dodele>
::= <promenljiva> := <izraz>
<iskaz>
::= <izraz> = <izraz>
<promenljiva>
::= slovo [slovo | cifra]*
<izraz>
::= pozcifra [cifra]*
<pozcifra>
::= 1|2|3|4|5|6|7|8|9
<cifra>
:: pozcifra | 0
Najprije, evo opisa za FLEX koji daje odgovarajui leksiki analizator. Opis se
nalazi u datoteci PR3RS.L:
int red = 1;
%{
#include "primer1.h"
%}
ID
NUM

[A-Za-z][A-Za-z0-9]*
[1-9][0-9]*

%%
WHILE|while
DO|do
BEGIN|begin
END|end
";"
{ID}
{NUM}
"="

return
return
return
return
return
return
return
return

WHILE;
DO;
BEG;
END;
SEMI;
PROM;
BROJ;
EQ;

Principi programiranja Prevodioci i interpretatori

":="
\n
[ \t]+
.
ECHO;
%%

59

return DODELA;
printf("Zavrsena analiza reda %d.\n", red++);
/* preskoci beline */
printf("nepoznat simbol u redu %d.\n", red);

Program za rekurzivni spust se nalazi u datoteci PR3RS.C


include <stdio.h>
#include "primer1.h"
#include "lexyy.c"
void iskaz(void);
void izraz(void);
extern int red;
extern char *yytext;
int
ukupno = 0;
TOKEN Lookahead;
#define match(X)
#define advance()
#define greska(X)

(X == Lookahead)
Lookahead = yylex()
{printf("GRESKA u redu %d: ", red),
printf(X),
printf(" umesto lekseme %s\n", yytext);
/*, exit(1)*/
ukupno++;
}

void naredba()
{
if( match(WHILE) )
{
advance();
iskaz();
if( !match(DO) )
greska("ocekivano DO ");
advance();
naredba();
if( !match( SEMI ) )
greska("ocekivana ';' ");
advance();
return;
}

Principi programiranja Prevodioci i interpretatori

if(match( BEG ) )
{
advance();
do
{
naredba();
if(!match(SEMI) )
greska("ocekivana ';' ");
advance();
} while( !match(END) );
advance();
return;
}
if( match( PROM ) )
{
advance();
if( !match(DODELA) )
greska("ocekivano ':=' ");
advance();
izraz();
return;
}
else
greska("nepoznata naredba ");
}
void izraz()
{
if( !match( BROJ ) )
greska("ocekivan broj ");
advance();
}
void iskaz()
{
izraz();
if(!match(EQ) )
greska("ocekivano '=' ");
advance();
izraz();
}
main( argc, argv )
int argc;
char **argv;
{

60

Principi programiranja Prevodioci i interpretatori

61

++argv, --argc;
if ( argc > 0 )
yyin = fopen( argv[0], "r" );
else
yyin = stdin;
advance();
naredba();
if (ukupno)
printf("\nU unetom fragmentu koda postoji %i
greska/greske/gresaka",ukupno);
else
printf("\nNema sintaksnih gresaka u unetom fragmentu
koda");
}
Kod ovog primjera zanimljiv je mehanizam poziva programa sa komandne linije.
Ovdje moemo program izvriti tako to (nakon kompajliranja) iz komandne linije
pozovemo izvrnu verziju programa s argumentom (imenom datoteke u kojoj se nalazi
input, tako da ga ne moramo unositi s standardnog ulaza- tastature). Meutim, program
moemo izvravati i na uobiajen nain.

Principi programiranja Prevodioci i interpretatori

62

E. PARSIRANJE ODOZDO NAGORE (BOTTOM UP)


Problem parsiranja sastoji se od nalaenja izvoenja odreene reenice pomou
date gramatike, ako takvo izvoenje postoji. U sluaju parsiranja odozdo nagore trai se
desno izvoenje. Pokazaemo kako se to moe uraditi pomou gramatike koja generie
jezik x*y*:
SXY
XxX
Xx
YyY
Yy
Traiemo izvoenje reenice xxxyy.
S XY XyY Xyy xXyy xxXyy xxxyy
Kod parsiranja odozgo nagore koraci izvoenja se ne identifikuju u tom
redosljedu, nego u suprotnom, od reenice ka startnom simbolu, pa je desno izvoenje
za to parsiranje
xxxyy xxXyy xXyy Xyy XyY XY S
Na svakom koraku primijeni se gramatiko pravilo tako to se njegova desna
strana zamijeni lijevom, koja se sastoji od samo jednog simbola.
Meutim, ako reenicu xxxyy itamo slijeva nadesno, nije odmah oigledno zato
se prvo x ne zamijeni sa X u prvom koraku parsiranja isto i za prvo y. Jesno je da je za
parsiranje, sem samih pravila, potrebno jo informacija.
U parsiranju odozdo nagore desne strane pravila ne prepoznaju se do nisu u
potpunosti oitane, te prema tome postoji potreba da se uvaju djelomino prepoznate
desne strane pravila, dok se ne zamijene odgovarajuim lijevim stranama. Stek (LIFO
lista) predstavlja odgovarajuu strukturu za uvanje tih djelimino prepoznatih niski.
Prema tome, detaljan opis procesa parsiranja odozdo nagore ukljuuje i prikaz sadraja
tog steka.
Koraci svakog od koraka parsera, ali ne objanjava nita o tome kada treba doi
do shift procesa parsiranja mogu se opisati kao da se sastoje od 2 tipa akcija:
1. stavljanje posljednjeg oitanog simbola na stek shift akcija (pomjeranje)
2. zamjena niske koja je na vrhu steka primjenom gramatikog pravila reduce
akcija (redukovanje).
Prikaz na sljedeoj strani prikazuje uinak ili reduce akcija, i koja od vie
moguih reduce akcija treba da bude poduzeta.

Principi programiranja Prevodioci i interpretatori

Ulazni string
xxxyy
xxxyy
xxxyy
xxxyy
xxxyy
xxxyy
xxxyy
xxxyy
xxxyy
xxxyy
xxxyy

Stek
x
xx
xxx
xxX
xX
X
Xy
Xyy
XyY
XY
S

Pravilo

Xx
XxX
XxX

Yy
YyY
SXY

63

Reenina forma
xxxyy
xxxyy
xxxyy
xxxyy
xxXyy
xXyy
Xyy
Xyy
Xyy
XY
S

S/R
S
S
S
R
R
R
S
S
R
R
R

Neophodan uslov za reduce akciju je to da je desna strana nekog pravila na vrhu


steka, inae dolazi do shift akcije i sljedei simbol e biti oitan na vrh steka. Ipak,
pojava desne strane nekog pravila na vrhu steka nije i dovoljan uslov da bi dolo do
redukcije. Takoe je mogue identifikovati desnu stranu vie od jednog pravila na vrhu
steka, tako da moe postojati vie moguih redukcija. Ako u odreenoj fazi parsiranja
izgleda mogua ili shift ili reduce akcija, onda se kae da postoji shift-reduce konflikt.
Ako izgledaju mogue 2 ili vie redukcija, onda se radi o reduce-reduce konfliktu.
Da bismo doli do deterministikog metoda parsiranja, mora da postoji nain
donoenja tih odluka. U praksi, to se rjeava koritenjem informacija sljedeih tipova:
1. dosadanji tok parsiranja
2. informacije vezane za nailazee simbole
kao i kod parsiranja odozgo nadole. esto se za donoenje tih odluka koristi jedan
nailazei simbol.
Gramatika kod koje je mogue rijeiti sve konflikte parsiranja odozdo nagore na
osnovu fiksirane koliine informacija koje se odnose na dosadanji tok parsiranja i
ograniene koliine nailazeih simbola naziva se LR(k) gramatika (itanje slijeva
nadesno, desna izvoenja, k nailazeih simbola).

Principi programiranja Prevodioci i interpretatori

64

F. YACC GENERATOR PARSERA


Korisnik priprema specifikaciju procesa inputa, koja ukljuuje pravila koja
opisuju strukturu inputa, kod koji treba pokrenuti kada se prepoznaju ta pravila i rutinu
niskog nivoa koja se bavi osnovnim inputom (leksikom analizom inputa). Na osnovu te
specifikacije YACC generie funkciju za kontrolu tog procesa. To je funkcija koju
zovemo parser. Ona obavlja sintaksnu analizu inputa, tj. u osnovi odgovara na pitanje da
li ulazna niska (dio ulaznog toka do graninika) pripada jeziku koji opisuju data
gramatika pravila. Ta funkcija poziva ulaznu rutinu niskog nivoa koju je obezbijedio
korisnik (leksiki analizator) da pokupi leksike klase (tokene, lekseme) iz ulaznog toka.
Te lekseme zatim organizuje prema pravilima koja struktura inputa mora da zadovoljava
gramatikim pravilima; kada se prepozna neko od tih pravila (tj, desna strana nekog od
tih pravila), izvrava se kod koji je korisnik obazbijedio za to pravilo (akcija). Akcije
mogu da vraaju vrijednosti i da koriste vrijednosti drugih akcija. Ako parser uspije da
strukturu ulazne niske usporedi s najirom klasom tj. poetnim simbolom, tj. ako otkrije
da postoji izvoenje te niske u toj gramatici, vratie pozitivan odgovor, odnosno,
obavijestie korisnika da ta niska pripada datom jeziku.
F1. Osnovna specifikacija
Svaka specifikaciona datoteka sastoji se od 3 sekcije: deklaracije, (gramatika)
pravila i programi. Sekcije se odvajaju sa %%. Puna specifikacija ima oblik:
deklaracije
%%
pravila
%%
programi
a najmanja specifikacija ima oblik
%%
pravila
jer ako nema sekcije programa ne mora se ukljuivati ni drugi red s procentima.
Sekcija deklaracija slui za deklaraciju imena (i globalnih C-ovskih
promjenljivih). Imena se odnose na terminale (lekseme) ili na neterminale (uglavnom,
ono s lijeve strane gramatikih pravila). Imena mogu biti razliitih duina i sastoje se od
slova, cifara, take i podvlake, pri emu moraju poinjati slovom. Velika i mala slova se
razlikuju. Imena koja predstavljaju lekseme (terminale) moraju se deklarisati, na sljedei
nain: u sekciju deklaracija upie se
%token ime1 ime2.
Svako ime koje nije deklarisano u sekciji deklaracija smatrae se imenom
neterminala. Svaki neterminal mora se pojaviti na lijevoj strani bar jednog pravila.

Principi programiranja Prevodioci i interpretatori

65

Od svih neterminala, jedan, nazvan startni simbol, ima posebnu vanost. Parser se
pravi da bi prepoznao startni simbol, te, prema tome, stratni simbol predstavlja najveu
i najoptiju strukturu koja je opisana gramatikim pravilima. Po defaultu se uzima da je
startni simbol lijeva strana prvog pravila navedenog u sekciji pravila, ali je poeljno
eksplicitno deklarisati startni simbol u sekciji deklaracija, uz koritenje kljune rijei
%start
Korisnik moe definisati i druge promjenljive koje e koristiti akcije. Deklaracije
i definicije pojavljuju se u sekciji deklaracija, ograniene s %{ i %}. Te promjenljive
imaju globalan opseg vaenja pa su poznate izrazima akcija i leksikom analizatoru. Npr.
%{
int promjenljiva=0;
%}
moe se postaviti u sekciju deklaracija i prom e biti dostupno svim akcijama.
YACC parser koristi samo imena koja poinju sa yy, tako da bi korisnik takva
imena trebao izbjegavati.
Sekcija pravila sastoji se od gramatikih pravila oblika
A: TIJELO ;
Pri emu je A ime neterminala, dvotaka predstavlja strelicu, a TIJELO je
sekvenca 0 ili vie imena i literala.
Imena koja se koriste u tijelu gramatikog pravila mogu predstavljati lekseme ili
neterminale.
Literali se sastoje od karaktera ogranienih jednostrukim navodnicima, , npr.
+. Pri tome se, kao i u C-u, koriste eskejp karakteri (kosa crta).
Ako nekoliko pravila ima istu lijevu stranu, umjesto ponovnog ispisa te lijeve
strane moe se koristiti vertikalna crta. Tada se mogu izostaviti taka-zarezi.
Dakle, umjesto:
A : F G;
A : X Y;
moe i
A : F G
| X Y ;

Principi programiranja Prevodioci i interpretatori

66

Ako neterminalu odgovara prazan skup (epsilon pravilo), to se moe prikazati na


oigledan nain:
P : ;
F2. Akcije
Sa svakim gramatikim pravilom, korisnik moe da povee akcije koje e se
izvriti svaki put kada se u procesu oitavanja inputa prepozna to pravilo. Te akcije mogu
vra'ati vrijednosti i mogu dobijati vrijednosti od prethodnih pravila. tavie, i leksiki
analizator moe vraati vrijednosti leksema.
Akcija je ma kakav izraz na C-u input, output, poziv funkcija, promjena
vijednosti globalnih promjenljivih... akcija je odreena s jednim ili vie izraza,
ogranienih velikim zagradama { i }, npr.
A : ( B )
{printf(evo ode B);}
ili
X : Y Z
{flag=26;}
Kako bi se omoguila komunikacija izmeu akcije i parsera, izrazi za akcije
koriste mehanizam tzv. dolarskih ($) promjenljivih.
Akcije za vraanje vrijednosti koriste pseudo-promjenljivu $$ koju postavljaju na
neku vrijednost. Npr. akcija koja ne radi nita drugo sem to vraa vrijednost 1 je
{$$=1;}
Za dobijanje vrijednosti koje vraaju prethodne akcije i leksiki analizator, akcija
moe koristiti pseudo promjenljive $1, $2, koje se odnose na vrijednosti koje redom
vraaju komponente desne strane pravila, s lijeva na desno. Tako, ako je pravilo
A : B C D ;
Onda $2 ima vrijednost koju vraa C. npr, kod pravila
izraz : ( izraz )
Vrijednost koji vraa to pravilo je obino vrijednost izraza u zagradama. To se
oznaava sa:
izraz : ( izraz ) {$$=$2;}

Principi programiranja Prevodioci i interpretatori

67

Po defaultu, vrijednost pravila je vrijednost prve komponente u njemu ($1). Tako,


gramatika pravila oblika
A : B
ne moraju pratiti eksplicitne akcije.
U dosadanjim primjerima sve akcije su se nalazile na kraju pravila. Ponekad je
poeljno preuzeti kontrolu prije nego to se pravilo u potpunosti parsira. YACC
dozvoljava da se akcija upie usred pravila. Pretpostavlja se da i ta akcija, kao
komponenta pravila, vraa vrijednost, koja je dostupna pravilima koja su joj zdesna
putem uobiajenog mehanizma, a ona moe imati pristup vrijednostima koje vraaju
komponente koje su joj slijeva. Tako, pravilo
A : B {$$=1;} C {x=$2; y=$3;}
;
ima sljedei uinak: postavljanje x na 1 i y na vrijednost koju vraa C.
Akcije koje ne zavravaju pravila YACC u stvari tretira na sljedei nain:
proizvede novo ime neterminala i novo pravilo koje povezuje to ime s komponentom
desne strane. Unutranja akcija je akcija pokrenuta prepoznavanjem pridodatog pravila.
YACC u stvari gornji primjer tretira na sljedei nain:
$ACT : /*prazno*/
{$$=1;}
;
A : B $ACT C
{x=$2; y=$3;}
;

F3. Leksiki analizator


Korisnik mora da obezbijedi leksiki analizator koji e oitavati ulazni tok i
prosljeivati lekseme (tokene), zajedno s njihovim vrijednostima, ako je to potrebno, do
parsera. Leksiki analizator je cjelobrojna funkcija koja se naziva yylex() to je naziv
koji e parser oekivati, ako parser dobijamo pomou alata kao to je YACC, pa emo ga
koristiti u svakom sluaju. Funkcija yylex() vraa cjelobrojnu vrijednost koja predstavlja
oznaku lekseme, odnosno vrstu tokena koji je oitan iz ulaznog toka. Ako je s tom
leksemom povezana neka vrijednost, nju u okviru leksikog analizatora treba povezati sa
spoljanjom promjenljivom yyval(), koja slui za prenos vrijednosti leksema do parsera.
Parser i leksiki analizator moraju se poklapati to se tie brojeva leksema,
odnosno oznaka za vrste tokena, kako bi mogla da postoji obostrano jednoznana
komunikacija izmeu njih. Te brojeve moe izabrati YACC ili sam korisnik. U svakom
sluaju, da bi leksiki analizator te brojeve vratio kao rezultat koristi se #define

Principi programiranja Prevodioci i interpretatori

68

mehanizam C-a. Npr. pretpostavimo da je ime lekseme CIFRA definisano u sekciji


deklaracija specifikacije (opisa) za YACC. Odgovarajua sekcija leksikog analizatora
moe izgledati ovako:
yylex()
{
extern int yyval;
int c;

c=getchar();

switch (c)
{

case 0:
case 1:

case 9: yyval=c-0;
return (CIFRA);

}
Namjera je da se vrati broj lekseme CIFRA oznaka da je iz ulaznog toka
prepoznata niska koja odgovara pravilima te leksike klase, kao i vrijednost koja je
jednaka numerikoj vrijednosti te cifre. Ako se kod leksikog analizatora ubaci u sekciju
programa specifikacije (opisa) za YACC, identifikator CIFRA e biti definisan kao broj
lekseme vezane za CIFRE.
Ovaj mehanizam daje jasne leksike analizatore koje je lako modifikovati; jedini
nedostatak predstavlja potreba da se izbjegne koritenje svih imena leksema koja su
rezervisana u C-u ili u parseru; npr. koritenje imena leksema kao to je if ili while
e skoro sigurno dovesti do tekih posljedica kada se leksiki analizator kompajlira. Ime
error je rezervisano za obradu greaka i takoe se ne smije koristiti naivno.
Kao to je ve pomenuto, brojevi leksema mogu biti izabrani od strane YACC-a,
ili ih moe izabrati korisnik. Default brojevi leksema za karaktere iz ASCII skupa
karaktera predstavlja njihova numerika vrijednost (redni broj) u ASCII skupu karaktera.
Kako u ASCII skupu karaktera ima 256 karaktera (koji ukljuuju slova, cifre,
interpunkcijske i specijalne znakove), ostalim imenima e se dodjeljivati brojevi od 257
pa nadalje.
Da bi se leksemama dodijelili brojevi, prva pojava imena lekseme u sekciji
deklaracija mora biti neposredno praena nenegativnim cijelim brojem. Uzima se da je taj
cijeli broj broj lekseme imena ili literala. Imena i literali koji nisu definisani ovim

Principi programiranja Prevodioci i interpretatori

69

mehanizmom zadravaju svoje default brojeve. Vano je da brojevi leksema budu


razliiti.
Iz istorijskih razloga, graninik mora imati broj lekseme 0 ili negativan broj. Taj
broj lekseme korisnik ne moe redefinisati; prema tome, svi leksiki analizatori trebaju
biti spremni da vrate 0 ili negativan broj kao broj lekseme kad dou do kraja ulaznog toka
time signalizuju parseru da je input uitan i da treba biti uporeen s najirom
gramatikom kategorijom poetnim simbolom.
Veoma koristan alat za konstruisanje leksikih analizatora je FLEX. Leksiki
analizator koji se dobijaju pomou FLEX-a stvoreni su da rade u harmoniji s parserima
koje pravi YACC. Specifikacije za leksike analizatore koriste regularne izraze umjesto
gramatikih pravila, ali su inae, kao to smo ve vidjeli, prilino sline specifikacijama
za parsere. FLEX lako proizvodi dosta komplikovane leksike analizatore, meutim,
postoje i jezici (recimo, FORTRAN) ije se lekseme ne uklapaju u bilo ta to bi se
moglo izraziti regularnim izrazima i za koje se leksiki analizatori moraju raditi runo.
Primjer Y1
Kao primjer uspjene simbioze FLEX-a i YACC-a, posmatraemo parser za jednostavan,
paskaloliki jezik, dobijen koritenjem YACC-a, iji je leksiki analizator dobijen
koritenjem FLEX-a. Najprije, evo gramatike tog jezika:
<naredba>::=<while_naredba>|<begin_naredba>
|<naredba_dodele>
<while_naredba>::=WHILE <iskaz> DO <naredba>
<begin_naredba>::=BEG <naredba1> END SEMI
<naredba_dodele>::=PROM DODELA <izraz> SEMI
<naredba1>::=<naredba>|<naredba><naredba>
<iskaz>::=<izraz> EQ <izraz>
<izraz>::=PROM | BROJ
Primijetiemo da nas leksiki analizator treba obavjetavati o pojavama sljedeih
leksema: rezervisanih rijei WHILE, DO, BEG, END, znakova SEMI (takazarez), DODELA (dvotaka-jednako) i EQ (jednako), te PROM (identifikatorima) i
BROJ (brojevima). Kako elimo samo da odgovorimo na pitanje da li neka niska pripada
jeziku definisanom tom gramatikom, a ne i da evaluiramo te niske, neemo traiti od
leksikog analizatora da vraa vrijednosti prepoznatih leksema, ve samo da obavjetava
parser o tome da ih je prepoznao.
Evo opisa za FLEX koji emo koristiti za dobijanje traenog leksikog
analizatora:
slovo
cifra
broj
prom

[a-z]
[0-9]
{cifra}+
{slovo}({cifra}|{slovo})*

Principi programiranja Prevodioci i interpretatori

%%
while
do
begin
end
:=
=
;
{broj}
{prom}

{
{
{
{
{
{
{
{
{

return
return
return
return
return
return
return
return
return

70

WHILE; }
DO; }
BEG; }
END; }
DODELA; }
EQ; }
SEMI; }
BROJ; }
PROM; }

Obratite panju da je trea sekcija, sekcija programa, u ovom opisu prazna (tj.
nema je!), za razliku od opisa za FLEX koje smo pisali u Poglavlju 1. To je zato to e
funkcije main() i yylex(), koje smo ablonski ukljuivali u tu sekciju, biti pozvane iz
parsera, tako da nema potrebe da ih tu navodimo.
Kao i programi za rekurzivni spust koje smo koristili u konjukciji s leksikim
analizatorima proizvedenim FLEX-om, i parser koji emo proizvesti YACC-om
oekivae da leksiki analizator nae u datoteci LEXYY.C, pa zbog toga prvo moramo
da fleksujemo gore navedeni opis za FLEX (koji se nalazi u datoteci PR1Y.L). Dakle,
izvrimo sljedeu naredbu:
C:\FLEX> flex pr1y.l
Tako smo dobili leksiki analizator. Naravno, on sada ne moe da se koristi sam
za sebe (sjetite se, nema main()!), ve treba napraviti i parser koji e ga pozivati. Ovo
je opis za parser za ovu gramatiku:
%{
#include <stdio.h>
%}
%token WHILE DO BEG END SEMI EQ DODELA PROM BROJ
%%
naredba
: while_naredba
| begin_naredba
| naredba_dodele
;
while_naredba : WHILE iskaz DO naredba
;
begin_naredba : BEG naredba1 END SEMI
;
naredba_dodele : PROM DODELA izraz SEMI
;
naredba1
: naredba
| naredba naredba1
;
iskaz
: izraz EQ izraz

Principi programiranja Prevodioci i interpretatori

izraz

71

;
: BROJ
| PROM
;

%%
#include "c:\flex\lexyy.c"
yyerror(char *s)
{
printf("%s\n",s);
}
main()
{
return yyparse();
}
Kako nismo koristili mehanizam #define, leksemama e biti dodjeljeni default
brojevi, tj. WHILE e dobiti broj 257, DO broj 258 itd. i funkcija yylex() e ba te
brojeve prosljeivati parseru kad u ulaznom toku naie na odgovarajue lekseme.
Obratite panju na sekciju programa ove specifikacije ona ukljuuje tri stvari
koje se, ablonski, nalaze u svakoj takvoj specifikaciji direktivu za ukljuenje datoteke
koja sadri leksiki analizator, funkciju za obradu greaka i funkciju main(), koja u biti
samo poziva funkciju yyparse(), koju emo dobiti kad YACC obradi tu specifikaciju.
Specifikacija se nalazi u datoteci PR1Y.Y (specifikacije za YACC nose ekstenziju
Y, kao to specifikacije za FLEX nose ekstenziju L!). Evo kako emo ju obraditi
YACC-om, tj. jakovati:
Preemo u direktorijum YACC:
C:\FLEX> cd\yacc
Sada pozovemo YACC naredba je BYACC, a argument je ime datoteke u
kojoj se nalazi specifikacija.
C:\YACC> byacc pr1y.y
Trebali biste dobiti kratki izvjetaj koji e Vas obavijestiti o broju konflikata u
Vaoj gramatici. Rezultat ove naredbe je to to sada u datoteci Y_TAB.C u direktorijumu
YACC imamo parser. Njega emo pokrenuti iz editora za turbo C:
Preemo u direktorijum TC, pokrenemo program TC, idemo sa Alt+F, Load,
upiemo C:\YACC\Y_TAB.C

Principi programiranja Prevodioci i interpretatori

72

U editoru bismo trebali vidjeti parser. Njega sad treba kompajlirati (Alt+C) i
pokrenuti (Alt+R), a zatim nahraniti niskama koje odgovaraju datoj gramatici (kao i,
naravno, niskama koje ne odgovaraju, da bismo vidjeli da li nam rutina za obradu greaka
radi kako treba). Unos niske prekidajte sa Ctrl+Z, a rezultate gledajte sa Alt+F, OS Shell.
F4. Dvosmislenost i konflikti
Skup gramatikih pravila je dvosmislen ako postoji ulazna niska koja se moe
struktuirati na dva ili vie razliitih naina. Npr. Pravilo
izraz : izraz '-' izraz
predstavlja prirodan nain izraavanja injenice da je jedan od naina na koji se mogu
formirati aritmetiki izrazi spajanje druga dva izraza s minusom izmeu njih. Naalost, to
gramatiko pravilo ne odreuje u potpunosti nain na koji treba struktuirati sve
kompleksne ulazne niske. Npr. Za ulaz
izraz izraz izraz
pravilo dozvoljava struktuiranje i kao
(izraz - izraz) izraz
i kao
izraz (izraz - izraz)
pravo predstavlja lijevu asocijativnost, a drugo desnu.
Kada pokuava da izgradi parser, YACC prepoznaje takve dvosmislenosti. Kada
mu je dat ulaz oblika
izraz izraz izraz
i kad je parser oitao drugi izraz s ulaza, ulaz koji je do tada vidio je
izraz izraz
koji odgovara desnoj strani gornjeg gramatikog pravila. Parser bi mogao da redukuje
input upotrebljavajui to pravilo nakon toga, ulaz bi bio redukovan na izraz (lijevu
stranu gramatikog pravila). Zatim bi parser oitao i preostali dio inputa
- izraz
i opet redukovao. Uinak tih koraka doveo bi do rezultata koji odgovara lijevoj
asocijativnosti.
Meutim, umjesto toga, kad parser vidi
izraz izraz
moe da odloi neposrednu primjenu pravila i nastavi da oitava ulaz dok ne vidi
izraz izraz izraz
Tu moe da primijeni pravilo na desna tri simbola, redukujui ih na izraz, to ostavlja
izraz izraz
Sada se moe jo jednom redukovati po tom pravilu, a rezultat je interpretacija koja
podrazumijeva desnu asocijativnost.
Tako, kad oita
izraz izraz
parser moe da uradi dvije stvari, koje su obje dozvoljene shift ili reduce akciju, i ne
postoji nain na koji on moe donijeti tu odluku. To se naziva shift/reduce konflikt.
Takoe se moe desiti da parser treba da donese odluku izmeu 2 dozvoljene redukcije

Principi programiranja Prevodioci i interpretatori

73

to se naziva reduce/reduce konflikt (ono to je parser oitao predstavlja desne strane vie
od jednog pravila). Primijetite da se nikad ne pojavljuju shift/shift konflikti (parser u
datom trenutku moe da iftuje samo jedan simbol nailazei, i to moe uraditi na samo
jedan nain).
Kada doe do shift/reduce ili reduce/reduce konflikata, YACC ipak proizvede
parser. To radi tako to kad god mora da donese odluku izabere jedan od 2 dozvoljena
koraka, primjenjujui dva pravila koja opisuju izbore koji se donose u takvoj situaciji, i
koja se nazivaju pravilima za razrjeavanje dvosmislenosti:
1. U situaciji kada imamo shift/reduce konflikt, po defaultu se obavlja shift
akcija.
2. U situaciji kada imamo reduce/reduce konflikt, default je da se redukuje po
ranijem gramatikom pravilu.
Pravilo 1 povlai da se redukcije odlau kad god za to postoji mogunost, tako da
se obavljaju sve mogue shift kacije.
Pravilo 2 daje korisniku dosta grub nain kontrolisanja ponaanja parsera u toj
situaciji, ali, ipak, reduce/reduce konflikte treba izbjegavati kad god je to mogue.
Konflikti mogu biti posljedica greaka u ulazu ili u logikoj postavci, ili toga da
gramatika pravila, iako su konzistentna, zahtjevaju kompleksniji parser od onog koji
moe proizvesti YACC. Koritenje akcija unutar pravila takoe moe izazvati konflikte,
ako se akcija mora izvriti prije nego to je parser siguran koje pravilo se prepoznaje. U
tim sluajevima upotreba pravila koja razrjeavaju dvosmislenosti nije odgovarajue
rjeenje, jer dovodi do netanog parsera. Iz tog razloga YACC izvjetava o broju
shift/reduce i reduce/reduce konflikata koji su razrjeeni upotrebom pravila1, odnosno
pravila 2.
Uopte, kad god je mogue primijeniti pravila koja razrjeavaju dvosmislenosti
radi konstrukcije tanog parsera, takoe je mogue i ponovo napisati gramatika pravila
tako da se oitava isti ulaz, ali bez konflikata. Iz tog razloga, veina ranijih generatora
parsera smatrali su da konflikti predstavljaju greke. Iskustvo kreatora YACC-a pokazalo
je da je to ponovno pisanje pravila radi eliminacije konflikata donekle neprirodno i da
dovodi do sporijih parsera. Prema tome, YACC e proizvesti parser ak i ako postoje
konflikti.
Kao primjer moi pravila koja razrjeavaju dvosmoslenosti, razmotriemo
fragment iz programskog jezika koji ukljuuje if-then-else konstrukciju
izraz : IF '(' uslov ')' izraz
| IF '(' uslov ')' izraz ELSE izraz
;
U ovim pravilima IF i ELSE su lekseme (terminali, tokeni), uslov je nezavrni
simbol koji opisuje kondicionalne (logike) izraze, a izraz je nezavrni simbol koji

Principi programiranja Prevodioci i interpretatori

74

opisuje izraze. Prvo pravilo bie nazvano jednostavno if pravilo a drugo if-else pravilo.
Ta dva pravila formiraju dvosmislenu konstrukciju, jer ulaz oblika
IF (C1) IF (C2) S1 ELSE S2
moe da se prema tim pravilima struktuira na dva naina:
IF (C1) {
IF (C2) S1
}
ELSE S2
i
IF (C1) {
IF (C2) S1
ELSE S2
}
Druga interpretacija je ona koju traimo kod veine programskih jezika koji
ukljuuju ovu konstrukciju. Svaki ELSE je vezan s posljednjim IF koji mu prethodi, a
koji jo nije vezan s nekim ELSE-om. U ovom primjeru, razmotrite situaciju do koje
dolazi kada je parser na ulazu vidio
IF (C1) IF (C2) S1
i razmatra ELSE. Moe odmah redukovati po jednostavnom if pravilu i dobiti
IF (C1) izraz
a onda uitati preostali ulaz
ELSE S2
i redukovati
IF (C1) izraz ELSE S2
po if-else pravilu. To dovodi do prve prikazane interpretacije tog ulaza.
S druge strane, ELSE se moe iftovati, nakon ega se uita S2 i desni dio
IF (C1) IF (C2) S1 ELSE S2
moe se redukovati po if-else pravilu, to daje
IF (C1) izraz
a to se onda moe redukovati po jednostavnom if pravilu. To dovodi do druge
interpretacije ulaza koja je navedena gore, to je ono to se obino i eli postii.
Opet, parser moe uraditi dvije dozvoljene stvari i postoji shift/reduce konflikt.
Upotreba pravila koje razrjeava dvosmislenost br.1. kae parseru da izvri shift akciju,
to dovodi do eljenog rezultata.
F5. Prioritet
Postoji jedna uobiajane situacija u kojoj pravila za razrjeavanje dvosmislenosti
nisu dovoljna, a to je parsiranje aritmetikih izraza. Najvei dio obino koritenih

Principi programiranja Prevodioci i interpretatori

75

konstrukcija za aritmetike izraze moe se prirodno opisati pojmom nivoa prioriteta za


operatore, uz informacije o tome da li su lijevo ili desno asocijativni. Pokazuje se da
dvosmislene gramatike s odgovarajuim pravilima za razrjeavanje dvosmislenosti mogu
da se koriste za stvaranje parsera koji su bri i laki od parsera konstrisanih iz gramatika
koje ne sadre dvosmislenosti.
Osnovna ideja ukljuuje pisanje gramatikih pravila u obliku
izraz : izraz OP izraz
i
izraz : UNARNI izraz
za sve binarne i unarne operatore koji se trae. To pravi vrlo dvosmislenu gramatiku s
mnogo konflikata. Radi uklanjanja dvopsmislenosti, korisnik odreuje prioritet, tj. jainu
okupljanja, za sve operatore, kao i asocijativnost binarnih operatora. Te informacije su
dovoljne da bi YACC razrijeio sve konflikte prema tim pravilima i da bi na kraju
realizovao traeni prioritet i asocijativnost.
Prioritet i asocijativnost dodjeljuje se leksemama u sekciji deklaracija. To se radi
unoenjem nekoliko linija koje poinju kljunim rijeima %left, % right ili %nonassoc,
nakon kojih slijede liste leksema.
Sve lekseme u istom redu imaju isti nivo prioriteta i asocijativnost koja je
naglaena kljunom rijeju kojom taj red poinje, a redovi se niu prema poveanju
prioriteta. Prema tome
%left '+' '-'
%left '*' '/'
opisuje prioritet i asocijativnost etiri aritmetika operatora, + i su lijevo asocijativni i
imaju manji prioritet od * i / koji su takoe lijevo asocijativni. Kljuna rije %right
koristi se za opis desno asocijativnih operatora, a kljuna rije %nonassoc za opis
operatora koji, kao npr. .LT. u Fortranu, ne mogu da budu asocijativni sa sobom, npr.
A.LT.B.LT.C nije dozvoljeno u Fortranu, i takav operator bio bi opisan sa %nonassoc u
YACC-u. Kao primjer ponaanja tih deklaracija, opis
%right '='
%left '+' '-'
%left '*' '/'
%%
izraz : izraz
| izraz
| izraz
| izraz
| izraz
| IME
;

'='
'+'
'-'
'*'
'/'

izraz
izraz
izraz
izraz
izraz

Principi programiranja Prevodioci i interpretatori

76

moe se koristiti za struktuiranje inputa


a=b=c*d-e-f*g
na sljedei nain
a=(b=(((c*d)-e)-(f*g))).
Kada se koristi taj mehanizam, unarni operatori moraju, u optem sluaju, imati
vii prioritet. Ponekad unarni i binarni operatori imaju istu simboliku reprezentaciju, ali
razliite nivoe prioriteta. Primjer za to imamo kod unarnog i binarnog -. Unarnom minus
se moe dati ista snaga kao mnoenju, ili ak i via, dok binarno minus ima niu snagu od
mnoenja. Kljuna rije %prec mijenja prioritetni nivo vezan s odreenim gramatikim
pravilom. %prec se pojavljuje odmah iza tijela gramatikog pravila, prije akcije ili takezareza, i prati ga ime lekseme ili literal. Dovodi to toga da prioritet tog gramatikog
pravila postane prioritet prateeg imena lekseme ili literala. Npr. da bi unarni minus imao
isti prioritet kao i mnoenje, pravila mogu izgledati ovako:
%left '+' '-'
%left '*' '/'
%%
izraz : izraz '+'
| izraz '-'
| izraz '*'
| izraz '/'
| '-' izraz
| IME
;

izraz
izraz
izraz
izraz
%prec '*'

Lekseme koje se deklariu sa %left, %right ili %nonassoc mogu, ali i ne moraju,
biti deklarisane i sa %token.
Primjer Y2
Kao primjer emo uzeti specifikaciju koja slui za evaluaciju jednostavnih aritmetikih
izraza. Opis za YACC koji generie parser nalazi se u datoteci PR2Y.Y:
%token BROJ
%left '+' '-'
%left '*' '/'
%left UMINUS
%%
s: izraz
{printf("%d\n",$1);};
izraz: izraz '+' izraz
{$$=$1+$3;}

Principi programiranja Prevodioci i interpretatori

77

| izraz '-' izraz


{$$=$1-$3;}
| izraz '*' izraz
{$$=$1*$3;}
| izraz '/' izraz
{if ($3==0) yyerror ("djeljenje nulom");
else $$=$1/$3;}
| '-' izraz
{$$=-$2;}
| '(' izraz ')' %prec UMINUS
{$$=$2;}
| BROJ;
%%
#include "c:\flex\lexyy.c"
yyerror(s)
char *s;
{printf("%s\n",s);
}
main()
{return yyparse();
}
Ovdje vidimo ilustraciju onog o to smo razmatrali u prethodnom tekstu s jedne
strane pitanja prioriteta i asocijativnosti, a s druge pitanja akcija.
Opis za FLEX koji generie odgovarajui leksiki analizator nalazi se u datoteci
PR2Y.L:
cijeli [0-9]+
%%
{cijeli} {yylval=atoi(yytext); return BROJ;}
[ \t]
; /*ignorisemo bjeline */
.
return yytext[0];

Kao to smo ve vidjeli, promjenljiva yytext uva posljednju prepoznatu nisku u


ovom sluaju prepoznatu nisku koja predstavlja cijeli broj, a C-ovska funkcija atoi(int)
pretvara nisku u cijeli broj. Bjeline ignoriemo, a taka predstavlja default u sluaju da
se prepozna bilo ta to nije ni cifra ni bjelina, leksiki analizator e to (prvi element
niske yytext) vratiti parseru.

Principi programiranja Prevodioci i interpretatori

78

Ako elimo da ovo isprobamo, najprije generiemo leksiki analizator,


fleksujui opis za FLEX:
C:\FLEX> flex pr2y.l
Zatim preemo i diektorijum YACC i jakujemo opis za YACC, kako bismo
realizovali i parser:
C:\FLEX> cd\yacc
C:\YACC> byacc pr2y.y
Primijetite da nema izvjetaja o broju konflikata u Vaoj gramatici. To je
posljedica toga to smo ih eliminisali eksplicitnim unoenjem prioriteta i asocijativnosti.
Rezultat ove naredbe je to to sada u datoteci Y_TAB.C u direktorijumu YACC
imamo parser. Njega emo pokrenuti iz editora za turbo C:
Preemo u direktorijum TC, pokrenemo program TC, idemo sa Alt+F, Load,
upiemo C:\YACC\Y_TAB.C
U editoru bismo trebali vidjeti parser. Njega sad treba kompajlirati (Alt+C) i
pokrenuti (Alt+R), a zatim nahraniti niskama koje odgovaraju datoj gramatici (kao i,
naravno, niskama koje ne odgovaraju, da bismo vidjeli da li nam rutina za obradu greaka
radi kako treba). Unos niske prekidajte sa Ctrl+Z, a rezultate gledajte sa Alt+F, OS Shell.
Pokuajte izmjeniti parser time to ete obrisati redove koji se odnose na prioritet
i asocijativnost. Ponovo jakujte i isprobajte parser. Vidjeete da niske nee biti
evaluirane na odgovarajui nain na primjer, niska 5*56-2 bie evaluirana sa 270,
a ne sa 278, kao kod korektnog parsera.
Primjer Y3
Sljedei primjer pokazuje kako se koritenjem YACC-a moe proizvesti jednostavan alat
za metriku evaluaciju. Pretpostavimo da se kompleksnost aritmetikih izraza definie
formulom:
A + 2B + 3C
gdje je A broj binarnih operatora sabiranja i oduzimanja, B broj unarnih operatora plus i
minus, a C broj operatora mnoenja i dijeljenja u izrazu.
Input za FLEX nalazi se u datoteci PR3Y.Y. U odnosu na prethodni primjer,
razlike su samo u akcijama, koje ovdje ne evaluiraju vrijednost izraza, nego njegovu
kompleksnost, prema gorenavedenom pravilu:
%token PROM

Principi programiranja Prevodioci i interpretatori

79

%left '+' '-'


%left '*' '/'
%%
s

: izraz
{printf("%d\n",$1);};
izraz : izraz '+' izraz
{$$=$1+$3+1;}
| izraz '-' izraz
{$$=$1+$3+1;}
| izraz '*' izraz
{$$=$1+$3+5;}
| izraz '/' izraz
{$$=$1+$3+5;}
| '-' izraz
{$$=$2+2;}
| '+' izraz
{$$=$2+2;}
| '(' izraz ')'
{$$=$2;}
| PROM
{$$=0;};
%%
#include "c:\flex\lexyy.c"
yyerror(char*s)
{ printf("%s\n",s);
}
main()
{ return yyparse();
}

Opis za FLEX nalazi se u datoteci PR3Y.L. Ovdje izrazi mogu ukljuivati i


identifikatore, tako da definiemo i leksiku klasu za njih. U nju emo ubaciti i brojeve,
jer se ne trai evaluacija njihove vrijednosti, ve samo obavjetenje da su prepoznati u
ulaznom toku. Tako za identifikatore i brojecve imamo jednu leksiku klasu:
prom [a-z0-9]+
bjelina [ \n\t]
bjeline {bjelina}+
%%

Principi programiranja Prevodioci i interpretatori

{bjeline}
{prom}
.

80

; /*ignorisi praznine*/
{return PROM;}
{return yytext[0];}

Primjer se izvrava analogno prethodnom.


Primjer Y4
Kompletna YACC specifikacija za kalkulator koji ima 26 registara oznaenih slovima od
a do z, koji prihvata aritmetike izraze koje ine operatori +, -, *, /, % (dijeljenje po
modulu), & (bitsko i), (bitsko ili) i operator dodjele. Ako je izraz na najviem nivou
dodjela, vrijednost se ne tampa, inae se tampa; ako cijeli broj poinje sa 0 smatra se
oktalnim, inae decimalnim. Opis za YACC nalazi se u datoteci PR4Y.Y:
%{
#include <ctype.h>
int registri[26];
int baza;
%}
%start list
%token CIFRA SLOVO
%left '|'
%left '&'
%left '+' '-'
%left '*' '/'
%left UMINUS
%%
list :
| list iskaz '\n'
| list error '\n'
{yyerrok;};
iskaz : izraz
{printf("%d\n", $1);}
| SLOVO '=' izraz
{registri[$1]=$3;};
izraz : '(' izraz ')'
{$$=$2;}
| izraz '+' izraz
{$$=$1+$3;}
| izraz '-' izraz

Principi programiranja Prevodioci i interpretatori

|
|
|
|
|
|
|
|

{$$=$1-$3;}
izraz '*' izraz
{$$=$1*$3;}
izraz '/' izraz
{$$=$1/$3;}
izraz '%' izraz
{$$=$1%$3;}
izraz '&' izraz
{$$=$1&$3;}
izraz '|' izraz
{$$=$3|$1;}
'-' izraz
%prec UMINUS
{$$=-$2;}
SLOVO
{$$=registri[$1];}
broj;

broj : CIFRA
{$$=$1; baza=($1==0)?8:10;}
| broj CIFRA
{$$=baza*$1+$2;};
%%
#include "c:\flex\lexyy.c"
yyerror(char *s){
fprintf(stderr,"%s\n",s);
}
main(){
return(yyparse());
}
Opis za FLEX nalazi se u datoteci PR4Y.L:
slovo [a-z]
cifra [0-9]
%%
{slovo} {yyval=yytext[0]-'a';
return SLOVO;}
{cifra} {yyval=yytext[0]-'0';
return CIFRA;}
.
{return(yytext[0]);}
Primjer se izvrava analogno prethodnom.

81

Principi programiranja Prevodioci i interpretatori

82

Za promjenu, moemo da pogledamo i parser koji ukljuuje direktno napisan


leksiki analizator dakle, funkcija yylex() upisana je direktno, bez koritenja FLEX-a.
Ovaj oblik kalkulatora nalazi se u datoteci PR4aY.Y, i kod izvravanja ga je dovoljno
jakovati, a zaim na uobiajen nain pokrenuti iz TC-a.
%{
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int registri[26];
int baza;
%}
%start list
%token CIFRA SLOVO
%left '|'
%left '&'
%left '+' '-'
%left '*' '/'
%left UMINUS
%%
list :
| list iskaz '\n'
| list error '\n'
{yyerrok;};
iskaz : izraz
{printf("%d\n", $1);}
| SLOVO '=' izraz
{registri[$1]=$3;};
izraz : '(' izraz ')'
{$$=$2;}
| izraz '+' izraz
{$$=$1+$3;}
| izraz '-' izraz
{$$=$1-$3;}
| izraz '*' izraz
{$$=$1*$3;}
| izraz '/' izraz
{$$=$1/$3;}
| izraz '%' izraz
{$$=$1%$3;}
| izraz '&' izraz
{$$=$1&$3;}
| izraz '|' izraz
{$$=$3|$1;}

Principi programiranja Prevodioci i interpretatori

83

| '-' izraz
%prec UMINUS
{$$=-$2;}
| SLOVO
{$$=registri[$1];}
| broj;
broj : CIFRA
{$$=$1; baza=($1==0)?8:10;}
| broj CIFRA
{$$=baza*$1+$2;};
%%
yylex()
{
/* rutina za leksicku analizu vraca SLOVO ako prepozna malo
slovo */
/* vraca CIFRA ako prepozna cifru, yyval=0 do 9 */
/* sve druge karaktere vraca odmah */
int c;
while ((c=getchar())==' ') {/*preskaci blankove*/}
if (islower(c))
{ yylval=c-'a';
return(SLOVO);
}
if (isdigit(c))
{ yylval=c-'0';
return(CIFRA);
}
return(c);
}
yyerror(char *s){
fprintf(stderr,"%s\n",s);
}
main(){
return(yyparse());
}
Primjer Y5
Kalkulator iz prethodnog primjera u ovom je primejru modifikovan tako da obuhvati i
izraze koji ukljuuju realne brojeve u pokretnom zarezu postoji 26 promjenljivih koje
su realni brojevi u pokretnom zarezu, oznaenih slovima od a do z, a takoe
razumije i intervale, koji se piu u obliku (x,y), pri emu je x manje ili jednako y. Postoji

Principi programiranja Prevodioci i interpretatori

84

26 intervalskih promjenljivih, oznaenih slovima od A do Z. Opis za YACC nalazi


se u datoteci PR5Y.Y, a leksiki analizator je ukljuen.
%{
#include <stdio.h>
#include <ctype.h>
typedef struct interval {
double lo,hi;
} INTERVAL;
INTERVAL vmul(), vdiv();
double atof();
double dreg[ 26 ];
INTERVAL vreg[ 26 ];
%}
%start lines
%union {
int ival;
double dval;
INTERVAL vval;
}
%token <ival> DREG VREG
%token <dval> CONST
%type <dval> dexp
%type <vval> vexp
%left '+' '-'
%left '*' '/'
%left UMINUS
%%
lines: /*prazno*/
| lines line
;
line : dexp '\n'
{ printf("%f\n", $1); }
| vexp '\n'
{ printf("(%f , %f )\n", $1.lo, $1.hi); }
| DREG '=' dexp '\n'
{ dreg[$1] = $3; }
| VREG '=' vexp '\n'
{ vreg[$1] = $3; }
| error '\n'
{ yyerrok; }

Principi programiranja Prevodioci i interpretatori

;
dexp : CONST
| DREG
{ $$ = dreg[$1]; }
| dexp '+' dexp
{ $$ = $1 + $3; }
| dexp '-' dexp
{ $$ = $1 - $3; }
| dexp '*' dexp
{ $$ = $1 * $3; }
| dexp '/' dexp
{ $$ = $1 / $3; }
| '-' dexp %prec UMINUS
{ $$ = -$2; }
| '(' dexp ')'
{ $$ = $2; }
;
vexp : dexp
{ $$.hi = $$.lo = $1; }
| '(' dexp ',' dexp ')'
{ $$.lo = $2;
$$.hi = $4;
if ( $$.lo > $$.hi ) {
printf("interval nije u redu\n");
YYERROR;
}
}
| VREG
{ $$ = vreg[$1]; }
| vexp '+' vexp
{ $$.hi = $1.hi + $3.hi;
$$.lo = $1.lo + $3.lo; }
| dexp '+' vexp
{ $$.hi = $1 + $3.hi;
$$.lo = $1 + $3.lo; }
| vexp '-' vexp
{ $$.hi = $1.hi - $3.lo;
$$.lo = $1.lo - $3.hi; }
| dexp '-' vexp
{ $$.hi = $1 - $3.lo;
$$.lo = $1 - $3.hi; }
| vexp '*' vexp
{ $$ = vmul( $1.lo, $1.hi, $3 ); }
| dexp '*' vexp
{ $$ = vmul( $1, $1, $3 ); }
| vexp '/' vexp
{ if ( dcheck( $3 ) ) YYERROR;

85

Principi programiranja Prevodioci i interpretatori

86

$$ = vdiv( $1.lo, $1.hi, $3 ); }


| dexp '/' vexp
{ if ( dcheck( $3 ) ) YYERROR;
$$ = vdiv( $1, $1, $3 ); }
| '-' vexp %prec UMINUS
{ $$.hi = -$2.lo; $$.lo = -$2.hi; }
| '(' vexp ')'
{ $$ = $2; }
;
%%
#define BSZ 50
yylex(){
register c;
while ((c=getchar())==' ');
if(isupper(c)){
yylval.ival=c-'A';
return(VREG);
}
if(islower(c)){
yylval.ival=c-'a';
return(DREG);
}
if(isdigit(c)||c=='.'){
char buf[BSZ+1], *cp=buf;
int dot=0, exp=0;
for(; (cp-buf)<BSZ; ++cp, c=getchar()){
*cp=c;
if(isdigit(c)) continue;
if(c=='.'){
if(dot++||exp) return('.');
continue;
}
if(c=='e'){
if(exp++) return('e');
continue;
}
break;
}
*cp='\0';
if((cp-buf)>=BSZ)
printf("konstanta
skracena\n");
else ungetc(c,stdin);
yylval.dval=atof(buf);
return(CONST);
}
return(c);

preduga:

Principi programiranja Prevodioci i interpretatori

87

}
INTERVAL hilo(double a, double b, double c, double d){
INTERVAL v;
if(a>b) { v.hi=a; v.lo=b; }
else { v.hi=b; v.lo=a; }
if(c>d) {
if(c>v.hi) v.hi=c;
if(d<v.lo) v.lo=d;
}
else {
if(d>v.hi) v.hi=d;
if(c<v.lo) v.lo=c;
}
return(v);
}
INTERVAL vmul(double a, double b, INTERVAL v){
return(hilo(a*v.hi, a*v.lo, b*v.hi, b*v.lo));
}
dcheck(INTERVAL v){
if(v.hi>=0 && v.lo<=0){
printf("djelilacki interval sadrzi nulu\n");
return(1);
}
return(0);
}
INTERVAL vdiv(double a, double b, INTERVAL v){
return(hilo(a/v.hi, a/v.lo, b/v.hi, b/v.lo));
}
yyerror(char *s){
fprintf(stderr,"%s\n",s);
}
main(){
return(yyparse());
}
Primjer Y6
Jo jedna metrika koja je po prirodi sintaksna (a ne leksika leksike metrike obavljaju
alati zasnovani na FLEX-u, to smo ve vidjeli) je McCabe-ova metrika, koja je
zasnovana na teoriji grafova i ekvivalentna je ciklomatskoj kompleksnosti usmjerenog
grafa. Ako se kontrolna struktura programa (ili dijela programa) predstavi u obliku

Principi programiranja Prevodioci i interpretatori

88

usmjerenog grafa, vrijednost McCabe-ove metrike je broj linearno nezavisnih puteva


kroz kontrolni graf. Dokazuje se da je taj broj jednak broju odluka u tom dijelu programa,
plus jedan. Kako je to lake za razumijevanje, pokazaemo kako se realizuje alat za
brojanje broja odluka u dijelu softvera. Koristiemo podskup Pascala, definisan
sljedeom gramatikom:
<proc>::=<procheading> < block> ;
<block>::=<constdec> <vardec> <procdecs> <stmpart> ;
<stmpart>::=<compoundstat> ;
<compoundstat>::= BEGIN <stmtseq> END;
<stmtseq>::= <statement> | <stmtseq> ; <statement>;
<statement>::= <compoundstat> | <strucstat>
|/*empty statement*/|<assignstat> | <procstat>;
<structstat>::= <condstat> | <whilestat> ;
<condstat>::= IF <condition>
THEN <statement> ELSE <statement>
| IF <condition> THEN <statement>
<whilestat>::= WHILE <condition> DO <statement>
Odreeni neterminali kao to je condition nisu proireni, iz razloga
jednostavnosti. Ako condition (uslov) proglasimo odlukom, mogli bismo u gramatiku
ukljuiti akcije koje bi brojela broj odluka i koje bi tampale vrijednost MCCabe-ove
metrike:
proc : procheading block
{printf(''%d/n'', $2+1);};
block : constdec vardec procdecs stmpart
{$$ = $4;};
stmpart: BEGIN stmtseq END
{$$ = $2;};
stmtseq: statement
{$$ = $1;}
| stmtseq ';' statement
{$$ = $1 + $3;};
statement: compoundstat
{$$ = $1;}
| structstat
{$$ = $1;}
| /*empty atetment*/
{$$ = 0;}
| assignstat
{$$ = 0;}
| procstat
{$$ = 0;};
structstat : condstat
{$$ = $1;}
| whilestat

Principi programiranja Prevodioci i interpretatori

89

{$$ = $1;};
condstat : IF condition THEN statement ELSE statement
{$$ = $4 + $6 +1;}
| IF condition THEN statement
{$$ = $4 + 1;};
whilestat : WHILE condition DO statement
{$$ = $4 +1;};

F6. Sheme sintaksnog prevoenja


Kao to smo vidjeli, akcije koje se ukljuuju u pravila mogu nam posluiti za
evaluaciju vrijednosti aritmetikih pravila. Uopteno gledano, ta evaluacija predstavlja
neku vrstu prevoenja iz jezika aritmetikih izraza u jezik njihovih vrijednosti.
Vidjeemo kako se i druge vrste prevoenja mogu postii istim mehanizmom.
Najprije emo uvesti sheme sintaksnog prevoenja, koje predstavljaju proireni
oblik gramatika, gdje se gramatikom uz strukturu inputa, predstavlja i struktura outputa,
odnosno prevoda.
Prvo emo napisati shemu sintaksnog prevoenja koja jezik (a + b)* prevodi u
jezik (0 + 1)*, tj koja u tom jeziju prevodi a u 0, a b u 1, prevodei, npr. nisku aba u nisku
010.
S a S, 0 S
S b S, 1 S
S ,
Pogledamo izvoenja:
S SS SSS SSa Sba aba
S SS SSS SS0 S10 010
Izvoenje polazne niske i prevoda moe se predstaviti u jednom, ako znamo koji
simboli pripadaju kojem jeziku:
S a0S a0b1S a0b1a0S a0b1a0
Ako izvuemo slova iz polazne azbuke, dobijamo nisku aba
A ako izvuemo slova iz ciljne azbuke, dobijamo nisku 010
ema se moe predstaviti i dijagramom toka, pri emu slova iz ciljne azbuke
obiljeavamo rafiranjem:

Principi programiranja Prevodioci i interpretatori

90

Ako shemu predstavljamo Backus-Naurovom formom, sa @ emo oznaiti


izlazna slova:
<S>::= ''a'' @ ''0'' <S>
| ''b'' @ ''1'' <S>
|
Primjer 1. Prevoenje u obrnutu poljsku notaciju
Obrnuta poljska notacija je nain zapisivanja aritmetikih izraza pri kome se binarni
operatori ispisuju posfiksno (umjesto infiksno, kao to je uobiajeno). Dakle, umjesto
a+b piemo ab+, dakle, prvo argumente a zatim oznaku operatora. Prednost obrnute
poljske notacije je u tome to takav zapis eliminie potrebu za zagradama i eksplicitnim
zadavanjem prioriteta, npr.
a-b-a
a - (b - a)

prevodi se u
prevodi se u

ab-aaba--

Izraunavanje izraza zapisanih obrnutom poljskom notacijom je najlake objasniti


pomou steka: dok oitavamo promjenljive slaemo ih na stek, a kad oitamo operator
primijenimo ga na dvije promjenljive koje su na vrhu steka i rezultat opet upiemo na vrh
steka. Na primjer:
Izraz 3 - 7 - 5, ija je vrijenost 9, prevodi se u 3 7 - 5 - , i izraunava na sljedei
nain

7
3

-4

5
-4

-9

S druge strane, izraz 3 - (7 - 5), ija je vrijednost 1, prevodi se u izraz 3 7 5 - -,


koji se izraunava na sljedei nain

7
3

5
7
3

2
3

Dakle, zadatak je sljedei: prebaciti u obrnutu poljsku notaciju pomou sheme


sintaksnog prevoenja.

Principi programiranja Prevodioci i interpretatori

91

Koristiemo Backus-Naurovu notaciju, najprije za izraze koji ukljuuju samo


operatore + i , i promjenljive a i b.
1. varijanta, lijevo rekurzivna gramatika
<izraz>::=<izraz> ''+'' <sabirak> | <izraz> ''-'' <sabirak> | <sabirak>;
<sabirak>::= ''a'' | ''b'' ;
Shema sintaksno voenog prevoenja za tu gramatiku je:
<izraz>::=<izraz> ''+'' <sabirak> @ ''+'' | <izraz> ''-'' <sabirak> @ ''-'' | <sabirak>;
<sabirak>::= ''a'' @ ''a'' | ''b'' @ ''b'' ;
2. varijanta, desno rekurzivna gramatika
<izraz>::=<sabirak> ''+'' <izraz> | <sabirak> ''-'' <izraz> | <sabirak>;
<sabirak>::= ''a'' | ''b'' ;
Shema sintaksno voenog prevoenja za tu gramatiku je:
<izraz>::=<sabirak> ''+'' <izraz> @ ''+'' | <sabirak> ''-'' <izraz> @ ''-'' | <sabirak>;
<sabirak>::= ''a'' @ ''a'' | ''b'' @ ''b'' ;
3. varijanta gramatike
<izraz>::=<sabirak><nastavak izraza>
<nastavak izraza>::= ''+'' <sabitak><nastavak izraza> | ''-'' <sabirak><nastavak izraza> |
<sabirak>::= ''a'' | ''b'' ;
Shema sintaksno voenog prevoenja za tu gramatiku je:
<izraz>::=<sabirak><nastavak izraza>
<nastavak izraza>::= ''+'' <sabitak> @ ''+'' <nastavak izraza>
| ''-'' <sabirak> @ ''-'' <nastavak izraza> |
<sabirak>::= ''a'' @ ''a'' | ''b'' @ ''b'' ;
Primjer Y7
Sada moemo napisati opis za YACC koji prevodi arzitmetike izraze u obrnutu poljsku
notaciju. Prva varijanta specifikacije, koja prihvata izraze s operatorima + i operandima
a i b nalazi se u datoteci PR7Y.Y. Kako se sve lekseme svode na samo po jedan karakter,
leksiki analizator se svodi na getchar() funkciju koja uzima jedan po jedan karakter s
ulaza:
%{
#include <stdio.h>
%}
%%
izraz : sabirak nastavak ;
nastavak : '+' sabirak { printf("+");} nastavak
| '-' sabirak { printf("-");} nastavak
| ;

Principi programiranja Prevodioci i interpretatori

92

sabirak : 'a' { printf("a");}


| 'b' { printf("b");};
%%
yylex()
{ getchar();
}
yyerror(char *s)
{ printf("%s",s);
}
main()
{ printf("\n");
printf("unesite aritmeticki izraz s operandima a i b i
operatorima + i -\n");
return yyparse();
printf("\n");
}
Druga verzija ukljuuje i operatore * i / i nalazi se u datoteci PR7aY.Y:
%{
#include <stdio.h>
%}
%%
izraz : sabirak nastavaki ;
nastavaki : '+' sabirak { printf("+");} nastavaki
| '-' sabirak { printf("-");} nastavaki
| ;
sabirak : faktor nastavaks ;
nastavaks : '*' faktor { printf("*");} nastavaks
| '/' faktor { printf("/");} nastavaks
| ;
faktor : 'a' { printf("a");}
| 'b' { printf("b");}
| "(" izraz ")" ;
%%
yylex()
{ getchar();
}
yyerror(char *s)
{ printf("%s",s);
}
main()
{ printf("\n");
printf("unesite aritmeticki izraz s operandima a i b \n");
printf("i operandima +, -, * i /. zagrade su dozvoljene
\n");
return yyparse();

Principi programiranja Prevodioci i interpretatori

93

printf("\n");
}
Trea verzija, koja se nalazi se u datoteci PR7bY.Y, kao argumente prihvata cijele
brojeve i identifikatore:
%{
#include <stdio.h>
%}
%token IDENT NCEO
%%
izraz : sabirak nastavaki ;
nastavaki : '+' sabirak { printf("+");} nastavaki
| '-' sabirak { printf("-");} nastavaki
| ;
sabirak : faktor nastavaks ;
nastavaks : '*' faktor { printf("*");} nastavaks
| '/' faktor { printf("/");} nastavaks
| ;
faktor : NCEO { printf("~%s~", yytext);}
| IDENT { printf("~%s~", yytext);}
| "(" izraz ")" ;
%%
#include "c:\flex\lexyy.c"
yyerror(char *s)
{ printf("%s",s);
}
main()
{ printf("\n");
printf("unesite aritmeticki izraz koji kao operande uzima
\n");
printf("cijele brojeve i identifikatore \n");
printf("a kao operande +, -, * i /. zagrade su dozvoljene
\n");
return yyparse();
printf("\n");
}
Leksiki analizator za ovu verziju dobija se preko FLEX-a, sa sljedeim opisom,
koji se nalazi u datoteci PR7bY.L:
slovo
cifra
bjel
ident

[A-Za-z]
[0-9]
[ \t\n]
{slovo}({slovo}|{cifra})*

Principi programiranja Prevodioci i interpretatori

94

nceo {cifra}+
%%
{ident} {return IDENT;}
{nceo} {return NCEO;}
{bjel} ;
.
{return yytext[0];}

Prevoenje rimskih brojeva u arapske


Zadatak je: napraviti emu prevoenja koja prevodi rimske brojeve u arapske
brojeve, pri emu je dozvoljen viak vodeih nula.
Kontekst slobodnu gramatiku za rimske brojeve smo ve pisali. Razdvajamo
sintaksne klase za jedinice, desetice, stotice i hiljade: skup neterminala, N = {P,Q,R,S,T}.
Skup terminala je ={I,V,X,L,C,D,M}. Gramatika pravila su:
STQRP
P I | II | III | IV | V | VI | VII | VIII | IX |
Q X | XX | XXX | XL | L | LX | LXX | LXXX | XC |
R C | CC | CCC | CD | D | DC | DCC | DCCC | DM |
T M | MM | MMM |
Pa je shema sintaksnog prevoenja sljedea
STQRP
P I 1 | II 2 | III 3 | IV 4 | V 5 | VI 6 | VII 7 | VIII 8 | IX 9 | 0
Q X 1 | XX 2 | XXX 3 | XL 4 | L 5 | LX 6 | LXX 7 | LXXX 8 | XC 9 | 0
R C 1 | CC 2 | CCC 3 | CD 4 | D 5 | DC 6 | DCC 7 | DCCC 8 | DM 9 | 0
T M 1 | MM 2 | MMM 3 | 0
Ako bismo imali program za raunanje vrijednosti arapskog broja, umjesto cifara
bismo stavljali operacije iste one koje se javljaju pri itanju arapskog broja,
f1
umjesto 1

f2
...
umjesto 2...

f0
umjesto 0

D je radni domen, D0 je poetna vrijednost


(n)fi = n * 10 + i
(n)f = n
f je zavrna funkcija, jer je tada vrijednost radnog domena odmah vrijednost
npr. 1984 = (0) f1 f9 f8 f4 f
= (1) f9 f8 f4 f
= (19) f8 f4 f

Principi programiranja Prevodioci i interpretatori

95

= (198) f4 f
= (1984) f
= 1984
Primjer Y8
Sada moemo da napravimo opis za YACC koji prevodi arapske brojeve u rimske. Ta
specifikacija nalazi se u datoteci PR8Y.Y, a kako se lekseme svode na po jedan karakter,
leksiki analitaor se svodi na poziv funkcije getchar(), koja uzima jedan po jedan karakter
iz ulaznog toka.
%{
#include <stdio.h>
%}
%%
s : t r q p
{$$=1000*$1+100*$2+10*$3+$4; printf("broj je %d\n", $$);}
;
p :
|
|
|
|
|
|
|
|
|

'I'
'I''I'
'I''I''I'
'I''V'
'V'
'V''I'
'V''I''I'
'V''I''I''I'
'I''X'
;

{$$=1;}
{$$=2;}
{$$=3;}
{$$=4;}
{$$=5;}
{$$=6;}
{$$=7;}
{$$=8;}
{$$=9;}
{$$=0;}

q :
|
|
|
|
|
|
|
|
|

'X'
'X''X'
'X''X''X'
'X''L'
'L'
'L''X'
'L''X''X'
'L''X''X''X'
'X''C'
;

{$$=1;}
{$$=2;}
{$$=3;}
{$$=4;}
{$$=5;}
{$$=6;}
{$$=7;}
{$$=8;}
{$$=9;}
{$$=0;}

r :
|
|
|

'C'
'C''C'
'C''C''C'
'C''D'

{$$=1;}
{$$=2;}
{$$=3;}
{$$=4;}

Principi programiranja Prevodioci i interpretatori

|
|
|
|
|
|
t :
|
|
|

'D'
'D''C'
'D''C''C'
'D''C''C''C'
'C''M'
;

{$$=5;}
{$$=6;}
{$$=7;}
{$$=8;}
{$$=9;}
{$$=0;}

'M'
'M''M'
'M''M''M'
;

{$$=1;}
{$$=2;}
{$$=3;}
{$$=0;}

%%
yylex()
{ getchar();
}
yyerror(char *s)
{ printf("%s\n",s);
}
main()
{ return yyparse();
}

96

Principi programiranja Prevodioci i interpretatori

97

Program se moe sagledati i kao


informacijska funkcija f koja ulazne podatke Upreslikava/transformira u izlazne
podatke I.
I= f(U)

Program kao informacijska funkcija transformacije ulaza u izlaze UI=fi(U)I


Svrha programa je rjeavanje problema:
najei nain prikaza rjeenja je pomou tri modela:
model podataka,
model procesa i
model resursa.
Program kao model rjeenja
model podataka sadri definicije podataka (nazive, vrste, dimenzije, ogranienja,
vrijednosti) te strukture podataka (nizovi, skupovi, datoteke, tablice ),
model procesa moe biti prikazan razliitim tehnikama (dijagram toka, dijagram
strukture, procesa, dijagrami akcija), ali svi oni, na svoj nain, prikazuju slijed
odvijanja procesa,
model resursa sadri definiciju raunalnih resursa i njihovih svojstava (operacijski
sustav, vrsta programskog prevodioca, svojstva jedinica raunala) koje su potrebne da
bi program uredno radio..
Model rjeenja
podaci se koriste u odreenim procesima,
procesi se izvode pomou odreenih resursa raunala,
resursi raunala primaju, obrauju, pohranjuju i prikazuju podatke.
statika slika:

model podataka i model resursa,


dinamika:
model procesa.

Principi programiranja Prevodioci i interpretatori

98

Algoritam i programer
Programer polazi od postavljenog problema i kreira proceduru algoritam *koja dati problem rjeava.
Programer je dakle prevodilacsa jezika procedure na jezik sa kojeg se moe dalje prevoditi na jezik
blizak raunalu.
Pored prevoenjaprogramer obino kreira i proceduru na osnovi postavljenog problema.
* Pojam algoritam potjee od znanstvenika Al-Horezmija koji je u IX vijeku definirao naine za
izraunavanja nad dekadskim brojevima koji se i danas koriste i ue u osnovnoj koli
Podjela programskih jezika
Po nainu rada:
imperativni jezici(postiu funkcionalnost postavljanjem vrijednosti varijablama naredbama
pridruivanja i ispitivanjem vrijednosti varijabli -FORTRAN, COBOL, ALGOL, C, Pascal, Ada,
Modula-2)
funkcionalni jezici(bez klasinog pridruivanja, graeni od definicija i poziva funkcija -LISP, ML,
LOGO)
logiki ili ciljno orijentirani jezici(postavlja se glavni cilj i daje lista podciljeva ije dostizanje znai i
dostizanje glavnog cilja -PROLOG)
objektno orijentirani jezici(objekti: strukture podataka s definiranim funkcijama nad njima Smalltalk, Eiffel) i
hibridni jezici(C++).
Podjela programskih jezika
Generacijska podjela
Jezici prve, druge, tree i etvrte generacije
Po strukturiranosti
nestrukturirani programski jezici
strukturirani programski jezici
Po proceduralnosti
proceduralni programski jezici (slijed naredbi koji odreuje KAKO obaviti neki posao)
neproceduralni programski jezici (slijed naredbi koji odreuje TO treba uiniti)
Poetak programiranja
Na prvim raunalima programiranjese provodilo fizikim lemljenjem pojedinih elektronskih dijelova
(projekt Eniac).
Jo je interesantnije da je prvi programski jezik razvio Konrad Zuse(Njemaka) 1946. godine (programski
jezik se zvao Plankalkul po poznatom nauniku Planku) i ovaj jezik nikada nije primijenjen na
elektronskom raunalu.
Prvi jezik koji je primijenjen na elektronskim raunalima bio je Short-code1949-te godine.
Tipovi i strukture podataka
Prema svojstvima pojedinih atributa koji opisuju entitete, podaci mogu biti tipa:

Principi programiranja Prevodioci i interpretatori

99

cjelobrojni, realni broj, logika varijabla, znakovno polje ....


Sa stajalita struktura, podatke moemo povezati u:
nizove (koji predstavljaju matematike vektore i matrice), skupove, slogove, datoteke ....
Tip podataka (objekata) odreuje i dozvoljene vrijednosti koje pojedini objekt moe poprimiti, kao i
skup dozvoljenih operacija. To ujedno ima i utjecaja na nain pohrane podataka
Osnovni (primitivni) tipovi podataka
NUMERIKI
Najei tipovi numerikih podataka:
cjelobrojni (integer)
realni (Float, FloatingPoint)
kompleksni
logiki (Boolean)
znakovni (character)
BROJNI SISTEMI
Nepozicioni
Svojstvo cifre ne zavisi od pozicije na kojoj se nalazi
Primjer: Rimski brojevi
2. Pozicioni ili teinski
Pozicioni brojni sistemi su oni u kojima se teina cifre
(njen udio u celokupnoj vrijednosti broja) odreuje na
osnovu njene pozicije u broju (to vea pozicija to je
vei i udeo u vrednosti broja)

Sa osnovom

Bez osnove
A = {c1, c2, c3, ., cb}
- A - skup cifara brojnog sistema
- b - osnova brojnog sistema, broj
cifara tog brojnog sistema
-Osnova - naziv brojnog sistema

2 - binarni

8 oktalni

10 decimalni
16 heksadecimalni

Principi programiranja Prevodioci i interpretatori

100

Principi programiranja Prevodioci i interpretatori

101

Principi programiranja Prevodioci i interpretatori

102

Principi programiranja Prevodioci i interpretatori

103

Principi programiranja Prevodioci i interpretatori

104

Principi programiranja Prevodioci i interpretatori

105

Principi programiranja Prevodioci i interpretatori

106

Principi programiranja Prevodioci i interpretatori

107

Principi programiranja Prevodioci i interpretatori

108

Principi programiranja Prevodioci i interpretatori

109

Principi programiranja Prevodioci i interpretatori

110

Principi programiranja Prevodioci i interpretatori

111

Principi programiranja Prevodioci i interpretatori

112

Principi programiranja Prevodioci i interpretatori

113

ivot se sastoji od ponavljanja nekih rutina.


Mnoge od tih rutina (odlazak na posao, polaganje ispita, izlazak na izbore) mogu
se opisati na nedvosmislen nain procedurom.
Procedura bi trebala da bude primjenljiva na sluaj svih moguih ulaznih
podataka (referendumi, republiki, lokalni izbori, razliiti ispiti, odlazak na posao
ponedjeljkom i utorkom itd).
Na slian nain se mogu predstaviti i obrade podataka (obraun penzija, rad u
katastru, raunanje poreza, obrada fotografije itd).
esto postoji potreba da se procedura provede nad ogromnom koliinom
podataka. Npr., popis stanovnitva, obrada penzija,...
To bi zahtjevalo ogroman ljudski rad i mnotvo greaka prouzrokovanih
nepanjom.
Jo u XIX vijeku su se javile ideje za konstruisanje maina za obradu podataka (to
su bile mehanike maine, modeli Bebida, Paskala itd).
Prve prave raunarske maine (kompjuteri) pojavile su se nakon II svjetskog rata i
bile su (kao i danas) realizovane na elektronskim ureajima.
Elektronske komponente koje su danas u upotrebi imaju logiku na nivou nula i
jedinica.
Pisanje male procedure na nivou nula i jedinica bilo bi veoma zametno za ljude sa
mogunou mnotva greaka.
Ljudi oigledno dobro razumiju jezik procedure.
Stoga su razvijeni programski jezici pomou kojih je proceduru mogue zapisati
na nain blizak ljudima, a ipak dovoljno jednostavno da se lako mogu prevesti na
jezik nula i jedinica razumljiv raunarima.
Programer polazi od postavljenog problema i kreira proceduru algoritam* koja
dati problem rjeava.
Programer je dakle prevodilac sa jezika procedure na jezik sa kojeg se moe
dalje prevoditi na jezik blizak raunaru.
Pored prevoenja programer obino kreira i proceduru na osnovu postavljenog
problema.
* Pojam algoritam potie od uzbekog naunika Al-Horezmija koji je u IX vijeku
definisao naine za izraunavanja nad dekadnim brojevima koji se i danas koriste i ue u
osnovnoj koli.

Kao malu digresiju podsjetimo se kako izgleda jedna instrukcija koju raunar razumije (pravilnije
je rei koju procesor izvrava)

Principi programiranja Prevodioci i interpretatori

114

U principu se mogu izvravati tri tipa instrukcija:


3+5 (operacije nad konstantama kada su operandi instrukcije ono nad ime se operacija
vri).
a+b (operacije nad promjenljivim kad su operandi instrukcije memorijske lokacije na
kojima se nalaze operandi operacije).
*a+*b (operacije putem pokazivaa kada su operandi instrukcije memorijske lokacije na
kojima se nalazi adresa na kojima se nalaze promjenljive nad kojima treba izvriti
operaciju).

Memorijski objekti sa kojima program izvodi operacije nazivaju se podaci.


Podaci moraju biti nekog od definisanih tipova podataka.
Tipove podataka dijelimo na:
elementarne i
sloene.
Elementarni tipove se realizuju hardverski, a sloeni kombinacijom elementarnih.
Najpoznatiji elementarni tipovi su cijeli broj (int ili integer), realni broj (float ili real) i karakter
(char ili character).

Tipovi podataka imaju sljedee karakteristike:


Memoriju koju zauzimaju;
Operacije koje se mogu nad njima izvravati;
Domen ili oblast definisanosti.
Svaki podatak u programu mora imati ime (postoje izuzeci kao to su konstante i privremeni
podaci, ali o tome kasnije).
Imena promjenljivih podlijeu odreenim pravilima koja variraju od programskog jezika do
programskog jezika.
Generalno, imena se mogu sastojati od slova i podvlake _. Zabranjeno je koristiti zagrade, oznake
matematikih operacija, znake interpunkcije itd. Mogu se koristiti cifre, ali ime promjenljive ne
moe njima da pone.
Postoje programski jezici koji prave razliku izmeu malih i velikih slova u nazivu podataka, a neki
stariji ne prave. Za nas ova razlika nije bitna!!!
A i A123 i ABC_ i C2D su dobri nazivi promjenljivih, dok 12A, (B, CD:E nijesu.
Naziv promjenljive se u izvrnom kodu programa mijenja sa memorijskom lokacijom gdje se
promjenljiva nalazi. Kaemo ime promjenljive je pristupna adresa toj promjenljivoj.

Principi programiranja Prevodioci i interpretatori

115

Memorija koju zauzimaju promjenljive se odreuje hardverski i zavisi od maine na kojoj se


implementacija vri, upotrijebljenog translatora i operativnog sistema.
Postoje metode da se program pie uspjeno i prenosivo sa raunara na raunar i pored ove razlike.
Na primer, promjenljiva tipa karakter obino zauzima 1Bajt (8 bita). Sadraj toga bajta memorije
se tumai (hardverski) na osnovu ASCII tabele kao karakter. Dakle, tumaenje se obavlja na
osnovu sadraja memorije i saznanja o tipu promjenljive!!!
Neke vane karakteristike ASCII tabele su:
Sadri 26 malih slova engleske abecede poreanih jedan za drugim (ako binarni zapis
sadraja memorije pretvorimo u dekadni to znai da je karakter a za jedan manji od
karaktera b).
Sadri 26 velikih slova engleske abecede (isto pravilo kao gore).
Sadri 10 cifara (poreanih od 0 do 9).
Sadri znake matematikih operacija, osnovne interpunkcijske simbole, prazninu blanko,
zagrade, itd.
Sadri i neke specijalne simbole (znak za novi red, tabulaciju, novu stranicu, zvonce,
kraj niza karaktera, vrati se gore znak, kraj fajla itd).
Sadraj memorijske lokacije na kojoj se uva karakter je 8-bitni binarni broj. to je onda domen za
prikaz karaktera?
Ranije prenos podataka na raunarima nije bio pouzdan, pa se, zapravo, kao nosioc informacija
koristilo 7 bita, a jedan dodatni (bit najvee vanosti) je bio za provjeru parnosti. Znai u 256
moguih karaktera je smjetano 2 puta po 128 istih karaktera. Dakle, domen je bio od 0 do 127
zapisano dekadno.
Danas je prenos (i skladitenje) podataka mnogo pouzdanije pa se sadraj memorije 00000000 do
11111111 moe tumaiti kao 0 do 255 dekadno ili 128 do 127 ako se jedinica na poetku tumai
kao predznak. Kod nekih jezika ovo moe da regulie programer. Za nas u ovom kursu nije
previe bitno.

Slino se mogu vriti i druge operacije sa karakterima.


Veina programskih jezika dozvoljava operacije tipa A>B, gdje su A i B promjenljive tipa
karaktera.
Ovaj izraz provjerava da li je A vee od B (provjera se vri na osnovu zapisa u ASCII kodu).
Ovo emo koristiti za sortiranje po abecednom redu (podsjetite se da su karakteri poreani po
abecednom redu i da oni na poetku niza, sa manjim ASCII kodom, su i na poetku abecede).
O komplikacijama kod logikih operacija i operacijama poreenja bie vie rijei u dijelu opisa prikaza
cijelih brojeva.

Unicode je kreirao posebni neprofitni konzorcijum (http://www.unicode.org) sastavljen od


velikih kompanija zainteresovanih za irenje trita;
vlada pojedinih zemalja sa komplikovanim setovima karaktera zainteresovanih za
kompjutersko opismenjavanje;
itd.
Prva verzija je podrazumjevala 2-bajtni zapis karaktera sa 216=65536 moguih karaktera.
Dananje verzije koriste 21 bit, ali ne ba u potpunosti, ve podijeljeno u pojedine segmente.
Cijeli brojevi (u kompjuterskoj terminologiji poznati kao int, integer, itd) zauzimaju memoriju od
2 ili 4 bajta (obino programer moe izabrati koliinu memorije u datim granicama; za nas opet
nebitno).
Ako zauzima 2 bajta to su brojevi od 215 do 215-1 ako se prvi bit tumai kao predznak. Ako se
prvi bit ne tumai kao predznak dobijaju se nenegativni cijeli brojevi od 0 do 216-1.
Ako se zauzima etiri bajta domen je 231 do 231-1 ili od 0 do 232-1.
Programeri opet u nekim programskim jezicima mogu da podeavaju nain tumaenja sadraja
memorije zauzete za cijele brojeve (prirodni ili svi cijeli brojevi). Za nas ova podeavanja nijesu
bitna.

Principi programiranja Prevodioci i interpretatori

116

TIPOVI PODATAKA

Realni brojevi u pokretnom zarezu (nazivaju se float ili real) zauzimaju 4 ili 8 bajtova memorije
raunara (za nas manje bitno).
Primjer kod 4-bajtnog (32-bitnog) zapisa:

Iz datog naina zapisa slijede i ogranienja u pogledu najmanjeg i najveeg broja koji se moe
zapisati pomou float-a, kao i preciznosti koju float moe da tretira (oigledno, broj p ne moe da
se zapie beskonano precizno).
A=B+C gdje su A, B i C float-i se obavlja na isti nain kao to je bilo opisano za cijele brojeve, ali
sa tom razlikom to se za privremene promjenljive u registrima ostavlja prostor i tumae na nain
pogodan za float.
A=B+C gdje su A i C float, a B cijeli broj se obavlja na sljedei nain:
kompajler vidi da se sabiraju razliiti tipovi podataka, uoava se da je za smjetaj float-a
potrebno vie prostora;
za operande operacije sabiranja u registrima procesora se zauzme prostor kao da je u
pitanju sabiranje float-a;
to znai da se prilikom presipanja B iz memorije u registre B konvertuje u float;
ovo se naziva implicitnom konverzijom;
zatim se operacija obavi kao za float;
opisana operacija nema efekta na promjenljivu B u memoriji, ve samo na privremenu
promjenljivu (kojoj se ne moe pristupiti iz programa) u registrima.

A=B+C, gdje su B i C float-i, a A cijeli broj:


obavlja se operacija kao kod floata i rezultat (privremena promjenljiva) smjesti u registre
procesora kao float;
sada rezultat treba presuti iz registara procesora u memorijsku lokaciju za promjenljivu
A;
kako je A cijeli broj dolazi do odsjecanja necjelobrojnog dijela.
Na primjer, A=2.3+4.1 rezultuje u ovom sluaju u A=6.
A=B+C gdje su B i C cijeli, a A float:
Operacija se obavi kao za cijele, a zatim dobijeni rezultat prebaci u promjenljivu koja je
float.
Na primjer, A=B/C gdje su B i C cijeli brojevi B=4 i C=3 daje A=1 bez obzira da li je u pitanju
float ili cijeli broj.
Isto to daje operacija na konstantama: A=4/3.
Operacija A=4.0/3.0 daje: A=1.333....338 ako je A float

Principi programiranja Prevodioci i interpretatori

117

Float se moe primjenljivati u svim relacijama poreenja sa pravilima kao u matematici.


Dozvoljeni zapisi konstanti tipa float su:
1, -134, 23 kao cijeli broj, ali ako ce cijeli broj pridruuje float-u A=-134;
1.0, 2.34, -0.35, .34 ak i za cijeli broj se moe koristiti taka da bi naglasila da je u
pitanju float ako je to potrebno; ako broj pone sa . to podrazumjeva da je nula ispred;
1e6, 1e-12 3.456e-3 ovo je tzv. eksponencijalni zapis gdje eb oznaava 10b; tako je 3.456
10-3 .
Ako promjenljive razliitih tipova podataka uestvuju u operacijama i raunar jednu promjenljivu
prebaci u vii tip podatka (tip podatka koji zauzima veu memoriju u registru) da bi izvrio
odgovarajuu operaciju nad usklaenim operandima to se naziva implicitnom konverzijom.
Korisnik moe da izvri eksplicitnu konverziju (ovo se preporuuje) kojom se podeava u kakvom
e se obliku pojaviti promjenljive u pojedinim operacijama. Npr. INT2FLOAT(I)+B i
FLOAT2INT(F)+A

Neki programski jezici e odraditi operaciju A=B/C ako je A float kao da je operacija sa float-ima
bez obzira to je na lijevoj strani.
Ovo obino rade matematiki paket (npr. MATLAB i MATHEMATICA) koji su namjenjeni za
matematika izraunavanja, a ne klasini vii programski jezici.
Mi emo ovu problematiku tretirati kao kod viih programskih jezika.
Mi emo podrazumjevati sljedee algoritamske korake:
Alokaciju promjenljivih (zauzimanje memorijskog prostora za promjenljive);
Unos podataka;
Sekvencu;
Selekciju;
Ciklus;
Izlaz podataka;
Smatraemo da se dealokacija (brisanje promjenljivih) obavlja automatski od strane
kompajlera;
Program moe da ima izostavljen neki od predmetnih koraka ili vie pojedinih koraka;
Program moe pozivati potprograme;
Program ima poetak i kraj.

Principi programiranja Prevodioci i interpretatori

118

Principi programiranja Prevodioci i interpretatori

119

Principi programiranja Prevodioci i interpretatori

X2

X
Y
4

120

X 4
2 X 4
X 0 ili X 1
drugdje

Principi programiranja Prevodioci i interpretatori

121

Principi programiranja Prevodioci i interpretatori

122

Principi programiranja Prevodioci i interpretatori

123

Principi programiranja Prevodioci i interpretatori

124

Principi programiranja Prevodioci i interpretatori

125

Principi programiranja Prevodioci i interpretatori

126

Principi programiranja Prevodioci i interpretatori

127

Principi programiranja Prevodioci i interpretatori

128

Principi programiranja Prevodioci i interpretatori

129

Principi programiranja Prevodioci i interpretatori

130

Principi programiranja Prevodioci i interpretatori

131

Principi programiranja Prevodioci i interpretatori

132

Principi programiranja Prevodioci i interpretatori

133

Principi programiranja Prevodioci i interpretatori

134

Principi programiranja Prevodioci i interpretatori

135

Principi programiranja Prevodioci i interpretatori

136

Principi programiranja Prevodioci i interpretatori

137

Principi programiranja Prevodioci i interpretatori

138

Principi programiranja Prevodioci i interpretatori

139

Principi programiranja Prevodioci i interpretatori

140

Principi programiranja Prevodioci i interpretatori

141

Principi programiranja Prevodioci i interpretatori

142

Principi programiranja Prevodioci i interpretatori

143

Principi programiranja Prevodioci i interpretatori

144

Principi programiranja Prevodioci i interpretatori

145

Principi programiranja Prevodioci i interpretatori

146

Principi programiranja Prevodioci i interpretatori

147

Principi programiranja Prevodioci i interpretatori

148

Principi programiranja Prevodioci i interpretatori

149

Principi programiranja Prevodioci i interpretatori

150

Principi programiranja Prevodioci i interpretatori

151

Principi programiranja Prevodioci i interpretatori

152

Principi programiranja Prevodioci i interpretatori

153

Principi programiranja Prevodioci i interpretatori

154

Principi programiranja Prevodioci i interpretatori

155

Principi programiranja Prevodioci i interpretatori

156

Principi programiranja Prevodioci i interpretatori

157

Principi programiranja Prevodioci i interpretatori

158

Principi programiranja Prevodioci i interpretatori

159

Principi programiranja Prevodioci i interpretatori

160

Principi programiranja Prevodioci i interpretatori

161

Principi programiranja Prevodioci i interpretatori

162

Principi programiranja Prevodioci i interpretatori

163

Principi programiranja Prevodioci i interpretatori

164

Principi programiranja Prevodioci i interpretatori

165

Principi programiranja Prevodioci i interpretatori

166

Principi programiranja Prevodioci i interpretatori

167

Principi programiranja Prevodioci i interpretatori

168

Principi programiranja Prevodioci i interpretatori

169

Principi programiranja Prevodioci i interpretatori

170

Principi programiranja Prevodioci i interpretatori

171

Principi programiranja Prevodioci i interpretatori

172

Principi programiranja Prevodioci i interpretatori

173

Principi programiranja Prevodioci i interpretatori

174

Principi programiranja Prevodioci i interpretatori

175

Principi programiranja Prevodioci i interpretatori

176

Principi programiranja Prevodioci i interpretatori

177

Principi programiranja Prevodioci i interpretatori

178

Principi programiranja Prevodioci i interpretatori

179

Principi programiranja Prevodioci i interpretatori

180

Principi programiranja Prevodioci i interpretatori

181

Principi programiranja Prevodioci i interpretatori

182

Principi programiranja Prevodioci i interpretatori

183

Principi programiranja Prevodioci i interpretatori

184

Principi programiranja Prevodioci i interpretatori

185

Principi programiranja Prevodioci i interpretatori

186

Principi programiranja Prevodioci i interpretatori

187

Principi programiranja Prevodioci i interpretatori

188

Principi programiranja Prevodioci i interpretatori

189

Principi programiranja Prevodioci i interpretatori

190

Principi programiranja Prevodioci i interpretatori

191

Principi programiranja Prevodioci i interpretatori

192

Principi programiranja Prevodioci i interpretatori

193

Principi programiranja Prevodioci i interpretatori

194

Principi programiranja Prevodioci i interpretatori

195

Principi programiranja Prevodioci i interpretatori

196

Principi programiranja Prevodioci i interpretatori

197

Principi programiranja Prevodioci i interpretatori

198

Principi programiranja Prevodioci i interpretatori

199

Principi programiranja Prevodioci i interpretatori

LITERATURA
Materijali za pripremu nastave interna upotreba

200

You might also like