Sunteți pe pagina 1din 9

Indrumator de laborator - LUCRAREA NR.

5
Analiza sintactic
Construirea unui analizor sintactic
Analizorul sintactic verifica dac un program respect regulile de sintax ale limbajului surs.
Intrarea analizorului sintactic este constituit din irul codurilor lexicale furnizate de analizorul lexical.
Regulile de sintax pe care analizorul sintactic trebuie s le cunoasc sunt descrise fie prin intermediul
gramaticilor, fie prin diagrame de sintax.
Descrierea limbajului printr-o gramatic faciliteaz i detectarea erorilor, iar dac limbajul evolueaz,
modificrile necesare n compilator sunt mai simple.
n cazul unei erori de sintax (de exemplu o parantez care nu are un corespondent), analizorul
sintactic trebuie s semnaleze ct mai precis, att locul n care a aprut eroarea, ct i cauza ei. Deoarece se
impune continuarea ct mai mult posibil a procesului de analiz sintactic, pentru a scurta timpul necesar
unei compilri corecte, analizorul sintactic trebuie s conin rutine de tratare i revenire din eroare.
Analiza sintactic execut n esen urmtoarele aciuni:

verific dac programul de la intrare poate fi generat de gramatica limbajului;

construiete arborele sintactic;

n cazul unei erori de sintax, apeleaz rutine de tratare i revenire din eroare.

n figura de mai jos se prezint felul n care analizorul sintactic interacioneaz cu celelalte
componente din partea de front-end a compilatorului.

Program
surs

Analizor
lexical

Analizor
sintactic

Analizor
semantic

Tabela de simboluri

Analizorul sintactic are la baz gramaticile independente de context. Acestea sunt folosite pentru
reprezentarea unor structuri recursive. Consecinele folosirii gramaticilor i a structurilor recursive ntr-un
analizor sintactic sunt:

pentru ca o gramatic s poat fi folosit n proiectarea unui analizor sintactic, ea nu trebuie s


fie ambigu.

structurile de date folosite sunt arbori (arbori sintactici)

procesul de analiz este mult mai complex n raport cu analiza lexical

exist mai multe metode de analiz sintactic

Analizorul sintactic construiete arborele sintactic prin derivarea produciilor gramaticii. O secven
de transformri n cadrul unei gramatici se numete derivare, iar n funcie de felul n care se alege
neterminalul care va fi expandat, derivarea poate fi canonic stnga sau canonic dreapta.
n funcie de strategia aleasa pentru construcia arborelui, analizoarele sintactice se clasific astfel:

direcia de construire a arborelui: descendent de la radacin spre frunze


ascendent de la frunze spre radacin
numrul de posibiliti la un pas al construciei:
- recursiv: exist mai multe posibilti care se iau n considerare, se alege una, dac
analiza nu se termin cu succes, atunci se revine i se face o alt analiz;
- liniar (sau predictiv): la un moment dat o singur alegere este posibil.
recursiv
liniar

descendent
descendent cu/fara revenire
LL(k)

ascendent
ascendent cu/fara revenire
de precedenta
LR(k)

Analizorul sintactic descendent cu revenire este foarte simplu din punct de vedere al algoritmului
i singura restricie impus gramaticii este s nu fie recursiv la stnga. Din cauza recursivitii, eficiena lui
este scazut i nu se aplic pentru gramatici mari.
Analizorul sintactic ascendent cu revenire are aceleai principii, direcia de contruire a arborelui
difer, dar este mult mai puin cunoscut.
Algoritmii liniari sunt cei mai performani i cei mai des folosii n implementarea compilatoarelor:
analizor sintactic LL(k) este o abreviere pentru Left-Left (Stnga-Stnga) i semnific:
secvena de intrare este citit de la stnga spre dreapta i arborele se construiete folosind
derivri de stnga;
analizor sintactic bazat pe relaii de preceden este o clas de algoritmi care lucreaz pe
gramatici de precenden, dar restriciile acestor gramatici fac ineficient aplicarea lor pentru
limbaje de programare; sunt algoritmi foarte eficieni pentru verificarea corectitudinii
sintactice a expresiilor aritmetice;
analizor sintactic LR(k) este o abreviere pentru Left-Right (Stnga-Dreapta) i semnific:
secvena de intrare este citit de la stnga spre drepata i arborele se construiete folosind
derivri de dreapta; este o clas de algoritmi avnd aceei strategie, dar care difer prin detalii
sau optimizri, incluznd: LR(0), SLR, LR(1), LALR.

Analiza sintactic descendent


Analiza sintactic descendent parcurge irul de atomi de la intrare de la stnga la dreapta. Pornind de
la simbolul de start, folosete produciile gramaticii i caut o succesiune de derivri stnga care conduc la
irul de la intrare.
Exist mai multe tipuri de analizoare sintactice descendente:

Analizoare sintactice descendente recursive cu reveniri (backtracking parsers) care execut o


revenire dac o decizie care se dovedete incorect, pentru a lua o decizie nou.

Analizoare sintactice descendente recursive fr reveniri, care iau decizii despre structura
arborelui folosind civa atomi care urmeaz la intrare (lookahead tokens), de obicei un singur atom.

Analizor sintactic descendent recursiv cu reveniri


nainte de a implementa un analizor sintactic descendent cu reveniri, se examineaz gramatica limbajului
pentru a preveni intrarea analizorului sintactic ntr-un ciclu infinit. n cazul n care se identific recursivitate
stnga fie direct, fie indirect, se aplic algoritmii prezentai n capitolul precedent pentru eliminarea

acesteia. Aceti algoritmi se pot aplica fie manual, fie automat, folosind programe de analiz i transformare
a gramaticii.
n momentul n care se dispune de o gramatic nerecursiv stnga, implementarea software a unui
analizor sintactic descendent recursiv cu reveniri presupune n fond scrierea unei funcii pentru fiecare
neterminal al gramaticii.
Analizorul apeleaz funcia pentru simbolul de start al gramaticii, deoarece el ncepe construirea
arborelui sintactic de la rdcin spre frunze.
Funcionarea analizorului const n ncercarea pe rnd a tuturor produciilor posibile ale gramaticii i
revenirea n cazul unei ncercri nepotrivite.
Analiza sintactic folosete o stiv pentru produciile acesteia. Stiva poate fi implementat explicit
sau implicit, folosind recursivitatea proprie limbajelor de programare.
Principalele operaii ale unui astfel de analizor sintactic sunt:

La un nod neterminal A, alege o producie n care A se afl n partea stng, nlocuiete n stiv pe A
cu simbolurile din partea dreapt a produciei respective i construiete fii nodului pentru fiecare din
aceste simboluri, operaie numit expandare;

n cazul n care ajunge la un terminal care nu corespunde cu intrarea, execut backtracking, operaie
numit revenire;

n cazul n care ajunge la un terminal care coincide cu terminalul de la intrare, terge un terminal din
vrful stivei, operaie numit avans.

Cu alte cuvinte, analizorul sintactic ncearc diverse alternative de derivare pentru fiecare neterminal.
Fiecare alternativ devine o structur posibil pentru simbolul de la intrare. Dac structura nu e regsit la
intrare, analizorul ncearc alt alternativ i aa mai departe. Dac alternativa este un simbol terminal i el
coincide cu atomul de la intrare, analizorul efectueaz o operaie de avans i consider obiectivul ndeplinit.
Arborele sintactic se construiete prin cutarea produciei a crei parte dreapt ncepe cu simbolul
curent de la intrare, ori poate fi derivat astfel nct s corespund cu acesta. Atomul curent de la intrare se
numete simbol de anticipare. Iniial, simbolul de anticipare este cel mai din stnga atom din irul de intrare.
Funcia pentru un neterminal al gramaticii analizeaz pe rnd alternativele de expandare ale neterminalului
respectiv. Trecerea la o nou alternativ are loc dac alternativa precedent a euat. Aceast trecere necesit
memorarea drumului parcurs n arbore, precum i a punctelor din irul de la intrare n care s-a nceput
expandarea neterminalelor.
Aceast trecere trebuie s restabileasc indicatorul irului de la intrare. Dac toate alternativele au
euat, se ntoarce eec. Dac o alternativ e satisfctoare, se ntoarce succes.
Dezavantajul principal al acestui analizor sintactic este faptul c implementarea operaiei de revenire
este dificil, deoarece toate aciunile compilatorului, pn n punctul de revenire trebuiesc anulate, deci
necesit backtraking, ceea ce implic i un timp mare de compilare.
Analizor sintactic descendent recursiv fr reveniri
n cazul acestui tip de analizor arborele sintactic nu apare explicit, ca o structur de date, ci poate fi
vzut mai degrab ca un arbore al apelurilor de proceduri.
Procesul de analiz const n derivri succesive ale neterminalelor, ncepnd cu rdcina. Derivrile
se materializeaz prin apelarea procedurilor corespunztoare simbolurilor ce apar pe o anumit ramur de
derivare. Pentru a putea implementa analizoare sintactice descendente recursive fr reveniri, se pot impune
asupra gramaticii restricii care s permit alegerea alternativei corecte fr ncercarea tuturor alternativelor
posibile.

Cunoscnd simbolul de intrare a, n situaia n care urmeaz s fie expandat un neterminal A, un


analizor sintactic descendent recursiv fr reveniri, trebuie s fie capabil s decid care din alternativele
posibile A1| 2 || n este unica alternativ din care se pot deriva iruri care s nceap cu simbolul a.
Cu alte cuvinte, analizorul sintactic descendent recursiv fr reveniri este capabil s aleag alternativa corect
numai pe baza simbolului curent de la intrare i nu mai necesit backtraking.
Fie de exemplu o gramatic cu urmtoarele producii:
SA | B
Ax
By
Un analizor sintactic descendent recursiv cu reveniri, pornind de la simbolul de start, va ncerca pe
rnd alternativele A i B.
n contrast cu acesta, un analizor sintactic descendent recursiv fr reveniri, analiznd atomul curent
de la intrare, cunoate cu certitudine care din produciile A sau B trebuie expandat.
O alternativ odat aleas, trebuie s permit recunoaterea irului de la intrare. Dac aceast
alternativ nu duce la recunoaterea acestuia, se semnaleaz eroare de sintax. Cu alte cuvinte, simbolul de
anticipare identificat univoc procedura care trebuie apelat la un moment dat, iar ordinea n care se apeleaz
procedurile definite pentru neterminalele gramaticii definete implicit arborele sintactic construit.
Analiza sintactic propriu-zis (programul principal) ncepe cu un apel la procedura pentru
neterminalul de start al gramaticii.
Sarcinile unui analizor sintactic descendent recursiv sunt:

construiete arborele sintactic ntr-o manier descendent;

folosete derivarea canonic stnga pentru produciile gramaticii;

decide care producie trebuie folosit pe baza simbolului de anticipare;

raporteaz eroare dac la intrare nu este un prefix al unui program corect;

nainte de a implementa un analizor sintactic descendent se examineaz gramatica limbajului astfel


nct:
- se elimin recursivitatea stnga dac este cazul;
- se factorizeaz stnga dac este cazul.
Implementarea software presupune scrierea unei funcii pentru fiecare neterminal al gramaticii i
apelul funciei pentru simbolul de start, aa cum se prezint n exemplul urmtor.
Exemplu de implementare a unui analizor sintactic descendent recursiv fr reveniri.
Fie o gramatic cu urmtoarele producii:
SA '.'
A B C B | B
B D E D |D
C '+'|'-'
D F '^' D|F
E '*'|'/'
F '(' A ')' | G
G 'A'|'B'|...|'Z'| 'a' | 'b' | ...| 'z'

(1)
(2)
(3)
(4)
(5)
(6)
(7)
(8)

Ea admite la intare construcii sintactice de forma:


a*b+c.
a/(b-c).
a+b^(c-d+e).

etc.
Examinnd produciile gramaticii, se observ c nu exist recursivitate stnga, nici direct, nici
indirect.
n schimb, produciile (2), (3) i (5) conin cte dou alternative care ncep cu acelai prefix i n acest
caz nu se poate implementa un analizor sintactic descendent recursiv fr reveniri. Este necesar s se aplice o
factorizare stnga.
Pentru producia: A B C B | B, se introduce un nou neterminal A1 i ea se nlocuiete cu
urmtoarele dou producii:
AB A1
A1|C B
Procednd similar cu produciile (3) i (5) se obine o nou gramatic echivalent cu prima, de forma:
S A '.'
AB A1
A1 |C B
B D B1
B1 |E D
C '+'|'-'
D F D1
D1 |'^' D
E '*'|'/'
F '(' A ')' | G
G 'A'|'B'|...|'Z'| 'a' | 'b' | ...| 'z'
Noua gramatic se preteaz pentru implementarea unui analizor sintactic descendent recursiv fr
reveniri. Preul transformrii const n faptul c gramatica este mai complex, avnd mai multe neterminale
i mai multe producii.
n continuare, se prezint un exemplu de implementare software a analizorului. Trebuie notat c, pe
de o parte, acesta nu este complet, deoarece, din motive de simplitate, apelul analizorului lexical este redus la
citirea unui singur caracter, iar pe de alt parte, analizorul sintactic din exemplu nu construiete efectiv
arborele sintactic i nu folosete tabela de simboluri.
n plus, codul se putea scrie mult mai simplu, ns s-a ales aceast form pentru a ilustra modul n
care trebuiesc aplicate cunotinele.
De asemenea, codul conine n plus funcii care afieaz felul n care se expandeaz neterminalele,
funcii care nu sunt necesare pentru funcionare. Ele au fost incluse cu scop pur demonstrativ.
/*
Analizor sintactic descendent recursiv cu gramatica:
S ---> A '.'
A ---> B | B C B
A--->B A1
A1--->epsilon|C B

B ---> D |D E D
B-->D B1
B1-->epsilon|E D
C ---> '+'|'-'
D ---> F '^' D|F
D--> F D1
D1 -->epsilon|'^' D
E ---> '*'|'/'
F ---> '(' A ')' | G
G ---> 'A'|'B'|...|'Z'| 'a' | 'b' | ...| 'z'
Se factorizeaza:
Neterminalele s-au notat cu majuscule
Terminalele s-au notat intre apostroafe
Accepta la intrare expresii aritmetice de forma: a^b., (a+b)*c. etc.
Atomii sunt simple caractere si de aceeea foloseste atom este de tip char
Apelul scan() din exemplu coincide cu apelul analizorului lexical
Afiseaza felul in care se expandeaza neterminalele
*/
#include
#include
#include
#include

<stdio.h>
<conio.h>
<stdlib.h>
<ctype.h>

char atom;
int nivel = 0;

//folosit numai pentru afisare intuitiva

// urmeaza declaratiile functiilor pentru fiecare neterminal


void A();
void A1();
void B();
void B1();
int C();
void D();
void D1();
int E();
void F();
void G();
void
void
void
void
void
void
void

eroare();
scan();
expandare(char);
//numai pentru afisare intuitiva
alt_net(char);
//numai pentru afisare intuitiva
spatii(int); //numai pentru afisare intuitiva
succes();
sp(int);

void scan()
{
while (isspace(atom = getchar()));
// atomul este un caracter diferit de spatiu de la intrare
}
void eroare()
{
printf("\n*** Eroare de sintaxa *** \n");
getch();
exit(1);
}
void succes()
{
printf("\nAnaliza sintactica terminata cu succes!\n");
getch();

}
void expandare(char nume)
{
// numai pentru afisare intuitiva
spatii(nivel++);
printf("+-%c: Expandat,", nume);
sp(1);
printf("atom == %c\n", atom);
getch();
}
void alt_net(char nume)
{
// numai pentru afisare intuitiva
spatii(--nivel);
printf("+-%c: Urmator,", nume);
printf("atom == %c\n", atom);
getch();
}
void spatii(int nivel_local)
{
while (nivel_local-- > 0)
printf("| ");
}
void sp(int nivel_local)
{
while (nivel_local-- > 0)
printf(" ");
}
void A()
{
expandare('A');
B();
A1();
alt_net('A');
}
void A1()
{
expandare('A1');
if( C()) B();
alt_net('A1');
}

void B()
{
expandare('B');
D();
B1();
alt_net('B');
}
void B1()
{
expandare('B1');
if(E()) D();
alt_net('B1');
}
int C()

{
expandare('C');
if (atom== '+'|| atom == '-')
{scan(); alt_net('C');return(1);}
else {return(0); alt_net('C');eroare();}
}
void D()
{
expandare('D');
F();
D1();
alt_net('D');
}
void D1()
{
expandare('D1');
if (atom == '^')
{scan(); D();}
alt_net('D');
}
int E()
{
expandare('E');
if (atom == '*'|| atom =='/') {scan();alt_net('E');return(1);}
else {alt_net('E');return(0);}
}
void F()
{
expandare('F');
if (atom == '(')
{scan(); A();
if (atom == ')') scan();
else eroare();
}
else G();
alt_net('F');
}
void G()
{
expandare('G');
if (isalpha(atom))
else eroare();
}

{scan(); alt_net('G');}

void main()
{
clrscr();
scan();
A();
if (atom != '.') eroare();
else succes();
}

- ncercai programul pentru mai multe tipuri de simboluri de intrare.


- Examinai modul n care se expandeaz neterminalele gramaticii.
De exemplu, pentru intrarea: (a+b)*c. , se expandeaz produciile gramaticii conform cu arborele din figura
1.

(a+b)*c.
A

A1
B1

D
D1

F
A

*
)

A1

B
D

B1

D1

D1

G
B

C
D

c
B1

+
F

D1

G
a
b

Arborele construit pentru intrarea (a+b)*c.

Tem:
1. Construii un analizor sintactic descendent recursiv fr reveniri pe baza urmtoarei gramatici (eliminai
recursivitatea stnga sau factorizai stnga, dac este nevoie):
S '.'
E '+' T | T
T T '*' F | F
F '(' E ')' | G
G 'A'|'B'|...|'Z'| 'a' | 'b' | ...| 'z'
2. Construii arborele pentru expresia (a+b)*c.

(1)
(2)
(3)
(4)
(5)

S-ar putea să vă placă și