Professional Documents
Culture Documents
Skripta PDF
Skripta PDF
S K R I P T A
Predmet: PRINCIPI PROGRAMIRANJA
Travnik, 2011.
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.
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
Podaci D
Program P' na jeziku L'
Rezultat R
prednosti i nedostaci
Tabele
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.
10
3.
4.
5.
6.
Dokazati da je (abc)+=a(bca)*bc
Dokazuje se indukcijom.
11
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.
13
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
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();
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*/
16
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]
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
19
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:
%{
21
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
23
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.
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");
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])
27
; /*eliminisemo bjeline*/
%%
main(argc, argv)
int argc;
char **argv;
{
28
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.
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.
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).
32
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.
S + S
S + S
S+S
a
33
S+S
a
S p A q
34
35
36
SxS
S y B
Sx
Sy
ByB
By
S
37
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
39
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).
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.
42
43
44
Pravilo zamjene
SXY
XxX
XxX
Xx
YyY
Yy
Reenina forma
XY
xXY
xxXY
xxxY
xxxyY
xxxyy
45
46
47
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}
49
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
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)={ ,)}
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
Pravilo
SZcY
Sa
Xe
XbSa
YdSa
Y
ZXZc
ZY
Izbor
e,b,d,c
a
e
b
d
,c,a
e,b
d,c
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
51
52
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
54
%%
{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();
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();
}
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
58
::= <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;
":="
\n
[ \t]+
.
ECHO;
%%
59
return DODELA;
printf("Zavrsena analiza reda %d.\n", red++);
/* preskoci beline */
printf("nepoznat simbol u redu %d.\n", red);
(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;
}
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
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.
62
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
64
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 ;
66
67
68
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
69
[a-z]
[0-9]
{cifra}+
{slovo}({cifra}|{slovo})*
%%
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
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
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
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
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
75
'='
'+'
'-'
'*'
'/'
izraz
izraz
izraz
izraz
izraz
76
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;}
77
78
79
: 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();
}
{bjeline}
{prom}
.
80
; /*ignorisi praznine*/
{return PROM;}
{return yytext[0];}
|
|
|
|
|
|
|
|
{$$=$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
82
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
84
;
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
86
preduga:
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
88
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;};
90
prevodi se u
prevodi se u
ab-aaba--
7
3
-4
5
-4
-9
7
3
5
7
3
2
3
91
92
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})*
94
nceo {cifra}+
%%
{ident} {return IDENT;}
{nceo} {return NCEO;}
{bjel} ;
.
{return yytext[0];}
f2
...
umjesto 2...
f0
umjesto 0
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;}
|
|
|
|
|
|
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
97
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:
99
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
Kao malu digresiju podsjetimo se kako izgleda jedna instrukcija koju raunar razumije (pravilnije
je rei koju procesor izvrava)
114
115
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.
117
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.
118
119
X2
X
Y
4
120
X 4
2 X 4
X 0 ili X 1
drugdje
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
LITERATURA
Materijali za pripremu nastave interna upotreba
200