Sunteți pe pagina 1din 123

UNIVERSITATEA ALEXANDRU IOAN CUZA

FACULTATEA DE INFORMATIC
DEPARTAMENTUL DE NVMNT LA DISTAN

Gheorghe Grigora

PROIECTAREA
COMPILATOARELOR
2006 2007

EDITURA UNIVERSITII ALEXANDRU IOAN CUZA


IAI

Adresa autorului:

Universitatea Alexandru Ioan Cuza Iai


Facultatea de Informatic
str. Berthelot 16
700483, Iai
grigoras@infoiasi.ro, http://www.infoiasi.ro/~grigoras

Prefa

Cuprins
1.

LIMBAJE..................................................................................................................2

1.1
1.1.1
1.1.2
1.1.3

Limbaje de programare ................................................................................................................ 3


Descrierea sintaxei i semanticii unui limbaj ....................................................................................................3
Implementarea limbajelor....................................................................................................................................7
Analiza lexical i analiza sintactic....................................................................................................................9

1.2.1
1.2.2
1.2.3
1.2.4

Limbaje formale .......................................................................................................................... 11


Expresii regulate..................................................................................................................................................11
Automate finite....................................................................................................................................................13
Gramatici independente de context.................................................................................................................17
Arbori sintactici. Ambiguitate...........................................................................................................................18

1.2

ANALIZA LEXICAL...........................................................................................23

2.1

Un algoritm de trecere de la o expresie regulat la automatul echivalent.................................. 24

2.2

De la un automat cu -tranziii la automatul determinist echivalent ......................................... 27

2.3

Proiectarea unui analizor lexical ................................................................................................ 28

2.4

Generatorul Lex.......................................................................................................................... 32

ANALIZA SINTACTIC DESCENDENT .......................................................35

3.1

Rolul analizorului sintactic ........................................................................................................ 36

3.2

Problema recunoaterii. Problema parsrii ................................................................................ 36

3.3
Analiza sintactic descendent .................................................................................................. 37
3.3.1
Parser descendent general..................................................................................................................................38
3.3.2
Gramatici LL(k)...................................................................................................................................................40
3.3.3
O caracterizare a gramaticilor LL(1)................................................................................................................41
3.3.4
Determinarea mulimilor FIRST i FOLLOW..............................................................................................42
3.3.5
Tabela de analiz sintactic LL(1) ...................................................................................................................45
3.3.6
Analizorul sintactic LL(1) ..................................................................................................................................45
3.3.7
Eliminarea recursiei stngi.................................................................................................................................47

ANALIZA SINTACTIC N GRAMATICI LR ................................................... 51

4.1

Gramatici LR(k) ..........................................................................................................................51

4.2

O caracterizare a gramaticilor LR(0).......................................................................................... 52

4.3

Algoritm de analiz sintactic LR(0).......................................................................................... 60

4.4

Gramatici i analizoare SLR(1)................................................................................................... 65

4.5

O caracterizare a gramaticilor LR(1)...........................................................................................71

4.6

Analiz sintactic LR(1) i LALR(1) .......................................................................................... 73

GENERATOARE DE ANALIZOARE SINTACTICE........................................79

ii

Prefa

5.1

Utilizarea generatorului de parsere YACC ................................................................................. 82

5.2

Aplicaii cu LEX i YACC .......................................................................................................... 86

6
6.1

ANALIZA SEMANTIC .......................................................................................91


6.1.1
6.1.2
6.1.3

Gramatici cu atribute.................................................................................................................. 91
Numere raionale n baza 2 (Knuth) ............................................................................................................... 91
Declararea variabilelor ntr-un program ......................................................................................................... 93
Definiia gramaticilor cu atribute..................................................................................................................... 96

6.2
Grafuri ataate unei gramatici cu atribute .................................................................................. 98
6.2.1
Graful DGp .......................................................................................................................................................... 98
6.2.2
Graful BGp........................................................................................................................................................... 99
6.2.3
Graful DGt .......................................................................................................................................................... 99
6.3
Metode de evaluare a atributelor ............................................................................................... 101
6.3.1
Evaluatorul rezultat din definiia gramaticii................................................................................................. 101
6.4
Traducere bazat pe sintax......................................................................................................102
6.4.1
Reprezentri intermediare ............................................................................................................................... 103
6.4.2
Traducerea codului n notaie postfix ........................................................................................................... 105
6.4.3
Traducerea codului n arbore sintactic.......................................................................................................... 106
6.4.4
Traducerea codului n secvene de quadruple.............................................................................................. 107
6.5

Proiectarea unui interpreter cu flex i yacc................................................................................113

BIBLIOGRAFIE .......................................................................................................... 121

1. Limbaje
Acest capitol trece n revist chestiuni legate de limbajele de programare de nivel nalt,
precum i elemente de limbaje formale necesare abordrii problemelor de analiz
structural n cadrul compilatoarelor . Este tiut faptul c exist i sunt folosite un
numr impresionant de limbaje de programare. Vom examina caracteristicile ctorva din
aceste limbaje, lucru util pentru compararea lor i, prin urmare, pentru alegerea
limbajului potrivit pentru o aplicaie anume. De asemenea, caracteristicile limbajelor de
programare sunt importante pentru proiectarea i implementarea compilatoarelor.
Limbajul este instrumentul principal al comunicrii. Gradul de succes sau de eec al
comunicrii este influenat de caracteristicile limbajului folosit. Comunicarea ntre
subieci de o anume specialitate se poate face prin intermediul unui limbaj ce
incorporeaz o terminologie special care face ca multe pri ale comunicrii s fie
neinteligibile pentru un neiniiat. Tot aa, comunicarea cu computerul presupune
utilizarea unui vocabular i a unei gramatici specializate n care regulile chiar dac sunt
mai uor de specificat dect pentru limbajele naturale trebuiesc cunoscute de ambele
pri. Oamenii comunic cu computerul prin limbaje specializate; exist numeroase
astfel de limbaje i se proiecteaz mereu altele noi. Aceste limbaje sunt numite, unele,
limbaje de programare, altele limbaje de comand sau limbaje de interogare a bazelor de
date etc.

Limbaje formale

1.1 Limbaje de programare


Limbajele de programare sunt instrumente folosite pentru a construi descrieri formale
ale algoritmilor. Un limbaj de programare trebuie s conin, pe lng operaiile de baz
ce transform o stare iniial dat ntr-o stare final, i modul n care paii ce conin aceste
operaii sunt nlnuii pentru a rezolva o anume problem. Pentru a rspunde la aceste
cerine, un limbaj de programare trebuie s conin trei componente:
tipuri de date, obiecte i valori mpreun cu operaiile ce se pot aplica acestora;
reguli pentru stabilirea relaiilor cronologice ntre operaiile specificate;
reguli ce stabilesc structura (static) a unui program.
Aceste componente constituie nivelul de abstracie la care putem reprezenta algoritmi n
limbajul respectiv. n raport cu acest nivel de abstracie, limbajele de programare se pot
clasifica simplificnd destul de mult lucrurile - n dou grupe mari: limbaje de nivel sczut
i limbaje de nivel nalt. Limbajele de nivel sczut sunt mai apropiate de limbajele main:
exist o coresponden destul de puternic ntre operaiile implementate de limbaj i
operaiile implementate de hardware-ul corespunztor. Limbajele de nivel nalt, pe de
alt parte, sunt mai apropiate de limbajele folosite de oameni pentru a exprima probleme
i algoritmi. Fiecare propoziie ntr-un limbaj de nivel nalt poate s fie echivalent cu o
succesiune de operaii (mai multe propoziii) dintr-un limbaj de nivel sczut. n realitate,
exist o mulime de limbaje ntre cele dou clase, de nivel sczut i de nivel nalt, i nu se
poate face o mprire strict a limbajelor doar n dou clase.
Abstractizrile ntr-un limbaj de programare permit programatorului s extrag
proprietile eseniale necesare pentru soluia problemei de rezolvat, ascunznd detaliile
de implementare a soluiei. Cu ct nivelul de abstractizare este mai nalt, programatorul
se gndete mai puin la maina (hardware-ul) pe care va fi implementat soluia
problemei. Cu alte cuvinte, ideile de programare pot fi separate n ntregime de
arhitectura sistemului pe care va fi executat programul. De pild, n limbajele de
programare de nivel nalt, programatorul lucreaz cu nume de variabile simbolice i nu
cu adrese numerice de memorie. Abstraciile de date permit ca locaiile de memorie s
fie privite drept celule de memorare pentru tipuri de date de nivel nalt; programatorul
nu trebuie s-i pun problema reprezentrii acestora. Abstraciile de control permit
programatorului s exprime algoritmii cu ajutorul structurilor de tipul if, while sau al
procedurilor. Cum sunt implementate acestea n maina respectiv, este un lucru care
nu-l intereseaz, n general, pe programator.
Un program scris ntr-un limbaj de nivel nalt este tradus n cod main echivalent, cu
ajutorul unui program special numit compilator. n aceast carte vom prezenta unele din
tehnicile cunoscute pentru proiectarea unui compilator.
1.1.1 Descrierea sintaxei i semanticii unui limbaj
Limbajele, formal vorbind, sunt mulimi de iruri de caractere dintr-un alfabet fixat.
Acest lucru este valabil att pentru limbajele naturale, ct i pentru cele artificiale. irurile
de caractere ale limbajului se numesc propoziii (sentences) sau instruciuni, afirmaii
(statements). Orice limbaj are un numr de reguli sintactice care stabilesc care din irurile
de caractere ce se pot forma cu simboluri din alfabet fac parte din limbaj. Dac pentru
limbajele naturale regulile sintactice sunt n general complicate, limbajele de programare
sunt relativ simple din punct de vedere sintactic. Exist n fiecare limbaj de programare
unitile sintactice de nivelul cel mai mic numite lexeme care nu sunt cuprinse n
descrierea formal a sintaxei limbajului. Aceste uniti sunt cuprinse ntr-o specificare
lexical a limbajului care precede descrierea sintaxei. Lexemele unui limbaj de
programare cuprind identificatorii, literalii, operatorii, cuvintele rezervate, semnele de
punctuaie. Un program poate fi privit ca un ir de lexeme i nu un ir de caractere;

4
Prefa
obinerea irului de lexeme din irul de caractere (programul obiect) este sarcina
analizorului lexical. Un token al unui limbaj este o categorie de lexeme. De exemplu, un
identificator este un token care are ca instane lexeme ca: suma, total, i, par5, etc.
Tokenul PLUS pentru operatorul aritmetic + are o singur instan. Instruciunea:
delta = b2 - ac

este compus din lexemii i tokenurile specificate n tabela urmtoare:


Lexem

Token
IDENTIFICATOR
ASIGNARE
IDENTIFICATOR
MINUS
IDENTIFICATOR
Un limbaj de programare poate fi descris, formal, prin aazisele mecanisme de generare
a irurilor din limbaj. Aceste mecanisme se numesc gramatici i au fost descrise de
Chomsky n anii 1956 1959. Nu mult dup ce Chomsky a descoperit cele patru clase
de limbaje formale, grupul ACM GAMM a proiectat limbajul Algol 58 iar la o
conferin internaional John Backus a prezentat din partea grupului acest limbaj
folosind o metod formal nou de descriere a sintaxei. Aceast nou notaie a fost
modificat de Peter Naur la descrierea limbajului Algol 60. Metoda de descriere a
sintaxei limbajelor de programare aa cum a fost folosit de Naur poart numele de
forma Backus - Naur sau simplu BNF. De remarcat faptul c BNF este aproape
identic cu mecanismul generativ al lui Chomsky gramatica independent de context.
BNF este un metalimbaj. Acesta folosete abstraciile pentru a descrie structurile
sintactice. O abstracie este scris ntre paranteze unghiulare, ca de exemplu:
<asignare>, <variabil>, <expresie>, <if_stm>, <while_stm> etc. Definiia unei
abstracii este dat printr-o regul sau producie care este format din:
partea stng a regulii ce conine abstracia care se definete.
o sgeat (sau caracterele ::= ) care desparte partea stng a regulii de partea
dreapt.
partea dreapt a regulii care este un ir format din abstracii, tokenuri, lexeme.
De exemplu, regula:
<asignare> <variabil> = <expresie>
definete sintaxa unei asignri: abstracia <asignare> este o instan a abstraciei
<variabil> urmat de lexemul = urmat de o instan a abstraciei <expresie>. O
propoziie care are structura sintactic descris de aceast regul este:
delta
=
b2
ac

total = suma1 + suma2

ntr-o regul BNF abstraciile se numesc simboluri neterminale sau simplu neterminali,
lexemii i token-urile se numesc simboluri terminale sau simplu terminali. O descriere BNF
sau o gramatic (independent de context) este o colecie de reguli.
Dac se ntmpl ca un neterminal s aib mai multe definiii acestea se pot insera ntr-o
singur regul n care partea dreapt conine prile drepte ale definiiilor sale desprite
prin simbolul |. De exemplu, definiiile:
<if_stm> if <expr_logic> then <stm>
<if_stm> if <expr_logic> then <stm> else <stm>
se scriu:
<if_stm> if <expr_logic> then <stm>
| if <expr_logic> then <stm> else <stm>
Iat un exemplu de gramatic (BNF) ce descrie un minilimbaj de programare:
<program>
begin <lista_de_instructiuni> end
<lista_de_instructiuni> <instructiune>

Limbaje formale

| <lista_de_instructiuni>; <instructiune>
<variabila> = <expresie>
LITERA
<expresie> + <expresie>
| <expresie> - <expresie>
| <variabila>
| NUMAR
Aici apar dou tokenuri: LITERA i NUMAR. Acestea pot la rndul lor s fie descrise
prin acelai mecanism:
LITERA a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z
NUMAR <cifra> | NUMAR <cifra>
<cifra>
0|1|2|3|4|5|6|7|8|9
Se observ c de data aceasta gramatica este de tip 3 (sau gramatic regulat) ceea ce
nseamn c tokenurile se pot descrie folosind expresiile regulate iar recunoaterea lor
(analiza lexical) se poate face cu automate finite deterministe.
BNF a fost extins n diverse moduri mai ales din raiuni de implementare a unui
generator de analizor sintactic. Orice extensie a BNF este numit EBNF (Extended
BNF). Extensiile BNF nu mresc puterea descriptiv a mecanismului ci reprezint
faciliti privind citirea sau scrierea unitilor sintactice. Extensiile ce apar frecvent sunt:
includerea, n partea dreapt a unei reguli, a unei pri opionale ce se scrie ntre
paranteze drepte. De exemplu, instruciunea if ar putea fi descris astfel:
<if_stm> if <expr_logic> then <stm> [else <stm>]
includerea n partea dreapt a unei reguli a unei pri ce se repet de zero sau
mai multe ori. Aceast parte se scrie ntre acolade i poate fi folosit pentru a
descrie recursia:
<lista_de_instructiuni> <instructiune> {; <instructiune>}
includerea n partea dreapt a unei reguli a opiunii de a alege o variant dintr-un
grup. Acest lucru se face punnd variante n paranteze rotunde, desprite prin
|. De exemplu, o instruciune for se poate defini astfel:
<for_stm> for <var> = <expr> (to | downto) <expr> do <stm>
Trebuie precizat c parantezele de orice fel din descrierea EBNF fac parte din
metalimbaj, ele nu sunt terminali ci doar instrumente de a nota o anume convenie. n
cazul n care unele din aceste simboluri sunt simboluri terminale n limbajul descris,
acestea vor fi puse ntre apostrof : [ sau { etc.
EBNF nu face altceva dect s simplifice ntr-un anume fel definiiile sintactice. Spre
exemplificare iat cum se descrie expresia aritmetic folosind cele dou mecanisme:
BNF:
<expresie> <expresie> + <termen>
| <expresie> - <termen>
| <termen>
<termen> <termen> * <factor>
| <termen> / <factor>
| <factor>
<factor> ( <expresie> )
| <identificator>
<instructiune>
<variabila>
<expresie>

EBNF:

<expresie> <termen> {(+| -) <termen>}


<termen> <factor> {(* | /) <factor>}
<factor> (<expresie> )| <identificator>

6
Prefa
Un exemplu de program n minilimbajul de programare descris mai sus este urmtorul:
begin a = b + c ; d = 5 - a end

O derivare a acestui program n gramatica dat este:


<program> begin <lista_de_instructiuni> end
begin <lista_de_instructiuni>; <instructiune> end
begin <instructiune>; <instructiune> end
begin <variabila>=<expresie>; <instructiune> end
begin LITERA=<expresie>; <instructiune> end
begin LITERA=<expresie>+<expresie>; <instructiune> end
begin LITERA=LITERA+<expresie>; <instructiune> end
begin LITERA=LITERA+LITERA; <instructiune> end
begin LITERA=LITERA+LITERA; <variabila>=<expresie>end
begin LITERA=LITERA+LITERA;LITERA=<expresie>end
begin LITERA=LITERA+LITERA;LITERA=<expresie>-<expresie> end
begin LITERA=LITERA+LITERA;LITERA=NUMAR-<expresie> end
begin LITERA=LITERA+LITERA;LITERA=NUMAR-LITERA end
Arborele de derivare pentru acest program este dat n figura 1.1. Se observ o strns
legtur ntre derivrile dintr-o gramatic i arborii de parsare.
<prog>

begin

<lista>

<lista>

<instr>

<var>

LITERA

=
<expr>

LITERA

end

<instr>

<var>

<expr>

LITERA

<expr>

=
<expr>

NUMAR

<expr>

<expr>

LITERA

LITERA

Figura 1.1
Exist reguli ale limbajelor de programare care nu pot fi descrise utiliznd BNF. De
pild faptul c o variabil nu poate fi utilizat nainte de a fi declarat este o caracteristic
ce nu poate fi descris prin BNF. Reguli de acest fel fac parte din semantica static a
limbajului: reguli ce in indirect de nelesul unui program relativ la execuie. Semantica
static este denumit aa pentru c poate fi verificat nainte de execuie, la momentul
compilrii. Un mecanism care descrie semantica static a fost introdus de Knuth n 1968
i este denumit gramatic cu atribute. O gramatic cu atribute este o gramatic
independent de context, care descrie sintaxa, la care s-au adugat urmtoarele:

Limbaje formale
7
fiecrui simbol al gramaticii i se ataeaz o mulime de atribute, unele dintre ele
sunt sintetizate (valorile lor depind de valorile atributelor descendenilor), altele
motenite (valorile lor depind de valorile atributelor prinilor i frailor);
fiecrei reguli din gramatic i se asociaz o mulime de funcii semantice care
determin valorile atributelor sintetizate ale prii stngi i valorile atributelor
motenite ale simbolurilor din partea dreapt.
Un arbore de derivare ntr-o gramatic cu atribute este arborele de derivare din
gramatica ce descrie sintaxa mpreun cu mulimea valorilor atributelor ataate fiecrui
nod al arborelui. Pentru a evita circularitatea n obinerea valorilor atributelor, deseori se
pune restricia ca un atribut motenit s depind doar de valorile atributelor prii stngi
a regulii i de atributele simbolurilor din stnga sa n irul din partea dreapt a regulii.
Aceast restricie permite proiectarea unui evaluator care parcurge arborele de derivare
n preordine. Nodurile frunze din arborele de derivare au atribute sintetizate ale cror
valori se determin n afara arborelui de derivare; acestea se numesc atribute intrinseci.
Spre exemplu, tipul unei instane a unei variabile ntr-un program se obine din tabela
simbolurilor care este construit la analiza lexical i conine numele variabilelor, tipul
lor etc.. n capitolul Analiza semantic vor fi date exemple de gramatici cu atribute i
modul de evaluare a atributelor.
Descrierea nelesului unui program nseamn semantica dinamic. Este vorba despre
nelesul expresiilor, a instruciunilor, a unitilor de program. Dac pentru descrierea
sintaxei formalismul BNF este universal acceptat, nu exist nc o notaie universal
acceptat pentru semantic (n continuare semantica dinamic o s fie numit
semantic). i aceasta n condiiile n care fiecare programator trebuie s cunoasc atunci
cnd folosete un limbaj, ce transformri face fiecare din instruciunile sale i cum sunt
obinute aceste transformri. De obicei ei nva semantica citind text n limbaj natural
care descrie fiecare instruciune. Or se tie c, din multe motive, aceste explicaii sunt
imprecise i / sau incomplete. Pe de alt parte, cei care implementeaz limbajele deseori
folosesc explicaiile n limbaj natural atunci cnd proiecteaz compilatoarele. Utilizarea
semanticii n limbaj natural att de ctre programatori, ct i de ctre implementatori se
poate explica prin aceea c mecanismele formale de descriere a semanticii sunt n general
complexe i greu de utilizat practic. De aceea domeniul acesta este nc n atenia multor
cercettori.
1.1.2 Implementarea limbajelor
Dou din componentele primare ale unui computer sunt memoria intern i procesorul.
Memoria intern este folosit pentru pstrarea programelor i a datelor. Procesorul este
o colecie de circuite care implementeaz operaii sau instruciuni main. n absena
altor componente software un computer nelege doar propriul limbaj main. Pentru a
fi folosit, computerul are nevoie de un sistem de operare - care reprezint interfaa
pentru alte sisteme software - i de un numr de limbaje de programare care s faciliteze
rezolvarea de probleme diverse.
Limbajele de programare pot fi implementate prin trei metode generale: compilare,
interpretare i sisteme de implementare hibride.
Un compilator este un sistem software care traduce programele scrise ntr-un limbaj de
programare (programe surs) n programe scrise n limbaj main (programe obiect) ce pot fi
executate direct de ctre computer. Avantajul acestei metode de implementare este acela
c execuia programelor este foarte rapid. Procesul de compilare, n cele mai multe
cazuri, se desfoar dup schema din figura 1.2. Analizorul lexical identific n textul
surs unitile lexicale (tokenurile) i transform acest text ntr-un ir de uniti lexicale,

8
Prefa
ignornd spaiile albe i comentariile. Uniti lexicale ntr-un limbaj de programare sunt
identificatorii, operatorii, cuvintele rezervate, semnele speciale.
Analizorul sintactic preia irul de uniti lexicale de la analizorul lexical i construiete
arborele de derivare (de parsare) al acestui ir n gramatica ce descrie sintaxa limbajului
de programare. De fapt, de cele mai multe ori, se obine o reprezentare a acestui arbore
de derivare, un set de informaii din care poate fi construit acest arbore.
Tabela de simboluri, construit de cele dou analizoare amintite mai sus, cuprinde
informaii privind numele, tipul, atributele tuturor identificatorilor definii de utilizator
n program. Aceste informaii sunt necesare analizorului semantic i generatorului de
cod.
Analizorul semantic este de regul un generator de cod intermediar care are rolul de a
verifica erorile ce nu pot fi detectate de analizorul sintactic (de exemplu cele referitoare
la tipul variabilelor) i de a obine din arborele de derivare un program echivalent cu cel
iniial, dar ntr-un limbaj intermediar ntre limbajul surs i limbajul main. O parte
opional a procesului de compilare este optimizarea. Aceasta are rolul de a mbunti
programul obinut, n ceea ce privete mrimea sa, prin eliminarea unor instruciuni sau
n ceea ce privete viteza de calcul. Optimizarea, atunci cnd se face, este mai uor de
aplicat codului intermediar dect codului main.
Program
surs

Analizor
lexical
Uniti lexicale

Analizor
sintactic
Arbore de derivare

Tabela de
simboluri

Analizor
semantic
Cod intermediar

Generator
de cod
Cod main

Computer
Rezultate
Figura 1.2

Date de intrare

Limbaje formale
9
Generatorul de cod traduce codul intermediar (optimizat) ntr-un program echivalent scris
n cod main. Acest program cod main are nevoie, de cele mai multe ori, pentru a fi
executat, de module program oferite de sistemul de operare (cum ar fi programe de
introducere sau extragere a datelor n/din memorie). Procesul de colectare a
programelor sistem necesare execuiei programului utilizator compilat i de legare a lor
la acesta se face de ctre un program special numit linker.
O tehnic destul de folosit n implementarea limbajelor de programare este interpretarea
sau interpretarea pur cum mai este cunoscut n literatura de specialitate. Programele
scrise n limbajul surs sunt interpretate de ctre un alt program, numit interpreter, care
acioneaz ca un simulator software al unei maini ce execut instruciune cu
instruciune programul scris n limbajul surs fr ca acesta s fie tradus n limbaj
main. Simulatorul software ofer o main virtual pentru limbajul surs n cauz.
Avantajul interpretrii este acela c permite implementarea uoar a operaiilor de
depanare a programelor, pentru c erorile de execuie se refer la uniti ale limbajului
surs i nu in de limbajul main. Din pcate, dezavantajele sunt mai numeroase: timpul
de execuie este serios diminuat n cazul interpretrii fa de compilare (un program
interpretat se execut de 10 pan la 100 ori mai lent dect unul compilat); spaiul folosit
de un program interpretat este mai mare; n cazul interpretrii, semantica unei
construcii trebuie determinat din programul surs la momentul execuiei. Aceste
dezavantaje fac ca doar limbajele cu structuri simple s fie implementate prin
interpretare (comenzi sistem n UNIX, APL, LISP). Exist i excepii: cu toate c nu
este un limbaj simplu, JavaScript este interpretat.
Sistemele de implementare hibrid folosesc att compilarea, ct i interpretarea pentru
implementarea limbajelor. Aceast tehnic se rezum la obinerea unui cod intermediar
pentru programul scris n limbaj de nivel nalt (compilare) i apoi interpretarea acestui
cod intermediar. Desigur, se alege limbajul intermediar aa fel ca el s poat fi uor
interpretat. Avantajul implementrilor hibride rezult din aceea c obinerea codului
intermediar se poate face automat iar execuia este mai rapid dect n cazul interpretrii
pure. Ca exemple de implementri hibride consemnm limbajul Perl i implementrile
de nceput ale limbajului Java care este tradus n cod intermediar numit byte code i
poate fi portat pe orice main care dispune de un interpreter de byte code asociat cu
un sistem de execuie, n fapt ceea ce se numete main virtuala Java.
1.1.3 Analiza lexical i analiza sintactic
Sintaxa unui limbaj de programare se descrie folosind formalismul gramaticilor
independente de context sau aa zisa BNF Backus Naur Form. Utilizarea BNF n
descrierea sintaxei este avantajoas pentru c este un formalism clar i concis i poate fi
uor transcris n sistemele software care implementeaz limbajul respectiv. n aproape
toate compilatoarele, verificarea corectitudinii sintactice se face n dou etape distincte:
analiza lexical i analiza sintactic. Analiza lexical se ocup de construciile simple ale
limbajului precum: identificatori, literali numerici, operatori, cuvinte rezervate, semne
speciale. Analiza sintactic se ocup de construciile evoluate din limbaj precum expresiile,
instruciunile, unitile de program. Tehnicile pentru analiza lexical sunt mult mai puin
complexe fa de cele ale analizei sintactice. Asta face ca implementarea separat a
analizei lexicale s duc la obinerea unor module relativ mici i uor de ntreinut pentru
fiecare din cele dou faze. n esen un analizor lexical este un program pentru
recunoaterea subirurilor de caractere dint-un ir dat, subiruri ce se potrivesc cu un
anume ablon (problema este cunoscut sub numele de pattern matching i se ntlnete
frecvent n editoarele de text). Analizorul lexical citete textul surs i colecteaz
caracterele n grupri logice care se numesc lexeme. Acestor grupri logice li se asociaz
coduri interne care se numesc tokenuri. De pild, textul urmtor:

10

Prefa
alpha = beta + 734;

n urma analizei lexicale se prezint astfel:


Lexem

Token

alpha

IDENT

ASIGN

beta

IDENT

PLUS

734

INT_LIT

PV

IDENT, ASIGN etc. sunt nite nume pentru nite coduri numerice care vor fi returnate
de ctre analizor atunci cnd grupul de caractere citit ndeplinete condiia de a fi
identificator, operator de asignare etc. Pentru c spaiile albe i comentariile nu sunt
relevante pentru execuia unui program, acestea sunt ignorate de ctre analizorul lexical.
Rolul analizorului lexical, n contextul compilrii unui program, este de a citi textul
surs i de a oferi analizorului sintactic tokenurile.
Partea de analiz a textului surs care se refer la unitile sintactice precum expresii,
instruciuni, blocuri etc. poart numele de analiz sintactic sau parsare. Rolul unui analizor
sintactic este dublu. n primul rnd, este acela de a verifica dac programul este corect
sintactic. Dac se ntlnete o eroare atunci analizorul produce un diagnostic i
analizeaz mai departe textul astfel nct s fie semnalate ct mai multe din erorile de
sintax existente n text. Al doilea rol este acela de a construi arborele de derivare al
programului respectiv n gramatica ce descrie limbajul. De fapt, sunt obinute
informaiile din care se poate construi arborele de derivare: derivarea extrem stng sau
derivarea extrem dreapt corespunztoare, care nsemna moduri de traversare a arborilor
de derivare. Sunt cunoscute dou tipuri de parsere, n funcie de direcia n care este
construit arborele de derivare: parsere top down (descendente) care construiesc arborele de
la rdcin ctre frunze i parsere bottom up (ascendente) care construiesc arborele de la
frunze ctre rdcin. Un parser top down traseaz derivarea extrem stng a cuvntului
de analizat. Arborele de derivare este construit n preordine: se ncepe cu rdcina i
apoi fiecare nod este vizitat nainte ca descendenii si s fie generai. Acetia sunt
vizitai n ordinea de la stnga la dreapta. n termenii derivrii, acest lucru se poate
sintetiza astfel: dat o form propoziional (un cuvnt dintr-o derivare extrem stnga),
sarcina parserului este de a determina urmtoarea form propoziional n derivare.
Dac forma propoziional curent este uA unde u este un cuvnt format din terminali
(tokenuri), A este neterminal iar este un cuvnt format din terminali i neterminali,
atunci sarcina parserului este de a nlocui pe A cu partea dreapt a unei reguli ce are pe
A n partea stng. Dac regulile corespunztoare lui A au n partea dreapt respectiv 1,
2,..., n i se alege k, atunci noua form propoziional este uk. n general, alegerea
corect a variantei de rescriere a lui A se face prin analiza simbolului urmtor lui u n
cuvntul de analizat uv. Dac acest lucru este posibil, atunci gramatica se numete
gramatic LL(1) i metoda de parsare se numete parsare LL(1). Implementarea se face
fie prin transformarea fiecrei reguli din gramatic (forma BNF) ntr-un numr de linii
de cod (parsare recursiv descendent) fie prin utilizarea unei tabele de parsare pentru
implementarea regulilor BNF. n capitolul al treilea vom trata pe larg parsarea LL(1).
Un parser bottom-up construiete arborele de derivare pornind de la frunze i naintnd
spre rdcin. Acesta produce reversul unei derivri extrem drepte a cuvntului de
analizat. Parsarea bottom-up se poate descrie succint astfel: dat forma propoziional
dreapta (un cuvnt din derivarea extrem dreapt) parserul trebuie s gseasc n
partea dreapt a unei reguli, iar aceasta se reduce la partea stng i se obine forma
propoziional precedent. Dac se gsete = u i este partea dreapt a regulii ce

Limbaje formale
11
are n stnga A, atunci precedenta form propoziional este Au. Algoritmii de analiz
bottom-up sunt cei bazai pe relaii de preceden sau, cei mai utilizai, cei din familia
LR.

1.2 Limbaje formale


n acest paragraf trecem n revist, succint, noiuni de limbaje formale necesare n
abordarea analizei lexicale, sintactice i semantice n cadrul procesului de compilare. Un
limbaj (formal) este o mulime de cuvinte peste un alfabet . Notm prin * limbajul
total: mulimea tuturor cuvintelor peste . Dac L1 i L2 sunt limbaje, atunci la fel sunt i
reuniunea lor, intersecia, complementara lui L1 fa de *. De asemenea, dac vom
considera produsul uv a dou cuvinte u i v ca fiind un nou cuvnt ce se obine prin
concatenarea celor dou, notm prin L1L2 produsul celor dou limbaje care nseamn
mulimea cuvintelor uv unde u este din L1 iar v este din L2. Vom nota prin cuvntul
nul, cel fr de litere. Atunci putem vorbi de puterea Ln a unui limbaj L folosind
produsul i stabilind c L0 este limbajul ce conine doar . Considerm i iteraia unui
limbaj L, notat L* ca fiind reuniunea limbajelor Ln pentru n = 0, 1, 2, .... Este
cunoscut ierarhia lui Chomsky n care limbajele se clasific n limbaje regulate, limbaje
independente de context, limbaje dependente de context i limbaje de tip 0. Limbajele regulate joac
un rol important n analiza lexical. Unitile lexicale pot fi descrise cu ajutorul expresiilor
regulate (algebric) sau al gramaticilor regulate (generativ) i pot fi recunoscute cu ajutorul
automatelor finite. n esen, un analizor lexical este implementarea unui automat finit. Vom
trece n revist pentru nceput chestiunile legate de expresii regulate automate finite, fr
a intra n detalii. Apoi, vom aminti cte ceva despre gramatici independente de context,
instrumentul generativ pentru descrierea sintaxei limbajelor de programare. Cititorul este
ndemnat a parcurge lucrri precum [Juc99], [Gri86] pentru a afla amnunte, mai ales de
ordin teoretic. Noi suntem interesai aici de utilizarea acestor modele de calcul pentru
dezvoltarea de algoritmi de analiz structural a textelor surs nct, vom prezenta att
ct este necesar pentru acest lucru.
1.2.1 Expresii regulate
Definiia 1.2.1 Fie un alfabet, simbolurile , , |, , *, (, ) care nu aparin lui i E un
cuvnt peste alfabetul { , , |, , *, ),( }. O expresie regulat peste se definete
inductiv astfel:
1. E este un atom regulat peste dac E este un simbol din { , } sau este de
forma (E1) unde E1 este o expresie regulat peste ;
2. E este un factor regulat peste dac E este un atom regulat peste sau este de forma
E1* unde E1 este un factor regulat peste ;
3. E este un termen regulat peste dac E este un factor regulat peste sau este de
forma E1E2 unde E1 este un termen regulat, iar E2 este un factor regulat peste ;
4. E este o expresie regulat peste dac E este un termen regulat peste sau este de
forma E1 | E2, unde E1 este o expresie regulat, iar E2 este un termen regulat peste
.
Aici , sunt privite ca simple simboluri fr vreo semnificaie. Mai jos, interpretarea
acestor expresii va fi desigur limbajul {} respectiv limbajul vid.
n definiia de mai sus, pe lng noiunea de expresie regulat se dau cele de termen
regulat, factor regulat, atom regulat care reflect precedena i asociativitatea operatorilor
|, , *. n cele ce urmeaz vom omite scrierea operatorului aa cum se obinuiete la
scrierea operatorului de nmulire.

12
Prefa
Definiia 1.2.2 Limbajul L(E) descris (sau notat, sau denotat) de expresia regulat E
peste alfabetul este definit inductiv astfel:
1. L( ) = , L() = {}, L(a) = {a}, pentru orice a din ;
2. L((E)) = L(E), pentru orice expresie regulat E peste ;
3. L(E*) = (L(E))*, pentru orice factor regulat E peste ;
4. L(E1E2) = L(E1)L(E2), pentru orice E1, termen regulat, i pentru orice E2 factor
regulat peste ;
5. L(E1 | E2) = L(E1) 4 L(E2), pentru orice E1 expresie regulat, i pentru orice E2
termen regulat peste .
Proprietile 1 i 2 de mai sus asigur faptul c L(E1E2) i L(E1 | E2) sunt bine definite.
Exemplul 1.2.1 Fie E1 = | (a | b)*(ab | baa) o expresie regulat.
Atunci limbajul descris de E1 este:
L(E1) = L( | (a | b)*(ab | baa) ) = L() 4 L( (a | b)*(ab | baa) ) =
= {} 4 L( (a | b)*) L((ab | baa) ) =
= {} 4 (L (a | b))* L(ab | baa ) =
= {} 4 (L(a | b))* (L(ab) 4 L( baa) ) =
= {} 4 (L(a) 4 L( b))* (L(a)L(b) 4 L(b)L(a)L(a) ) =
= {} 4 ({a} 4 {b})*({a}{b} 4 {b}{a}{a}) =
= {} 4 {a,b}*{ab, baa}.
Analog pentru E2 = (a | b)(a | b) obinem L(E2) = {aa, ab, ba, bb}
Este binecunoscut faptul c mulimea limbajelor descrise de expresiile regulate coincide
cu mulimea limbajelor regulate [Juc99]. Asta nseamn c pentru fiecare expresie
regulat E poate fi construit (efectiv) un automat finit care s recunoasc exact limbajul
descris de expresia E. De asemenea, pentru orice automat finit, exist o expresie regulat
(care poate fi obinut efectiv) ce descrie limbajul recunoscut de el. Cum suntem
interesai n proiectarea unui analizor lexical, parte a unui compilator, vom descrie
algoritmic n paragrafele urmtoare trecerea de la o expresie regulat la un automat finit;
algoritmii difer substanial de cei utilizai n demonstraiile teoretice.
Definiia 1.2.3 Dou expresii regulate E1, E2 peste alfabetul , sunt echivalente dac
L(E1) = L(E2). Notm acest lucru prin E1 = E2.
Exemplul 1.2.2 Se verific uor c au loc urmtoarele :
a | b = b | a i | (a | b)* = (a | b)*.
Lema 1.2.1 (Proprieti algebrice ale expresiilor regulate). Fie E, E1, E2, E3 expresii
regulate peste alfabetul . Sunt adevrate urmtoarele:
1. E1 | E2 = E2 | E1
2. (E1 | E2) | E3 = E1 | (E2 | E3)
3. (E1E2)E3 = E1(E2E3)
4. E1(E2 | E3) = E1E2 | E1E3
5. (E1 | E2)E3 = E1E3 | E2E3
6. E = E, E = E, E = , E =
7. E* = (E | )*, * =
8. (E*)* = E*
9. (E1*E2*)* = (E1 | E2)*
Demonstraie Rezult din definiia anterioar i proprietile operaiilor cu mulimi.

Limbaje formale
13
n procesul de descriere a limbajelor, un rol important l joac noiunea de ambiguitate.
Dac D este o descriere a unui limbaj iar L(D) este limbajul descris de D, spunem c D
este ambigu dac exist mcar un cuvnt din L(D) care este descris n dou moduri
diferite.
De exemplu expresia regulat E = ab | (a | b)* este ambigu deoarece cuvntul ab n
L(E) are dou descrieri: una n expresia ab i alta n (a | b) *.
Formal, neambiguitatea unei expresii regulate este definit inductiv astfel:
Definiia 1.2.4 (Expresii regulate neambigue)
1. Expresiile regulate , , a i (E) sunt neambigue, pentru orice a din i oricare ar fi
expresia neambigu E peste ;
2. Pentru orice factor E peste , E* este neambigu dac E este neambigu i pentru
orice u L(E*) exist exact un numr n 0 i o secven unic (u1, u2,, un) de
cuvinte din L(E) astfel nct u1u2un = u (pentru cazul n = 0, u1u2un = ) ;
3. Pentru orice termen E1 i pentru orice factor E2 peste , E1E2 este neambigu dac:
a. L(E1E2) = , sau
b. E1 i E2 sunt neambigue i pentru orice u L(E1E2) exist o pereche unic
(u1,u2) n L(E1) L(E2) astfel nct u1u2 = u.
4. Pentru orice expresie E1 i orice termen E2, expresia E1 | E2 este neambigu dac E1
i E2 sunt neambigue i n plus L(E1) 3 L(E2) = .
1.2.2 Automate finite
Definiia 1.2.5 Un automat finit este sistemul A = (Q, , , q0, F) unde Q i sunt
mulimi finite, nevide, numite mulimea strilor respectiv alfabetul de intrare, q0 Q este
starea iniial, F Q este mulimea strilor finale iar este o funcie
: Q ({}) 2Q,
numit funcia de tranziie (unde prin 2Q am notat mulimea prilor lui Q ).
Observaia 1.2.1 Modelul definit mai sus este cel cunoscut n literatur i sub denumirea
de sistem tranziional (sau automat nedeterminist cu - tranziii). Prin particularizri ale lui
vom obine modelul de automat nedeterminist (fr - tranziii) i cel de automat determinist.
Un automat finit poate fi reprezentat prin tabela de tranziie (funcia ) sau prin graful de
tranziie. n reprezentarea grafului de tranziie facem convenia ca strile care nu sunt
finale s le reprezentm prin cercuri iar cele finale prin ptrate. De asemenea tranziiile sunt reprezentate prin arce neetichetate.
Exemplul 1.2.3 Fie Q = {0, 1, 2}, = {a, b, c}, F = {2}, q0 = 0, iar este dat astfel:
Tabela de tranziie:
b

Graful de tranziie:

0
1
2

{0}
{1}

{1}
{2}

{2}

c
2

Figura 1.3
Exemplul 1.2.4 Q = {0, 1, 2}, = {a, b}, F = {2}, q0 = 0, iar este:

14

Prefa
Tabela de tranziie:

Graful de tranziie:
a

0
1
2

a
{0, 1}
{1}
{2}

{2}

{0, 1}

a
a

1
b

2
a

Figura 1.4
Exemplul 1.2.5 Q = {0, 1, 2, 3}, = {a, b}, F = {0}, q0 = 0, iar este dat n figura
1.5.

Tabela de tranziie:

0
1
2
3

a
1
0
3
2

Graful de tranziie:

b
3
2
1
0

a
0
b

a
b

b
a

b
2

a
Figura 1.5
Exemplul 1.2.6 Q = {0, 1, 2,}, = {a, b}, F = {2}, q0 = 0, iar este:
Tabela de tranziie:
a
b

0
{0,1, 2} {1, 2}
{1, 2}
1

Graful de tranziie:
c
{2}
{2}
{2}

0
a

a, b
a, b, c
2
c

Figura 1.6
Definiia 1.2.6 Un automat A = (Q, , , q0, F) se numete:

1
b,c

Limbaje formale
15
1. nedeterminist (fr -tranziii), dac (q, ) = , q Q
2. determinist, dac (q, ) = , q Q i |(q, a)| 1, q Q, a
n literatura de specialitate, de obicei, un automat finit determinist are proprietatea c
|(q, a)| = 1, q Q, a . Condiia |(q, a)| 1, dat n definiia precedent,
permite ca un automat determinist s fie parial definit (eventual) dar nu este restrictiv
relativ la puterea de calcul: putem obine automatul total definit dac adugm la
automat o stare special (numit stare moart sau stare de blocare) i definim astfel:
(q, a) = , dac |(q,a)| < 1 i (, b) = , b .
Aadar, putem subnelege, atunci cnd este nevoie, c este ndeplinit condiia |(q,a)|
= 1 (de exemplu cnd un automat trebuie s ,,citeasc n ntregime orice cuvnt, chiar
dac nu-l accept).
Pentru definirea limbajului acceptat (recunoscut) de un automat finit, s extindem funcia de
tranziie la cuvinte. Vom nota mai nti, pentru q Q :
Cl(q) = {q |q Q, n graful automatului A exist un drum de la q la q
de lungime k 0 ale crui arce sunt etichetate cu }.
Facem precizarea c q Cl(q) pentru c q este legat de q printr-un drum de lungime 0.
Dac S Q, atunci notm:
Cl(S) = 4qSCl(q) iar (S, a) = 4qS (q, a).
Definiia 1.2.7 Dac A = (Q, , , q0, F) este un automat, atunci extensia lui la
cuvinte este funcia : Q * 2Q care se definete astfel:
1. (q, ) = Cl(q), q Q ;
2. (q, ua) = Cl(( (q, u), a)), q Q, u *, a .
Lema 1.2.2 Fie A = (Q, , , q0, F) un automat finit, o stare q Q i un cuvnt w
*, unde w = a1a2 an. Atunci q (q, w) dac i numai dac n graful automatului,
exist un drum de la q la q de lungime m n cu arcele etichetate respectiv y1, y2, ,ym
(n aceast ordine) astfel nct y1y2 ym = w.
Demonstraie.
1. Se demonstreaz prin inducie dup lungimea lui w.
2. Se demonstreaz prin inducie dup lungimea drumului de la q la q.

Observaia 1.2.2 Pentru automatele fr -tranziie (n care (q, ) = , q Q),


deoarece Cl(q) = {q} are loc (q, a) = (q, a), q Q, a . Din acest motiv,
pentru astfel de automate va fi notat de asemenea cu .
n cazul automatelor cu - tranziii vom pstra notaia pentru extensie pentru c, n
general, (q, ) (q, ) i, de asemenea, (q, a) (q,a), a . De pild, n Exemplul
1.2.4 avem:
(0, a) ={0, 1, 2} iar (0, a) = {0} i (0, ) = {0, 1, 2} iar (0, ) ={1}.
Lema 1.2.3 Fie A = (Q, , , q0, F) un automat finit i cuvintele u, v *. Atunci are
loc:
(q, uv) = ( (q, u), v).
Demonstraie. Inducie dup |v|.

Definiia 1.2.8 Limbajul acceptat (recunoscut) de automatul A = (Q, , , q0, F) este


mulimea notat L(A) dat de expresia:
L(A) = {w | w *, (q0, w) 3 F }.

16
Prefa
Aadar, un cuvnt w este recunoscut de un automat A dac, dup citirea n ntregime a
cuvntului w, automatul (pornind din starea iniial q0 ) poate s ajung ntr-o stare
final. n cazul automatelor finite deterministe, putem scrie:
L(A) = {w | w *, (q0, w) F}.
deoarece (q0, w) este o mulime format dintr-un singur element i identificm aceast
mulime cu elementul respectiv.
Teorema 1.2.1 Pentru orice expresie regulat E peste exist un automat finit (cu tranziii) A, astfel nct L(E) = L(A).
Demonstraie Procedm prin inducie asupra complexitii expresiei E.
Dac E {, , a} ( a ) atunci automatul corespunztor este respectiv:

Figura 1.7
Dac E = E1 | E2, E = E1E2 sau E = E1* atunci presupunem c exist automatele finite
A1 = (Q1, , q01, 1, {f1}), A2 = (Q2, , q02, 2, {f2}), cu Q1, Q2 disjuncte i 1(f1, a) =
2(f2, a) = , a (ipoteza inductiv care are loc pentru E = , sau a ), care
recunosc limbajele L(E1) respectiv L(E2). S considerm dou stri noi q0, f (ce nu sunt
din Q1 4 Q2). Atunci, automatul A = (Q, , , q0, F) se construiete astfel:
1. E = E1 | E2 : Q = Q1 4 Q2 {q0, f }, F = {f }, (q0, ) = {q01, q02},
(f1, ) = (f2, ) = {f }, (q, a) = i(q, a) q Qi, a , i = 1, 2.
2. E = E1E2 : Q = Q1 4 Q2, q0 = q01, F = {f2 }, (f1, ) = q02,
(q, a) = i(q, a) q Qi, a , i = 1,2.
3. E = E1* : Q = Q1 4 {q0, f }, (q0, ) = { q01, f }, (f1, ) = {q01, f },
(q, a) = 1(q, a) q Q1, a .
Schematic, acest lucru se exprim ca n figura 1.8.
Se dovedete uor c automatele construite mai sus recunosc respectiv limbajele L(E1 4
E2 ), L(E1E2), L(E1*).

Clasa limbajelor recunoscute de automatele finite coincide cu clasa limbajelor descrise de


expresii regulate i aceast clas este cunoscut sub numele de clasa limbajelor regulate
(sau de tip 3). Aceast clas conine numai limbaje neambigue: un automat finit
determinist este un mecanism neambiguu. De aici rezult c pentru orice expresie
regulat E exist o expresie regulat E neambigu, echivalent cu E. Teoretic vorbind,
trecerea de la o expresie ambigu E la echivalenta sa neambigu E se face astfel: se
construiete A, automatul finit determinist echivalent cu E (trecnd prin automatul cu
- tranziii i apoi prin cel nedeterminist fr - tranziii) pentru ca apoi s se determine
expresia regulat E care descrie limbajul L(A); aceasta este neambigu. Practic ns,
descrierile ambigue pot s fie mai concise i mai uor de neles dect echivalentele lor
neambigue (care cost i timp substanial pentru a fi obinute).

Limbaje formale

17
q01

f1
f

q0
f2

q02

q01

q0

f1

q02

q01

f2

qf

Figura 1.8
Exemplul 1.2.7 Limbajul L = {w {0,1}*|w conine 000 sau 111} este descris de
expresia ambigu E = (0 | 1)*(000 | 111)(0 | 1)*. Expresia E neambigu, echivalent cu
E, n afar de faptul c se obine greoi, nu mai exprim n mod evident forma cuvintelor
din L(E). Cititorul se poate convinge de acest lucru printr-un exerciiu.
1.2.3 Gramatici independente de context
Definiia 1.2.9 O gramatic independent de context este sistemul G = (V, T, S, P) unde V i
T sunt mulimi nevide, finite, disjuncte de neterminali (variabile), respectiv terminali, = V
T se numete vocabularul gramaticii, S V este o variabil special numit simbol de
start sau axioma gramaticii, iar P V (V 4 T)* este mulimea regulilor de producie (derivare)
sau simplu mulimea produciilor.
Convenii de notaie. n scopul simplificrii expunerii, vom face cteva convenii care
vor fi pstrate de-a lungul acestei lucrri, cu excepia situaiilor n care se fac precizri
suplimentare.
1. Urmtoarele simboluri noteaz neterminali:
S, folosit pentru a nota axioma gramaticii;
A, B, C , (literele majuscule de la nceputul alfabetului);
numele italice scrise cu minuscule: expresie, instruciune,...
2. Urmtoarele simboluri noteaz terminali:
literele mici de la nceputul alfabetului: a, b, c,...
operatori: +, -, *, /, etc.
simboluri de punctuaie, paranteze.
cifrele: 0, 1, , 9
numele scrise cu fontul courier (unitile lexicale): id, if, while,
begin, etc..
3. Literele mari de la sfritul alfabetului, X, Y, Z,... noteaz simboluri gramaticale
(neterminali sau terminali, adic elementele din );
4. Literele mici de la sfritul alfabetului, u, v, x, y, z, w noteaz cuvinte din T*
(formate din terminali);

18

Prefa
5. Literele greceti , , , reprezint cuvinte peste . Aadar, o producie a
gramaticii se va nota prin A (n loc de (A, ) P, convenim s scriem A
).
6. Dac A 1, A 2,, A n sunt toate produciile care au A n partea
stng (numite A-producii), le vom nota prin:
A 1 | 2 |,, |n
7. O gramatic va fi dat prin produciile sale. De aici se subneleg mulimile V i
T, iar simbolul de start este partea stng a primei producii.
Exemplul 1.2.8 Gramatica:
E EOE |(E) |-E | id
O+ | - | * | /
este constituit din elementele V = {E, O }, T = { id, +, -, *, /, (,) }, E este
simbolul de start, iar produciile sunt cele indicate mai sus.
Definiia 1.2.10 Fie G = (V, T, S, P) o gramatic independent de context. Relaia de
derivare n gramatica G este o relaie din *V**, notat G, i este definit astfel:
1 G 2 dac i numai dac 1 = A, 2= i A .
Vom nota prin *G (+G) nchiderea reflexiv i tranzitiv (nchiderea tranzitiv) a
relaiei G. Atunci cnd nu exist confuzii, vom renuna la a indica G n aceste relaii
(scriem simplu , *, + ). Dac spunem c deriveaz ntr-un pas ; dac
*G ( +G ) spunem c deriveaz ( deriveaz propriu ):
*G dac i numai dac exist 0, 1,, n, n 0 astfel ca:
= 0 1 n =
+G dac i numai dac exist 0, 1,, n, n 1 astfel ca:
= 0 1 n =
Dac S *G spunem c este form propoziional n gramatica G, iar dac este format
din terminali, = w, spunem c w este o fraz n gramatica G.
Definiia 1.2.11 Limbajul generat de gramatica G (notat L(G)) este mulimea frazelor
gramaticii G, L(G)= {w| w T*, S + w }.
Pentru c n aceast lucrare ne vom ocupa numai de gramatici independente de context,
le vom numi pe acestea simplu gramatici. Pentru studiul ierarhiei lui Chomsky, cititorul
este invitat a consulta lucrrile [Gri86], [Juc99]. De asemenea, vom considera c
gramaticile cu care lucrm n continuare sunt gramatici reduse, adic orice simbol X
este simbol util:
este accesibil: S + X, pentru anumii , *;
este productiv: X * w, pentru un anume w T*.
Este cunoscut faptul c pentru orice gramatic G exist o gramatic G', cu L(G) = L(G')
(G i G' se zic echivalente n acest caz), astfel nct orice simbol al vocabularului
gramaticii G' este util (vezi [Gri86], [Juc99], [JuA02]). O gramatic fr simboluri inutile
(simboluri care nu sunt utile) se numete gramatic redus. n continuare vom considera
numai gramatici reduse.
1.2.4 Arbori sintactici. Ambiguitate
Un arbore sintactic ilustreaz ntr-o gramatic modul n care axioma genereaz o (se rescrie
ntr-o) fraz a gramaticii. Ideea care st la baza atarii unui arbore unei derivri este
aceea c, dac ntr-o derivare se folosete o producie de forma A X1X2 Xn, atunci
n arbore exist un nod interior etichetat A care are n descendeni etichetai respectiv cu
X1, X2, , Xn de la stnga la dreapta.

Limbaje formale
19
Definiia 1.2.12 Fie G = (V, T, S, P) o gramatic. Un arbore sintactic (arbore de derivare,
arbore de parsare) n gramatica G este un arbore ordonat, etichetat, cu urmtoarele
proprieti:
1. rdcina arborelui este etichetat cu S ;
2. fiecare frunz este etichetat cu un simbol din T sau cu ;
3. fiecare nod interior este etichetat cu un neterminal;
4. dac A eticheteaz un nod interior care are n succesori etichetai de la stnga la
dreapta respectiv cu X1, X2, , Xn, atunci A X1X2 Xn este o producie.
Cazul n care producia este A este un caz special: nodul etichetat A are un
singur descendent etichetat .
Frontiera unui arbore de derivare este cuvntul w = a1a2an unde ai, 1 i n sunt
etichetele nodurilor frunz n ordinea de la stnga la dreapta.
Exemplul 1.2.10 S considerm gramatica care descrie expresiile aritmetice ce se
construiesc cu +, *, id i paranteze:
E E+E | E*E | (E) | id.
Un arbore de derivare n aceast gramatic este prezentat n figura 1.9.
S observm c acestui arbore de derivare i putem ataa derivrile:
E E+E id+E id+E*E id+id*E id+id*id
E E+E E+E*E E+E*id E+id*id id+id*id
E E+E E+E*E E+id*E id+id*E id+id*id
Este clar c cele trei derivri (mai pot fi scrise i altele) sunt distincte. Ele difer prin
ordinea de aplicare a regulilor. n prima derivare, la fiecare pas s-a rescris cea mai din
stnga variabil a formei propoziionale (curente); astfel de derivri se numesc derivri
extrem stngi. Vom nota o derivare extrem stnga prin :
st

uA u dac u T*, A P, *
st

Aadar, prima din derivrile de mai sus se scrie: E id+id*id.


st

id

id
Figura 1.9

id

20
Prefa
Cea de-a doua derivare are proprietatea c, la fiecare pas, s-a rescris cea mai din dreapta
variabil din forma propoziional. Vom numi astfel de derivri derivri extrem drepte i le
vom nota prin :
dr

Au u dac u T*, A P, *
dr

Prin urmare cea de-a doua derivare se scrie: E id+id*id.


dr

Aadar, arborele sintactic este o reprezentare grafic a unei derivri n care a disprut
alegerea ordinii n care se aplic regulile de producie. Unui arbore sintactic i
corespunde o singur derivare extrem stng (respectiv o singur derivare extrem
dreapt). Se demonstreaz relativ uor urmtoarea lem (vezi [Gri86] i[Juc99]):
Lema 1.2.4. Fie G o gramatic i w T*. Atunci, exist o derivare S + w (sau
echivalent w L(G)) dac i numai dac exist un arbore de derivare cu frontiera w.
Mai mult, dac A V, * are loc: A + dac i numai dac exist un arbore
(construit dup aceleai reguli ca arborele de derivare) n care rdcina este etichetat cu
A, iar frontiera este .

S observm acum c, pentru cuvntul w= id+id*id (gramatica din exemplul precedent),


se mai poate construi un arbore de derivare distinct de cel de mai sus, anume cel din
figura 1.10. Acestui arbore i corespunde derivarea extrem stng:
S E*E E+E*E id+E*E id+id*E id+id*id
care difer de derivarea extrem stng precedent. Cele dou derivri extrem stngi
(respectiv cei doi arbori de derivare) corespund aceluiai cuvnt din L(G). Fenomenul
este cunoscut sub numele de ambiguitate.
Definiia 1.2.13 O gramatic G=(V, T, S, P) se zice c este ambigu dac exist w
L(G) pentru care se pot construi mcar dou derivri extrem stngi distincte (respectiv
doi arbori de derivare distinci).
E

id

id

id
Figura 1.10

Exemplul 1.2.10 Gramatica urmtoare ce descrie construciile if-then i if-thenelse este ambigu:

Limbaje formale

21

I if e then I | if e then I else I | a

n adevr, pentru fraza:


w = if e then if e then a else a
putem construi dou derivri extrem stngi distincte:
I if e then I

if
if
if
I if e then I else I

e then if e then I else I


e then if e then a else I
e then if e then a else a
if e then if e then I else I
if e then if e then a else I
if e then if e then a else a

care corespund, evident, la arbori de derivare distinci.


Este de dorit ca, atunci cnd abordm analiza sintactic, s o facem pentru o specificare
neambigu. Uneori, o gramatic ambigu se poate transforma ntr-o gramatic
echivalent neambigu. Aceast transformare este legat de anumite reguli care se
impun. De pild, n gramatica ambigu:
E E+E | E*E | (E) | id
nu este specificat nici o ordine de prioritate ntre operatorii + i *. Dac impunem ca *
s aib prioritate asupra lui +, atunci o nou gramatic se poate scrie pentru descrierea
expresiilor aritmetice:
E E+T | T
T T*F | F
F (E) | id
n aceast nou gramatic, noii neterminali au semnificaia natural: T noteaz ceea ce se
cheam termen, F este notaia pentru factor, nct putem spune c de data aceasta
gramatica exprim mai fidel ceea ce se numete expresie. Dac ncercm s impunem
reguli similare pentru cazul if-then-else, putem ajunge la o exprimare de forma:
I I1 | I2
I1 if e then I1 else I1 | a
I2 if e then I | if e then I1 else I2
Prin aceasta s-a exprimat regula: se asociaz else acelui then precedent, cel mai
apropiat care nu are corespondent.
n capitolele urmtoare vom construi o serie de algoritmi. Pentru descrierea acestora
vom folosi un limbaj(algoritmic) bazat pe limbajul C: vom utiliza operatori specifici
limbajului C ca ++(incrementare), ==(egalitate logic), !=(neegalitate logic), !(negaie)
blocurile vor fi specificate prin acolade, instruciunile while, for, if care vor fi folosite
funcioneaza ca n C.

Analiza lexical

23

2 Analiza lexical
Rolul pe care l are un analizor lexical este de a citi textul surs, caracter cu caracter, i a-l
transforma ntr-o secven de uniti primitive (elementare) numite uniti lexicale
(tokens n limba englez). Fiecare unitate lexical descrie o secven de caractere cu o
anumit semnificaie i este tratat ca o entitate logic. Pentru fiecare limbaj de
programare se stabilesc (atunci cnd se proiecteaz limbajul) unitile lexicale. n
majoritatea limbajelor se disting urmtoarele uniti lexicale:
Nr.
Crt.
1
2
3
4
5

Unitatea
Lexical
Constanta
Identificator
Operator
Cuvnt rezervat
Semn special

Exemple
573 -5.89 2e+3
alpha un_ident
+ * / < >
begin while
; . :

n acest capitol vom discuta tehnicile de analiz lexical, proiectarea i implementarea


unui analizor lexical. Problema analizei lexicale prezint cel puin dou aspecte. Mai
nti, trebuie gsit o modalitate de descriere a unitilor lexicale. Se constat c
expresiile regulate modelul algebric de descriere a limbajelor regulate - sunt
mecanismele care pot descrie orice unitate lexical. Un al doilea aspect este cel al
recunoaterii acestor uniti lexicale (analiza lexical propriu-zis). Dac descrierea se
face cu expresii regulate atunci mecanismul de recunoatere este automatul finit
determinist. Se tie din teoria limbajelor formale c mulimea limbajelor descrise de
expresii regulate coincide cu cea a limbajelor recunoscute de automate finite (clasa
limbajelor regulate). Vom descrie n acest capitol algoritmii (eficieni) de construcie a
unui automat finit (determinist) echivalent cu o expresie regulat i, apoi, implementarea
unui automat finit (analizorul lexical).
Unitile lexicale sunt de dou categorii:
uniti care descriu un ir de caractere anume (de exemplu if, while, ++,
:=, ; );
uniti care descriu o clas de iruri: (identificatori, constante etc.).
n cel din urm caz, vom trata o unitate lexical ca fiind o pereche format din tipul i
valoarea unitii lexicale. Pentru unitile lexicale care descriu un ir anume, convenim c
tipul este acel ir, iar valoarea coincide cu tipul. De exemplu caracterul ( este de tip
parantez stng iar alpha este unitatea lexical de tip identificator care are valoarea
alpha. n literatura de specialitate alpha se mai numete instan a tokenului
identificator sau lexem.
Vom descrie unitile lexicale prin expresii regulate. De pild, o constant ntreag fr
semn este descris de expresia:
(1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)(0 | 1 | 2 |3 | 4 | 5 | 6 | 7 | 8 | 9)*
Pentru comoditatea notaiilor, se pot da nume unor expresii regulate iar cu ajutorul
acestora pot fi definite alte expresii regulate. Fie un alfabet, d1, d2, , dn nume
distincte date unor expresii regulate iar E1, E2,, En expresii regulate astfel nct E1 este
expresie regulat peste iar Ei (1 < i n) este expresie regulat peste alfabetul {d1,
d2,, di-1},. Atunci o definiie regulat este un ir de definiii de forma:

24

Analiza lexical

d1 E1
d2 E2

dn En
De exemplu, mulimea identificatorilor PASCAL poate fi dat prin definiia regulat
urmtoare (din comoditate s-a scris dar trebuiesc nelese caracterele
corespunztoare):
<liter> A | B | | Z | a | b | | z
<cifr> 0 | 1 | | 9
<identificator> <liter> (<liter> | <cifr>)*
Constantele fr semn n PASCAL se definesc astfel:

0 | 1 | | 9
<cifr>

<cifr><cifr>*
<cifre>

.(<cifre> | )
<fracie>
(E | e)(+ | - | )<cifre>
<exponent>

<cifre> | <cifre> <fracie><exponent>


<numr>
Operatorul unar * semnific repetarea de zero sau mai multe ori a unei instane. Dac
folosim operatorul unar + pentru a desemna operaia ,,cel puin o instan din, adic
E+ = EE*, atunci definiia numelui <cifre> se poate scrie:
<cifre> <cifr>+

2.1 Un algoritm de trecere de la o expresie regulat la


automatul echivalent
S transpunem construciile din teorema 1.2.1 ntr-un algoritm pentru transformarea
unei expresii regulate n automat finit. Mai nti s observm c fiecare din apariiile
operatorilor | i * dintr-o expresie regulat E introduce dou noi stri n automatul
construit, pe cnd operatorul * nu introduce alte stri (figura 1.8). De asemenea, pentru
orice apariie a unui simbol din , ct i pentru , dac acesta apare explicit n E, este
nevoie de 2 stri n automatul construit. Aadar, dac n este numrul de simboluri din E
iar m este numrul de paranteze mpreun cu apariiile simbolului *, atunci numrul
strilor automatului echivalent cu E este 2(n m).
S mai observm c, din orice stare a automatului, se fac cel mult dou tranziii: fie o
tranziie cu un simbol din , fie una sau dou - tranziii, fie zero tranziii. Atunci,
reprezentarea automatului echivalent cu o expresie regulat se poate face cu un tablou
de dimensiune p3 unde p este numrul strilor (acestea sunt numerotate de la 1 la p),
prima coloan conine simbolurile cu care se fac tranziiile iar urmtoarele dou coloane
conin strile rezultate n urma tranziiilor. Pentru descrierea algoritmului o s
identificm cele trei coloane prin vectorii simbol, next1, next2.
Algoritmul pe care-l descriem mai jos este datorat lui Rytter (Ryt91).
Algoritmul 2.1.1
Intrare: Expresia regulat E cu n simboluri dintre care m sunt paranteze i apariii ale
operatorului produs;
Ieire: Vectorii simbol, next1, next2 de dimensiune p = 2(n-m) ce descriu automatul cu
- tranziii echivalent cu E;
Metoda:
1. Se construiete arborele ataat expresie E (o metod se d mai jos);

Un algoritm de trecere de la o expresie regulat la automatul echivalent


25
2. Se parcurge arborele n preordine i se ataeaz nodurilor vizitate, exceptnd pe cele
etichetate cu *, respectiv numerele 1, 2, , n-m;
3. Se parcurge arborele n postordine i se ataeaz fiecrui nod N o pereche de
numere (i, f) care reprezint starea iniial respectiv final a automatului
corespunztor subarborelui cu rdcina N, astfel:
3.1. Dac nodul are numrul k (de la pasul 2) atunci N.i = 2k-1, N.f = 2k;
3.2. Dac nodul este etichetat * atunci N.i = S.i iar N.f = D.f (S i D sunt fii lui N,
stng respectiv drept);
4. for(1<= j <=2(n - m)) {simbol[j] = , next1[j] = next2[j] = 0}
5. Se parcurge din nou arborele obinut. Dac N este nodul curent iar S i D sunt fii
si, atunci, n funcie de eticheta lui N, se execut urmtoarele:
5.1. Dac N este etichetat cu |:
next1[N.i] = S.i, next2[N.i] = D.i, next1[S.f] = N.f, next1[D.f] = N.f
5.2. Dac N este etichetat cu * atunci next1[S.f] = D.i
5.3. Dac N este etichetat cu * (D nu exist n acest caz):
next1[N.i] = S.i, next2[N.i] = N.f, next1[S.f] = S.i, next2[S.f] = N.f
5.4. Dac N este etichetat cu a (deci este frunz):
simbol[N.i] = a, next1[N.i] = N.f
Construcia arborelui:
Intrare:
Expresia regulat E = e0e1en-1
Precedena operatorilor: prec(|) = 1, prec(*) = 2, prec(*) = 3.
Ieire:
Arborele asociat t.
Metoda:
Se consider dou stive: STIVA1 stiva operatorilor, STIVA2 stiva
operanzilor (care va conine arborii pariali construii).
1. i = 0;
2. while(i < n){
3.
c = ei;
4.
switch(c){
5.
case ( : {STIVA1.push(c); break;}
6.
case operand : {STIVA2.push(c); break;}
7.
case ) : {
8.
do{build_tree();}while(STIVA1.top()!= ));
9.
STIVA1.pop(); break;
}//endcase
10.
case operator: {
11.
while(prec(STIVA1.top())>=prec(c))build_tree();
12.
STIVA1.push(c); break
}//endcase
}//endswitch
}//endwhile
13.
while(STIVA1!= ) build_tree();
14.
t = STIVA2.pop();
build_tree(){
op = STIVA1.pop();
D = STIVA2.pop();
switch(op){
case *: {
t = tree(op, D, NULL);
STIVA2.push(t); break;
}
case |: case*:{
S = STIVA2.pop();
t = tree(op, S, D);
STIVA2.push(t); break;
}
}

26
Analiza lexical
Exemplul 2.1.1 S considerm expresia E = a*b | bb(a | c)*.
1. Arborele expresiei este prezentat n figura 2.1.
2. Nodurilor arborelui le sunt ataate respectiv numerele 1, 2, , 10 prin parcurgerea
n preordine i exceptnd nodurile produs.
3. Numrul strilor automatului va fi p = 20. Dup parcurgera n postordine a
arborelui, fiecare nod are ataat o pereche (i, j) (figura 2.2).
4. Se iniializeaz vectorii simbol, next1 i next2.
5. Tabela de tranziie a automatului, n urma aplicrii procedurilor descrise la 5 n
fiecare nod al arborelui, este dat n continuare.
|
1
*
*

*
4

b
9

10

Figura 2.1

*
(3,4)
(5,6)

+ 1
(1,2)

(3,8)
4

b
* (9,12)
(7,8)
6 b
5
b
(9,10)
(11,12) 9
a
(17,18)

(9,14)
7

(13,14)
8
(15,16)

+
10

(19,20)

Figura 2.2
Tabela de tranziie a automatului:
p
1
2
3
4
5
6
7
8
9

simbol[p]

a
b
b

next1[p]
3
0
5
7
6
5
8
2
10

next2[p]
9
0
4
0
0
4
0
0
0

Un algoritm de trecere de la o expresie regulat la automatul echivalent


10
11
0
11
b
12
0
12
13
0
13
15
14
14
2
0
15
17
19
16
15
14
17
a
18
0
18
16
0
19
c
20
0
20
16
0

27

Teorema 2.1.1 Algoritmul 2.1.1 este corect: automatul cu - tranziii obinut este
echivalent cu expresia regulat E.
Demonstraie S observm c modul n care au fost alese perechile (i, f) de stri pentru
fiecare nod al arborelui construit corespunde construciilor din teorema 1.2.1. De
asemenea tranziiile care se definesc n pasul 5 al algoritmului urmresc construcia din
teorema amintit. Aadar, automatul obinut este echivalent cu expresia dat la intrare.

2.2 De la un automat cu -tranziii la automatul


determinist echivalent
n paragraful precedent am descris procedeul de trecere de la o expresie regulat la
automatul finit echivalent. Programul care descrie activitatea acestui automat finit pentru
un cuvnt de intrare este de fapt analizorul lexical. Pentru ca acest analizor s fie eficient
este de dorit ca automatul obinut s fie determinist. Cum prin procedeul descris n
teorema precedent se obine un automat cu - tranziii (nedeterminist deci), s indicm
o modalitate de trecere de la un automat cu - tranziii la automatul determinist
echivalent. Mai nti s descriem un algoritm pentru calculul mulimii Cl(S) = (S, ).
Algoritmul 2.2.1
Intrare:
Automatul (cu - tranziii) A = (Q, , , q0, F), S Q.
Ieire:
Cl(S) = (S, ).
Metoda:
Stiva STIVA se iniializeaz cu S i pentru fiecare q din top, strile din
(q, ) ce nu au fost puse n Cl(S) se adaug n Cl(S) i n stiv.
1. R = S; STIVA = S;
2. while (STIVA ) {
3.
q = STIVA.pop(); // Se extrage q din stiv
4.
T = (q, ) ;
5.
if(T ) {
6.
for ( p T ) {
7.
if ( p R) {
8.
R = R {p} ;
9.
STIVA.push(p);//Se adaug p in stiva
}//endif
}//endfor
}//endif
}//endwhile
10.
Cl(S) = R;

28
Analiza lexical
Algoritmul 2.2.2 Transformarea unui automat cu - tranziii n automat finit
determinist.
Intrare:
Automatul (cu - tranziii) A = (Q, , , q0, F).
Procedura de calcul pentru Cl(S) (Algoritmul 2.2.1).
Ieire:
Automatul determinist A = (Q, , , q0, F), echivalent cu A.
Metoda:
Se construiesc strile lui A ncepnd cu q0 i continund cu cele
accesibile prin tranziii cu simboluri din alfabet.
1. q0 = Cl(q0); Q = {q0} ;
2. marcat(q0) = false; F = ;
3. if ( q0 3 F ) then F = F 4 {q0} ;
4. while (q Q && !marcat(q)){//q este nemarcat
5.
for(a ){
6.
p = Cl(( q, a)); // = (q, a)
7.
if ( p ) {
8.
if ( p Q) {
9.
Q = Q 4 {p};
10.
marcat(p) = false;
11.
(q,a) = p ;
12.
if(p 3 F != )then F = F 4 {p};
}//endif
}//endif
}//endfor
13.
marcat(q) = true;
}//endwhile

2.3 Proiectarea unui analizor lexical


Definiia 2.4.1 Fie un alfabet (al unui limbaj de programare). O descriere lexical peste
este o expresie regulat E = (E1 | E2 || En)+, unde n este numrul unitilor lexicale,
iar Ei descrie o unitate lexical, 1 i n.
Exemplul 2.4.1 S considerm = {a, b, , y, z, 0, 1,,9, :, =} i urmtoarele expresii
regulate:
Litera a | b | c | | z
Cifra 0 | 1 | | 9
Id Litera ( Litera | Cifra)*
Intreg Cifr+
Asignare :=
Egal =
Douapuncte :
O descriere lexical care cuprinde aceste uniti lexicale este:
E = ( Id | Intreg | Asignare | Egal | Douapuncte)+
Definiia 2.4.2 Fie E o descriere lexical peste ce conine n uniti lexicale i w +.
Cuvntul w este corect relativ la descrierea E dac w L(E). O interpretare a cuvntului w
L(E) este o secven de perechi (u1, k1), (u2, k2), , (um, km), unde w = u1u2um, ui
L(Eki) 1 i m, 1 ki n.
Uneori, n loc de ki n (ui, ki), punem chiar Eki, asta nsemnnd faptul c ui este o unitate
de tipul Eki.

Generatorul Lex
29
Exemplul 2.4.2 Fie E din Exemplul 2.4.1 i w = alpha:=beta=542. O interpretare a
cuvntului w este:
(alpha, Id), (:=, Asignare), (beta, Id), (=, Egal), (542, Intreg)
Sigur c aceasta nu este singura interpretare. Urmtoarea secven este de asemenea o
interpretare a aceluiai cuvnt: (alpha, Id), (:, Douapuncte), (=, Egal), (b, Id}), (eta,
Id), (=, Egal), (5, Intreg), (42, Intreg).
Faptul c am obinut interpretri diferite pentru acelai cuvnt este consecin a
ambiguitii expresiei regulate E: unitatea lexical Asignare are dou descrieri distincte n
E : Asignare i produsul DouapuncteEgal. La fel se ntmpl cu Id i cu Intreg. Din
teorie se tie c, pentru orice expresie regulat E, exist o expresie regulat E
neambigu, echivalent cu E. Soluia de a construi E pentru a obine interpretri unice
pentru fiecare cuvnt nu este convenabil pentru c, pe de o parte, transformarea E n
E consum timp iar, pe de alt parte, E nu descrie n mod natural limbajul n cauz. n
toate cazurile, se prefer s se dea nite reguli n plus pentru a putea alege o singur
interpretare a unui cuvnt atunci cnd descrierea lexical este ambigu.
Definiia 2.4.3 Fie E o descriere lexical peste i w L(E). O interpretare a
cuvntului w, (u1, k1)(u2, k2), (um, km), este interpretare drept - orientat dac (i) 1 i m,
are loc:
|ui| = max{|v| /v L(E1 | E2 || En) Pref(uiui+1um)}.
(unde prin Pref(w) am notat mulimea prefixelor cuvntului w ).
Observaia 2.4.1 Exist descrieri lexicale E n care nu orice cuvnt din L(E) admite o
interpretare drept-orientat. Fie descrierea E = (a | ab | bc)+ peste alfabetul = {a, b}
i w = abc. Singura interpretare a cuvntului w este (a, 1), (bc, 3) dar aceasta nu este
drept orientat pentru c a nu este cel mai lung cuvnt din L(a | ab | bc) 3 Pref(abc),
acesta din urm fiind ab.
Definiia 2.4.4 O descriere lexical E este bine - format dac orice cuvnt w din limbajul
L(E) are exact o interpretare drept-orientat.
Definiia 2.4.5 Fie E = (E1 | E2 | | En)+ o descriere lexical bine format peste .
Un analizor lexical (scanner) pentru E este un program ce recunoate limbajul L(E) i
produce, pentru fiecare w L(E), interpretarea sa drept-orientat.
Dat o descriere lexical E peste alfabetul , proiectarea unui analizor lexical cuprinde
urmtoarele proceduri:
1. Se construiete automatul finit (cu - tranziii) A, astfel ca L(E) = L(A)
(utiliznd Algoritmul 2.1.1).
2. Se aplic Algoritmul 2.2.1 i se obine automatul determinist echivalent cu E, fie
acesta A.
3. (Opional) Se aplic Algoritmul 2.3.1 pentru a obine automatul minimal
echivalent cu A.
4. Se scrie un program care implementeaz evoluia automatului obinut.
Ne vom ocupa n continuare de modificrile ce trebuiesc aduse construciilor 1-3 pentru
a putea obine o interpretare orientat dreapta pentru un cuvnt w din L(E). Distingerea
ntre clasele de uniti lexicale E1, E2,, En, poate fi fcut adugnd fiecrei expresii Ei
o marc de sfrit #i, 1 i n. Noua descriere lexical, notat E# este:
E# = (E1#1 | E2#2 || En#n)+, peste alfabetul {#1,,#n }.
Automatul determinist ce recunoate L(E#) va avea, desigur, tranziii pentru simbolurile
#i. Pentru c aceste simboluri nu apar n irul de intrare (se analizeaz cuvintele w +
), vom trata aceste tranziii ntr-un mod special (n programul scris la etapa 4): cnd
apare o tranziie de tipul #i, programul raporteaz o unitate lexical din clasa Ei.
Exemplul 2.4.3 S considerm descrierea lexical:

30

Analiza lexical
cifra 0 | 1 || 9
litera a | b ||z
identificator litera (litera | cifra)*
semn + | numar (semn | ) cifra+
operator + | - | * | / | < | > | <= | >= | < >
asignare :=
doua_puncte :
cuvinte_rezervate if | then |else
paranteze ) | (
Ai

An

Ao

q0

Aa

Ad

Ac

Ap

Figura 2.5
Dup cele discutate n paragraful precedent, exist automatele (cu - tranziii) Ai, An, Ao,
Aa, Ad, Ac, Ap care recunosc respectiv limbajele descrise de expresiile regulate din E.
Atunci, un automat cu - tranziii ce recunoate E se construiete dup schema din
figura 2.5.
Pentru acest automat cu - tranziii, exist un automat determinist echivalent care
recunoate L(E). Acest automat determinist poate fi nc simplificat prin gsirea
automatului cu cele mai puine stri care recunoate acelai limbaj. n sfrit, se scrie un
program - analizorul lexical - care simuleaz acest automat. Automatul determinist
(minimal) echivalent cu cel descris n fig. 2.5 i cu tranziiile corespunztoare
simbolurilor #i, #n, #o, #a, #d, #c, #p, care desemneaz sfritul unei uniti lexicale de
tip identificator, numr, operator, asignare, doua_puncte, cuvinte_rezervate, paranteze, este dat n
figura 2.6.

Generatorul Lex

31
litera, cifra
litera

#i sau #c

1
cifra

cifra

#n

cifra
+, -

operator-{+,-}

#o

#o

#a
#d

), (

#p

Figura 2.6
Aadar, 0 este stare iniial i final, tranziiile notate cu liter, cifr nseamn c sunt
tranziii pentru orice simbol din clasa liter respectiv cifr. Tranziia cu un delimitator #
semnific faptul c s-a identificat o unitate lexical; aceasta se raporteaz i se reia
activitatea automatului din starea iniial.
Programul (analizorul lexical) const din cte un segment de program pentru fiecare
stare a automatului. Dac vom nota prin buffer zona de memorie unde se ncarc o
unitate lexical, getnext(ch), store(ch) funciile ce citesc respectiv memoreaz ch
n buffer, atunci pentru starea q care are tranziii respectiv n strile qi pentru
simbolurile ai, 1 i m i tranziia cu #h n q0, segmentul de program este dat mai jos.
q : switch(ch){
case: a1
store(ch);
getnext(ch);
goto q1;
case: a2
store(ch);
getnext(ch);
goto q2;
.
.

case: am
store(ch);
getnext(ch);
goto qm;
default:
write(buffer, i);
empty(buffer);
goto q0;
}

Aadar, n cazul n care caracterul citit este ai, se memoreaz acesta, se citete alt caracter
i se continu programul pentru starea qi, 1 i m, iar dac nu este nici unul dintre
simbolurile a1, a2, , am, s-a depistat unitatea lexical de tip i, se raporteaz aceasta (

32

Analiza lexical
write(buffer,i) ), se golete zona buffer pentru a primi o nou unitate lexical
dup care se trece la programul strii iniiale.
Dac din starea q nu exist # - tranziie, grupul default se nlocuiete prin:
default:
write(buffer,"eroare");
empty(buffer);
goto q0;

Pentru starea iniial q0 (care este n acelai timp i stare final, grupul default se
nlocuiete prin:
default:
if(EOF){
write(Sfarsitul analizei);
exit(0);
}else{
write(buffer,"eroare");
empty(buffer);
getnext(ch);
goto qo;
}

2.4 Generatorul Lex


LEX este un instrument software deosebit de util celor care doresc s implementeze
limbaje de programare, dar poate fi folosit i n multe alte aplicaii. Acesta a fost
dezvoltat la Bell Laboratories n anul 1975 de ctre M.E. Lesk i E. Schmidt. LEX
citete un fiier de intrare (n general cu extensia .l) care conine expresii regulate ce
definesc unitile lexicale i genereaz un program C care realizeaz analiza lexical a
unui text, n conformitate cu specificaiile date.
LEX a devenit instrument standard UNIX ncepnd cu versiunea a 7-a a acestui sistem
de operare. Proiectul GNU al Fundaiei Free Software distribuie FLEX (Fast LEXical
Analyzer Generator) care este o mbuntire a generatorului LEX. De asemenea exist
versiuni pentru sistemele de operare DOS i WINDOWS; una dintre acestea este
PCLEX lansat de Abraxax Software Inc..
Pentru a utiliza LEX, se parcurg trei pai:
1. Scrierea specificaiilor LEX ntr-un fiier cu extensia .l care reprezint
descrierea lexical pentru care dorim s construim un analizor lexical.
2. Executarea programului LEX (sau PCLEX) cu intrarea fiierul construit:
lex [optiuni] <nume_fiier>
flex [optiuni] <nume_fiier>
pclex [optiuni] <nume_fiier>
Pentru a afla descrierea opiunilor ce se pot folosi, executai programul lex h.
Ca rezultat al execuiei cu argumentul <nume_fiier> se obine un fiier cu acelai
nume dar cu extensia .c, care este de fapt un program n limbaj C.
3. Se compileaz acest program mpreun, eventual, cu alte fiiere surs.
Programul C obinut n urma executrii LEX - ului este de fapt o funcie cu numele
yylex() care, pentru a fi executat, trebuie inclus ntr-o funcie main() (furnizat de
utilizator) care conine apelul yylex() sau, se integreaz aceast rutin cu altele (de
exemplu ntr-o aplicaie yacc).
O specificare LEX (fiierul de intrare) are structura:
Declaraii
%%

Reguli
%%

Rutine auxiliare

Generatorul Lex
33
Seciunea Declaraii precum i Rutine auxiliare pot lipsi nct o specificare minim trebuie
s conin cele dou linii %% ntre care sunt scrise liniile ce conin reguli de specificare a
unitilor lexicale.
Seciunea Declaraii conine dou pri. Prima parte const din declaraii C pentru
variabilele sau constantele care se vor folosi ulterior iar a doua parte conine definiii ce
se folosesc n specificaiile LEX n seciunea Reguli. O definiie (macro pentru expresii
regulate) are forma:
<nume> <expresie>
unde <expresie> este o expresie (regulat) ce poate folosi orice caracter mpreun cu
urmtorii operatori:
" \ [ ] ^ - ? . * + | () $ / {} % <>

Aceti operatori au o anume semnificaie iar dac se dorete a-i folosi drept caractere
trebuiesc precedai de \ (backslash). Semnificaia operatorilor este:
"
Caracterele text sunt incluse ntre ghilimele "". Construcia "c" este
echivalent cu \c.
[ ]
Definete o clas de caractere sub forma unei liste alternative. n interior se
poate folosi caracterul pentru a indica un domeniu. De exemplu, o cifr ntreag se
poate defini prin [0123456789] sau prin [0-9]. Expresiile urmtoare: [-a+], [+a-],
[a\-+] sunt echivalente i desemneaz unul din caracterele a, + sau . Dac se
folosete n [] atunci el trebuie pus primul, ultimul sau sub forma \.
^
Acest simbol este considerat operator dac este primul ntr-o anume descriere,
altfel este caracter text. Se folosete pentru a exclude din definiie anumite caractere. De
exemplu expresia [^ \t\n] desemneaz un caracter diferit de spaiu, tab sau linie nou.
?
Elementul precedent acestui operator este opional. Expresia ab?c desemneaz
abc sau ac iar a[b-c]?d noteaz abd, acd sau ad.
*
Operatorul iteraie de la expresii regulate.
+
Operatorul iteraie proprie (repetare de 1 sau mai multe ori) de la expresii
regulate.
|
Operatorul alternativ (reuniune) de la expresii regulate.
()
Operatorul () (grupare) de la expresii regulate.
{}
n macro-definiii acest operator se poate folosi pentru a specifica numrul de
repetiii ale unei expresii. De pild AAA i A{3} sunt echivalente, iar dac scriem [az]{1,5} aceasta desemneaz una pn la 5 litere mici ale alfabetului.
.
Acest caracter (punct) desemneaz orice caracter n afar de \n. De exemplu,
descrierea a.b reprezint a0b,a1b,a\b,axb etc. ntr-o descriere lexical ce urmeaz a
fi inclus ntr-un fiier intrare pentru lex, alternativa default se descrie printr-o linie ce
are punctul n prima coloan, asta nsemnnd orice alt caracter ce nu a fost inclus pn
la aceast linie n vreo definiie sau descriere.
Urmtoarele dou linii n seciunea declaraii:
cifra [0-9]
litera [a-zA-Z]

sunt definiii ale expresiilor regulate cifra, litera care pot fi folosite pentru
definirea altor expresii n seciunea Reguli. Numele definite aici trebuiesc folosite ntre
acolade: {litera}, {cifra}.
Tot n aceast seciune pot fi scrise segmente de cod C care urmeaz a fi incluse n
procedura yylex (de pild linii pentru preprocesor de tipul #include sau #define).
Aceste elemente trebuiesc incorporate ntr-un bloc de forma %{%}. De exemplu:
%{
#include <stdio.h>
%}

Seciunea Reguli definete funcionalitatea analizatorului lexical. Fiecare regul este


compus dintr-o expresie regulat i o aciune. Aadar, aceast seciune are forma:

34

Analiza lexical
m1
m2
.
.
.
mn

{Aciune1}
{Aciune2}

{Aciunen}

unde mi sunt expresii regulate iar Aciunei este un segment de program C care se va
executa atunci cnd analizorul ntlnete un exemplar din unitatea lexical descris de mi
(1 i n). Pentru scrierea expresiilor regulate se folosesc operatorii pe care i-am descris
mai sus, numele unor expresii deja definite, cuprinse ntre acolade precum i orice alte
caractere text. Spre exemplu, expresia {litera}({litera}|{cifra})* desemneaz
un identificator.
Seciunea a treia, Rutine auxiliare, conine toate procedurile auxiliare care ar putea fi utile
n procesul de analiz lexical. Dac analizorul lexical creat este independent, aceast
seciune trebuie s conin o funcie main() care face apel la funcia yylex().
Exemplul 2.5.1 Iat coninutul fiierului de intrare pentru descrierea lexical din
paragraful precedent:
%{
/* scaner1.l
// Exemplu de fisier de intrare pentru flex */
# include <stdio.h>
%}
litera [a-zA-Z]
cifra [0-9]
cifre ({cifra})+
semn [+-]
operator [+*/<>=-]
spatiu [' \t\n]
%%
"if" | "then" | "else"
{
printf("%s cuvant rezervat\n", yytext);}
({litera})({litera}|{cifra})* {
printf("%s identificator\n", yytext);}
{cifre}|({semn})({cifre})
{
printf("%s numar intreg\n", yytext);}
{operator} {printf("%c operator\n", yytext[0]);}
\:\=
{printf("%s asignare\n", yytext);}
\:
{printf("%c doua puncte\n", yytext[0]);}
(\()|(\))
{printf("%c paranteza\n", yytext[0]);}
{spatiu}
{}
.
{printf("%c caracter ilegal\n", yytext[0]);}
%%
int main( ){
yylex( );
return 0;
}

Exemplul 2.5.2 Urmtorul fiier constituie o descriere lexical pentru un limbaj ce


conine cuvintele rezervate set, sqrt, quit, identificatori, numere i operatori.
%{
/* scaner2.l
// Scaner expresii */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
%}
%%

Problema recunoaterii. Problema parsrii

35

quit {printf(" Cuvant cheie: %s\n", yytext);}


set
{printf(" Cuvant cheie: %s\n", yytext);}
sqrt {printf(" Cuvant cheie: %s\n", yytext);}
\+
{printf(" Operator: %c\n", yytext[0]);}
\{printf(" Operator: %c\n", yytext[0]);}
\*
{printf(" Operator: %c\n", yytext[0]);}
\/
{printf(" Operator: %c\n", yytext[0]);}
\(
{printf(" Paranteza stanga: %c\n", yytext[0]);}
\)
{printf(" Paranteza dreapta: %c\n", yytext[0]);}
\:\= {printf(" Asignare: %s\n", yytext);}
(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))((e|E)(\-|\+)?[0-9]+)? {
printf(" Numar: %f\n", atof(yytext));}
[_A-Za-z]+
{printf(" Identificator: %s\n", yytext);}
[ \t]+
; /* Eliminare spatii */
\n
{return 0;}
.
{printf(" Caracter ilegal: %c\n", yytext[0]);}
%%
int main(){
printf("******************************************\n");
printf("**
Exemplu de scaner
**\n");
printf("**
Cuvinte cheie: set sqrt quit
**\n");
printf("**
Operator: + - * / ( )
**\n");
printf("** Operanzi: numere, identificatori
**\n");
printf("******************************************\n");
yylex();
return 0;
}

3 Analiza sintactic descendent


La proiectarea limbajelor de programare se stabilesc reguli precise care descriu structura
sintactic a programelor. n limbajul Pascal spre exemplu, un program este format din
blocuri, un bloc conine instruciuni, o instruciune are n componena sa expresii, iar o
expresie este o niruire de o anumit form de uniti lexicale.
Sintaxa unui limbaj de programare poate fi descris cu ajutorul gramaticilor
independente de context sau a notaiei BNF (Backus - Naur Form). Descrierea sintaxei
prin gramatici independente de context are avantaje att din punct de vedere al
proiectrii, ct i din cel al implementrii limbajelor. Aceste avantaje pot fi sintetizate
astfel:
o gramatic independent de context ofer o specificare sintactic precis i n
acelai timp uor de neles;
pentru anumite clase de gramatici este posibil construirea automat a unui analizor
sintactic eficace. Analizorul sintactic are rolul de a determina dac un program surs
este corect din punct de vedere sintactic. Mai mult, n procesul de construcie a
analizorului sintactic pot fi depistate ambiguiti sintactice sau alte construcii
dificile, care pot rmne nedetectate n timpul proiectrii unui limbaj;
descrierea sintaxei printr-o gramatic independent de context impune limbajului o
structur care se dovedete a fi util pentru traducerea programului pus n cod
obiect corect, precum i pentru detectarea erorilor.

36

Analiza lexical

3.1 Rolul analizorului sintactic


Un analizor sintactic primete la intrare un ir de uniti lexicale i produce arborele de
derivare al acestui ir n gramatica ce descrie structura sintactic (mai degrab o anume
reprezentare a acestui arbore). n practic, odat cu verificarea corectitudinii sintactice,
se produc i alte aciuni n timpul analizei sintactice: trecerea n tabela de simboluri a
unor informaii legate de unitile sintactice, controlul tipurilor sau producerea codului
intermediar i alte chestiuni legate de analiza semantic (unele din acestea le vom discuta
n capitolul ce trateaz semantica). S ne referim n cteva cuvinte la natura erorilor
sintactice i la strategiile generale de tratare a acestora.
Dac un compilator ar trebui s trateze doar programele corecte, atunci proiectarea i
implementarea sa s-ar simplifica considerabil. Cum ns programatorii scriu adesea
programe incorecte, un compilator bun trebuie s asiste programatorul n identificarea i
localizarea erorilor. Cea mai mare parte a specificrilor limbajelor de programare nu
descriu modul n care implementarea (compilatorul) trebuie s reacioneze la erori; acest
lucru este lsat pe seama celor care concep compilatoarele. Erorile ntr-un program pot
fi:
lexicale: scrierea eronat a unui identificator, a unui cuvnt cheie, a unui operator
etc. ;
sintactice: omiterea unor paranteze ntr-o expresie aritmetic, scrierea incorect a
unei instruciuni, etc. ;
semantice: aplicarea unui operator aritmetic unor operanzi logici, nepotrivirea
tipului la atribuiri etc. ;
logice: un apel recursiv infinit.

3.2 Problema recunoaterii. Problema parsrii


Problema recunoaterii n gramatici independente de context este urmtoarea: Dat o
gramatic G = (V, T, S, P) i un cuvnt w T*, care este rspunsul la ntrebarea w
L(G)? Se tie c problema este decidabil; mai mult, exist algoritmi care n timp
O(|w|3) dau rspunsul la ntrebare (Cooke-Younger-Kasami, Earley, vezi [Gri86]).
Problema parsrii (analizei sintactice) este problema recunoaterii la care se adaug: dac
rspunsul la ntrebarea w L(G) este afirmativ, se cere arborele sintactic (o reprezentare
a sa) pentru w.
p

st

dr

Fie G = (V, T, S, P) o gramatic i w L(G). S notm prin ( ) relaia de derivare


extrem stng (dreapt) n care, pentru rescriere s-a aplicat producia p P (q P).
Atunci, pentru w L(G) exist o derivare extrem stng:
p1

p2

p3

pn

st

st

st

st

q1

q2

q3

qm

dr

dr

dr

dr

S = 0 1 2 n = w
i o derivare extrem dreapt:
S = 0 1 2 m = w
Atunci, arborele sintactic corespunztor se poate reprezenta prin secvena de producii
p1p2pn P+ (respectiv q1q2qm P+ ) care s-au aplicat, n aceast ordine, n derivarea
extrem stng (dreapt) pentru obinerea lui w.

Definiia 3.2.1 Secvena de producii = p1p2pn P+ pentru care S w se


st

numete analizare stng (parsare stng) pentru cuvntul w L(G).

Problema recunoaterii. Problema parsrii

37
~

Secvena de producii = q1q2qm P+ pentru care S w se numete analizare dreapt


dr
(parsare dreapt) pentru cuvntul w (unde ~ = q q q ).

Exemplul 3.2.4 Fie gramatica


1. E E+T 2. E T
5. F (E)
4. T F
unde
1,
2,
,
6
identific

m m-1

3. T T*F,
6. F id
cele
6
producii

ale

acesteia.

E
E

id

id

F
id

Figura 3.1
Pentru cuvntul w = id+id*id, arborele sintactic este dat n figura 3.1.
Analizarea stng pentru acest cuvnt este: 1 = 12463466 iar analizarea dreapt este 2 =
64264631. Aici am identificat fiecare producie prin numrul su.

3.3 Analiza sintactic descendent


Analiza sintactic descendent (parsarea descendent) poate fi considerat ca o tentativ de
determinare a unei derivri extrem stngi pentru un cuvnt de intrare. n termenii
arborilor sintactici, acest lucru nseamn tentativa de construire a unui arbore sintactic
pentru cuvntul de intrare, pornind de la rdcin i construind nodurile n manier
descendent, n preordine (construirea rdacinii, a subarborelui stng apoi a celui drept).
Pentru realizarea acestui fapt avem nevoie de urmtoarea structur (figura 3.2):
o band de intrare n care se introduce cuvntul de analizat, care se parcurge de la
stnga la dreapta, simbol cu simbol;
o memorie de tip stiv (pushdown) n care se obin formele propoziionale stngi
(ncepnd cu S). Prefixul formei propoziionale format din terminali se compar
cu simbolurile curente din banda de intrare obinndu-se astfel criteriul de
naintare n aceast band;
o band de ieire n care se nregistreaz pe rnd produciile care s-au aplicat n
derivarea extrem stng care se construiete.

38

Analiza lexical
Intrare
a1

ai

an

Stiva
X1

TABELA
DE
PARSARE

CONTROL

X2
.
.

p1

p2

Ieire

Figura 3.2
Criteriul de oprire cu succes este acela n care s-a parcurs ntreaga band de intrare, iar
memoria pushdown s-a golit. n acest caz n banda de ieire s-a obinut parsarea stng a
cuvntului respectiv. Iat un exemplu de cum funcioneaz acest mecanism (considerm
gramatica din exemplul precedent i cuvntul w = id+id*id). Vom considera caracterul
# pentru marcarea sfritului benzii de intrare (marca de sfrit a cuvntului) precum i
pentru a marca baza stivei.
Tabelul 1: Analiza sintactic descendent pentru w = id+id*id
Pasul
1
2
3
4
5
6
7
8
9
10
11
12

Banda de intrare
id+id*id#
id+id*id#
id+id*id#
id+id*id#
id+id*id#
id*id#
id*id#
id*id#
id*id#
id#
id#
#

Stiva de lucru

Banda de ieire

E#
E+T#
T+T#
F+T#
id+T#
T#
T*F#
F*F#
id*F#
F#
id#
#

1
12
124
1246
1246
12463
124634
1246346
1246346
12463466
12463466

Se obine la ieire = 12463466.


3.3.1 Parser descendent general
Fie G = (V, T, S, P) o gramatic care descrie sintaxa unui limbaj de programare. S
considerm mulimea C = T*# *# P* numit mulimea configuraiilor (unui parser).
Interpretarea pe care o dm unei configuraii este urmtoarea: dac c = (u#, #, ),
atunci u# este cuvntul rmas de analizat (coninutul benzii de intrare la un moment
dat), # este coninutul stivei, cu primul simbol din n top-ul stivei (= X1X2...Xn ca n
figura 3.2), iar # la baza acesteia, iar este coninutul benzii de ieire: dac = p1p2
pk, pn n acest moment s-au aplicat produciile din n aceast ordine.

Parser descendent general


39
Definiia 3.3.1 Parserul (analizorul sintactic) descendent ataat gramaticii G = (V, T, S, P)
este perechea (C0, d ) unde C0 = {(w#, S#, ) | w T* } C} este mulimea
configuraiilor iniiale, iar d C C este relaia de tranziie ntre configuraii dat de
urmtoarea schem:
1. (u#, A#, ) d (u#, #, r), unde r = A P.
2. (uv#, u#, ) d (v#, #, ).
3. ( #, #, ) este configuraie de acceptare dac .
4. O configuraie c pentru care nu exist c astfel ca c d c (n sensul 1, 2) spunem
c produce eroare.
S considerm relaia d+ (d* ) nchiderea tranzitiv (i reflexiv) a relaiei d definit mai
sus. Este clar c, dac vom considera parserul ca un sistem de rescriere, acesta este
nedeterminist aa cum este definit tranziia de tip 1: dac n P exist produciile A 1
i A 2 cu 1 2, atunci au loc, n acelai timp: (u#, A#, ) d (u#, 1#, ), (u#,
A#, r1) d (u#, 2#, r2) unde r1 = A 1, r2 = A 2. Pentru a fi corect, parserul
definit mai sus va trebui s transforme (n mod nedeterminist, n general) configuraia
(w#, S#, ) n configuraia (#, #, ) pentru cuvintele w L(G) i numai pentru acestea!
Vom demonstra n continuare corectitudinea n acest sens.
Lema 3.3.1 Dac n parserul descendent ataat gramaticii G = (V, T, S, P) are loc

calculul (uv#, #, ) d* (v#, )#, ), atunci n gramatica G are loc derivarea u),
st

oricare ar fi u, v T*, , ) *, P*.


Demonstraie. S demonstrm afirmaia din lem prin inducie asupra lungimii lui
(notat ||):
Dac || = 0 nseamn c n calculul considerat s-au folosit doar tranziii de tip

2. Atunci = u) i are loc u), cu = ;


st

Dac || > 0 fie = 1r, unde r = A este ultima producie care se


folosete n calculul considerat i presupunem afirmaia adevrat pentru calcule
ce produc la ieire 1. Aadar, putem scrie:
(uv#, #, ) = (u1u2v#, #, ) d* (u2v#, A1#, 1) d (u2v#, 1#, 1r)=
= (u2v#, u2)#, 1r) d* (v#, )#, 1r)

Atunci din (u1u2v#, #, ) d* (u2v#, A1#, 1), conform ipotezei inductive, are loc
st

u1A1. Cum n u1A1, u1 T*, iar A V este cea mai din stnga variabil, aplicnd
acesteia producia A , obinem:

st

st

u1A1 u11 = u1u2) = u) i demonstraia este ncheiat.

Corolarul 3.3.1 Dac n parserul descendent are loc (w#, S#, ) d (#, #, ) atunci n
+

gramatica G are loc S w.


st

Demonstraie Se aplic lema precedent pentru u = w, v = , = S, ) = .

Lema 3.3.2 Dac n gramatica G are loc derivarea u) i 1:) V {} (vezi


st

definiia 3.3.2), atunci n parserul descendent are loc calculul:


(uv#, #, ) d* (v#, )#, ), v T*.

40
Analiza lexical
Demonstraie Procedm de asemenea prin inducie dup lungimea lui :
Dac || = 0, atunci = u) i calculul are loc doar cu tranziii de tipul al doilea:
(uv#, #, ) = (uv#, u)#, ) d* (v#, #, ).
Dac || > 0 fie = 1r unde r = A este ultima producie aplicat n derivarea
1

st

st

considerat: u1)1 = u1A)2 u1)2 = u1u2) = u).


Am folosit aici efectiv ipoteza referitoare la primul simbol din ) : primul simbol din )1
este o variabil ( i nu ) pentru c se aplic efectiv un pas cu producia r. Cum |1| <
1

||, din u1)1, conform ipotezei inductive are loc (u1v1#, #, ) d* (v1#, )1#, 1)
st

v1 T*. Considernd v1 = u2v cu v T* oarecare, putem scrie:


d* (u2v#, )1#, 1)
(u1v1#, #, ) = (u1u2v#, #, )
d (u2v#, )2#, 1r)
= (u2v#, A)2#, 1)
d* (v#, )#, )
= (u2v#, u2)#, )

=
=

Corolarul 3.3.2 Dac n G are loc derivarea S w atunci n parserul descendent are
st

loc calculul (w#, S#, ) d* ( #, #, ).


Demonstraie n lema 3.3.2 se consider u = w, v = , = S i ) = .

Teorema 3.3.1 (Corectitudinea parserului descendent general). Se consider gramatica redus G


= (V, T, S, P) i w T*. Atunci, n parserul descendent general ataat acestei gramatici
are loc (w#, S#, ) d* (#, #, ) (acceptare) dac i numai dac w L(G) i este o
analizare sintactic stng a frazei w.
Demonstraie Teorema sintetizeaz rezultatele din lemele 3.3.1 i 3.3.2.

3.3.2 Gramatici LL(k)


Parserul general introdus n paragraful precedent este un model nedeterminist iar
implementarea sa pentru gramatici oarecare este ineficient. Cea mai cunoscut clas de
gramatici pentru care acest model funcioneaz determinist este aceea a gramaticilor
LL(k): Parsing from Left to right using Leftmost derivation and k symbols lookahead. Intuitiv, o
gramatic este LL(k) dac tranziia de tip 1 din Definiia 3.3.1 se face cu o unic regul A
determinat prin inspectarea a k simboluri care urmeaz a fi analizate n banda de
intrare. Dar s definim n mod riguros aceast clas de gramatici.
Defininiia 3.3.2 Fie G = (V, T, S, P) o gramatic, * i k 1 un numr natural.
Atunci definim:
k: = if || k then else 1, unde = 1, |1| = k,
:k = if || k then else 2, unde = 2, |2| = k.
Defininiia 3.3.3 O gramatic independent de context redus este gramatic LL(k), k
1, dac pentru orice dou derivri de forma:
*

S uA u1 ux
st

st

st
*

S uA u2 uy
st

st

st

unde u, x, y T*, pentru care k:x = k:y, are loc 1 = 2.

O caracterizare a gramaticilor LL(1)


41
Definiia prezentat exprim faptul c, dac n procesul analizei cuvntului w=ux a fost
obinut(analizat) deja prefixul u i trebuie s aplicm o producie neterminalului A
(avnd de ales pentru acesta mcar din dou variante), aceast producie este
determinat n mod unic de urmtoarele k simboluri din cuvntul care a rmas de
analizat (cuvntul x).

3.3.3 O caracterizare a gramaticilor LL(1)


Pentru ca un parser SLL(k) s poat fi implementat, trebuie s indicm o procedur
pentru calculul mulimilor FIRSTk i FOLLOWk. Pentru c n practic se folosete
destul de rar (dac nu chiar deloc) cazul k m 2, vom restrnge discuia pentru cazul k =
1. Vom nota n acest caz FIRST1 i FOLLOW1 prin FIRST respectiv FOLLOW.
Aadar, dac +, A V :
*

FIRST() = {a|a T, au } 4 if ( ) then { } else f.


st

st

FOLLOW(A) = {a|a T 4 { }, S uA, a FIRST () }


st

Mai nti s artm c gramaticile SLL(1) coincid cu gramaticile LL(1).


Teorema 3.3.10 O gramatic G = (V, T, S, P) este gramatic LL(1) dac i numai dac
pentru orice A V i pentru orice producii A 1 | 2 are loc:
FIRST (1FOLLOW (A)) 3 FIRST (2FOLLOW (A)) = f
Demonstraie S presupunem c G este LL(1). Acest lucru este echivalent cu(dup
Definiia 3.3.3):
*

st

st

st

st

st

st

S uA u1 uv1
S uA u2 uv2

1 = 2

1:v1 = 1:v2
S presupunem c G nu este SLL(1): exist produciile A 1, A 2 i
a FIRST (1FOLLOW (A)) 3 FIRST (2FOLLOW (A))
Distingem urmtoarele cazuri:

st

st

a FIRST (1) 3 FIRST (2), adic 1 au1, 2 au2

Atunci, putem scrie derivrile ( G este redus):


*

S uA u1 uau1v
st

st

st
*

S uA u2 uau2v
st

st

st

ceea ce contrazice faptul c G este LL(1).

a FIRST (1) 3 FOLLOW (A) i 2


st

Atunci: 1 au1 i S uA, a FIRST () adic au2, i putem scrie:


st

st

st

S uA u1 uau1v
st

st

st

st

st

S uA u2 u uau2
st

st

ceea ce contrazice de asemenea ipoteza.

42

Analiza lexical

a FIRST (2) 3 FOLLOW (A) i 1 , caz ce se trateaz analog cu


st

precedentul.

st

st

1 , 2 i atunci a FOLLOW(A).

n acest caz avem:


*

st

st

st

st

S uA u1 u uau2
st

st

S uA u2 u uau2
st

st

Deci din nou se contrazice proprietatea LL(1) pentru gramatica G.


Invers, s presupunem c pentru orice dou producii A 1, A 2 distincte are loc
FIRST (1FOLLOW (A)) 3 FIRST (2FOLLOW (A)) = f i, prin reducere la absurd,
G nu este LL(1). Asta nseamn c exist dou derivri:
*

S uA u1 uav i
st

st

st

S uA u2 uav
st

st

st

cu 1:v = 1:v dar 1 g 2.


Analiznd cele dou derivri, se constat uor c:
a FIRST (1FOLLOW (A)) 3 FIRST (2FOLLOW (A))
ceea ce contrazice ipoteza.

3.3.4 Determinarea mulimilor FIRST i FOLLOW


Vom indica n acest paragraf modalitatea de determinare a mulimilor FIRST i
FOLLOW pentru o gramatic G.
Un algoritm pentru determinarea mulimilor FIRST(X) este descris mai jos. Cititorul
este invitat s dovedeasc, pornind de la definiia lui FIRST, c acest algoritm este
corect.
Algoritmul 3.3.2
Intrare:
Gramatica G = (V, T, S, P) redus;
Ieire:
FIRST (X), X .
Metoda:
Mulimile FIRST sunt completate prin inspectarea regulilor
gramaticii
1. for (X )
2.
if (X T) FIRST(X) = { X } else FIRST(X) = ;
3. for (A a P)
4.
FIRST (A) = FIRST(A) 4 {a };
5. FLAG = true;
6. while(FLAG) { // FLAG marcheaza schimbarile in FIRST
7.
FLAG = false;
8.
for(A X1X2Xn P) {
9.
i = 1;
10.
if((FIRST(X1) FIRST(A)){
11.
FIRST(A) = FIRST(A)4(FIRST(X1);
12.
FLAG = true;
}//endif
13.
14.

while(i < n && Xi

)
st

if((FIRST(Xi+1) FIRST(A)){

Tabela de analiz sintactic LL(1)

43

FIRST(A) = FIRST(A) 4 FIRST(Xi+1);


FLAG = true; i++;
}//endif
}//endwhile
}//endfor
}//endwhile
17.
for (A V)

15.
16.

18.

if(A

) FIRST(A) = FIRST(A) 4 { } ;
st

Pentru a verifica dac o gramatic este LL(1), conform teoremei de caracterizare, este
necesar s calculm FIRST(), unde *. Vom descrie n algoritmul urmtor acest
lucru.
Algoritmul 3.3.3
Intrare:
Gramatica G=(V, T, S, P).
Mulimile FIRST(X), X .
= X1X2 Xn, Xi , 1 i n.
Ieire:
FIRST( ).
Metoda:
1. FIRST() = FIRST(X1) - { }; i =1;
+

2. while(i < n && Xi


3.
4.

) {
st

FIRST() = FIRST() 4 (FIRST(Xi+1) - { }) ;


i = i+1 ;
}//endwhile
+

5. if(i == n && Xn
6.

)
st

FIRST() = FIRST( ) 4 { } ;

Corectitudinea algoritmului descris rezult din observaia c, dac pentru cuvntul =


+

X1X2 Xn are loc X1X2 Xi , i < n ( adic X1, X2, , Xi se pot terge), atunci
st

i+1

U FIRST(X ) - { }) FIRST( ).
k=1

Dac n plus , atunci FIRST( ) conine i .


st

Exemplul 3.3.1 Fie gramatica ce are urmtoarele reguli:


E E+T |E-T |T, T T*F | T/F | F, F (E) | a.
Aplicnd algoritmul pentru calculul mulimilor FIRST, obinem:
FIRST(E) = FIRST(T) = FIRST(F) = {(, a },
FIRST(T*F) = FIRST(T) = {(, a }.
Exemplul 3.3.2 Fie gramatica:
E
SE|B
C | ; SC
B a | begin SC end
FIRST(E) = { }
FIRST(S) = {a, begin, }
FIRST(C) = {;, }.
FIRST(B) = {a, begin }
Aplicnd Algoritmul 3.3.3 se gsete: FIRST(SEC) = {a, begin, ;, }, FIRST(SB)=
{a, begin }, FIRST(;SC)= {;}.

44
Analiza lexical
S trecem acum la determinarea mulimilor FOLLOW(A), A V. Reamintind c
*

FOLLOW(A) = {a T 4{ } | S A, a FIRST( )}, s facem cteva observaii


st

care vor fi utile n determinarea lui FOLLOW:

FOLLOW(S) pentru c S S.
st

Dac A BX P i , atunci FIRST(X)-{} FOLLOW (B). n adevr,


st

st

st

putem scrie S 1A1 1BX1 1BX1, i atunci rezult FIRST(X)-

{ } FOLLOW (B). Acest aspect, de fapt, se poate exprima mai simplu prin:
Dac A B P atunci FIRST()-{ } FOLLOW (B).

Dac A B P i , atunci FOLLOW(A) FOLLOW(B). n adevr,

st

dac a FOLLOW(A) atunci exist derivarea S 1A i a este din FIRST( ).


st

Din cele stipulate n ipotez putem continua n aceast derivare:


*

st

st

S 1A 1B 1B
i deci a FOLLOW(B).
innd cont de aceste observaii rezult urmtorul algoritm:
Algoritmul 3.3.4 (Determinarea mulimilor FOLLOW)
Intrare:
Gramatica G = (V, T, S, P) redus ; Procedura FIRST(), +.
Ieire:
Mulimile FOLLOW (A), A V.
Metoda:
1. for (A ) FOLLOW(A) = ;
2. FOLLOW(S)= { } ;
3. for (A X1X2Xn) {
4.
i = 1;
5.
while (i < n){
6.
while (Xi V) ++i;
7.
if (i < n) {// Xi este neterminal
8.
FOLLOW(Xi) = FOLLOW(Xi)4(FIRST(Xi+1Xi+2Xn)-{});
9.
++i;
}//endif
}//endwhile
}//endfor
10.FLAG = true;
11.while (FLAG) { // FLAG semnaleaza schimbarile in FOLLOW
12. FLAG = false;
13. for (A X1X2Xn) {
14.
i = n;
15.
while (i 1 && Xi c V){
16.
if (FOLLOW (A) FOLLOW(Xi) ){
17.
FOLLOW(Xi) = FOLLOW(Xi) 4 FOLLOW (A);
18.
FLAG = true;
}//endif
+

19.
20.

if(Xi

)--i;// la Xi1 daca Xi se sterge


st

else continue; // la urmatoarea productie


}//endwhile
}//endfor
}//endwhile

Tabela de analiz sintactic LL(1)


3.3.5 Tabela de analiz sintactic LL(1)

45

Pentru a implementa un analizor sintactic pentru gramatici LL(1), s considerm o tabel


de analiz, sau tabel de parsare LL(1):
M : V (T 4{ # }) {(, p) |p = A P }4 {eroare}
construit dup algoritmul urmtor:
Algoritmul 3.3.6 (Tabela de analiz sintactic)
Intrare:
Gramatica G = (V, T, S, P);
Mulimile FIRST(), FOLLOW(A), A P.
Ieire:
Tabela de parsare M.
Metoda:
Se parcurg regulile gramaticii i se pun n tabel
1.
for(A V )
2.
for( a T 4 { # })
3.
M(A, a) = ;
4.
for (p = A P) {
5.
for(a FIRST()-{})
6.
M(A, a) = M(A, a) 4 {(, p)} ;
7.
if( FIRST(B)){
8.
for(b FOLLOW(A)){
9.
if(b == )M(A, #) = M(A, #) 4 {(, p)};
10.
else M(A, b) = M(A, b) 4 {(, p)};
}//endfor
}//endif
}//endfor
11. for(A V)
12.
for( a T 4 { # })
13. if(M(A, a) = ) M(A, a) = {eroare} ;

Lema 3.3.6 (caracterizarea gramaticilor LL(1)) Gramatica redus G este LL(1) dac i
numai dac pentru orice A V i orice a T 4 { # } M(A, a), dac nu este eroare,
conine cel mult o pereche (, p).
Demonstraie Dac G este LL(1), atunci A V, oricare ar fi dou producii cu partea
stng A, A 1, A 2, are loc, conform teoremei 3.3.10, FIRST(1FOLLOW(A)) 3
FIRST(2FOLLOW(A))= . Dac ar exista n tabela construit A V i a V 4 { # }
astfel nct M(A, a) {(, p), (, q)}, atunci, dac a T rezult
a FIRST(FOLLOW(A)) 3 FIRST(FOLLOW(A))
ceea ce contrazice ipoteza. Dac a = #, atunci ar fi comun n FIRST(FOLLOW(A))
i FIRST(FOLLOW(A)), din nou absurd. Deci M ndeplinete condiia enunat. n
mod analog se dovedete implicaia invers.

3.3.6 Analizorul sintactic LL(1)


Parserul (analizorul sintactic) LL(1) este un parser top-down n care tranziiile sunt dictate
de tabela de parsare. Aadar, un astfel de parser are ca i configuraie iniial tripleta
(w#, S#, ) unde w este cuvntul de analizat, iar tranziiile se descriu astfel:
1. (u#, A#, ) d (u#, #, r) dac M(A, 1:u#) = (, r). (operaia expandare)
2. (uv#, u#, ) d (v#, #, ). (operaia potrivire)
3. (#, #, ) d acceptare dac .
4. (au#, b#, ) d eroare dac a b.
5. (u#, A#, ) d eroare dac M(A, 1:u#) = eroare.

46

Analiza lexical

Teorema de corectitudine a parserului descendent general mpreun cu teorema de


caracterizare LL(1), a modului n care s-a definit tabela M, dovedesc corectitudinea
parserului LL(1). Suntem acum n msur s indicm modul de implementare al unui
analizor sintactic LL(1). Presupunem c dispunem de o band de intrare (fiier de
intrare) din care, cu o funcie getnext(), se obine caracterul (tokenul) urmtor. De
asemenea dispunem de o stiv cu funciiile specifice pop() i push(). Produciile care
se aplic pe parcursul analizei le scriem ntr-o band (fiier) de ieire cu funcia write().
Atunci algoritmul de analiz sintactic se descrie astfel:
Algoritmul 3.3.6 (Parser LL(1) )
Intrare:
Gramatica G = (V, T, S, P).
Tabela de analiz LL(1) notat M.
Cuvntul de intrare w#.
Ieire:
Analiza sintactic stng a lui w dac w L(G),
eroare n caz contrar.
Metoda:
Sunt implementate tranziiile 1 5 folosind o stiv St.
1. St.push(#),St.push(S)// St = S#
2. a = getnext(), = ;
3. do {
4.
X = St.pop();
5.
if(X == a)
6.
if(X != #) getnext();
7.
else{
8.
if ( != ){write(acceptare); exit(0);}
9.
else {write(eroare); exit(1);}
} //endelse
10.
else{
11.
if(XT){write(eroare); exit(1);}
12.
else{
13.
if(M(X,a) == eroare)
14.
{write(eroare); exit(1);}
15.
else {
// M(X,a)=(,r), r=X , =Y1Y2Yn
// inlocueste pe X in stiva
16.
for(k = n; k>0; --k) push(Yk);
17.
write(r); //se adauga r la
} //endelse
} //endelse
} //endelse
} while(1);

Exemplul 3.3.4 S relum gramatica din exemplele precedente:


1.
2.
3.
4.

S
S
E
B

E
B

5. B begin SC end
6. C
7. C ;SC

Mulimile FIRST i FOLLOW sunt date n tabelul urmtor:


X
S
E
B
C

FIRST(X)

FOLLOW(X)

a begin

a begin

end ;
end ;
end ;
end

Analizorul sintactic LL(1)


Tabela de analiz LL(1)

pentru

aceast

gramatic

este

dat

mai

begin

end

S
E
B
C

(B, 2)
eroare
(a, 4)
eroare

(B, 2)
eroare
(begin SC end, 5)
eroare

(E, 1)
(, 3)
eroare
(, 6)

(E, 1)
(, 3)
eroare
(;SC, 7)

(E, 1)
(, 3)
eroare
eroare

47
jos:

Se vede din tabel c aceast gramatic este LL(1) (conform teoremei 3.3.13). Iat i
dou exemple de analiz, unul pentru cuvntul begin a;;a end care este din limbajul
generat de gramatica dat, iar altul pentru begin aa end care nu este corect.
PAS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

PAS
1
2
3
4
5
6
7

INTRARE
begin a;;a end#
begin a;;a end#
begin a;;a end#
a;;a end#
a;;a end#
a;;a end#
;;a end#
;;a end#
;a end#
;a end#
;a end#
;a end#
a end#
a end#
a end#
end#
end#
#

INTRARE
begin aa end#
begin aa end#
begin aa end#
aa end#
aa end#
aa end#
a end#

STIVA
S#
B#
begin SC end#
SC end#
BC end#
aC end#
C end#
;SC end#
SC end#
EC end#
C end#
;SC end#
SC end#
BC end#
aC end#
C end#
end#
#

STIVA
S#
B#
begin SC end#
SC end#
BC end#
aC end#
C end#

OPERAIE
expandare
expandare
potrivire
expandare
expandare
potrivire
expandare
potrivire
expandare
expandare
expandare
potrivire
expandare
expandare
potrivire
expandare
potrivire
acceptare

IEIRE

OPERAIE
expandare
expandare
potrivire
expandare
expandare
potrivire
eroare

IEIRE

2
5
2
4
7
1
3
7
2
4
6

2
5
2
4

3.3.7 Eliminarea recursiei stngi


O gramatic stng recursiv nu este LL(k) pentru nici un numr k. Este cazul, spre
exemplu, al gramaticii care genereaz expresiile aritmetice:
E E+T |E - T |-T |T
T T*F | T/ F | F
F (E) | a

48
Analiza lexical
Prin eliminarea recursiei stngi, exist posibilitatea obinerii unei gramatici din clasa LL.
Acesta este un procedeu utilizat frecvent n proiectarea analizoarelor sintactice. S
reamintim c o gramatic G este stng recursiv dac exist un neterminal A pentru care
+

A A. Spunem c un neterminal A este stng recursiv imediat dac exist o


producie A A n P. S indicm mai nti un procedeu de eliminare a recursiei stngi
imediate.
Lema 3.3.7 Fie G = (V, T, S, P) o gramatic pentru care A este stng recursiv imediat.
S considerm toate A produciile gramaticii, fie ele A A1 | A2 | |An cele
cu recursie i A 1 | 2 | |m cele fr recursie imediat (prile drepte nu ncep
cu A). Exist o gramatic G = (V, T, S, P) echivalent cu G, n care A nu este stng
recursiv imediat.
Demonstraie S considerm un nou neterminal A V i fie V = V 4{A} iar P se
obine din P astfel:
Se elimin din P toate A -produciile;
Se adaug la P urmtoarele producii:
1 i m.
A iA
A jA | 1 j n.
Notm cu P mulimea de producii obinut.
S observm c A nu mai este stng recursiv n G. Noul simbol neterminal introdus,
notat cu A, este de data aceasta drept recursiv. Acest lucru ns nu este un inconvenient
pentru analiza sintactic descendent.
S observm c, pentru orice derivare extrem stnga n gramatica G, de forma:
A Akn Akn-1kn Ak1k2kn hk1k2kn
exist n gramatica G o derivare extrem dreapta de forma:
A hA hk1A hk1k2kn A hk1k2kn.
De asemenea, afirmaia reciproc este valabil.
Aceste observaii conduc la concluzia c cele dou gramatici sunt echivalente.

Exemplul 3.3.5 n gramatica expresiilor aritmetice:


E E+T |E-T |-T |T
T T*F | T/F | F
F (E) | a
se fac transformrile urmtoare:
Produciile E E+T |E-T |-T |T se nlocuiesc cu:
E TE | -TE, E +T E|-TE |
Produciile T T*F | T/ F | F se nlocuiesc cu:
T FT, T *FT | /FT |
Se obine astfel gramatica echivalent:
E TE | -TE
E +T E|-TE |
T FT
T *FT | /FT |
F (E) | a
Aplicnd algoritmii pentru determinarea mulimilor FIRST i FOLLOW se obine:

Eliminarea recursiei stngi


X
E
E
T
T
F

49
FIRST(X)
( a + -
( a
* /
( a

FOLLOW(X)
)
)
+ - )
+ - )
*/+ - )

Tabela de parsare pentru aceast gramatic este dat mai jos.


M
E
E
T
T
F

a
(TE, 1)
eroare
(FT, 6)
eroare
(a, 11)

+
eroare
(+TE,3)
eroare
(, 9)
eroare

(-TE, 2)
(-TE, 4)
eroare
(, 9)
eroare

*
eroare
eroare
eroare
(*FT,7)
eroare

/
eroare
eroare
eroare
(/FT,8)
eroare

(
(TE, 1)
eroare
(FT, 6)
eroare
((E), 10)

)
eroare
(, 5)
eroare
(, 9)
eroare

#
eroare
(, 5)
eroare
(, 9)
eroare

Se observ de aici c gramatica este LL(1). S urmrim analiza sintactic pentru expresia
(a+a)*a.
PASUL
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.

INTRARE
-(a+a)*a#
-(a+a)*a#
(a+a)*a#
(a+a)*a#
(a+a)*a#
a+a)*a#
a+a)*a#
a+a)*a#
a+a)*a#
+a)*a#
+a)*a#
+a)*a#
a)*a#
a)*a#
a)*a#
)*a#
)*a#
)*a#
*a#
*a#
a#
a#
#
#
#

STIVA
E#
-TE#
TE#
FTE#
(E)TE#
E)TE#
TE)TE#
FTE)TE#
aTE)TE#
TE)TE#
E)TE#
+TE)TE#
TE)TE#
FTE)TE#
aTE)TE#
TE)TE#
E)TE#
)TE#
TE#
*FTE#
FTE#
aTE#
TE#
E#
#

OPERAIE

expandare
potrivire
expandare
expandare
potrivire
expandare
expandare
expandare
potrivire
expandare
expandare
potrivire
expandare
expandare
potrivire
expandare
expandare
potrivire
expandare
potrivire
expandare
potrivire
expandare
expandare
acceptare

IEIRE
4
6
10
1
6
11
9
3
6
11
9
5
7
11
9
5

50
Analiza lexical
S artm cum eliminm n general recursia stng ntr-o gramatic. Vom face ipoteza c
+

gramatica G nu are - producii i nu are cicluri de forma A A. Aceasta nu este de


fapt o restricie: este tiut faptul c, pentru orice gramatic G exist G fr -producii
i fr cicluri astfel nct L(G) = L(G) - { }. (vezi [Gri86], [Juc99]). Aadar are loc
urmtoarea teorem:
Teorema 3.3.13 Pentru orice gramatic G fr - producii i fr cicluri, exist o
gramatic echivalent G care nu este stng recursiv.
Demonstraie S considerm urmtorul algoritm:
Intrare:
Gramatica G = (V, T, S, P) fr -producii, fr cicluri.
Ieire:
Gramatica G fr recursie stng astfel ca L(G)=L(G).
Metoda:
Dup ordonarea neterminalilor se fac transformri ce nu schimb
limbajul gramaticii, inclusiv prin eliminarea recursiei imediate
1. Se consider V = {A1, A2, ,An}
2. for (i=1; i n; ++i) {
3.
for (j=1; j i - 1; ++j) {
4.
for (Ai Aj P) {
5.
P = P { Ai Aj };
6.
for (Aj P ) P = P 4 { Ai };
} //endfor
7.
Se elimina recursia imediata pentru Ai;
//endfor
} //endfor
8. V = V; P = P;// V s-a modificat la 7 iar P la 4 - 6

S observm c transformrile din 4 - 7 nu modific limbajul generat de gramatic nct


L(G) = L(G). S artm acum c G nu are recursie stng. Acest lucru rezult din
faptul c, dup iteraia i - 1 a buclei 2, fiecare producie de forma Ak Al pentru k < i
are proprietatea c l > k. Aceasta datorit transformrilor 4 - 6 i eliminrii recursiei
stngi imediate n linia 7: dup execuia buclei 3 se obin producii Ai Am cu m i,
iar dac m = i, producia Ai Ai se elimin n linia 7. Aadar, gramatica obinut este
fr recursie stng.

Exemplul 3.3.6 Fie gramatica:


S Aa | b A Ac | Sd | a
Considerm neterminalii n ordonai (S, A). Aplicnd bucla 2 pentru i = 1 nu se face nici
o modificare deoarece n producia S Aa ordinea neterminalilor este corect. Pentru i
= 2 trebuie modificat producia A Sd (pentru c S este mai mic dect A). A produciile se modific astfel:
A Ac |Aad |bd |a
Gramatica, n acest moment, este
S Aa | b
A Ac |Aad |bd |a
i conine doar recursivitate imediat ( A ). Aplicnd algoritmul de eliminare a recursiei
stngi imediate (vezi Lema 3.3.7), obinem:
A bdA |aA
A cA | adA |
S Aa | b
Se poate observa c rezultatul nu este o gramatic LL(1) deoarece pentru primele dou
producii FIRST(Aa) 3 FIRST(a) = {b}. Aadar eliminarea recursiei stngi nu conduce
neaprat la proprietatea LL(1).

O caracterizare a gramaticilor LR(0)

51

4 Analiza sintactic n gramatici LR


Vom prezenta n acest capitol o tehnic eficace de analiz sintactic ascendent care este
utilizat pentru o clas larg de gramatici: gramaticile LR(k). Denumirea LR(k) vine de
la: Left to right scanning of the input, constructing a Rightmost derivation in reverse, using k symbols
lookahead. Aceast metod este cu siguran cea mai des utilizat metod de analiz
sintactic, din urmtoarele motive:
se pot construi analizoare sintactice LR pentru recunoaterea tuturor
construciilor din limbajele de programare care se pot descrie printr-o gramatic
independent de context;
clasa limbajelor ce pot fi analizate sintactic cu analizoare LR(1) coincide cu clasa
limbajelor de tip 2 deterministe;
metoda de analiz LR este o metod de tip deplasare-reducere relativ uor de
implementat i eficace n acelai timp;
un analizor LR poate detecta o eroare de sintax cel mai rapid posibil
parcurgnd irul de intrare de la stnga la dreapta.
Dezavantajul principal al metodei este acela c determinarea tabelei de analiz necesit
un volum mare de munc; exist ns generatoare de analizoare de tip LR, precum yacc
sau bison, care produc un astfel de analizor.
Vom considera n continuare o gramatic G = (V, T, S, P) redus i gramatica
augmentat G = (V, T, S, P) unde P = P 4 {S S }, V = V 4 { S }, S fiind un
simbol nou. Gramatica G este echivalent cu G i are proprietatea c simbolul de start
nu apare n nici o parte dreapt a produciilor din P, condiie esenial pentru studiul
gramaticilor LR(k). S mai facem observaia c, pentru gramaticile care au proprietatea
amintit (simbolul de start nu apare n nici o parte dreapt a produciilor), nu este
necesar augmentarea.

4.1 Gramatici LR(k)


Definiia 5.1.1 O gramatic G se numete gramatic LR(k), k 0, dac pentru orice dou
derivri de forma:
*

S S Au u = u
dr

dr

S S Au u = v = v
dr

dr

pentru care k:u = k:v, are loc: = , A = A, = .


S vedem care este semnificaia acestor condiii n contextul analizei sintactice. S ne
amintim c un pas n analiza sintactic ascendent nseamn:
determinarea, n = u, a obiectului derivrii ;
reducerea lui la A.
Cum n algoritmul general de deplasare-reducere, se afl n stiv iar u se afl n banda de
intrare, rezult c o gramatic G este LR(k) dac, n orice pas al analizei sintactice
ascendente, obiectul derivrii i producia A cu care se face reducerea sunt
determinate n mod unic prin inspectarea primelor k simboluri care au rmas de analizat
(pe banda de intrare). Definiia pe care am dat-o pentru gramatici LR(k) este dinamic:
condiia ca o gramatic s fie LR(k) trebuie s fie ndeplinit de orice dou perechi de
derivri. Cum, n general, numrul acestora este infinit, nu se poate verifica dac o
gramatic este LR(k) folosind definiia. Vom ncerca s gsim condiii echivalente cu

52
Analiza lexical
definiia pentru cazurile k = 0 i k = 1. S stabilim n continuare cteva proprieti ale
gramaticilor LR(k).
Teorema 5.1.1 Dac G este gramatic LR(k), k 0, atunci G este neambigu.
Demonstraie Fie G = (V, T, S, P) o gramatic LR(k) i s presupunem, prin reducere
la absurd, c ea este ambigu. Exist atunci un cuvnt w L(G) care are dou derivri
extrem drepte distincte:
S S = 0 1 n = w
dr

dr

dr

S S = 0 1 m = w
dr

dr

dr

S observm c n = m = w. Cele dou derivri sunt presupuse distincte, deci exist un


numr i, 1 i min(m, n) astfel nct n-i-1 m-i-1, iar n-i = m-i. Avem atunci:
*

S S n-i-1 = Au u = n-i
dr

dr

S S m-i-1 = Au u = u = m-i
dr

dr

Dar G este LR(k) i pentru c are loc k:u = k:u rezult = , A = A, = ceea ce
este o contradicie. Aadar, presupunerea c exist un numr i, 1 i min(m, n) astfel
nct n-i-1 m-i-1 este fals. Rezult m = n i i = i, 1 i n.

4.2 O caracterizare a gramaticilor LR(0)


Caracterizarea pe care o vom da pentru gramaticile LR(0) folosete un automat finit
determinist care joac un rol esenial n construcia analizoarelor de tip LR.
Definiia 5.2.1 Fie G = (V, T, S, P) o gramatic independent de context redus. S
presupunem c simbolul * nu este n . Un articol pentru gramatica G este o producie A
n care s-a adugat simbolul * ntr-o anume poziie din . Notm un articol prin A
* dac = . Un articol n care * este pe ultima poziie se numete articol complet.
Definiia 5.2.2 Un prefix viabil pentru gramatica G = (V, T, S, P) este orice prefix al unui
*

cuvnt dac S Au u. Dac = 12 i = 1 spunem c articolul A


dr

dr

1*2 este valid pentru prefixul viabil .


Exemplul 5.2.1 Fie gramatica S A, A aAa | bAb | c. Articolele acestei gramatici
sunt:
S *A, S A*, A *aAa, A a*Aa, A aA*a, A aAa*,
A *bAb, A b*Ab, A bA*b, A bAb*, A *c, A c*.
Dac, n plus, avem i regula A , atunci singurul articol corespunztor acesteia este
articolul (complet) A *. n urmtorul tabel sunt date i cteva exemple de articole
valide pentru prefixe viabile:
Prefixul
Articole
viabil
valide
ab
A b*Ab
A *aAa
A *bAb

Derivarea corespunztoare
S A aAa abAba
S A aAa abAba abaAaba
S A aAa abAba abbAbba

O caracterizare a gramaticilor LR(0)

SA
S *A
A *bAb S A bAb
SAc
A *c

53

Lema 5.2.1 Fie G = (V, T, S, P) o gramatic i A 1*B2 un articol valid pentru


prefixul viabil . Atunci, oricare ar fi producia B , articolul B * este valid
pentru .
Demonstraie Dac A 1*B2 este valid pentru exist derivarea:
*

S Au 1B2u = B2u
dr

dr

Dar G este redus, deci exist derivarea 2 v i atunci putem scrie:


dr

S Au 1B2u 1Bvu 1vu = vu


dr

dr

dr

dr

ceea ce nseamn c B * este valid pentru .

Teorema 5.2.1 Gramatica G = (V, T, S, P) este gramatic LR(0) dac i numai dac,
oricare ar fi prefixul viabil , sunt ndeplinite condiiile:
1. nu exist dou articole complete valide pentru .
2. dac articolul A * este valid pentru , nu exist nici un articol
B 1*a2, a T, valid pentru .
Demonstraie S presupunem c G este gramatic LR(0). Atunci, conform definiiei,
oricare ar fi dou derivri:
*

S S Au u = u
dr

dr

S S Au u = v
dr

dr

(1)
(2)

are loc: = , A = A, = .
S presupunem, prin reducere la absurd, c exist un prefix viabil pentru care nu sunt
ndeplinite simultan 1 i 2. Dac 1 nu are loc, exist articolele B * i B *,
diferite, valide pentru . Asta nseamn c:
*

S S Bx x = x
dr

dr

S S Bx x = x
dr

dr

(3)
(4)

Ori, (3) i (4) contrazic faptul c G este LR(0).


Dac 2 nu are loc, exist articolele B *, C 1*a2 valide pentru , adic are loc
derivarea (3) i derivarea:
*

S S Cy 1a2y = a2y (5)


dr

dr

Dac n (5) 2 T* atunci din (3), (5) i faptul c G este LR(0), rezult c = = , B =
C, = 1a2. Dar = = 1 ceea ce nseamn c 1a2 = 1 ceea ce este
imposibil deoarece a T.
Dac n (5) 2 conine mcar o variabil, cum G este redus, exist derivarea:
*

2 u1Du3 u1u2u3
dr

Aplicnd acest lucru n (5) se obine:

dr

54

Analiza lexical
*

dr

dr

S S a2y au1Du3y au1u2u3y (6)


dr

Din (3) i (6), pentru c G este LR(0), rezult = au1, B = D, = u2. Dar din derivarea
(3) are loc = i egalitatea = au1 este imposibil pentru c a este terminal (nu
poate fi nul). Aadar, o implicaie a teoremei este dovedit.
S presupunem acum c oricare ar fi prefixul viabil au loc condiiile 1 i 2 din teorem
i, prin reducere la absurd, G nu este LR(0). Asta nseamn c exist dou derivri de
forma (1) i (2) dar nu este adevrat c = , A = A i = . Fr a restrnge
generalitatea, presupunem c n (1) i (2) avem ndeplinit condiia || = ||
||. Distingem dou cazuri:
Cazul 1: || ||. Schematic acest lucru arat astfel:

u
v

Rezult de aici c = 12, v = 2u, = 1 i 2 T*. Atunci, din (1) rezult c A


* este articol valid pentru iar din (2) rezult c articolul A 1*2 este valid de
asemenea pentru . Asta contrazice 1 dac 2 = respectiv 2 dac 2 . Aadar, n
acest caz, presupunerea c G nu este LR(0) este fals.
Cazul 2: ||>||. Din nou schematic condiia arat astfel:

u
v

Asta nseamn c = u1, v = u1u T* (|u1| 1). n derivarea (2), punem n


eviden prima form propoziional care are prefixul ; aceasta exist deoarece S nu are
prefixul , iar u = v are prefixul . Aadar (2) devine:
*

S S 1A1u1 11u1 = 111u1 = 1u1 v


dr

dr

dr

(7)

Din (7) rezult c A1 1*1 este articol valid pentru (iar din (1) rezult c A *
este valid de asemenea pentru ).
Distingem i aici trei subcazuri:
Cazul 2.1: 1= , contradicie: condiia 1 din teorem.
Cazul 2.2: 1= a2, a T. Atunci A1 1*a2, A * sunt valide pentru , ceea ce
contrazice 2 din teorem.
Cazul 2.3: 1= B2, B V. S observm atunci c exist un articol C *a valid
pentru (eventual C = B) pentru c gramatica este redus i exist mcar o producie a
crei parte dreapt ncepe cu un terminal (se folosete i Lema 5.2.1). i n acest caz se
contrazice 2 din teorem. n concluzie, G este LR(0).

Teorema precedent ne ndreptete s cercetm mai ndeaproape mulimea prefixelor


viabile pentru o gramatic G. Are loc:

O caracterizare a gramaticilor LR(0)


55
Teorema 5.2.2 Fie G = (V, T, S, P) o gramatic independent de context. Mulimea
prefixelor viabile pentru gramatica G este limbaj regulat.
Demonstraie S considerm gramatica G mbogit cu S S. Vom construi un
automat (nedeterminist cu -tranziii) care recunoate mulimea prefixelor viabile ale lui
G. Considerm automatul M = (Q, , , q0, Q), unde Q este mulimea articolelor
gramaticii G, = V T, q0 = S *S iar funcia de tranziie : Q ( 4 {}) 2Q
este definit astfel:
(A *B, ) = {B * | B P}.
(A *X, X) = { A X*}, X .
(A *a, ) = , a T.
(A *X, Y) = , X,Y cu X Y.
Aadar, automatul are -tranziii numai din articolele (strile) care au un neterminal dup
punct iar X - tranziii, X , din articolele care au X (acelai simbol) dup punct. S
dovedim c are loc:
A * (q0, ) este prefix viabil i A * este valid pentru .
S presupunem mai nti c este prefix viabil i A * este valid pentru . Asta
nseamn c are loc o derivare de forma:
*

S Au u = u
dr

dr

Vom dovedi, prin inducie dup k, lungimea derivrii, c A * (q0, ). Se


deduce apoi c (q0, ) conine articolul A * (din definiia automatului i a
extensiei lui ).
k = 1: n acest caz derivarea este S , deci S P, = u = i, dup
dr

definiia lui are loc S * (q0, ).


k > 1: n derivarea de lungime k punem n eviden pasul la care s-a introdus
neterminalul A:
*

S 1Bu2 11A1u2 11Au1u2 = Au u = u


dr

dr

dr

dr

unde B 1Au2 P, 1 u1, u1u2 = u, 11 = . E clar c derivarea


dr

S 1Bu2 11A1u2
dr

dr

are lungimea cel mult k 1 i, dup ipoteza inductiv, are loc B *1Au2 (q0, 1)
iar din definiia extensiei lui avem i B 1*Au2 (q0, 11). Atunci A *
(B 1*Au2, ) (definiia lui ) i A * (q0, 11) = (q0, ) (definiia lui ).
S presupunem acum c A * (q0, ). Atunci, n graful automatului M exist
mcar un drum de la q0 la starea A * etichetat cu = (drum ce conine
eventual i arce etichetate ). Considerm drumul de lungime minim (unul din ele dac
sunt mai multe) cu aceast proprietate. Fie k lungimea acestuia. Demonstrm prin
inducie dup k faptul c este prefix viabil pentru G iar articolul A * este valid
pentru .
k = 1: Drumul este de la q0 = S *S la o stare S * sau la S S*. n primul caz
= care este prefix viabil iar A * = S * este valid pentru pentru c S S
dr

56

Analiza lexical

. n al doilea caz = S este prefix viabil iar A * = S S* este valid pentru S


dr

pentru c S S.
dr

k > 1: n funcie de ultimul arc din drum distingem dou cazuri:


Cazul 1: Ultimul arc este etichetat cu X . Atunci el are forma din figura 5.2.1. Aadar
= 1X = 1X.
X

A 1*X

A 1X*

Figura 5.2.1
Dup ipoteza inductiv, 1 este prefix viabil iar articolul A 1*X este valid pentru el.
De aici rezult existena unei derivri
*

S Au 1Xu = 1Xu = u
dr

dr

de unde rezult faptul c este prefix viabil iar A 1X* este valid pentru .
Cazul 2: Ultimul arc al drumului este etichetat cu . Atunci = iar arcul este de forma
celui din figura 5.2.2.
B 1*A

A *

Figura 5.2.2
Dup ipoteza inductiv B 1*A1 este valid pentru , adic exist derivarea:
*

S Bu 1A1u = Au
dr

dr

Dac aplicm v (gramatica este redus) n derivarea de mai sus obinem:


dr

S Bu 1A1u = A1u Avu vu


dr

dr

dr

dr

ceea ce dovedete c i A * este valid pentru . Cu aceasta demonstraia teoremei


este ncheiat.

Exemplul 5.2.2 Fie gramatica S S, S aSa |bSb | c. Automatul care recunoate


prefixele viabile este reprezentat n figura 5.2.3. Arcele neetichetate constitue -tranziii.
Condiia necesar i suficient ca o gramatic G s fie LR(0), dat n Teorema 5.2.1, se
traduce relativ la automatul construit n Teorema 5.2.2 astfel:
1. Nu exist n graful automatului dou noduri cu etichete articole complete,
respectiv A * i B *, i cte un drum de la q0 la fiecare din acestea
etichetate cu acelai cuvnt .
2. Nu exist n graful automatului dou noduri ce au etichete A * respectiv
B *a, i cte un drum de la q0 la fiecare din acestea etichetate cu acelai
cuvnt ;
Aceste dou condiii sunt greoi de verificat pe automatul (nedeterminist) cu -tranziii
construit. Pentru acest automat, exist un automat determinist echivalent, fie acesta M =
(T, , g, t0, T) unde T 2Q (o stare pentru automatul determinist este o submulime de
articole), t0 conine toate articolele accesibile prin -tranziii din q0 = S *S, iar g:T
T este parial definit (vezi 2.2.2). Cum M recunoate mulimea prefixelor viabile i
articolul A * este n (q0, ) dac i numai dac A * este valid pentru

O caracterizare a gramaticilor LR(0)


57
prefixul viabil , nseamn c, n automatul determinist, o stare t T conine toate
articolele valide pentru o anume mulime de prefixe viabile i numai pe acestea. Aadar,
dac am construit M, automatul determinist echivalent cu M, condiiile necesare i
suficiente pentru ca G s fie gramatic LR(0) sunt:
1. Orice stare t T conine cel mult un articol complet.
2. Pentru orice t T, dac t conine un articol complet, atunci t nu conine nici un
articol cu terminal dup punct.
S S

S aSa

S c

S S

S bSb

a
S aSa

S c

S bSb

S
S bSb

S aSa

a
S aSa

S bSb

Figura 5.2.3
Aceste dou condiii sunt uor de verificat dac M este construit. M se construiete
aplicnd algoritmul de trecere de la un automat (nedeterminist) cu -tranziii la
automatul determinist echivalent (vezi Teorema 2.2.2 ). Pentru c aceast problem este
deosebit de important pentru analiza sintactic de tip LR, vom indica n continuare un
algoritm pentru obinerea automatului M, numit automatul LR(0) al gramaticii G. Mai
nti vom descrie o procedur, numit nchidere(t) care are la intrare o mulime t de
articole (stri ale lui M) i obine la ieire toate strile lui M accesibile din t prin .
Algoritmul 5.2.1 (procedura nchidere(t))
Intrare:
Gramatica G = (V, T, S, P);
Mulimea t de articole din gramatica G;
Ieire:
t = nchidere( t ) = {q Q | p t, q (p, ) } = (t, )
Metoda:
1. t = t ; flag = true;
2. while( flag ) {
3.
flag = false;
4.
for ( A *B t ) {

58

Analiza lexical
for ( B P )
if ( B * t ) {
t = t 4 {B *};
flag = true;
}//endif
}//endforB
}//endforA
}//endwhile
9. return t;

5.
6.
7.
8.

S observm c algoritmul precedent nu folosete automatul M. Legtura cu acest


automat este dat de:
Lema 5.2.2 Dac M = (Q, , , q0, Q) este automatul cu -tranziii al gramaticii G i t
Q, atunci, n urma aplicrii algoritmului precedent, se obine t = (t, ).
Demonstraie Fie t = nchidere(t). S artm mai nti c t (t, ). Fie articolul A
* t. Procedm prin inducie asupra momentului cnd a fost adugat acest
articol la t. Dac acest lucru a fost fcut la linia 1, rezult c A * t evident t
(t, ) nct, A * (t, ). Dac adugarea s-a fcut la linia 7, nseamn c n t,
n acel moment, exist B *A care a fost adugat anterior iar = . Dup ipoteza
inductiv, B *A B (t, ) iar din definiia lui (Teorema 5.2.2) rezult c A
* (B *A, ) nct A * este n (t, ).
Invers, fie A * (t, ). Prin inducie asupra lungimii drumului de la o stare din t
la A * se arat uor c A * se adaug la t (n linia 1 sau n linia 7).

S descriem acum algoritmul pentru construcia automatului LR(0) pentru gramatica G.


Algoritmul 5.2.2 Automatul LR(0)
Intrare:
Gramatica G = (V, T, S, P) la care s-a adugat S S;
Ieire:
Automatul determinist M = (T, , g, t0, T) echivalent cu M.
Metoda:
1. t0=nchidere(S*S); T={t0}; marcat(t0)=false;
2. while(tT && !marcat(t)) { // marcat(t) = false
3.
for( X ) {
4.
t = ;
5.
for( A *X t )
6.
t = t 4 {B X* | B *X t};
7.
if( t ){
8.
t = nchidere( t );
9.
if( t T ) {
10.
T = T 4 { t };
11.
marcat( t ) = false;
}//endif
12.
g(t, X) = t;
}//endif
}//endfor
}//endfor
13.
marcat( t ) = true;
}// endwhile

Lema 5.2.3 Algoritmul 5.2.2 descris mai sus este corect: automatul M obinut este
determinist i este echivalent cu M.

O caracterizare a gramaticilor LR(0)


59
Demonstraie Faptul c M este determinist rezult din linia 12. Condiia din linia 7 face
ca g s fie parial definit: dac (t, X) = n automatul M, atunci g (t, X) nu este
definit. S mai observm c, dac t n linia 7, atunci faptul c g (t , X) = ( t , X)
ne ndreptete s afirmm c M este echivalent cu M.

Exemplul 5.2.3 Pentru gramatica S S, S aSa | bSb | c, automatul LR(0) este dat
n figura 5.2.4. Se constat cu uurin c automatul construit ndeplinete condiiile
LR(0) deci gramatica este LR(0). Dac nlocuim producia A c cu A , n starea
iniial, n loc de A *c o s avem articolul A *, care este un articol complet. Cum n
aceeai stare exist articolul A *aAa, aceasta contrazice condiia 2 din caracterizarea
LR(0); gramatica S S, S aSa | bSb | , nu este LR(0).
S S
S aSa
S bSb
S c
a
S
S
S
S

aSa
aSa
bSb
c

c
S c

S aSa

S S

b
c

S
S
S
S

bSb
aSa
bSb
c
S

S bSb

S aSa

S bSb
Figura 5.2.4

A cA | c
Bd
Exemplul 5.2.4 Fie gramatica: S aAd | bAB
Automatul LR(0) corespunztor este dat n figura 5.2.5. Se observ c starea:

A cA
A c
A cA
A c

conine articolul complet A c* i articolele A *cA, A *c care au terminal dup


punct; gramatica nu este LR(0).

60

Analiza lexical

S S
S aAd
S bAB

a
S aAd
A cA
A c

S S

b
A cA
A c
A cA
A c

S bAB
A cA
S c

S aAd
d

S aAd

S bAB
B d

A cA

B
S bAB

B d

Figura 5.2.5
Clasa limbajelor LR(0) are mai multe caracterizri. Urmtoarea teorem are o importan
(mai ales teoretic) deosebit; demonstraia ei se poate gsi n [Har78] pag 515.
Teorema 5.2.3 Fie L *. Urmtoarele propoziii sunt echivalente:
1. L este limbaj LR(0) ( exist G, o gramatic LR(0), cu L = L(G)).
2. L este un limbaj independent de context determinist cu proprietatea:
x +, w, y *, dac w L i wx L iar y L, atunci yx L.
3. Exist un automat pushdown determinist A = (Q, , , , q0, Z0, F), unde F =
{qf} i exist Zf astfel ca:
L = T(A, Zf) = T(A, ) = {w * | (q0, w, Z0) d* (qf, , Zf) }.
4. Exist limbajele strict deterministe L0 i L1 astfel nct L = L0L1*.

4.3 Algoritm de analiz sintactic LR(0)


Modelul de analizor sintactic LR(k) este dat n figura 5.3.1.

Algoritm de analiz sintactic LR(0)

61

Intrare
a1

ai

an

Stiva
tm

TABELA
DE
PARSARE

CONTROL

tm-1
.
.
t0

p1

p2

Ieire
Figura 5.3.1

n cazul k = 0, tabela de parsare coincide cu automatul LR(0), M. O configuraie a


analizorului LR(0) este o triplet (, u#, ) unde t0T*, ( este o secven de stri
care ncepe cu t0), u T*, P*. Configuraia iniial este (t0, w#, ), unde w este
cuvntul care trebuie analizat. Tranziiile pe care le execut analizorul LR(0) sunt:
Deplasare: ( t, au#, ) d ( tt, u#, ) dac g (t, a) = t.
Reducere: ( tt, u#, ) d ( tt, u#, r) dac A * t,
r = A , |t | = || i t= g (t, A).
Acceptare: (t0t1, #, ) este configuraia de acceptare dac S S* t1. Analizorul
se oprete cu acceptarea cuvntului de analizat, iar este parsarea acestuia (irul
de reguli care s-a aplicat, n ordine invers, n derivarea extrem dreapt a lui w).
Eroare: o configuraie creia nu i se poate aplica nici o tranziie este configuraie
de eroare. Analizorul se oprete cu respingerea cuvntului de analizat.
Algoritmul de analiz sintactic LR(0) funcioneaz astfel:
Se pornete cu starea iniial a automatului LR(0) n topul stivei i cuvntul de analizat n
banda de intrare. Se execut o serie de operaii de deplasare - reducere, pn cnd, eventual,
ntreg cuvntul de intrare a fost parcurs i ultima reducere a fost fcut cu regula S S.
Dac nu se obine acest lucru, atunci analiza sintactic eueaz: cuvntul nu este corect
sintactic. Alegerea operaiei de reducere sau de deplasare este dictat de starea din top-ul
stivei:
dac aceast stare nu conine un articol complet i n automat exist tranziie din
aceasta cu simbolul curent de intrare, se produce o deplasare: se nainteaz cu o
poziie n banda de intrare (n alte implementri simbolul curent din intrare este
deplasat n stiv) i se adaug n stiv starea obinut n automatul LR(0) prin
tranziia cu simbolul n discuie. n acest mod se parcurg n automatul LR(0),
ncepnd cu t0, drumuri corespunztoare prefixelor viabile din derivarea extrem
dreapt pentru cuvntul de analizat.
dac starea din top-ul stivei conine un articol complet A *, atunci se
produce o reducere: n forma propoziional curent, se nlocuiete cu A. n
termenii configuraiei de aici, asta nseamn c vor fi eliminate din stiv un
numr de stri egal cu lungimea lui (stri care reprezint n automatul LR(0)

62

Analiza lexical
drumul etichetat cu ) i se adug starea obinut prin tranziie din starea
rmas n topul stivei cu A. n acest caz se raporteaz la ieire aplicarea
produciei p = A . S descriem algoritmul LR(0) i n pseudocod.
Algoritmul 5.3.1 (Analiz sintactic LR(0))
Intrare: Gramatica G = (V, T, S, P) care este LR(0) augmentat cu S S.
Automatul LR(0) al gramaticii G, notat M = (T, , g, t0, T ).
Cuvntul de intrare w T*.
Ieire: Analiza sintactic (parsarea) ascendent a lui w dac w L(G);
Eroare, n caz contrar.
Metoda: Fie STIVA o stiv, a simbolul curent,

char ps[] = w#; //ps este sirul de intrare w


i = 0; // pozitia in sirul de intrare
STIVA.push(t0); // se initializeaza stiva cu t0
while(true) { // se repeta pana la succes sau eroare
t = STIVA.top();
a = ps[i] // a este simbolul curent din intrare
if( g(t, a) { //deplasare
STIVA.push(g(t, a));
i++; //se inainteaza in intrare
}
else {
if(A X1X2Xm* t){
if(A == S)
if(a == #)exit( acceptare);
else exit(eroare);
else // reducere
for( i = 1; i m; i++) STIVA.pop();
STIVA.push(g(top(STIVA), A));
} //endif
else exit(eroare);
}//endelse
}//endwhile

Exemplul 5.3.1 Fie gramatica:


E E+T
T (E)
S S
ET
Ta
S E$
Aceast gramatic genereaz expresiile aritmetice care au doar operatorul +, paranteze i
operandul a. Producia S E$ asigur c orice expresie se termin cu simbolul special $
ceea ce face ca limbajul gramaticii s fie liber de prefixe: nici un prefix propriu al unui
cuvnt din limbaj nu este n limbaj.
Automatul LR(0) pentru aceast gramatic este dat n figura 5.3.2. Strile automatului
sunt notate 0, 1, 2,..., 10 iar tabela de tranziie a automatului, prezentat mai jos, este i
tabel de parsare LR(0).
G
0
1
2
3
4
5
6
7

a
5

(
4

S
1

E
2

T
3

Algoritm de analiz sintactic LR(0)


8
7
9
10

63
10

Marca $ care este simbol terminal al gramaticii asigur c gramatica este LR(0), lucru
care se poate verifica uor inspectnd cele 10 stri ale automatului i constatnd c sunt
ndeplinite condiiile LR(0).
n tabela de mai jos, n care se vizualizeaz att configuraiile parserului, ct i aciunea
ce se execut (deplasare sau reducere), este detaliat parsarea expresiei a+(a+a)$.

0
E T*

T (*E)

E *E+T
E *T
T *(E)
T *a
E

(
a

S *S
S *E$
E *E+T
E *T
T *(E)
T *a

S S*

S
2

S E*$
E E*+T

$
+

T a*

T (E*)

E E*+T

)
10

S E$*

E E+*T
T *(E)
T *a

E E+T*

T (E)*

Figura 5.3.2
STIVA
0
05
03
02
027
0274
02745
02743
02748
027487
0274875
0274879
02748
02748(10)
0279
02
026
01

INTRARE
a+(a+a)$#
+(a+a)$ #
+(a+a)$ #
+(a+a)$ #
(a+a)$ #
a+a)$ #
+a)$ #
+a)$ #
+a)$ #
a)$ #
)$ #
)$ #
)$ #
$#
$#
$#
#
#

ACIUNE
deplasare
reducere
reducere
deplasare
deplasare
deplasare
reducere
reducere
deplasare
deplasare
reducere
reducere
deplasare
reducere
reducere
deplasare
reducere
acceptare

IEIRE
Ta
ET

Ta
ET
Ta
E E+T
T (E)
E E+T
S E$

64
Analiza lexical
Dup acest exemplu putem s facem cteva consideraii privind testul LR(0) pentru
gramatici.
Definiia 5.3.1 Fie G o gramatic i M automatul LR(0) ataat lui G. Spunem c o stare
a lui M are un conflict reducere/reducere dac ea conine dou articole complete distincte A
*, B *. Spunem c o stare a lui M are un conflict deplasare/reducere dac ea conine
un articol complet A * i un articol cu terminal dup punct de forma B *a.
Spunem c o stare este consistent dac ea nu conine conflicte i este inconsistent n caz
contrar. Cu aceste noiuni, teorema de caracterizare a gramaticilor LR(0) se poate
reformula astfel:
Teorema 5.3.1 Fie G o gramatic i M automatul su LR(0). Gramatica G este LR(0)
dac i numai dac automatul M nu conine stri inconsistente.

Dac n gramatica din exemplul precedent n loc de producia S E$ considerm S


E, starea 2 din automat va fi {S E*, E E*+T} care este inconsistent deoarece
conine un conflict deplasare/reducere, deci gramatica nu este LR(0).
Pentru demonstrarea corectitudinii programului de analiz sintactic LR(0), s
considerm relaia d+ care noteaz nchiderea tranzitiv a relaiei d definit pe mulimea
configuraiilor. Vom dovedi dou rezultate mai generale, care prin particularizare
conduc la corectitudinea algoritmului.
Lema 5.3.1 Fie G = (V, T, S, P) o gramatic LR(0), t0, t0 drumuri n automatul LR(0)
etichetate cu respectiv i u, v T*. Atunci, dac n parserul LR(0) are loc (t0, uv#,
~

) d (t0, v#, ), atunci n G are loc derivarea u.


+

dr

Demonstraie Procedm prin inducie dup lungimea lui .


||= 0: n acest caz parserul produce doar deplasri i din definiia acestor aciuni
rezult c = u, i derivarea din concluzie are loc n mod trivial.
||> 0: Fie = r i r = A . Calculul considerat se scrie (punnd n eviden
reducerea r ) astfel:
(t0, uv#, ) = (t0, u1u2v#, ) d+ (t01, u2v#, ) =
= (t0tt, u2v#, ) d (t0tt, u2v#, r) d+ (t0, v#, r)
unde eticheta subdrumului t este , u1 = , fiind eticheta drumului t0t, iar n
automatul M are loc g(t, A) = t. Dup felul cum au fost definite tranziiile rezult c are
loc i:
(t0tt, u2v#, ) d+ (t0, v#, )
ceea ce nseamn, conform ipotezei inductive:
~

'

Au2 ( A este eticheta drumului t0tt )


dr

Aplicnd n continuare regula r = A obinem:


~

'

dr

dr

Au2 u2 = u1u2 = u,
ceea ce trebuia demonstrat.

Corolarul 5.3.1 Dac n parserul LR(0) are loc calculul (t0, w#, ) d+ (t0t1, #, ) unde S
~

S* t1, atunci n gramatica G are loc derivarea S w.


dr

Demonstraie Lema 5.3.1 pentru cuvintele = , u = w, v = i t0 = t0t1.

Algoritm de analiz sintactic LR(0)


65
+
Lema 5.3.2 Fie G = (V, T, S, P) o gramatic LR(0) i o form propoziional a
~

lui G astfel nct u, cu * i dac este nenul atunci :1 este neterminal.


dr

Atunci n parserul LR(0) are loc calculul


(t0, uv#, ) d* (t0, v#, )
unde t0 i t0 sunt drumuri n automatul LR(0) etichetate cu respectiv , pentru orice
cuvnt v T*.
Demonstraie Procedm din nou prin inducie dup lungimea lui .
|| = 0: n acest caz = u i se verific uor c are loc calculul precizat cu tranziii de
tip deplasare.
|| > 0: Fie = r, r = A . Atunci ~ = ~ ' r i derivarea din G se scrie:
~

'

dr

dr

1Au2 1u2 = u1u2 = u


r

Din 1Au2 1u2 = u1u2 = u i faptul c este o form propoziional a lui G


dr

(deci i 1Au2 este form propoziional), rezult c n automatul M exist un drum


t01 etichetat cu 1 = u1 (pentru c acesta este un prefix viabil), i:
(t0, u1u2v#, ) d* (t01, u2v#, ) = (t0tt, u2v#, ) d (t0tt, u2v#, r)
unde A * t, | t| = ||, iar drumul t0tt este etichetat cu A. Dup ipoteza
~

'

inductiv, din 1Au2 rezult calculul:


dr

(t0tt, u2v#, ) d* (t0, v#, ).


Asta nseamn c putem scrie:
(t0, u1u2v#, ) d* (t01, u2v#, )d (t0tt, v#, r) d* (t0, v#, r)
ceea ce trebuia demonstrat.

'

Corolarul 5.3.2 Dac n gramatica G, care este LR(0), avem S w, atunci n parserul
dr

LR(0) are loc calculul (t0, w#, ) d+ (t0t1, #, ) unde S S* t1.


Demonstraie Rezult din lema precedent lund = S, = , u = w, v = .

Combinnd cele dou leme obinem teorema de corectitudine a algoritmului de analiz


sintactic LR(0):
Teorema 5.3.2 Dac G este gramatic LR(0) atunci, oricare ar fi cuvntul de intrare w
T*, parserul LR(0) ajunge la configuraia de acceptare pentru w, adic (t0, w#, ) d+
~

(t0t1, #, ), dac i numai dac S w.


dr

Demonstraie Rezult din lemele 5.3.1, 5.3.2 i corolarele acestora.

4.4 Gramatici i analizoare SLR(1)


Este mult prea optimist s credem c putem face analiza sintactic LR(0) pentru
gramatici care descriu diverse construcii din limbajele de programare. n paragraful
precedent am vzut c n cazul expresiilor aritmetice, pentru a obine o gramatic LR(0),
trebuie s punem o marc de sfrit ($) la aceste expresii. Dar expresiile aritmetice apar

66
Analiza lexical
n diverse contexte n limbajele de programare nct soluia aceasta nu este viabil. Dac
renunm la marca $ atunci, n automatul LR(0), se obine cel puin o stare inconsistent
(cu conflict deplasare/reducere).
Ne propunem n acest paragraf s gsim o modalitate - dac este posibil - de eliminare a
conflictelor ntr-un automat LR(0). Dac o anumit stare t conine un conflict
deplasare/reducere, de pild articolele A * i B 1*a2, nseamn c exist n
gramatic un prefix viabil pentru care cele dou articole sunt valide:
*

S Au u = u
dr

dr

S Bv 1a2v = a2v
dr

dr

Problema este s eliminm conflictul, adic s gsim o modalitate de alegere n mod


determinist a aciunii care trebuie fcut, deplasare sau reducere. Aceasta ar putea fi fcut
dac simbolul a nu este primul simbol din cuvntul u, ceea ce nseamn c a
FOLLLOW(A). n acest caz am putea alege deplasare dac simbolul din banda de intrare
este a i reducere dac acesta ar fi unul din simbolurile din FOLLOW(A).
n cazul unui conflict reducere/reducere ntr-o stare t, rezult existena unui prefix viabil
pentru care articolele diferite A *, B * din t sunt valide:
*

S Au u = u
dr

dr

S Bv v = v
dr

dr

Eliminarea conflictului ar putea fi fcut dac mulimile FOLLOW(A) i FOLLOW(B)


sunt disjuncte. Astfel, dac n banda de intrare urmeaz un simbol din FOLLOW(A), se
face reducere cu regula A , dac acesta este din FOLLOW(B) se face reducerea cu
B , iar n celelalte cazuri se obine eroare. Din aceste considerente suntem
ndreptii s introducem clasa de gramatici SLR(1) (simplu LR(1)) prin:
Definiia 5.4.1 Fie G o gramatic pentru care automatul LR(0) conine stri
inconsistente (deci G nu este LR(0)). Gramatica G este gramatic SLR(1) dac oricare ar
fi starea t a automatului LR(0) sunt ndeplinite condiiile:
1. Dac A *, B * t atunci FOLLOW(A) 3 FOLLOW(B) = ;
2. Dac A *, B *a t atunci a FOLLOW(A).
Modelul de algoritm de analiz sintactic SLR(1) este acelai ca n cel descris n
paragraful precedent cu deosebirea c tabela de analiz sintactic se construiete ntr-un
anume mod. Ea va conine dou zone. Prima, numit ACIUNE, determin dac
parserul va face deplasare respectiv reducere, n funcie de starea ce se afl n topul stivei
i de simbolul urmtor din intrare, iar cea de a doua, numit GOTO, determin starea ce
se va pune n stiv n urma unei reduceri. Urmtorul algoritm descrie obinerea tabelei
de parsare SLR(1).
Algoritmul 5.4.1 (construcia tabelei de parsare SLR(1) )
Intrare:
Gramatica G = (V, T, S, P) augmentat cu S S;
Automatul M = (T, , g, t0, T );
Mulimile FOLLOW(A), A V ( se nlocuiete cu #).
Ieire:
Tabela de analiz SLR(1) compus din dou pri:
ACIUNE(t, a), t T, a T { # }.
GOTO(t, A), t T, A V.
Metoda:
1. for(t T)//Se initializeaza tabela de parsare
2.
for (a T) ACTIUNE(t, a) = eroare;

Gramatici i analizoare SLR(1)

67

3.
for (A V) GOTO(t, A) = eroare;
4. for(t T}{
5.
for(A *a t)
6.
ACTIUNE(t,a)=D g(t, a);//deplasare in g(t, a)
7.
for(B * t ) // acceptare sau reducere
8.
if(B == S) ACTIUNE(t, a) = acceptare;
else
9.
for(aFOLLOW(B))ACTIUNE(t,a)=R B;
}// endfor
10.
for (A V) GOTO(t, A) = g(t, A);
}

Toate intrrile n tabela SLR(1) sunt iniial completate cu eroare (linia 1-3) astfel nct
intrrile nedefinite n liniile 6, 8, 9, 10 rmn cu aceast valoare. S observm c, dac
gramatica G este SLR(1), atunci tabela dat de algoritmul de mai sus este bine definit n
sensul c o tentativ de adugare a unei valori n partea ACTIUNE sau partea GOTO se
face doar pentru intrrile cu valoarea eroare. Algoritmul de analiz sintactic SLR(1)
funcioneaz pe aceleai principii ca i cel LR(0). Deosebirea este c aciunile sunt dictate
de tabela de analiz SLR(1) nct tranziiile pe care le execut acesta sunt:
Deplasare: ( t, au#, ) d ( tt, u#, ) dac ACTIUNE(t, a) = Dt ;
Reducere: ( tt, u#, ) d ( tt, u#, p) dac ACTIUNE(t, a) = Rp unde p =
A , |t | = || i t= GOTO(t, A);
Acceptare: (t0t, #, ) dac ACTIUNE(t,a) = acceptare; Analizorul se oprete cu
acceptarea cuvntului de analizat iar este parsarea acestuia (irul de reguli care
s-a aplicat, n ordine invers, n derivarea extrem dreapt a lui w).
Eroare: (t, au#, ) d eroare dac ACTIUNE(t,a) = eroare; Analizorul se oprete
cu respingerea cuvntului de analizat.
Algoritmul 5.4.2 (Analiz sintactic SLR(1) )
Intrare:
Gramatica G = (V, T, S, P) care este SLR(1) ;
Tabela de parsare SLR(1) ( ACTIUNE, GOTO);
Cuvntul de intrare w T*.
Ieire:
Analiza sintactic (parsarea) ascendent a lui w dac w L(G);
eroare, n caz contrar.
Metoda:
Se folosete stiva St pentru a implementa tranziiile de mai sus.
char ps[] = w#; //ps este cuvantul de intrare w
int i = 0; // pozitia curenta in cuvantul de intrare
St.push(t0); // se initializeaza stiva cu t0
while(true) { // se repeta pana la succes sau eroare
t = St.top();
a = ps[i] // a este simbolul curent din intrare
if(ACTIUNE(t,a) == acceptare) exit(acceptare);
if(ACTIUNE(t,a) == Dt){
St.push(t);
i++; // se inainteaza in w
}//endif
else {
if(ACTIUNE(t,a) == R A X1X2Xm){
for( i = 1; i m; i++) St.pop();
St.push(GOTO(top(STIVA), A));
} //endif
else exit(eroare);

68

Analiza lexical
}//endelse
}//endwhile

Corectitudinea algoritmului SLR(1) se dovedete n acelai mod ca i cea a algoritmului


LR(0). Aa cum am precizat, pentru o gramatic n care automatul LR(0) are stri
inconsistente, prin construcia tabelei de analiz SLR(1) nu am fcut altceva dect s
eliminm conflictele de deplasare/reducere sau de reducere/reducere. Faptul c este
corect strategia propus rezult din analiza fcut nainte de a defini gramatica SLR(1).
Lsm aadar cititorului, ca un exerciiu de acum simplu, demonstrarea corectitudinii
algoritmului SLR(1).
Teorema 5.4.1 Dac G este o gramatic SLR(1) atunci, oricare ar fi cuvntul de intrare
w, algoritmul de analiz SLR(1) se oprete cu acceptare i obine la ieire dac i
~

numai dac S w.
dr

Exemplul 5.4.1 S aplicm algoritmul


genereaz expresiile aritmetice:
SE
E E+T
ET

de analiz sintactic SLR(1) pentru gramatica ce


T T*F
TF
F (E)

Fa

S *E
E *E+T
E *T
T *T*F
T *F
F *(E)
F *a

S E*
E E*+T

T F*

F (*E)
E *E+T
E *T
T *T*F
T *F
F *(E)
F *a

5
2

F a*

E T*
T T**F

F (E*)
E E*+T

E E+*T
T *T*F
T *F
F *(E)
F *a

T T**F
F *(E)
F *a

10
T T*F*

11

E E+T*
T T**F

F (E)*

Figura 5.4.1
Tabela de tranziie a automatului LR(0) este:
g
0
1
2
3
4
5
6
7

a
5

(
4

E
1

T
2

F
3

3
10

6
7
5

5
5

4
4

Gramatici i analizoare SLR(1)

69

8
9
10
11

11
7

Strile automatului LR(0) sunt date n figura 5.4.1. Din acest automat se deduce c G nu
este LR(0): strile 1, 2, 9 conin conflict de deplasare/reducere. Pentru a verifica dac
gramatica este SLR(1) avem nevoie de mulimile FOLLOW(S) i FOLLOW(E). Se
deduce uor c: FOLLOW(S) = { } i FOLLOW(E) = {+, ), }. Atunci gramatica
este SLR(1) pentru c:
n starea 1: + FOLLOW(S);
n starea 2: * FOLLOW(E);
n starea 9: * FOLLOW(E).
Pentru construcia tabelei de parsare avem nevoie de mulimile FOLLOW pentru toi
neterminalii (s-a nlocuit cu #):
NETERMINAL
S
E
T
F

FOLLOW
#
#, +, )
#, +, *, )
#, +, *, )

Tabela de analiz SLR(1) pentru acest gramatic este dat mai jos.
STARE
0
1
2
3
4
5
6
7
8
9
10
11

a
D5

ACIUNE
(
)
D4

D6
R2
R4

D7
R4

R6

R6

D5

R2
R4

acceptare
R2
R4

R6

R6

D4

D5
D5

E
1

GOTO
T
F
2
3

D4
D4
R1
R3
R5

D7
R3
R5

D11
R1
R3
R5

3
10

R1
R3
R5

Aciunile analizorului SLR(1) pentru cuvntul de intrare w = a*(a+a) sunt date n tabela
urmtoare:
STIVA
0
05
03
02
027
0274
02745
02743
02742
02748
027486
0274865

INTRARE
a*(a+a)#
*(a+a)#
*(a+a)#
*(a+a)#
(a+a)#
a+a)#
+a)#
+a)#
+a)#
+a)#
a)#
)#

ACIUNE
deplasare
reducere
reducere
deplasare
deplasare
deplasare
reducere
reducere
reducere
deplasare
deplasare
reducere

IEIRE
Fa
TF

Fa
TF
ET
Fa

70

Analiza lexical
0274863
0274869
02748
02748(11)
027(10)
02
01

)#
)#
)#
#
#
#
#

reducere
reducere
reducere
reducere
reducere
reducere
acceptare

TF
E E+T
F (E)
T T*F
ET

1
S S*

S
L

2
S L*=R
R L*

S L=*R
R *L
L **R
L *a

S *S
S *L=R
S *R
L **R
L *a
R *L
a

S R*

*
4
L **R
R *L
L **R
L *a

L a*

a
*
L

L
R L*

7
L *R*

S L=R*

Figura 5.4.2
Vom vedea n paragraful urmtor c orice gramatic SLR(1) este gramatic LR(1), prin
urmare este gramatic neambigu. Exist ns gramatici neambigue (chiar gramatici
LR(1)) care nu sunt SLR(1). Vom da n continuare un exemplu de astfel de gramatic.
Exemplul 5.1.2 Fie gramatica dat de produciile:
L *R|a
RL
S L=R|R
Se poate constata c aceast gramatic nu este ambigu. Automatul LR(0) este dat n
figura 5.4.2.
n starea 2 exist un conflict deplasare/reducere: articolul S L*=R definete operaia
de deplasare (dac urmtorul simbol este =) iar articolul R L* definete o reducere.
Constatm c FOLLOW(R) = { , = } ceea ce nseamn c gramatica nu este SLR(1)
pentru c simbolul = este n aceast mulime. Vom arta n paragraful urmtor c
aceast gramatic este LR(1).

O caracterizare a gramaticilor LR(1)

71

4.5 O caracterizare a gramaticilor LR(1)


Definiia 5.5.1 Fie G = (V, T, S, P) o gramatic independent de context redus. Un
articol LR(1) pentru gramatica G este o pereche (A *, a) unde A este un
articol LR(0), iar a FOLLOW(A) (se pune # n loc de ).
Definiia 5.5.2 Articolul (A 1*2, a) este valid pentru prefixul viabil 1 dac are loc
derivarea
*

S Au 12u
dr

dr

iar a = 1:u (a = # dac u = ).


Teorema 5.5.1 O gramatic G = (V, T, S, P) este gramatic LR(1) dac i numai dac
oricare ar fi prefixul viabil , nu exist dou articole distincte, valide pentru , de forma
(A *, a), (B *, b) unde a FIRST( b).
Demonstraie S interpretm mai nti condiia (necesar i suficient) pentru ca G s
fie LR(1). Este de ateptat ca algoritmul de analiz LR(1) s funcioneze dup aceleai
principii ca i cel SLR(1). Asta nseamn c un articol de forma (A *, a) invoc
reducere cu regula A dac a este simbolul curent din intrare, iar un articol de forma
(B *a, b) invoc deplasare dac a este la intrare. Aadar, condiia ca nici un prefix
viabil s nu aib dou articole valide de forma indicat se traduce prin:
1. nu exist conflict deplasare/reducere. Un astfel de conflict nseamn dou articole
(A *, a) i (B *a, b) valide pentru acelai prefix.
2. nu exist conflict reducere/reducere. Un astfel de conflict nseamn dou articole
complete (A *, a) i (B *, a) valide pentru acelai prefix.
Demonstraia se face analog cu cea de la caracterizarea LR(0).

Pentru a testa dac o gramatic este LR(1) i apoi pentru obinerea tabelei de analiz
sintactic LR(1) se construiete automatul LR(1) care recunoate prefixele viabile ale
gramaticii. Mai nti, ca i n cazul LR(0), s definim o procedur nchidere(I) care
gsete toate articolele care sunt valide pentru aceleai prefixe pentru care sunt valide
articolele din I.
Algoritmul 5.5.1 (procedura nchidere(I))
Intrare:
Gramatica G = (V, T, S, P);
Mulimea I de articole LR(1) din gramatica G; Mulimile FIRST.
Ieire:
I devine mulimea tuturor articolelor valide pentru prefixele viabile date
iniial.
Metoda:
asemntoare cu cea de la LR(0).
flag = true;
while( flag ) {
flag = false;
for ( (A *B, a) I) {
for ( B P )
for( b FIRST(a)){
if ( (B *, b) I) {
I = I {(B *, b)};
flag = true;
}//endif
}//endforb
}//endforB
}//endforA
}//endwhile
return I;

72
Analiza lexical
n continuare, folosind aceast procedur, descriem algoritmul pentru construirea
automatului LR(1).
Algoritmul 5.5.2 (Automatul LR(1))
Intrare:
Gramatica G = (V, T, S, P) la care s-a adugat S S;
Ieire:
Automatul determinist LR(1) M = (T, , g, t0, T) ce recunoate prefixele
viabile i are ca stri mulimi de articole LR(1).
Metoda:
1. t0 = nchidere((S*S,#));T={t0};marcat(t0)=false;
2. while(tT && !marcat(t)){ // marcat(t) = false
3.
for( X ) {
4.
t = ;
5.
for( (A *X,a) t )
6.
t = t 4 {(B X*,a) | (B *X,a) t};
7.
if( t ){
8.
t = nchidere( t );
9.
if( t T ) {
10.
T = T { t };
11.
marcat( t ) = false;
}//endif
12.
g(t, X) = t;
} //endif
} //endfor
13.
marcat( t ) = true;
} // endwhile

S dovedim acum corectitudinea algoritmului descris mai sus.


Teorema 5.5.2 Automatul M construit n algoritmul 5.5.2 este determinist i L(M)
coincide cu mulimea prefixelor viabile ale lui G. Mai mult, pentru orice prefix viabil ,
g(t0,) reprezint mulimea articolelor LR(1) valide pentru .

Automatul LR(1) pentru o gramatic G, se folosete pentru a verifica dac G este LR(1).
Conform teoremei de caracterizare LR(1) acest lucru decurge astfel:
Conflict reducere/reducere: Dac n T exist o stare ce conine articole de forma
(A *, a), (B *, a) atunci gramatica nu este LR(1);
Conflict deplasare/reducere: Dac n T exist o stare ce conine articole de forma
(A *, a) i (B 1*a2, b), atunci G nu este LR(1).
O gramatic este LR(1) dac orice stare t T este liber de conflicte.
Exemplul 5.5.1 S considerm din nou gramatica:
S L=R|R
L *R|a
RL
i s construim automatul LR(1) pentru aceasta.
n continuare, articolele ce au aceeai prim component le notm condensat prin (A
*, {a1, a2,, an}). De exemplu, articolele (A *, a), (A *, b), se scriu (A
*, {a, b}).
Am vzut n paragraful precedent c aceast gramatic nu este SLR(1). Dac analizm
strile automatului LR(1), prezentate n figura 5.5.1, constatm c gramatica este LR(1):
toate strile sunt libere de conflict.

O caracterizare a gramaticilor LR(1)


0

73

(S *S, #)
(S *L=R, #)
(S *R, #)
(L **R, {=,#})
(L *a, {=,#})
(R *L, #)

(S S*, #)

(S R*, #)

(S L=*R, #)
(R *L, #)
(L **R, #)

(L a*, {=,#})
11

(L *R*, {=,#})

(S L=R*, #)

10

(R L*, {=,#})

(L *a, #)

(L **R, {=, #})


(R *L, {=, #})
(L **R, {=, #})
(L *a, {=, #})

(S L*=R, #)
(R L*, #)

(L **R, #)
(R *L, #)

(L **R, #)
(L *a, #)

(R L*, #)

12

13

(L a*, #)

(L *R*, #)

Figura 5.5.1
Tabela de tranziie a automatului este prezentat n continuare.
g
0
1
2
3
4
5
6
7
8
9
10
11
12
13

a
5

*
4

S
1

L
2

R
3

6
5

12

11

10

12

11

10

13

4.6 Analiz sintactic LR(1) i LALR(1)


Algoritmul de analiz sintactic LR(1) este, n esen, acelai cu algoritmul de analiz
sintactic SLR(1). Ceea ce l distinge este modul de construire a tabelei de analiz cu cele
dou pri, ACIUNE i GOTO. Vom indica n continuare construirea acestei tabele,
pornind de la automatul LR(1).
Algoritmul 5.6.1 (Construcia tabelei de analiz LR(1))
Intrare:
Gramatica LR(1) G =( V, S, T, P) augmentat cu S S.
Automatul LR(1) M = (T, , g, t0, T) .
Ieire:
Tabela de analiz cu componentele:
ACIUNE(t, a), t T, a T 4 {#}
GOTO(t, A), t T, A V.
Metoda:
for(t T){ //Se initializeaza tabela de parsare

74

Analiza lexical
for (a T) ACTIUNE(t, a) = eroare;
for (A V) GOTO(t, A) = eroare;

}
for(t T}{
for((A *a, L) t)
ACTIUNE(t,a)=D g(t, a);//deplasare in g(t, a)
for((B *, L) t ) {
for(c L){
if(B == S) ACTIUNE(t, c) = acceptare;
else
ACTIUNE(t,c)=R B;//reducere cu B
}// endforc
}// endforB
} // endfor
for (A V) GOTO(t, A) = g(t, A);

Aadar, strile automatului LR(1) determin aciunea analizorului sintactic :


Dac starea t din topul stivei conine un articol cu terminalul a dup punct i la
intrare, se produce o deplasare, anume starea g(t,a)se adaug n stiv;
Dac starea din topul stivei conine un articol complet (A *, a) unde a este
terminalul curent din intrare, se produce reducere cu producia A . n cazul n
care A = S S, obinem acceptare;
n celelalte cazuri se obine o eroare.
Exemplul 5.6.1 S construim dup algoritmul tocmai descris tabela de analiz pentru
gramatica din exemplul 5.4.1:
0: S S
3: L *R
STARE
0
1
2
3
4
5
6
7
8
9
10
11
12
13

1: S L=R
4: L a

2: S R
5: R L

ACIUNE
=
*
D4

a
D5

D4
R4

D12

10

10

13

R4
D11

R3
R5
D12

GOTO
L
R
2
3

acceptare
R5
R2

D6
D5

S
1

R3
R5
R1
R5
D11
R4
R3

Aplicarea algoritmului (de deplasare/reducere) folosind aceast tabel de analiz, asupra


cuvintelori **aa respectiv **a=a, este ilustrat n tabelele urmtoare:
STIVA
0
04
044
0445
STIVA

INTRARE
**aa#
*aa#
aa#
a#
INTRARE

ACIUNE
deplasare D4
deplasare D4
deplasare D5
eroare
ACIUNE

IEIRE

IEIRE

Analiz sintactic LR(1) i LALR(1)


0
04
044
0445
0448
0447
048
047
02
026
026(12)
026(10)
0269
01

**a=a#
*a=a#
a=a#
=a#
=a#
=a#
=a#
=a#
=a#
a#
#
#
#
#

75
deplasare D4
deplasare D4
deplasare D5
reducere R4
reducere R5
reducere R3
reducere R5
reducere R3
deplasare D6
deplasare D12
reducere R4
reducere R5
reducere R1
acceptare

La
RL
L *R
RL
L *R
La
RL
S L=R

S observm c automatul LR(1) construit n paragraful precedent, pentru gramatica


care tocmai am discutat-o, are un numr mai mare de stri dect numrul strilor
automatului LR(0) pentru aceast gramatic. Unele din strile automatului LR(1) au ceva
n comun:
starea 5:
cu starea 12:
(L a*, {=,#})

starea 4:

starea 7:
starea 8:

(L **R, {=,#})
(R *L, {=,#})
(L **R, {=,#})
(L *a, {=,#})

(L *R*, {=,#})
(R L*, {=,#})

(L a*, #)

cu starea 11:

cu starea 13:
cu starea 10:

(L **R, #)
(R *L, #)
(L **R, #)
(L *a, #)

(L *R*, #)

(R L*, #)

Aceste stri au respectiv prima component a articolelor aceeai.


Definiia 5.6.1 Fie t o stare n automatul LR(1) al unei gramatici G. Nucleul acestei stri
este mulimea articolelor LR(0) care apar ca prime componente n articolele LR(1) din t.
Defininiia 5.6.2 Dou stri t1 i t2 ale automatului LR(1) pentru gramatica G se
numesc echivalente dac ele au acelai nucleu.
Cum fiecare stare a automatului LR(1) este o mulime de articole LR(1), avnd dou stri
t1 i t2 putem s vorbim de t1 t2. De pild, dac
t1= {(L *R*, {=, # })}, t2= {(L *R*, #)}
atunci t1 4 t2 = t1 pentru c t2 t1.
Definiia 5.6.3 Fie G gramatic LR(1) i M = (T, , g, t0, T) automatul LR(1)
corespunztor. Spunem c gramatica G este LALR(1) ( Look Ahead LR(1)}) dac
oricare ar fi perechea de stri echivalente t1, t2 din automatul LR(1), starea t1 t2 este
liber de conflicte.
Am introdus aceste noiuni n scopul de a simplifica, atunci cnd este posibil, tabela de
analiz LR(1), n sensul de a restrnge numrul liniilor tabelei. Acest lucru este posibil
dac prin reuniunea a dou stri echivalente nu se introduc conflicte. n acest mod

76
Analiza lexical
putem obine o tabel de analiz de tip LR(1) dar cu numrul de linii (stri ale
automatului) egal cu cel al tabelei SLR(1), adic, cu cel al numrului de stri LR(0). n
exemplul considerat mai sus avem:
4 4 11 = 4
5 4 12 = 5
7 4 13 = 7
8 4 10 = 8
Aadar, gramatica n discuie este LALR(1) pentru c nu s-au obinut stri cu conflicte.
n continuare se reconstruiete automatul LR(1) prin reuniunea strilor echivalente i se
construiete tabela de analiz pornind de la acest nou automat LR(1). Mai facem
observaia c, dac strile t1 i t2 sunt echivalente, atunci i strile g(t1, X), g(t2, X) sunt
echivalente, pentru orice X din . S descriem n ntregime acest algoritm.
Algoritmul 5.6.2 (Construcia tabelei de analiz LALR(1))
Intrare:
Gramatica G = (V, T, S, P) augmentat cu S S;
Ieire:
Tabela de analiz LALR(1) ( ACIUNE i GOTO ).
Metoda:
1. Se construiete automatul LR(1), M = (T, , g, t0, T) folosind Algoritmul 5.5.2.
Fie T = {t0, t1,, tn}. Dac toate strile din T sunt libere de conflict, urmeaz 2,
altfel algoritmul se oprete: gramatica nu este LR(1).
2. Se determin strile echivalente din T i, prin reuniunea acestora, se obine o
nou mulime de stri T = {t0, t1,, tm}
3. Dac n T exist stri ce conin conflicte, algoritmul se oprete: gramatica G nu
este LALR(1). Altfel se continu cu 4.
4. Se construiete automatul M = (T, , g, t0, T), unde t T:
Dac t T atunci g(t, X) = g(t, X), X ;
Dac t = t14 t2 4, t1, t2, T, atunci
g(t, X) = g(t1, X) 4 g(t2, X) 4.
5. Se aplic algoritmul 5.6.1 pentru construirea tabelei LR(1) pornind de la
automatul M. Tabela obinut se numete tabela LALR(1) pentru gramatica G.
Exemplul 5.6.2. Aplicnd algoritmul de mai sus automatului M din exemplul 5.5.1,
obinem automatul M dat de tabela de tranziie de mai jos (strile au fost redenumite
prin 0, 1, , 9).
g
0
1
2
3
4
5
6
7
8
9

a
5

*
4

S
1

L
2

R
3

6
5

Pornind de la acest automat, aplicd algoritmul de construcie al abelei LR(1), obinem


tabela de parsare LALR(1) pentru gramatica dat:
STARE
0

a
D5

ACIUNE
*
D4

S
1

GOTO
L
2

R
3

Analiz sintactic LR(1) i LALR(1)


1
2
3
4
5
6
7
8
9

77
acceptare
R5
R2

D6
D5

D4
R4

D5

R4
D4

R3
R5

R3
R5
R1

Analiza cuvntului w = **a=a este ilustrat n tabela urmtoare:


STIVA
0
04
044
0445
0448
0447
048
047
02
026
0265
0268
0269
01

INTRARE
**a=a#
*a=a#
a=a#
=a#
=a#
=a#
=a#
=a#
=a#
a#
#
#
#
#

ACIUNE
deplasare D4
deplasare D4
deplasare D5
reducere R4
reducere R5
reducere R3
reducere R5
reducere R3
deplasare D6
deplasare D5
reducere R4
reducere R5
reducere R1
acceptare

IEIRE

La
RL
L *R
RL
L *R
La
RL
S L=R

Dac vom compara tabela LR(1) cu tabela LALR(1) pentru gramatica din exemplul dat,
vom observa c tabela LALR(1) coincide cu liniile 0-9 din tabela LR(1) cu deosebirea c
n locul strile 10, 11, 12 i 13 apar strile 8, 4, 5, 7 respectiv. Acest fapt face ca erorile,
n unele cuvinte, s fie semnalate mai trziu n analiza LALR(1) dect s-ar face n analiza
LR(1). n exemplul precedent, pentru tabela LR(1) avem ACIUNE(12, =) = eroare pe
cnd ACIUNE(5, =) = R4 nct, pentru c strile 5 i 12 sunt echivalente, n tabela
LALR(1) rmne doar ACIUNE(5, =) = R4. Cititorul este invitat s verifice c n
analiza LR(1), pentru cuvntul de intrare *a=a=#, se obine o eroare la configuraia
(026(12), =#, ), pe cnd algoritmul LALR(1) va ajunge, pentru acelai cuvnt, n
configuraia (0265, =#, ) care are tranziie n configuraia (0268, =#, 4) apoi n (0269,
=#, 45) i abia aceasta din urm produce eroarea.

Avantajul metodei LALR(1) const n aceea c dimensiunea tabelei LALR(1) este aceeai
cu cea a tabelei SLR(1); tabela LR(1) pentru o gramatic care descrie pri ale sintaxei
unui limbaj de programare poate fi foarte mare!
S artm n continuare c exist gramatici LR(1) care nu sunt LALR(1).
Exemplul 5.6.3 Fie gramatica:
S aAb | bAd | aBd | bBc
Ae Be
Strile automatul LR(1) pentru aceast gramatic sunt cele din figura 5.6.1.

78

Analiza lexical
0

(S *aAb, #)
(S *bAd, #)
(S *aBd, #)
(S *bBc, #)

(S a*Ab, #)
(S a*Bd, #)
(A *e, b)
(B *e, d)

(S b*Ad,#)
(S b*Bc, #)
(A *e, d)
(B *e, b)

(S aA*b, #)

(S bA*d,#)

(S bB*c, #)

(A e*, d)
(B e*, b)

10

11

12

(S aAb*, #)

(S aBd*, #)

(S bAd*, #)

(S bBc*, #)

5
(A e*, b)
(B e*, d)

4
(S aB*d, #)
8

Figura 5.6.1
S observm c toate strile automatului LR(1) sunt libere de conflicte; gramatica este
LR(1). Strile 5 i 8 sunt stri echivalente. Constatm c 5 4 8 = = {(A e*, {b, d }),
(B e*, {b, d}) }, iar aceast nou mulime conine conflicte de reducere/reducere,
ceea ce nseamn c gramatica considerat nu este gramatic LALR(1).

Generatoare de analizoare sintactice

79

5 Generatoare de analizoare sintactice


Pentru a uura munca implementatorilor de limbaje au fost concepute instrumente care
genereaz automat analizoare (lexicale sau sintactice). Printre cele mai populare
instrumente de acest fel se numr sistemul YACC (Yet Another Compiler Compiler)
conceput i realizat n 1975 de S. C. Johnson la Bell Laboratory n USA. Produsul a fost
inclus n sistemul de operare UNIX i a fost folosit la scar industrial pentru
implementarea multor limbaje. Un alt produs, distribuit n sistemele de operare Linux
este Bison, compatibil n ntregime cu YACC, scris de Robert Corbett i Richard
Stallmann. n 1990 a aprut versiunea pentru MS-DOS a produsului YACC, numit
PCYACC, dezvoltat de Abraxax Software Inc.
Exist astzi numeroase astfel de generatoare, construite de grupuri de cercetare din
universiti sau din societi specializate pe software. Enumerm cteva din acestea
mpreun cu resursele web corespunztoare.
La adresa http://dinosaur.compilertools.net/ se gsesc informaii (inclusiv
documentaie) despre Lex, Yacc, Flex, i Bison.
La adresa http://epaperpress.com/lexandyacc/index.html se gsete un ghid compact
care este util celor care vor s utilizeze lex i yacc.
Pagina oficial destinat proiectului GNU Flex (Fast lexical analyser generator) este
http://www.gnu.org/software/flex/. Aa cum se preciza i n capitolul 3.5, Flex este un
instrument pentru generare de programe care realizeaz pattern-matching pentru texte
iar
mpreun
cu
Bison
constituie
perechea
de
instrumente
(www.gnu.org/software/bison/) cel mai des utilizat pentru scrierea compilatoarelor.
Bison, ca i Yacc, este un generator de parsere care convertete descrierea unei gramatici
context free LALR(1) ntr-un program C care realizeaz analiza sintactic n gramatica
descris.
Iat i alte instrumente de acest fel:
Spirit C++ Parser Framework - http://spirit.sourceforge.net/
Spirit este un instrument ce implementeaz parsarea recursiv descendent pornind de la
descrierea EBNF(Extended Backus Normal Form) a unei gramatici. Implementarea este
orientat obiect n C++.
BtYacc: BackTracking Yacc - http://www.siber.com/btyacc/
BtYacc este o versiune modificat a lui Yacc care suport backtraking. Pentru corectarea
bug-urilor vizitai http://www.vendian.org/mncharity/dir3/btyacc/
LEMON - http://www.hwaci.com/sw/lemon/
LEMON este un generator de parsere LALR(1), este open-source, produce cod C i
pretinde c produce un parser mai rapid dect yacc/bison.
SYNTAX: http://www-rocq.inria.fr/oscar/www/syntax/
SYNTAX cuprinde un set de instrumente pentru proiectarea i implementarea prii
front-end a unui compilator. Are toate capabilitile lex i yacc iar partea de procesare a
erorilor este mult dezvoltat.
PCCTS : http://www.polhode.com/pccts.html
PCCTS este un generator pentru parsare recursiv descendent LL(k). Proiectul s-a
transformat n ANTLR.
ANTLR: http://www.antlr.org
ANTLR (ANother Tool for Language Recognition) este un instrument pentru
construcia compilatoarelor care suport descrierea aciunilor n Java, C# sau C++.
ANTLR conine module pentru construcia arborilor, traversarea arborilor, construcia
translatoarelor. ANTLR implementeaz parsarea LL(k) i este n domeniul public.

80

Generatoare de analizoare sintactice


Visual Parse++ 5.0 - http://www.sand-stone.com/
Visual Parse permite proiectarea vizual a analizoarelor lexicale i sintactice i utilizarea
lor n aplicaii C, C++, C#, Delphi, Java, sau Visual Basic sub sistemele de operare
UNIX, Linux i Windows. Este un produs comercial.
AnaGram: - http://www.parsifalsoft.com/
AnaGram este un genarator de parsere LALR(1) care produce C/C++ ce poate fi
compilat pe orice platform i poate fi executat sub Win9x/NT. Este un produs
comercial dar firma ofer i o variant gratuit. De asemenea, este disponibil XIDEK
(Extensible Interpreter Development Kit) ce poate fi folosit pentru proiectarea i
implementarea interpreterelor.
Yacc++(R) - http://world.std.com/~compres/
Yacc++(R) este o rescriere orientat obiect a instrumentelor Lex i Yacc pentru C++.
LLgen - http://www.cs.vu.nl/~ceriel/LLgen.html
LLgen implementeaz eficient parsere recursiv descendente ale gramaticilor ELL(1).
Dispune de faciliti statice i dinamice pentru tratarea ambiguitilor.
Elkhound: - http://www.cs.berkeley.edu/~smcpeak/elkhound/
Este un generator de parsere bazat pe tehnica GLR; fiind dezvoltat la Universitatea
Berkeley este open source.
Grammatica - http://www.nongnu.org/grammatica/
Grammatica este un generator de parsere LL(k) pentru C# i Java. Este distribuit sub
licena GNU i suport gramatici cu un numr nelimitat de token-uri look-ahead. De
asemenea, are un modul puternic pentru recuperarea automat a erorilor.
jay este o variant Java pentru Yacc.
http://www.informatik.uni-osnabrueck.de/alumni/bernd/jay/
RDP este un generator de parsere pentru gramatici LL(1) cu atribute.
http://www.dcs.rhbnc.ac.uk/research/languages/projects/rdp.shtml
TPG - http://christophe.delord.free.fr/en/tpg/
TPG (Toy Parser Generator) este un generator care, pentru o gramatic cu atribute
produce un parser recursiv descendent n limbajul Python.
ProGrammar - http://www.programmar.com/main.shtml
ProGrammar este un mediu vizual pentru proiectarea de parsere ce sunt independente
de platforma i de limbajul de programare.
GOLD Parser - http://www.devincook.com/goldparser/
GOLD (Grammar Oriented Language Developer) este un generator care
implementeaz parsarea LALR(1) ntr-o nou manier (fa de yacc de exemplu):
descrirea gramaticii este analizat de un modul builder care creeaz tabela de parsare
ce este salvat ntr-un fiier separat, independent de parser. Acesta este ncrcat de
motorul de parsare care este disponibil pentru limbajele Java, C# (.NET), ActiveX.
Win32 Lex/YACC - http://www.monmouth.com/~wstreett/lex-yacc/lexyacc.html Flex i Bison pentru platforma Win32.
Depot4 - http://www.math.tu-dresden.de/wir/depot4/
Depot4 este un generator de translator dezvoltat la Universitatea din Dresda.
IParse - http://home.planet.nl/~faase009/MM.html
Program ce implementeaz un parser greedy backtraking. Are la intrare un fiier ce
conine descrierea unei gramatici i un altul ce conine fraza de parsat n conformitate cu
gramatica dat. Produce arborele abstract al frazei.
Styx - http://www.speculate.de/styx/
Styx este un generator de scaner i parser LALR(1). Este disponibil att sub Lynux, ct
i sub Windows.

Generatoare de analizoare sintactice


81
YaYacc(Yet another Yacc) accept gramatici yacc i produce cod C++.
http://www.gradsoft.com.ua/eng/Products/YaYacc/yayacc.html
Rie - http://www.is.titech.ac.jp/~sassa/lab/rie-e.html
Rie este un generator de compilator (front end) bazat pe o clas de gramatici LR cu
atribute numit ECLR-attributed grammar. Generatorul este scris n C, este open source
i este cu siguran o extensie a sistemelor Yacc/Bison.
Coco/R, http://www.ssw.uni-linz.ac.at/Research/Projects/
Compiler.html. Coco/R este un generator de scaner i parser LL(1). Sunt disponibile
versiuni Java, C#, Oberon, C, Pascal, Modula2.
PRECC - http://www.afm.sbu.ac.uk/precc/
PREC(PREttier Compiler-Compiler) este un generator de parser pentru gramatici
dependente de context care genereaz cod C. Este disponibil sub Unix sau sub MS-Dos.
SGLR: - http://www.cwi.nl/projects/MetaEnv/sglr/
SGLR (Scannerless Generalized LR) este un generator de parser LR care nu utilizeaz
scanarea: tabela de parsare generat de la o definiie a sintaxei conine informaii
suficiente pentru a realiza analiza sintactic.
SLK - http://home.earthlink.net/~slkpg/
Este un generator de parser LL(k) ce produce cod C, C++, C#, i Java. Disponibil
gratuit pentru Unix i Windows.
YooLex (Yet another Object-Oriented Lex)
http://yoolex.sourceforge.net/ . Produce cod C++ compatibil cu STL.
Happy - http://haskell.cs.yale.edu/happy/ Generator pentru Haskell.
VLCC (Visual Languages Compiler-Compiler)
www.dmi.unisa.it/people/costagliola/www/home/ricerca/vlcc/vlcc.htm
Hapy - http://hapy.sourceforge.net/ genereaz un parser scris n C++ folosind
descrierea sintaxei cu o interfa similar cu EBNF.
CppCC (C++ Compiler Compiler) - http://cppcc.sourceforge.net/
Este un generator de scaner i parser LL(k).
ClearParse - http://www.clearjump.com/products/ClearParse.html
Parser comercial.
TPLex/Yacc for Delphi 3 - http://www.17slon.com/gp/gp/tply.htm
JB2CSharp - http://sourceforge.net/projects/jb2csharp/
Este un proiect al Universitii Boulder Colorado de a porta instrumentele JavaBison/Flex pentru platforma .NET: aciunile pot fi scrise n C#.
oolex http://www.inf.uos.de/alumni/bernd/oolex/
oolex (object-oriented lexer) este un generator pentru analiza lexical construit n
manier orientat obiect. Exist i un succesor al lui oolex lolo care se gsete la
http://www.inf.uos.de/alumni/bernd/lolo/index.html . De altfel, mpreun cu oops
(http://www.inf.uos.de/alumni/bernd/oops/) aceste sisteme fac parte dintr-un proiect
dezvoltat la Universitatea din Osnabruck privind construcia compilatoarelor folosind
programarea orientat obiect n Java.
EAG - http://www.cs.ru.nl/~kees/eag/
Acest sistem folosete formalismul Extended Affix Grammar (EAG) care descrie
sintaxa i semantica limbajelor de programare.
Bison++, Flex++: http://www.kohsuke.org/flex++bison++/
Sunt variantele Bison, Flex care produc cod C++.

82

Generatoare de analizoare sintactice

5.1 Utilizarea generatorului de parsere YACC


YACC poate genera un traductor n modul urmtor: Specificaiile sintactice precum
i unele elemente semantice sunt incluse ntr-un fiier de intrare pentru YACC. Acest
fiier are n general extensia .y. Linia de comand (n UNIX):
yacc nume_fisier.y
produce, plecnd de la specificaiile cuprinse n fiierul de intrare nume_fisier.y, un
program scris n limbaj C care implementeaz metoda de analiz sintactic LALR(1)
pentru gramatica respectiv n spe funcia yyparse(). Fiierul de intrare (programul
surs) pentru YACC are structura urmtoare:
Declaraii
%%
Reguli
%%
Rutine C
Partea Declaraii a fiierului surs este opional i poate cuprinde dou seciuni. O prim
seciune cuprinde ntre delimitatorii %{ i %} declaraii n limbajul C pentru variabilele
care se utilizeaz n regulile de traducere sau n procedurile din cea de-a treia parte a
fiierului. Aadar, textul cuprins ntre %{ i %} se copie nealterat n fiierul C produs de
YACC. A doua seciune a acestei prime pri conine declaraii ale unitilor lexicale ale
gramaticii, declaraii de asociativitate i preceden a operatorilor, declaraii ale tipurilor
de date pentru valorile semantice ale simbolurilor gramaticii.
Simbolurile terminale ale gramaticii unitile lexicale cu excepia celor formate dintrun singur caracter (precum +, * etc.), trebuiesc declarate. Aceste simboluri sunt
reprezentate n YACC prin nite coduri numerice; funcia yylex transmite codul unitii
respective funciei yyparse. Programatorul nu trebuie s tie aceste coduri numerice, ele
sunt generate automat folosind opiunea d la lansarea lui YACC i sunt trecute ntr-un
fiier nume.tab.h (sau yytab.h sub DOS).Unitile lexicale se definesc prin linii de forma:
% token <nume _unitate _lexical>
i pot fi utilizate n celelalte dou pri ale fiierului de intrare. Tot n aceast seciune se
poate introduce simbolul de start al gramaticii, n cazul n care acesta nu este partea
stng a primei reguli sintactice:
start <nume _simbol _de _start>
Alte linii care pot fi incluse n aceast seciune sunt:
type - pentru definirea tipului;
left - pentru asociativitate stnga a operatorilor;
right - pentru asociativitate dreapta a operatorilor;
prec - pentru precizarea precedenei operatorilor;
nonassoc - pentru declaraiile de neasociativitate.
Simbolurile gramaticii pot avea valori semantice. n mod implicit, aceste valori sunt
valori ntregi si sunt specificate n YYSTYPE. Pentru a specifica alt tip, n partea de
declaraii C se adaug o directiv define pentru YYSTYPE:
#define YYSTYPE <nume tip>
declar tipul valorilor semantice pentru simbolurile gramaticii ca fiind <nume tip>.
Utilizarea de tipuri diferite pentru simboluri diferite se realizeaz prin specificarea
acestor tipuri ntr-o declaraie union (specific YACC ) i declararea tipului simbolurilor
cu type. Iat un exemplu:
%union {
int intval;

Aplicaii cu LEX i YACC

83

double doubleval;
symrec *tptr;
}
type <intval> INT
type <doubleval> REAL
type <tptr> ID

Prin aceste declaraii se specific tipul valorilor pentru token-urile INT, REAL, ID ca
fiind respectiv int, double, pointer la symrec (pointer n tabela de simboluri).
Partea a doua a fiierului de intrare, Reguli, este partea obligatorie. Aici se specific
regulile gramaticii care descriu sintaxa pe care dorim s o verificm cu acest analizor.
Fiecare regul conine:
partea stng;
partea dreapt;
partea de aciune.
Partea stng trebuie s conin un neterminal al gramaticii, iar partea dreapt irul
format din terminali i neterminali corespunztor unei reguli. Cele dou pri sunt
desprite prin : (dou puncte). Partea de aciune, cuprins ntre{ i }, conine un text
scris n limbajul C care va fi inclus n analizor i reprezint operaiile care se execut
atunci cnd analizorul realizeaz o reducere cu regula specificat. O regul se termin
prin ; (punct virgul). Dac sunt mai multe reguli care au aceeai parte stng, acestea
se scriu o singur dat i prile drepte se despart prin |. Iat, spre exemplu, cum se
scriu regulile gramaticii care descrie expresiile aritmetice:
expr : NUMAR
| expr + expr
| expr - expr
| expr * expr
| expr / expr
| ( expr)
;

Aici, expr este numele neterminalului care definete expresia aritmetic, terminalii +, -,
*, /, (, ) se pun ntre apostrof, iar NUMAR reprezint token-ul (unitatea lexical) numr (ce
trebuie declarat n seciunea declaraii). Dac dorim ca analizorul pe care-l construim s
realizeze i aciuni semantice, de pild s evalueze expresiile (pentru irul 25+15, pe
lng faptul c verific sintaxa, raporteaz i rezultatul adunrii, valoarea 40), se adaug
la reguli partea de aciune:
expr : NUMAR
| expr + expr
| expr - expr
| expr * expr
| expr / expr
| ( expr)
;

{$$
{$$
{$$
{$$
{$$
{$$

=
=
=
=
=
=

$1;}
$1+$3;}
$1-$3;}
$1*$3;}
$1/$3;}
$2;}

n partea de aciune se scriu instruciuni n limbajul C. Aici, $$ reprezint valoarea unui


atribut al neterminalului expr din stnga regulii sintactice iar $i reprezint valoarea
atributului celui de-al i-lea simbol din partea stng a regulii.
Ultima parte a fiierului de intrare conine rutine scrise n limbaj C care se includ
nealterate n analizorul obinut de YACC. n aceast parte trebuie furnizat un analizor
lexical, adic o funcie yylex() precum i apelul (n programul principal) la funcia
yyparse() pe care o creeaz YACC-ul.
Exemplul 6.1.1
/* Exemplul 1 de fisier intrare YACC */
%{
int yylex(void);
void yyerror(char* s);

84

Generatoare de analizoare sintactice

%}
%token ID
%token WHILE
%token BEGIN
%token END
%token DO
%token IF
%token THEN
%token ELSE
%token SEMI
%token ASSIGN
%start prog
%%
prog: stmlist
;
stm: ID ASSIGN ID
| WHILE ID DO stm
| BEGIN stmlist END
| IF ID THEN stm
| IF ID THEN stm ELSE stm
;
stmlist: stm
| stmlist SEMI stm
;
%%

Dac se compileaz acest fiier cu YACC, acesta construiete automatul LALR(1) pentru
gramatica descris n seciunea Reguli. Aceast gramatic are 3 neterminali: prog
(simbolul de start al gramaticii), stm i stmlist. Terminalii sunt declarai prin directiva
%token. YACC raporteaz conflictele de deplasare reducere i cele de reducerereducere dac exist. Aceste conflicte sunt rezolvate de ctre YACC astfel:
conflictul de deplasare-reducere este rezolvat n favoarea deplasrii;
conflictul de reducere-reducere este rezolvat n favoarea reducerii cu regula care
apare prima n lista de reguli
Pentru descrierea precedent este raportat un singur conflict de deplasare reducere (este
obinut datorit celor dou forme ale instruciunii IF). Automatul LALR(1) obinut este
vizibil dac n linia de comand pentru lansarea YACC ului se scrie opiunea v
(pentru a afla ce opiuni pot fi folosite lansai YACC h). Iat cum se descrie automatul
n exemplul dat:
-*-=-*-=-*-=-*-=-*- LALR PARSING TABLE
-*-=-*-=-*-=-*-=-*+---------------------STATE 0
--------------------+
+ CONFLICTS:
+ RULES:
$accept : ^prog $end
+ ACTIONS AND GOTOS:
ID : shift & new state 4
WHILE : shift & new state 5
BEGIN : shift & new state 6
IF : shift & new state 7
: error
prog : goto state 1
stmlist : goto state 2
stm : goto state 3
+---------------------STATE 1
--------------------+
+ CONFLICTS:
+ RULES:
$accept : prog^$end
+ ACTIONS AND GOTOS:

Aplicaii cu LEX i YACC


$end : accept
: error
+---------------------STATE 2
--------------------+
+ CONFLICTS:
+ RULES:
prog : stmlist^
(rule 1)
stmlist : stmlist^SEMI stm
+ ACTIONS AND GOTOS:
SEMI : shift & new state 8
: reduce by rule 1
.............................................................
+---------------------STATE 21
-------------------+
+ CONFLICTS:
+ RULES:
stm : IF ID THEN stm ELSE stm^
(rule 6)
+ ACTIONS AND GOTOS:
: reduce by rule 6
====================
SUMMARY
====================
...........................................................
-*-=-*-=-*-=-*-=-*END OF TABLE
-*-=-*-=-*-=-*-

Exemplul 6.1.2
/* Intrare YACC pentru expresii aritmetice de forma a*(a+a) */
%{
#define QUIT
((double) 101010)
%}
%start S
%%
S:

| S '\n'
| S E '\n'

;
E :
|
|
;
T :
|
|
;
F :
|
;
%%

{ prompt(); }
{ prompt(); }
{ if ($2 == QUIT) {
return(0);
} else {
printf("Expresia este corecta.\n");
prompt();
}
}

E '+' T
E '-' T
T

{printf(" E->E+T\n");}
{printf(" E->E-T\n");}
{printf(" E->T\n");}

T '*' F
T '/' F
F

{printf(" T->T*F\n");}
{printf(" T->T/F\n");}
{printf(" T->F\n");}

'(' E ')'
'a'

{printf(" F->(E)\n");}
{printf(" F->a\n");}

#include <stdio.h>
#include <ctype.h>
int main(){
printf("\n**********************************************\n");
printf("**
**\n");
printf("**
Parser pentru expresii aritmetice
**\n");
printf("**
cu operanzi a si operatori +,-,*,/,( )
**\n");
printf("**
La promterul READY>
**\n");

85

86

Generatoare de analizoare sintactice

printf("**
Introduceti o expresie. Ex. a*(a+a)-a
**\n");
printf("**
Pentru iesire tastati quit
**\n");
printf("**
**\n");
printf("\n**********************************************\n");
yyparse();
}
yyerror(s){
printf("Expresia este incorecta\n");
}
yylex(){
int c;
while ((c=getchar()) == ' ' || c == '\t' )
;
if(c==EOF)
return 0;
if (c == 'Q' || c == 'q')
if ((c=getchar()) == 'U' || c == 'u')
if ((c=getchar()) == 'I' || c == 'i')
if ((c=getchar()) == 'T' || c == 't') {
yylval = QUIT;
return( EOF );
}
else return '?';
return c;
}
void prompt( void ){
printf("READY> ");
}

YACC poate fi folosit pentru proiectarea de aplicaii diverse. Dezvoltarea unui proiect
cu ajutorul YACC - ului presupune parcurgerea urmtoarelor etape:
1. Identificarea problemei. Acest lucru presupune o viziune de ansamblu asupra
proiectului stabilindu-se arhitectura sistemului ce urmeaz a fi proiectat,
descompunerea sistemului n funciile componente, etc;
2. Definirea limbajului surs. Obiectul acestui pas este specificarea limbajului
care urmeaz a fi tradus. Cel mai adecvat mecanism pentru aceast specificare
este gramatica independent de context pentru c se poate descrie aceast
gramatic direct n limbajul YACC;
3. Scrierea programului de descriere a gramaticii n fiierul de intrare pentru
YACC;
4. Scrierea codului auxiliar. n acest pas se stabilesc i se scriu aciunile ataate
regulilor sintactice, funciile necesare lansrii aplicaiei (main(), yylex(), yyerror())
precum i alte instruciuni C care urmeaz a fi ncorporate n programul generat
de YACC;
5. Obinerea funciei yyparse(). Odat creat fiierul de intrare, se lanseaz YACC
pentru acesta i se obine programul surs C. Dac sunt necesare i alte funcii
C, acestea se pot incorpora n programul obinut de YACC;
6. Compilarea textului obinut i obinerea programului executabil.

5.2 Aplicaii cu LEX i YACC


Instrumentele Lex i Yacc pot fi folosite mpreun pentru a realiza analiza lexical i
sintactic a unui text i chiar pentru a obine un interpreter sau un compilator prin
mecanismul aciunilor semantice. Schema de utilizare mpreun a celor dou generatoare
este dat n figura 6.2.1. Dac fiierul calc.l descrie unitile lexicale ale limbajului pe care

Aplicaii cu LEX i YACC


87
dorim s-l implementm iar calc.y descrie sintaxa limbajului, atunci comenzile (Lynux)
pentru a obine fiierul calc.exe din calc.l i calc.y sunt urmtoarele:
yacc d calc.y
lex calc.l
cc lex.yy.c y.tab.c ocalc.exe

Comanda yacc produce fiierul y.tab.c care conine funcia de parsare yyparse().
Opiunea d produce fiierul header y.tab.h care conine directivele ce atribuie unitilor
lexicale coduri numerice. Acestea vor fi folosite de analizorul lexical produs de lex.
Fiierul lang.l va trebui s conin directiva #include y.tab.h.
Comanda lex va produce fiierul lex.yy.c care conine definiia funciei yylex() care este
invocat de yyparse() pentru obinerea, pe rnd, a unitilor lexicale din textul surs. Cele
dou fiiere .c vor fi compilate mpreun, i se obine n urma editrii legturilor
executabilul lang.exe ce poate fi lansat pentru a analiza un text surs din limbajul dat.
calc.y

yacc

y.tab.c

y.tab.h

calc.l

sursa

cc

calc.exe

lex.yy.c

lex

ieire

Figura 6.2.1
Exempul 6.2.1 Implementarea unui calculator de buzunar
Vom construi fiierele pentru lex i yacc care descriu sintaxa expresiilor aritmetice ce se
pot forma cu constante ntregi (CINT), variabile (VAR), operaiile aritmetice uzuale i
paranteze. Desigur, orice variabil ce poate aprea ntr-o expresii trebuie s fie iniializat
cu valoarea unei expresii . Astfel, un program n accepiunea de aici este o succesiune de
expresii aritmetice i/sau asignri. Gramatica ce descrie un astfel de limbaj este
urmtoarea:
program
statement
expression

|
|
|
|
|

program statement |
expression | VAR '=' expression
CINT | VAR
expression '+' expression
expression '-' expression
expression '*' expression
expression '/' expression
'(' expression ')'

Fiierul pentru yacc calc.y de mai jos este scris n aa fel nct aciunile semantice au ca
efect obinerea valorii numerice a unei expresii atunci cnd ea este transmis la intrare
programului calc.exe ce se obine. Tabloul sym[26] este folosit pentru a pstra valorile
variabilelor ce apar n programe; o variabil este aici o liter (va fi descris n fiierul
pentru lex).
// Fisierul calc.y
%{
#include <stdio.h>
void yyerror(char *);
int yylex(void);
int sym[26];
%}

88

Generatoare de analizoare sintactice

%token CINT VAR


%left '+' '-'
%left '*' '/'
%%
program:
program statement '\n'
| /* NULL */
;
statement:
expression
| VAR '=' expression
;
expression:
CINT
| VAR
| expression '+'
| expression '-'
| expression '*'
| expression '/'
| '(' expression
;

expression
expression
expression
expression
')'

{printf("%d\n", $1);}
{ sym[$1] = $3; }

{
{
{
{
{
{

$$
$$
$$
$$
$$
$$

=
=
=
=
=
=

sym[$1];
$1 + $3;
$1 - $3;
$1 * $3;
$1 / $3;
$2; }

}
}
}
}
}

%%
void yyerror(char *s) {
fprintf(stderr, "%s\n", s);
}
int main(void) {
yyparse();
}

Declararea token-urilor CINT i VAR prin %token INT VAR n fiierul calc.y produce
obinerea fiierului y.tab.h (dac la invocarea lui yacc se folosete parametrul d ) n care
sunt definite valorile numerice pentru aceste token-uri. Acest fiier, care este listat mai
jos, trebuie inclus n fiierul calc.l.
// Fisierul y.tab.h
#ifndef YYSTYPE
#define YYSTYPE int
#endif
#define
CINT 258
#define
VAR
259
extern YYSTYPE yylval;

Fiierul calc.l conine descrierea token-urilor VAR i CINT. Valorile asociate token-urilor
sunt returnate de lex n variabila (standard) yylval. Pentru variabile yylval va nsemna
indexul n tabloul sym iar pentru constante yylval este numrul ntreg corespunztor
(scanat de analizorul lexical). Variabila yytext este pointer la unitatea lexical tocmai
depistat, de aceea pentru operatori (token-uri semn) definiia n fiierul lex este:
[-+()=/*\n]
// Fisierul calc.l
%{

#include "y.tab.h"

{ return *yytext; }

Aplicaii cu LEX i YACC

89

#include <stdlib.h>
void yyerror(char *);
%}
%%
[a-z]

{
yylval = *yytext - 'a';
return VAR;
}

[0-9]+

{
}

yylval = atoi(yytext);
return CINT;

[-+()=/*\n]

{ return *yytext; }

[ \t]

/* ignora spatiile */

yyerror("Caracter necunoscut");

%%
int yywrap(void) {
return 1;
}

Dup obinerea fiierului calc.exe dup schema din figura 6.2.1, o sesiune de lucru cu
acest program ar putea fi:
utilizator:
calc:
utilizator:
calc:
utilizator:
utilizator:
utilizator:
utilizator:
utilizator:
calc:
utilizator:
calc:
utilizator:
calc:

x=4
y=12
(x-y)*(x-y)
64
a=3
b=7
u=y*y-x*x
v=b*b-a*a
u+v
168
(u+v)/2+(u-v)/2
128
u
128

Gramatici cu atribute

91

6 Analiza semantic
6.1 Gramatici cu atribute
n 1968 Donald Knuth [Knu68] introduce un formalism pentru specificarea semanticii
limbajelor de programare: gramaticii independente de context care exprim sintaxa i se
adaug un sistem de atribute care exprim semantica. Acest formalism s-a dovedit
deosebit de util n analiza semantic n cadrul compilatoarelor. O gramatic cu atribute este o
extindere a unei gramatici independente de context: fiecare simbol al acesteia are ataat
o mulime de atribute care pot lua valori ntr-o mulime precizat. Valorile atributelor se
calculeaz dup nite reguli ataate produciilor gramaticii, numite reguli semantice. O regul
semantic definete modul de calcul al unui atribut al prii stngi a produciei i atunci
atributul se zice c este sintetizat sau al unui atribut al unui simbol din partea dreapt a
produciei i atunci atributul se zice c e motenit. Valoarea unui atribut este definit n
funcie de valorile altor atribute ale simbolurilor din aceeai producie. Se justific
denumirea de atribut sintetizat: valorile sale depind de valorile atributelor fiilor ntr-un
arbore de derivare (eventual i de unele atribute proprii) i se propag n arbore de la
frunze ctre rdcin, precum i de atribut motenit: valorile sale depind de valorile
atributelor prinilor i frailor, propagndu-se de la rdcin ctre frunze. ntr-un arbore
de derivare al gramaticii independente de context un anume atribut poate avea mai
multe apariii, dup cum simbolul la care aparine are mai multe apariii. Valorile
acestora, desigur, pot fi diferite. Este posibil ca simboluri diferite ale gramaticii s aib
atribute cu acelai nume; acestea se consider totui atribute diferite. De pild, dac
simbolurile X i Y au cte un atribut numite val, atunci acestea se exprim prin X.val
respectiv Y.val. Simbolul de start al gramaticii are un atribut sintetizat care este
desemnat a defini semantica cuvntului w din limbajul gramaticii. Este natural s
considerm c o gramatic cu atribute care are ca suport (sintactic) gramatica G este
bine definit (lucru ce se refer la regulile semantice), dac pentru orice cuvnt w L(G)
se poate determina semantica acestuia, respectiv, valoarea atributului desemnat pentru
simbolul de start n arborele de derivare al cuvntului w. De asemenea, este natural s
spunem c un anume atribut este util dac el contribuie la calculul semanticii lui w, adic
semantica lui w depinde, direct sau indirect, de valoarea acestui atribut. Astfel, o
gramatic cu atribute se zice c este bine definit (necircular) dac, pentru orice arbore de
derivare construit n gramatica suport, se pot calcula valorile tuturor apariiilor
atributelor utile n acest arbore (se mai spune c arborele poate fi decorat). Aadar, exist
dou probleme importante legate de gramaticile cu atribute:
Problema circularitii: este gramatica cu atribute bine definit?
Problema evalurii: care este cea mai bun strategie de evaluare a atributelor n
gramatica dat (presupus necircular).
Abordarea acestor probleme conduce la identificarea unor clase de gramatici cu atribute
pe care le vom trece n revist n acest capitol. nainte de a defini riguros noiunea de
gramatic cu atribute i a studia clasele importante, s prezentm nite exemple.
6.1.1 Numere raionale n baza 2 (Knuth)
Se consider gramatica independent de context cu urmtoarele reguli:
L LB
B 0
N L
L B
B 1
N L.L
Se constat uor c limbajul generat de aceast gramatic este:

92
Analiza semantic
L(G) = {b1b2bn | bi {0, 1}, 1 i n } 4
4 { b1b2bn . bn+1bn+2bm | bi {0, 1}, 1 i m, m > n 1}
Dorim s nzestrm aceast gramatic cu atribute i reguli semantice astfel ca pentru
fiecare cuvnt w generat de aceasta s putem determina numrul raional (n baza 10) a
crui reprezentare n baza 2 este w. Pentru aceasta considerm atributele:
Pentru simbolul B (bit):
Un atribut sintetizat, numit val, care are ca domeniu al valorilor mulimea
numerelor raionale i care trebuie interpretat astfel: dac B genereaz 0,
atunci B.val are valoarea 0, iar dac B genereaz 1 atunci B.val are valoarea
2s unde s este locul lui 1 n reprezentarea binar n cauz (s se numete
scal);
Un atribut motenit numit scala care va defini exponentul lui 2 pentru
calculul valorii lui B. Domeniul valorilor acestui atribut este mulimea
numerelor ntregi;
Pentru variabila L (list de bii):
Dou atribute sintetizate: val i lung care au ca domenii de valori mulimea
numerelor raionale, respectiv naturale iar ca interpretare, valoarea numeric
+

zecimal, respectiv lungimea lui w dac L w;

Un atribut motenit scala care are ca domeniu de valori mulimea numerelor


ntregi i care va fi folosit pentru calculul scalei lui B.
Pentru variabila N (numr):
Un atribut sintetizat val care reprezint semantica cuvintelor generate n
gramatic. Domeniul su de valori este mulimea numerelor raionale.
Regulile semantice sunt ataate regulilor sintactice (produciilor gramaticii) i sunt date n
tabela de mai jos. Facem precizarea c, n produciile n care L are apariii multiple (L
LB, N L.L), aceste apariii au fost indexate pentru a evita confuzia.
Reguli sintactice
B 0
B 1
L B
L 1 L2 B
N L
N L1.L2

Reguli semantice
B.val = 0
B.val = 2B.scala
L.val = B.val; L.lung = 1; B.scala = L.scala
L1.val = L2.val + B.val; L1.lung = L2.lung + 1
L2.scala = L1.scala + 1; B.scala = L1.scala
N.val = L.val; L.scala = 0
N.val = L1.val + L2.val;
L1.scala = 0; L2.scala = - L2.lung

S determinm, folosind regulile semantice date mai sus, semantica cuvntului w =


101.11. Arborele de derivare pentru acest cuvnt n gramatica suport este dat n figura
7.1.1

Gramatici cu atribute

93
N
L

L
L

Figura 7.1.1
5.75

0.75

0.5

0.5

-2

-1

0.25

-2

-1

Figura 7.1.2
Arborele de derivare decorat este reprezentat n figura 7.1.2. S precizm faptul c
pentru nodurile etichetate cu L s-au reprezentat valorile pentru atributele val, lung
respectiv scala, n aceast ordine iar pentru cele etichetate cu B sunt reprezentate
valorile atributelor val i scala. Arcele definesc un graf de dependen care d ordinea
de determinare a valorilor atributelor.
6.1.2 Declararea variabilelor ntr-un program
S considerm gramatica urmtoare:
Z begin D ; I end
D D type V | type V
I I ; use V | use V
V V, v | v unde v Var = {a, b, c, }.
Aceast gramatic genereaz programe care ncep cu begin, se sfresc cu end iar ntre
aceti doi delimitatori pot fi liste de declaraii urmate de liste de instruciuni. O declaraie

94
Analiza semantic
este de forma : type v1, v2, vn iar o instruciune are forma use v1, v2, vm. Iat
exemple de programe corecte n aceast descriere sintactic:
1)
2)
3)
4)

begin
begin
begin
begin

type
type
type
type

a,
a,
x,
x,

b;
b;
a;
c;

type c;
type a,
use x ;
type x,

use a,
c; use
use a,
z; use

c; use b, c end
a, c; use c end
c end
x; use z, x end

n descrierea (semantic) a limbajelor de programare exist n general regulile:


Orice variabil din program trebuie declarat.
Declararea multipl a unei variabile nu este permis.
Dup aceste reguli, programele 2, 3, 4 de mai sus nu sunt corecte. Ne propunem s
descriem o gramatic cu atribute care s descrie prin regulile semantice cele precizate
mai sus.
Variabilelor gramaticii de mai sus le atam atributele:
Variabila Z:
atributul sintetizat Rspuns cu valori n mulimea {true, false} care d rspuns la
ntrebarea: este programul corect?
Variabila D:
Atributul sintetizat Dec, care are ca valori submulimi de variabile i eventual
constanta literal eroare. D.Dec conine toate variabilele care sunt declarate n
subarborele lui D, i numai pe acelea, iar dac o variabil are declaraii multiple,
D.Dec va conine i eroare.
Variabila I:
Atributul sintetizat Rspuns cu valori n {true, false}; I.Rspuns este true dac i
numai dac variabilele folosite n instruciunile generate de I au fost declarate n
acelai program.
Atributul motenit Tabel cu valori n 2Var. I.Tabel conine toate variabilele
declarate n programul curent.
Variabila V:
Atributul sintetizat Lista cu valori n 2Var{eroare}. V.Lista reprezint lista variabilelor
generate de V i eventual constanta eroare.
Atributul motenit Loc cu valori n mulimea {dec, inst} .V.Loc este dec dac V face
parte din subarborele cu rdcina D i este inst dac V face parte din subarborele cu
rdcina I.
Regulile semantice pentru calculul valorilor atributelor sunt date n tabelul de mai jos.
SINTAXA
SEMANTICA
if (eroare D.Dec)
then false else I.Rspuns
I.Tabel = D.Dec {eroare}

Z.Rspuns =

Z begin D ; I
end

D1 D2 ; type V
D type V
I1 I2 ; use V
I use V

V1 V2, v

D1.Dec = D2.Dec V.Lista


if(V.Lista D2.Dec)
then {eroare} else
V.Loc = dec
D.Dec = V.Lista; V.Loc = dec.
I1.Rspuns = if(V.Lista I1.Tabel)
then false else I2.Rspuns
I2.Tabel = I1.Tabel; V.Loc = inst.
I.Rspuns = if(V.Lista I.Tabel)
then true else false
V.Loc = inst
V1.Lista = V2.Lista {v}
if(V1.Loc = dec and v V2.Lista)
then eroare else ;

Gramatici cu atribute

95
V2.Loc = V1.Loc
V.Lista = {v}

Vv
Fie acum programul:

begin type x, a; use x ; use a, c end

Arborele de derivare pentru acesta este dat n figura 7.1.3. Din motive lesne de neles,
etichetele nodurilor corespunztoare terminalilor begin, end, type i use sunt
reprezentate n arbore respectiv prin b, e, t i u.
Z

Figura 7.1.3
Dac pentru I reprezentm atributele n ordinea I.Tabel, I.Rspuns iar pentru V n
ordinea V.Lista, V.Loc, atunci valorile calculate ale atributelor pentru acest arbore sunt
date n graful din figura 7.1.4. Nodurile acestui graf corespund apariiilor atributelor
neterminalilor Z, D, I i V din arborele de mai sus, iar arcele reprezint dependenele
ntre valorile acestor atribute prin regulile semantice.
false
x, a

x, a

false

x, a

dec

x, a

true

a, c

ins

dec

ins

ins

Figura 7.1.4

96

Analiza semantic
Z

Figura 7.1.5
Pentru programul:
begin type x, c; type x, z; use x; use z, x end

arborele de derivare este dat n figura 7.1.5 iar valorile atributelor n figura 7.1.6.

false
x,c,z,eroare

x, c

x, c

dec

dec

x,c,z

true

x, z

dec

x,c,z

true

z, x

ins

dec

ins

ins

Figura 7.1.6
6.1.3 Definiia gramaticilor cu atribute
Definiia 7.1.1 O gramatic cu atribute este un 4-uplu GA = (G, D, A, R) unde:
1. G = (V, T, Z, P) este o gramatic independent de context redus, numit gramatica
suport pentru GA. Notm prin = V 4 T vocabularul gramaticii. Aici Z reprezint
simbolul de start al gramaticii.
2. D = (M, F) este domeniul semantic format din:
2.1. M o familie finit de mulimi;
2.2. F o familie finit de funcii total definite i calculabile de forma
f : M1M2Mn M0 , unde Mi M, 0 i n.
3. A = (I, S, type) este o descriere a atributelor gramaticii, unde;

Gramatici cu atribute
97
3.1. I = 4xI(X) este mulimea atributelor motenite;
3.2. S = 4xI(X) este mulimea atributelor sintetizate;
3.3. Pentru orice atribut I 4 S, type() M este mulimea din domeniul
semantic n care poate lua valori;
3.4. Atributele motenite I(X) i cele sintetizate S(X) pentru simbolul X se consider
disjuncte, oricare ar fi X . De asemenea, notnd A (X) = I (X) 4 S (X), vom
considera A(X) 3 A(Y) = , X, Y , X Y, chiar dac, uneori, atribute ale
unor simboluri diferite au aceleai nume (ca n exemplele prezentate). Referirea
la atributul al simbolului X se face prin X..
3.5. Simbolul de start Z al gramaticii are un atribut sintetizat desemnat a reprezenta
semantica unei construcii w L(G).
4. R = 4pPR(P) este mulimea (finit) a regulilor semantice (reguli de evaluare a
atributelor). Dac producia p este de forma: Xp0 Xp1Xp2 Xpnp atunci o regul
semantic este de forma Xpk.a = f(Xpk1.a1, Xpk2.a2, , Xpkm.am,) unde :
4.1. f F, f : type(a1) type(a2) type(am) type(a) ;
4.2. Pentru k = 0 atunci a S(Xp0) cu precizarea c R(P) conine cte o regul i
numai una pentru fiecare atribut din S(Xp0).
4.3. Pentru k > 0 atunci a I(Xpk) iar R(p) conine cte o regul i numai una pentru
fiecare atribut din 41knpI(Xpk).
4.4. ai I(Xp0) 4 (41knpA(Xpk)), 1knp.
Exemplul 7.1.1 S punem n eviden elementele din definiia gramaticii cu atribute
pentru gramatica prezentat n paragraful 7.1.1.
1. G = ( {N, L, B}, {0, 1, .}, N, P) unde
P = {N L, N L.L, L LB, LB, B 0, B 1};
2. M = {N, Z, Q}, F = {c0, c1, id, suc, minus, suma, exp}, unde c0 i c1 sunt funciile
constante 0 respectiv 1, suc este funcia succesor, minus(x) = -x, suma(x, y) = x +
y, exp(x) = 2x. Domeniile acestor funcii sunt adecvate regulilor semantice
descrise.
3. A(N) = S(N) = {val}, type(val) = Q
S(L) = {val, lung}, type(val) = Q, type(lung) = N
I(L) = {scala}, type(scala) = Z
S(B) = {val}, type(val) = Q
I(B) = {scala}, type(scala) = Z
Observaie. Aparent I(L) = I(B) = {scala}. Trebue precizat ns c cele dou atribute
cu acelai nume sunt diferite: L.scala B.scala i acest lucru se vede din definirea
acestora prin regulile semantice. Acelai lucru este valabil pentru atributul val care apare
la S, L i B.
4. Regulile semantice sunt cele din tabela de la paragraful 7.1.1. Este uor de
verificat c au loc 6.1 6.4.
Exemplul 7.1.2 S punem n eviden elementele din definiia gramaticii cu atribute
pentru gramatica prezentat n 7.1.2.
1. G = ({Z, D, I, V}, {begin, end, tip, use, ;, a, b, }, S, P) unde
P ={Z begin D;Iend, D D;type V, D type V, I I; use V,
I use V, V v, V V, v}.
2. M = {Bool, 2Var, 2Var {eroare}, {dec, inst}} unde Bool ={true, false}. F conine toate
funciile folosite n tabelul ce definete regulile semantice.

98

Analiza semantic
3. A(Z) = S(Z) = {Rspuns}, type(Rspuns) = Bool
A(D) = S(D) = {Dec}, type (Dec) = 2Var {eroare}
S(I) = {Rspuns}, type(Rspuns) = Bool
I(I) = {Tabel}, type(Tabel) = 2Var
S(V) = {Lista}, type(Lista) = 2Var {eroare})
I(V) = {Loc}, type(Loc) = {dec, inst}
4. Regulile semantice sunt cele din tabela de la 7.1.2.

6.2 Grafuri ataate unei gramatici cu atribute


n figurile 7.1.2, 7.1.4 i 7.1.6 sunt puse n eviden nite grafuri ataate arborilor de
derivare pentru anumite cuvinte generate de gramatica suport a gramaticii cu atribute.
Aceste grafuri (i altele pe care le vom defini n continuare) joac un rol important n
verificarea corectitudinii definirii regulilor semantice. n esen o regul semantic
definete o relaie de dependen ntre atributele din gramatica respectiv, iar aceast
relaie definete un graf.
6.2.1 Graful DGp
Definiia 7.2.1 Fie GA = (G, D, A, R) o gramatic cu atribute i producia p dat de X0
X1X2 Xnp . Graful DGp = (X, E) asociat produciei p, numit graful de dependen al
produciei p, este graful n care:
nodurile sunt toate apariiile atributelor simbolurilor din producia p, adic X =
41 i np A(Xi);
arcele sunt de forma (Xi., Xj.) dac Xj. depinde printr-o regul semantic de
Xi. (existent n R(p)), adic:
E = {(Xi., Xj.) | Xj. = f(..., Xi., ...)R(p) }
Exemplul 7.2.1 Fie gramatica din exemplul 7.1.1. Atunci, grafurile ataate produciilor
gramaticii sunt prezentate n figurile 7.2.1 7.2.6
DG1:
B.v

DG2:
B.v

B.s

Figura 7.2.1
DG3:
L.v

L.l

B.v

B.s

Figura 7.2.2
DG4:
L.s

L.v

L.l

L.s

L.l

L.s

B.v

B.s
L.v

Figura 7.2.3
DG5:

Figura 7.2.4
DG6:

B.s

Traducere bazat pe sintax

99
N.v

N.v

L.v

L.l

L.v

L.s

L.l

Figura 7.2.5

L.s

L.v

L.s

L.l

Figura 7.2.6

Cititorul este invitat s construiasc grafurile DGp pentru gramatica din exemplul 7.1.2.
6.2.2 Graful BGp
Definiia 7.2.2 Fie GA = (G, D, A, R) o gramatic cu atribute i producia p dat de X0
X1X2 Xnp. Graful BGp asociat produciei p este graful n care:
nodurile sunt X1, X2, ..., Xnp, privite ca elemente distincte(dac un simbol se
repet n p, atunci apariiile lui se numeroteaz);
arcele sunt toate perechile (Xi, Xj) i numai acelea, pentru care exist un atribut
motenit al lui Xj care depinde (printr-o regul semantic) de un atribut
sintetizat al lui Xi , 1 i, j np.
Exemplul 7.2.2 S considerm gramatica din exemplul 7.1.2. Atunci graful BG1,
corespunztor produciei Z begin D;I end, este dat n figura 7.2.7. Pentru
gramatica din exemplul 7.1.1 graful BG6 este dat n figura 7.2.8.

L1

Figura 7.2.7

L2

Figura 7.2.8

6.2.3 Graful DGt


Definiia 7.2.3 Fie GA = (G, D, A, R) o gramatic cu atribute i t un arbore de derivare
n gramatica suport G. Graful DGt ataat arborelui t se definete recursiv astfel:
Dac t = p P atunci DGt = DGp;
Dac t este un arbore de adncime mai mare ca 1, cu rdcina etichetat X0 iar
producia ce se aplic n rdcin este p = X0 X1X2...Xnp, atunci DGt se obine
din DGp i DGti 1 i np, prin alipire: nodurile din DGp corespunztoare
atributelor lui Xi se identific respectiv cu nodurile din DGti corespunztoare
acelorai atribute ale lui Xi. n cazul n care un anume Xi este terminal, graful
DGti corespunztor are ca noduri atributele lui Xi i nu are nici un arc.
Exemplul 7.2.3 Pentru gramatica din exemplul 7.1.1 graful DGt pentru arborele din
figura 7.2.9 este dat n figura 7.2.10. El se obine din grafurile DG4 (corespunztor
produciei din rdcin) la care se alipesc grafurile DG4 i DG2 ca n figura 7.2.11.
L.v

L.l

L.v

L.l

L.s

L.l

L.s

B.v

L.v

L.s

B.v

B.s

B.s

100

Analiza semantic
Figura 7.2.9

Figura 7.2.10

L.v

L.v

L.l

L.s

L.v

L.l

L.s

B.v

B.s

L.v

L.l

L.s

B.v

B.s

L.l

L.s

B.v

B.s

Figura 7.2.11
Pentru arborele din figura 7.1.1 graful DGt este obinut n acelai mod i este dat n
figura 7.2.12.
N.v

L.v

L.v

L.v

L.l

L.s

L.s

B.v

L.s

L.l

B.v

L.l

B.v

L.v

B.s

L.v

B.s

L.l

B.v

L.s

L.l

L.s

B.v

B.s

B.s

B.s

Figura 7.2.12
Graful DGt pentru un arbore de derivare t complet (n care rdcina este etichetat cu
simbolul de start Z iar frunzele sunt etichetate cu terminali) se obine, se nelege, n
acelai mod. Dac acest graf nu are circuite, atunci el definete o ordine parial n
mulimea apariiilor atributelor n arborele t: dac (X., Y.) este un arc n DGt, atunci,
pentru a putea calcula valoarea lui Y. avem nevoie de valoarea lui X..
Definiia 7.2.4 O gramatic cu atribute GA este circular (sau nu este bine definit) dac
exist mcar un arbore de derivare t pentru care graful DGt are circuite. O gramatic cu
atribute GA este necircular (este bine definit) dac pentru orice arbore de derivare t graful
DGt nu are circuite.
Pentru o gramatic cu atribute necircular, dat un arbore de derivare t, deci un cuvnt w
L(G), se pot calcula valorile tuturor apariiilor (instanelor) atributelor din arborele t,
deci se poate determina semantica lui w. Aadar, dou din problemele importante ce se
pun referitor la o gramatic cu atribute sunt:
problema circularitii: dat GA, este ea necircular?
problema evalurii atributelor: dat GA necircular, s se construiasc o procedur
care, atunci cnd este dat un arbore t, s determine valorile tuturor apariiilor
atributelor etichetelor arborelui.

Traducere bazat pe sintax


101
nainte de a aborda problema circularitii, s mai definim nite grafuri ce vor fi utile
rezolvrii acesteia.

6.3 Metode de evaluare a atributelor


Un evaluator pentru o gramatic cu atribute este un program care, pentru un arbore de
derivare t al gramaticii suport, determin valorile tuturor apariiilor atributelor din
arborele t. Un generator de evaluator este un program care, pentru o gramatic cu
atribute ca intrare, o respinge (dac ea este circular) sau o accept i construiete pentru
ea un evaluator.
n funcie de modul cum sunt evaluate atributele, un evaluator poate fi:
recursiv sau iterativ;
aplicativ sau imperativ;
tree-walking sau tree-free;
pass-oriented sau visit-oriented;
Un tip de evaluator corespunde unei clase de gramatici cu atribute pentru care se poate
construi acel tip de evaluator.Vom trece n revist cteva metode de evaluare mpreun
cu clasele de gramatici pentru care se aplic aceste metode.
6.3.1 Evaluatorul rezultat din definiia gramaticii
Pentru a putea calcula valorile tuturor apariiilor atributelor ntr-un arbore de derivare t,
graful DGt trebuie s fie liber de circuite, aa cum a fost artat n 7.2.3. S considerm
cazul general al relaiilor pentru a defini o ordine de evaluare.
Dac V este o mulime finit iar R VV este o relaie peste V, atunci (V, R) este un
graf orientat. S notm prin R+ nchiderea tranzitiv a relaiei R. Atunci R este necircular
(sau aciclic, sau ireflexiv) dac v V, (v, v) R+. Relaia T VV este o ordine total
pe V dac :
T+ = T (T este tranzitiv);
T este necircular;
T este total: x V, y V , x = y sau (x, y) T sau (y, x) T.
Pentru orice ordine total T pe mulimea V, exist o enumerare a elementelor lui V, s
zicem v1, v2, , vn, astfel ca (vi, vj) T dac i numai dac i < j. Aceast enumerare se
numete secven de evaluare.
Spunem c o ordine total T este o ordine de evaluare a relaiei R pe V, dac R T. Dac
v1, v2, , vn este secvena de evaluare pentru T, atunci, (vi, vj) R implic i < j. Un
element v V se numete element minimal pentru R dac nici un element din V nu este n
relaie cu v ( w V, (w, v) R). Analog, v V este maximal pentru R dac el nu este
n relaie cu nici un element ( w V, (v,w) R).
Teorema 7.4.1 (de evaluare) Fie R o relaie pe V. R este necircular dac i numai dac
R admite o ordine de evaluare.
Demonstraie. Dac R admite o ordine de evaluare T, aceasta este necircular (din
definiia ordinii totale). Cum R T rezult c i R este necircular. Invers, s
presupunem c R este o relaie necircular pe V. Urmtorul algoritm, numit de sortare
topologic, determin o secven de evaluare pe R, ceea ce este suficient pentru o ordine
de evaluare pe R:
Algoritmul 7.4.1
Intrare:
Mulimea V i relaia R VV necircular;
Ieire:
O secven de evaluare eval = {v1, v2, , vn}, unde |V| = n;
Metoda:
1. eval = ; k = 0;

102

Analiza semantic
2. while (eval V) {
3.
Se alege v un element minimal din V eval;
4.
k = k + 1;
5.
vk = v;
6.
eval = eval{vk};
}

S observm c n V eval exist ntotdeauna un element minimal pentru c R este


necircular pe V, deci i restricia sa la V eval este necircular. Algoritmul este finit iar
din modul cum s-au adugat elementele n eval rezult c aceasta reprezint o secven
de evaluare pe R.

Algoritmul 7.4.1 poate fi folosit pentru proiectarea unui evaluator pentru o gramatic cu
atribute care este necircular. Considernd un arbore de derivare t, aplicm algoritmul
precedent pentru relaia ce definete graful DGt pentru a afla ordinea de evaluare a
atributelor n t.
Evaluatorul P1
Intrare:
Gramatica cu atribute GA = (G, D, A, R) necircular;
Graful DGt = (N, E) pentru un arbore t (N este mulimea
tuturor apariiilor atributelor din t);
Ieire:
Valorile tuturor apariiilor atributelor n t;
Metoda:
1. eval = ; k = 0;
2. while(eval N) {
3.
Se alege v un element minimal din N eval;
4.
evaluare(v);//se aplica o regul semantica
5.
k = k + 1;vk = v; eval = eval {vk};
}

n linia 4 din algoritm se apeleaz o procedur care evalueaz, conform cu regulile


semantice, valorile atributului v. Acest lucru este posibil pentru c valorile atributului v
depind de valorile unor atribute ce au fost calculate nainte, conform secvenei de
evaluare.
Evaluatorul tocmai prezentat este important pentru faptul c reprezint o caracterizae a
gramaticilor cu atribute necirculare: o gramatic cu atribute este necircular dac i
numai dac P1 este un evaluator pentru aceasta. P1 este cunoscut n literatur ca i
defining evaluator el exprim faptul c gramatica respectiv este bine definit.

6.4 Traducere bazat pe sintax


Una din cele mai frecvente tehnici folosite n analiza semantic este cea a evalurii
atributelor n timpul analizei sintactice ascendente (LR). Am vzut n paragraful
precedent cum pot fi evaluate atributele n cadrul unui parser SLR(1). Dac toate
atributele gramaticii sunt sintetizate atunci lucrurile se simplific: la fiecare reducere cu o
producie de forma A se aplic regulile semantice pentru evaluarea atributelor
(sintetizate) ale lui A. Pentru analiza semantic a limbajelor de programare n acest mod,
precum i pentru obinerea codului (intermediar) echivalent cu codul surs, se folosesc
aciunile semantice ataate regulilor sintactice, care sunt aplicate atunci cnd analizorul
sintactic realizeaz o reducere. Se obine astfel o structur care nu este altceva dect o
gramatic cu atribute n care exist un atribut care are ca valori secvene de cod
intermediar. Aa cum o s vedem n continuare, determinarea valorilor atributelor se
poate face prin aplicarea uor proceduri specifice. Regulile semantice nu mai sunt chiar
de forma pe care am specificat-o n definiia gramaticii cu atribute ci poate aprea, pe
lng definiii ale atributelor cu ajutorul unor funcii calculabile, i apelul unor proceduri

Traducere bazat pe sintax


103
care genereaz cod intermediar. De aceea numim aciuni semantice aceste proceduri
ataate regulilor sintactice. O astfel de gramatic cu atribute este cunoscut n literatur
sub numele de schem de traducere bazat (orientat) pe sintax. Schemele de traducere bazate
pe sintax sunt deosebit de utile celor ce implementeaz limbaje de programare; ntr-o
astfel de schem poate fi implementat generarea de cod intermediar pornind de la
structura sintactic a limbajului surs. Odat obinut codul intermediar, acesta urmeaz a
fi transformat n cod obiect pentru o main anume. n acest paragraf vom prezenta mai
nti cteva tipuri de cod intermediar pentru ca apoi s vedem cum se abordeaz
traducerea structurilor sintactice importante din limbajele de programare.
6.4.1 Reprezentri intermediare
Analiza sintactic ofer la ieire reprezentarea programului ca arbore de derivare
(parsare) n gramatica ce descrie sintaxa limbajului. Pentru a crea codul obiect echivalent
cu cel surs, n general se folosete o reprezentare intermediar a acestuia, un cod
intermediar. Tipurile de cod intermediar utilizate n procesul de compilare sunt:
Notaia postfix (Notaia polonez invers). Aceast reprezentare se genereaz uor
de la arborele abstract printr-o traversare postordine, sau cu un parser bottom-up.
Avantajul acestei reprezentri este acela c evaluarea expresiilor se face uor i eficient
folosind o stiv. Instruciunile pot fi privite ca i operatori (de o anume aritate), nct
reprezentarea unui program se face uniform. De pild, dac notm prin ? operatorul
ternar if_then_else, atunci expresia
if a + b > c then x = a c else x = a b

are reprezentarea n notaia postfix astfel:

a b + c > x a c - = x b c - = ?

Forma postfix a unui program se poate obine folosind Yacc (vezi exemplul 6.1.3).
Exist i un dezavantaj major n aceast abordare: dac ne referim la reprezentarea
operatorului if_then_else, atunci evaluarea expresiei presupune evaluarea tuturor
subexpresiilor, chiar dac acest lucru nu este necesar. Pentru optimizare se introduc
nite operatori de salt condiionat n cadrul reprezentrii.
S

Figura 7.5.1
Arborele sintactic (abstract). Acesta se obine din arborele (concret) de parsare n care
neterminalii sunt nlocuii cu operatorii corespunztori (dac ne referim la expresii). S
analizm, spre exemplu, arborele de parsare (figura 7.5.1) pentru atribuirea x = a * b
+ a * b ce se obine n gramatica:
S id = E E E + E | E * E | (E) | id
Arborele sintactic corespunztor este dat n figura 7.5.2.

104

Analiza semantic
=

Figura 7.5.2
n general, arborele sintactic se poate defini astfel:
Pentru o regul sintactic de forma A a (regul ce produce terminali) arborele are
un singur nod etichetat cu a.
Pentru o regul de forma A B op C (regul corespunztoare operatorilor binari),
arborele are o rdcin etichetat cu op, un subarbore stng corespunztor regulii ce
rescrie pe B i un subarbore drept corespunztor regulii ce rescrie pe C.
Pentru o regul de forma A op B (regul corespunztoare operatorilor unari)
arborele are o rdcin etichetat cu op i un subarbore (stng) corespunztor regulii
ce rescrie pe B.
Pentru o regul corespunztoare unui operator ternar, de exemplu cea care descrie
instruciunea if, S if B then S else S, arborele sintactic are o rdcin
(etichetat cu numele operatorului, de exemplu if) i trei subarbori corespunztori,
n ordine, celor trei operanzi.
n acest mod, oricrui program scris ntr-un limbaj de programare i putem ataa un
arbore sintactic. Acest arbore se poate obine n procesul analizei sintactice ascendente
aplicnd aciuni semantice corespunztoare fiecrei reduceri efectuate de analizorul
sintactic.
Codul cu trei adrese Aceast reprezentare este avantajoas pentru cazul n care
instruciunile limbajului intermediar au cel mult 3 variabile i cel mult un operator.
Acestea se traduc uor n limbaj de asamblare. Reprezentarea se face prin quadruple n
care, pe lng adresele celor doi operanzi i a rezultatului, este pstrat i codul operaiei.
De pild, instruciunea
delta := b*b 4*a*c
se traduce prin codul:
T1 = b*b
T2 = 4*a
T3 = T2*c
T4 = T2 T3
delta = T4
care este reprezentat de quadruplele:
0
1
2
3

Operator
*
*
*
-

Argument 1
b
4
T2
T2

Argument 2
b
a
c
T3

Rezultat
T1
T2
T3
T4

Traducere bazat pe sintax


4

105
T4

delta

Aici T1, T2,... sunt nume temporare care sunt adugate la tabela simbolurilor pe msur
ce sunt create nct, n cmpurile relative la argumente i rezultat, apar pointeri la tabela
de simboluri corespunztoare numelor ce apar acolo.
6.4.2 Traducerea codului n notaie postfix
O schem de traducere pentru obinerea unui cod postfix este relativ uor de obinut. O
s exemplificm acest lucru pentru atribuiri cu expresii aritmetice generate de gramatica:
A id
S A := E E E op E | (E) | id
Dac vom nota prin id.Loc pointerul ctre id n tabela de parsare (numele
identificatorului) i prin E.Cod traducerea expresiei E n cod intermediar n notaia
postfix, atunci schema de traducere este dat n tabela de mai jos, unde prin || am notat
operaia de concatenare a codurilor:
Regula sintactic
S A := E
E E1 op E2
E (E1)
E id
A id

Aciunea semantic
S.Cod = A.Cod || E. Cod || :=
E.Cod = E1.Cod || E2.Cod ||op
E.Cod = E1.Cod
E.Cod = id.Loc
A.Cod = id.Loc

Aceast schem de traducere poate fi implementat n cadrul unui parser bottom-up


preciznd care sunt aciunile ce se aplic la fiecare reducere:
Regula sintactic
S A := E
E E1 op E2
E (E1)
E id
A id

Aciunea (segment de program)


{ print := }
{ print op }
{}
{ print id.Loc }
{ print id.Loc }

Traducerea textului d := b + a*c cu un parser ascendent este dat mai jos:


Stiva
#
#d
#A
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#S

b
E
E+
E+a
E+E
E+E*
E+E*c
E+E*E
E+E
E

Intrare
d := b+a*c#
:= b+a*c#
:= b+a*c#
b+a*c#
+a*c#
+a*c#
a*c#
*c#
*c#
c#
#
#
#
#
#

Aciune
Deplasare
Reduce si
Deplasare
Deplasare
Reduce si
Deplasare
Deplasare
Reduce si
Deplasare
Deplasare
Reduce si
Reduce si
Reduce si
Reduce si
ACCEPTARE

Aadar, forma postfix a textului dat este d b a c * + :=.

d
print
:=
b
print
+
a
print
*
c
print
print
print
print

d
b
a
c
*
+
:=

106
6.4.3 Traducerea codului n arbore sintactic

Analiza semantic

Arborele de derivare (parsare) conine, n general informaii redundante (relativ la


generarea de cod obiect) care pot fi eliminate obinnd astfel un arbore mai economic ce
reprezint codul surs. Acest arbore se numete arbore sintactic (vezi figura 7.5.2).
S artm cum se obine arborele abstract pentru aceeai gramatic ce genereaz
instruciunile de atribuire:
A id
S A := E E E op E | (E) | id
S notm prin A.Val, E.Val respectiv S.Val acest arbore corepunztor lui A, expresiilor
E respectiv asignrilor S. S considerm arbinar(op, st, dr) o funcie ce
construiete un arbore cu rdcina op i subarborii st respectiv dr, iar frunza(id) o
funcie ce construiete arborele cu rdcina id fr subarbori. Fiecare din aceste funcii
returneaz pointeri la arborii construii. Atunci schema de traducere este urmtoarea:
Reguli sintactice
S A := E
E E1 op E2
E (E1)
E id
A id

Aciuni semantice
S.Val = arbinar(:=, A.Val, E.Val)
E.Val = arbinar(op, E1.Val, E2. Val)
E.Val = E1.Val
E.Val = frunza(id.Loc)
A.Val = frunza(id.Loc)

Traducerea aceluiai text d := b + a*c, ca n paragraful anterior, cu un parser


ascendent este dat mai jos:
Stiva
#
#d
#A
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#A:=
#S

b
E
E+
E+a
E+E
E+E*
E+E*c
E+E*E
E+E
E

Intrare
d := b+a*c#
:= b+a*c#
:= b+a*c#
b+a*c#
+a*c#
+a*c#
a*c#
*c#
*c#
c#
#
#
#
#
#

Aciune
Deplasare
Reduce si
Deplasare
Deplasare
Reduce si
Deplasare
Deplasare
Reduce si
Deplasare
Deplasare
Reduce si
Reduce si
Reduce si
Reduce si
ACCEPTARE

d
t1=frunza(d)
:=
b
t2=frunza(b)
+
a
t3=frunza(a)
*
c
t4=frunza(c)
t5=arbinar(*,t3,t4)
t6=arbinar(+,t2,t5)
t7=arbinar(:=,t1,t6)

Arborele construit este cel din figura 7.5.3. Traducerea codului obiect n arbore abstract
este utilizat n proiectarea de interpretoare dar i de compilatoare folosind generatoare
de parsere yacc sau bison. In paragraful 7.6 prezentm un exemplu de interpreter
construit n acest mod.

Traducere bazat pe sintax

107
:=

Figura 7.5.3
6.4.4 Traducerea codului n secvene de quadruple
Codul cu trei adrese este o secven de instruciuni simple care conin cel mult trei
referine la o tabel de simboluri. Odat transformat codul surs n cod cu trei adrese,
traducerea n cod main este relativ simpl deoarece, n general, exist o coresponden
ntre instruciunile codului intermediar cu trei adrese i cele ale mainii. Avantajul este
c, pn la acest nivel, proiectarea compilatorului nu depinde de maina pentru care se
implementeaz. Instruciunile din codul cu trei adrese se implementeaz ca i structuri
care conin patru cmpuri: pe lng cele trei adrese se reprezint i operatorul ce
intervine n instruciune. Iat cele mai uzuale instruciuni ale codului cu trei adrese:
A = B op C
A = op B
goto L
if A goto L
if A rel B goto L
A = B[I]
A[I] = B

n acest paragraf dorim s dezvoltm o schem de traducere care s transforme un text


surs n cod intermediar cu trei adrese. De exemplu, codul:
while (a != b)
if(a < b) then b = b a else a = a - b

va fi transformat n codul intermediar urmtor:


101
102
103
104
105
106
107
108
109
110
111

if a != b goto 103
go to 111
if a < b goto 105
goto 108
T1 = b a
b = T1
goto 101
T2 = a b
a = T2
goto 101
...

Vom considera limbajul descris de urmtoarea gramatic:


SA
| begin L end
| if B then S
| if B then S else S
| while B do S
LL;S

108

Analiza semantic

|S
A id = E
E E + E |E E |E * E| E / E | - E | ( E ) | id
B B or B | B and B | not B | ( B )
| id < id | id > id | id == id | id != id | id
Acestei sintaxe i vom aduga o semantic (aciuni semantice) care s permit traducerea
programelor n cod intermediar.
Traducerea instruciunilor de atribuire
Vom ncepe descrierea schemei de traducere cu cea pentru instruciunile de atribuire.
Intenia este de a traduce un text de forma delta = b*b 4*a*c n secvena:
T1 = b * b
T2 = 4*a
T3 = T2*c
T4 = T1- T3
delta = T4
Pentru a obine aceast traducere s definim nite atribute pentru simbolurile E i id:
E.Nume este numele ce pstreaz valoarea expresiei E. Pentru evaluarea
expresiei vom folosi nume temporare care s pstreze valorile subexpresiilor.
Aceste nume le vom nota cu T1, T2, i ele sunt furnizate, n aceast ordine, de
apelul succesiv al unei funcii pe care o notm newtemp()i care se poate
implementa uor.
id.Nume este numele instanei identificatorului id respectiv valoarea constantei
atunci cnd id este instaniat cu o constant.
Pentru obinerea unei instruciuni din codul intermediar folosim o funcie pe care o
notm GEN( I ) unde I este o instruciune. De pild, GEN(E.Nume = E1.Nume +
E2.Nume) va genera instruciunea T1 = b * b dac E.Nume este T1, E1.Nume i
E2.Nume sunt b. Cu acestea schema de traducere se descrie astfel:
Reguli sintactice
A id = E
E E1 + E2
E E1 - E2
E E1 * E2
E E1 / E2
E -E1
E (E1)
E id

Aciuni semantice
GEN(id.Nume = E.Nume)
E.Nume = newtemp()
GEN(E.Nume = E1.Nume + E2.Nume)
E.Nume = newtemp()
GEN(E.Nume = E1.Nume - E2.Nume)
E.Nume = newtemp()
GEN(E.Nume = E1.Nume * E2.Nume)
E.Nume = newtemp()
GEN(E.Nume = E1.Nume / E2.Nume)
E.Nume = newtemp()
GEN(E.Nume = - E1.Nume )
E.Nume = E1.Nume
E.Nume = id.Nume

Codul intermediar se obine n timpul parsrii folosind un parser bottom-up mpreun


cu aceast schem de traducere: de fiecare dat cnd se aplic o reducere (regul
sintactic), se aplic i aciunile semantice corespunztoare. Vom exemplifica obinerea
codului intermediar pentru instruciunea delta = b*b 4*a*c. Cuvntul ce trebuie

Traducere bazat pe sintax


109
analizat este id = id*id id*id*id unde id.Nume are respectiv valorile delta, b, b, 4, a, c.
Aceste valori sunt luate din tabela identificatorilor i a constantelor construite de
analizorul lexical. De fapt, id.Nume pot fi reprezentai ca i pointeri la tabela
simbolurilor i pot fi pstrai n stiva de parsare mpreun cu simbolurile
corespunztoare: o intrare n stiv este o pereche (X, X.Nume). n tabela urmtoare este
dat ntreaga analiz sintactic i semantic:
Intrare
delta = b*b-4*a*c#
= b*b-4*a*c#
b*b-4*a*c#
*b-4*a*c#
*b-4*a*c#
b-4*a*c#
-4*a*c#
-4*a*c#
-4*a*c#
4*a*c#
*a*c#
*a*c#
a*c#
*c#
*c#
*c#
c#
#
#
#
#
#

Simbol stiv
id
id=
id=id
id=E
id=E*
id=E*id
id=E*E
id=E
id=Eid=E-id
id=E-E
id=E-E*
id=E-E*id
id=E-E*E
id=E-E
id=E-E*
id=E-E*id
id=E-E*E
id=E-E
id=E
A

Simbol.Nume
delta
delta_
delta_b
delta_b
delta_b_
delta_b_b
delta_b_b
delta_T1
delta_T1_
delta_T1_4
delta_T1_4
delta_T1_4_
delta_T1_4_a
delta_T1_4_a
delta_T1_ T2
delta_T1_ T2_
delta_T1_ T2_c
delta_T1_ T2_c
delta_T1_ T3
delta_T4

Aciune
deplasare
deplasare
deplasare
reducere
deplasare
deplasare
reducere
reducere
deplasare
deplasare
reducere
deplasare
deplasare
reducere
reducere
deplasare
deplasare
reducere
reducere
reducere
reducere

Cod

T1=b*b

T2=4*a

T3= T2*c
T4= T1-T3
delta = T4

Traducerea expresiilor logice


Evaluarea expresiilor logice poate fi fcut n diverse moduri. Dac se codific valorile
de adevr prin valori numerice (ca n limbajul C ), atunci evaluarea se face analog cu
evaluarea expresiilor aritmetice. n acest caz o schem de traducere pentru expresii
logice poate fi obinut uor din schema de traducere a expresiilor aritmetice. O alt
modalitate este aceea de a traduce expresia logic ntr-o succesiune de instruciuni (cod
intermediar) de forma celor care conin goto sau if i de a obine valorile adevrat
repectiv fals prin atingerea unor poziii n acest cod intermediar. De exemplu, expresia:
a < b or b > c and not d
ar putea fi tradus n codul:
101 if a < b goto ?
102 goto 103
103 if b > c goto 105
104 goto ?
105 if d goto ?
106 goto ?
cu precizarea c ieirile (intele lui goto) din acest cod la liniile 101 i 106 sunt
corespunztoare valorii adevrat pentru expresia dat iar cele de la 104 i 105 sunt
corespunztoare valorii fals. Dac aceast expresie apare n contextul unui program, de
pild:
if(a < b or b > c and not d ) then x = x + y else x = x - y

110
atunci traducerea acestuia ar putea fi:
101 if a < b goto 107
102 goto 103
103 if b > c goto 105
104 goto 110
105 if d goto 110
106 goto 107
107 T1 = x + y
108 x = T1
109 goto 113
110 T2 = x y
111 x = T2
112 goto 113
113

Analiza semantic

Vom indica n continuare o schem de traducere a unei expresii logice ntr-o secven de
cod intermediar ce conine instruciuni de salt condiionat (if A goto L, if A rel B goto
L) i salt necondiionat (goto L). n acest cod, unele din instruciuni vor avea intele L
ctre o locaie dintr-o mulime notat B.True n cazul n care expresia B are valoarea
adevrat iar altele ctre o locaie dintr-o mulime B.False n cazul n care expresia B are
valoarea fals. B.True i B.False vor fi aadar atribute ale simbolului B, care au ca valori
liste de pointeri ctre quadruplele ce formeaz codul intermediar. Dac, de exemplu,
avem expresia B = B1 or B2 atunci B.True este, deocamdat, B1.True pentru c, odat ce
am evaluat expresia B1 i am constatat c are valoarea adevrat, atunci E are valoarea
adevrat. Dac B1 are valoarea fals, atunci trebuie evaluat expresia B2 pentru a obine
valoarea lui B, nct B.False va fi B1.False iar B. True este reuniunea celor dou mulimi
(liste) B1.True i B2.True. Aici apare urmtoarea problem: in quadruplele din B1.False
trebuie ca inta L s aib ca valoare pointerul la quadrupla de nceput al codului lui B2.
Ori acest pointer nu este cunoscut nainte de a termina generarea codului pentru B1.
Soluia este de a genera instruciuni goto care nu au completat inta L; aceasta va fi
stabilit atunci cnd se face o reducere cu regula B B1 or B2 pentru c n acest punct
se cunoate codul generat att pentru B1, ct i pentru B2. Asemntor se petrec lucrurile
la expresiile de forma B1 and B2. sau la descrierea semanticii instruciunilor if sau while.
Pentru a descrie o schem de traducere n aceast idee, avem nevoie de nite funcii
(vom pstra denumirea din AhU83):
makelist(i): funcia creeaz o nou list de quadruple care conine doar lista
cu indexul i (pointer la tabloul quadruplelor ce se genereaz) i returneaz
pointer la lista creat;
merge(p1, p2): funcia concateneaz listele pointate de p1 i p2 i returneaz
pointer la noua list obinut;
backpatch(p, i): funcia completeaz cu i intele instruciunilor goto din
quadruplele listei pointat de p.
Vom nota de asemenea cu NEXTQUAD un pointer ce pstreaz, la un moment dat,
numrul primei quadruple ce trebuie generat, i care va permite identificarea punctului
de intrare n codul unei subexpresii. Acest lucru se poate realiza prin introducerea unui
neterminal M n sintaxa expresiilor, care s marcheze nceputul codului unei subexpresii
prin atributul M.Quad (=NEXTQUAD). Astfel, regulile sintactice B B or B i B
B and B se transform n B B or M B, B B and M B i M . Cu aceste
pregtiri, schema de traducere pentru expresii logice este urmtoarea (am notat prin rel
un operator relaional):

Traducere bazat pe sintax


Reguli sintactice
Aciuni semantice
backpatch(B
.False,
M.Quad);
1
B B1 or M B2

111

B.True = merge(B1.True, B2.True);


B.False = B2.False;
backpatch(B1.True, M.Quad);
B.True = B2.True;
B.False = merge(B1.False, B2.False);
B.True = B1.False ;
B.False = B1.True;
B.True = B1.True;
B.False = B1.False;
B.True = makelist(NEXTQUAD);
B.False = makelist(NEXTQUAD + 1);
GEN( if id1.Nume rel id2.Nume go to );
GEN(go to );
B.True = makelist(NEXTQUAD);
B.False = makelist(NEXTQUAD + 1);
GEN( if id.Nume go to );
GEN(go to );
M.Quad = NEXTQUAD;

B B1 or M B2
B not B1
B ( B1 )
B id1 rel id2
B id

S observm c am tratat aici un caz particular, cnd expresiile relaionale sunt doar de
forma id rel id. Cititorul este invitat s trateze cazul general al sintaxei B E rel E unde
E este o expresie aritmetic.
Dac realizm analiza sintactic a expresiei
a < b or b > c and not d
i aplicm aciunile semantice la fiecare reducere, obinem rezultatele din tabela de mai
jos. Pentru variabila B vom nota prin (q1, q2, ; p1, p2, ) valorile atributelor B.True
respectiv B.False.
Intrare

Simbol stiv

Valorile atributelor

a < b or b > c and not d


#
< b or b > c and not d
#
b or b > c and not d #
or b > c and not d #
or b > c and not d #
b > c and not d #
b > c and not d #
> c and not d #
c and not d #
and not d #
and not d #
not d #
not d #
d#
#
#

id

id <
id < id
B
B or
B or M
B or M id
B or M id >
B or M id > id
B or M B
B or M B and
B or M B and M
B or M B and M not
B or M B and M not id
B or M B and M not B

B or M B and M B

#
#

B or M B
B

a_
a_b
(101; 102)
(101; 102) _
(101; 102) _103
(101; 102) _103b
(101; 102) _103b_
(101; 102) _103b_c
(101; 102) 103(103; 104)
(101; 102) 103(103; 104)_
(101; 102) 103(103; 104)_105
(101; 102) 103(103; 104)_105_
(101; 102) 103(103; 104)_105_d
(101; 102) 103(103; 104)_105_
(105;106)
(101; 102) 103(103; 104)_105
(106;105)
(101; 102) 103(106; 104,105)
(101, 106; 104,105)

(1)

101 if a<b goto


102 goto

Cod

(1)

(2)

(3)
(4)
(5)

112

Analiza semantic
(2)
(3)
(4)
(5)

103 if b < c goto


104 goto
105 if d goto
106 goto
n lista{103} se pune 105 ca int pentru goto
Se determin conform aciunilor semantice B.True i B.False
n lista{102} se pune 103 ca int pentru goto
Se determin conform aciunilor semantice B.True i B.False

Urmare a acestor operaii codul obinut este:


101 if a < b goto
102 goto 103
103 if b > c goto 105
104 goto
105 if d goto
106 goto
iar listele ataate lui B, corespunztoare valorilor adevrat respectiv fals, sunt:
B.True = {101, 106}, B. False = {104, 105}.
Traducerea instruciunilor
S dezvoltm mai nti o schem de traducere pentru instruciunile de control. Dac ar
trebui s traducem o instruciune de forma if B then S else S, atunci codul obinut
trebuie s conin codul pentru B, urmat de codul pentru primul S apoi codul pentru al
doilea S. n determinarea codului pentru B se obin, aa cum am vzut mai sus, listele
B.True i B.False. intele pentru goto din B.True trebuie s coincid cu eticheta de
nceput a codului pentru primul S iar cele din B.False trebuie s coincid cu eticheta de
nceput a codului celui de-al doilea S. De asemenea, n codul obinut trebuie s precizm
i faptul c, dup execuia instruciunilor desemnate de S (att primul, ct i al doilea)
urmeaz instruciunea de dup if. De aceea vom considera un atribut S.Next care
reprezint un pointer la o list de quadruple ce conin goto, condiionat sau nu, la
instruciunea ce urmeaz dup S n ordinea execuiei. Mecanismul de completare a
acestor inte va fi cel de la traducerea expresiilor logice nct va trebui i aici s
considerm un neterminal ce marcheaz nceputul codului lui S i un neterminal N care
printr-un atribut N.Next permite saltul dup execuia ramurii then a instruciunii if.
Aadar, schema de traducere pentru instruciunile if se poate descrie astfel:
1. S if B then M1 S1 N else M2 S2

backpatch(B.True, M1.Quad);
backpatch(B.False, M2.Quad);
S.Next = merge(S1.Next, N.Next, S2.Next);

2. N
3. M

N.Next = makelist(NEXTQUAD);
GEN(goto);
M.Quad = NEXTQUAD;

4. S if B then M S1

backpatch(B.True, M.Quad);
S.Next = merge(B.False, S1.Next);

Traducere bazat pe sintax


113
n mod analog tratm cazul instruciunii while. Dac sintaxa este while B do S atunci va
trebui s stabilim n quadruplele din B.True din codul lui B, ca int, nceputul codului
lui S, iar n quadruplele din B.False inta trebuie s fie nceputul codului instruciunii ce
urmeaz lui while. De asemenea, trebuie s ne asigurm c, dup execuia lui S, se reia
evaluarea lui B. Atunci, traducerea acestei instruciuni se face dup schema:
5. S while M1 B do M2 S1
backpatch(S1.Next, M1.Quad);
backpatch(B.True, M2.Quad);
S.Next = B.False;
GEN(goto M1.Quad);

S observm c apare pentru prima dat generarea unei instruciuni goto care are inta
fixat: goto M1.Quad. Asta asigur faptul c, n toate cazurile, dup execuia lui S1
urmeaz evaluarea din nou a expresiei B.
Pentru traducerea celorlalte instruciuni din limbajul specificat la nceputul paragrafului
lucrurile sunt ct se poate de simple:
6. S begin L end
7. S A

S.Next = L.Next;
S.Next = makelist(); // lista vid

8. L L1; M S
9. L S

backpatch(L1.Next, M.Quad);
L.Next = S.Next;
L.Next = S.Next;

S mai observm c doar n cazurile N i la instruciunea while sunt generate noi


quadruple; codul este asociat n rest expresiilor aritmetice sau logice. Traducerea
instruciunilor nseamn n plus asocierea intelor pentru goto care au fost generate fr
a fi complete (apelul funciei backpatch).
Dac aplicm un parser bottom-up pentru textul:
while (a != b)
if(a < b)then
b = b a
else
a = a b

i realizm inclusiv traducerea sa conform schemei de traducere prezentat, obinem un


cod intermediar de forma celui anunat la nceputul acestui paragraf:
101
102
103
104
105
106
107
108
109
110
111

if a != b goto 103
go to 111
if a < b goto 105
goto 108
T1 = b a
b = T1
goto 101
T2 = a b
a = T2
goto 101

6.5 Proiectarea unui interpreter cu flex i yacc


Vom descrie n acest paragraf modul cum se poate proiecta un interpreter folosind
instrumentele flex i bison. Dorim s interpretm programe de forma:

114

Analiza semantic
a = 36;
b = 42;
while (a != b)
if(a > b) a = a - b;
else b = b - a;
print a;

Pentru aceasta vom descrie fiierele de intrare pentru flex respectiv bison. Fiierul
pentru flex descrie unitile lexicale care vor fi transmise analizorului sintactic construit
de bison. Fisierul flex este urmtorul:
/* Fisierul glang.l intrare pentru flex. Se foloseste comanda
flex glang.l dupa comanda bison d glang.y
*/
%{
#include <stdlib.h>
#include "glang.h"
#include "glang.tab.h"
void yyerror(char *);
%}
%%
[a-z]

{ yylval.sIndex = *yytext - 'a'; return VAR; }

[0-9]+
{ yylval.iVal = atoi(yytext);
[-()<>=+*/;{}.] { return *yytext;}
">="
{ return GE;
}
"<="
{ return LE;
}
"=="
{ return EQ;
}
"!="
{ return NE;
}
"while"
{ return WHILE;
}
"if"
{ return IF;
}
"else"
{ return ELSE;
}
"print"
{ return PRINT;
}
[ \t\n]+

return INT; }

; /* Spatiile sunt ignorate */

.
yyerror(" Caracter ilegal!\n");
%%
int yywrap(void) {
return 1;
}

Fiierul glang.h descrie structurile de date folosite pentru reprezentarea constantelor, a


identificatorilor precum i a tipurilor de noduri din arborele abstract care va fi costruit
de anlizorul sintactic. Identificatorii sunt definii aici ca fiind litere i de aceea am folosit
extern int sym[26] ca tabel a identificatorilor. O s indicm mai trziu cum se
poate aborda cazul general.
/* Fisierul glang.h
*/
typedef enum { typeCon, typeId, typeOper } nodeEnum;
/* constante */
typedef struct {
int val;
} conNodeType;
/* identificatori */

/* valoarea constantei */

Proiectarea unui interpreter cu flex i yacc


typedef struct {
int i;
} idNodeType;

115

/* indice in tabela de simboluri */

/* operatori */
typedef struct {
int oper;
int nops;
struct nodeTypeTag *op[1];
} oprNodeType;
typedef struct nodeTypeTag {
nodeEnum type;
union {
conNodeType con;
idNodeType id;
oprNodeType opr;
};
} nodeType;
extern int sym[26];

/* operator */
/* nr. operanzi */
/* operanzi */

/* tipul nodului */
/* constante */
/* identificatori */
/* operatori */

Fiierul glang.tab.h este obinut de ctre yacc (bison) atunci cnd se folosete opiunea
d. Acest fiier definete codurile numerice pentru unitile lexicale:
/* Fisierul glang.tab.h creat
# ifndef BISON_GLANG_TAB_H
# define BISON_GLANG_TAB_H
#ifndef YYSTYPE
typedef union {
int iVal;
/*
char sIndex;
/*
nodeType *nodPtr;
/*
} yystype;
# define YYSTYPE yystype
# define YYSTYPE_IS_TRIVIAL 1
#endif
# define
INT
257
# define
VAR
258
# define
WHILE 259
# define
IF
260
# define
PRINT 261
# define
IFX
262
# define
ELSE 263
# define
GE
264
# define
LE
265
# define
EQ
266
# define
NE
267
# define
UMINUS
268

de bison cu optiunea d

*/

valoare intreaga */
index in tabela de simboluri */
pointer la un nod in arbore */

extern YYSTYPE yylval;


#endif /* not BISON_GLANG_TAB_H */

Fiierul de intrare pentru bison descrie sintaxa limbajului de programare mpreun cu


aciunile semantice ce trebuiesc aplicate n cazul reducerilor. Aici este vorba de apelul
funciei de construire a arborelui abstract corespunztor. Iat mai jos fiierul de intrare
pentru bison:
/* Fisierul glang.y intrare pentru bison. Se foloseste
comanda : bison d glang.y
*/
%{

116

Analiza semantic

#include
#include
#include
#include

<stdio.h>
<stdlib.h>
<stdarg.h>
"glang.h"

/* prototipurile functiilor */
nodeType *nodOper(int oper, int nops, ...);
/* nod operator in arbore
*/
nodeType *nodId(int i);
/* nod frunza identificator
*/
nodeType *nodCon(int value);
/* nod frunza constanta
*/
void freeNode(nodeType *p);
/* eliberare memorie
*/
int interpret(nodeType *p);
/* functia de interpretare
*/
int yylex(void);
/* functia creata de flex
*/
void yyerror(char *s);
int sym[26];
%}

/* tabela de simboluri */

%union {
int iVal;
char sIndex;
nodeType *nodPtr;
};

/* valoare intreaga */
/* index in tabela de simboluri */
/* pointer la un nod in arbore */

%token <iVal> INT


%token <sIndex> VAR
%token WHILE IF PRINT
%nonassoc IFX
/* rezolvarea ambiguitatii if-then-else */
%nonassoc ELSE
%left GE LE EQ NE '>' '<'
%left '+' '-'
%left '*' '/'
%nonassoc UMINUS
%type <nodPtr> stmt expr stmt_list
%%
program: function
;
function: function stmt
| /* NULL */
;
stmt:

{ exit(0); }
{ interpret($2); freeNode($2); }

';'
{ $$ = nodOper(';', 2, NULL, NULL); }
| expr ';'
{ $$ = $1; }
| PRINT expr ';'
{ $$ = nodOper(PRINT, 1, $2); }
| VAR '=' expr ';'
{$$ = nodOper('=', 2, nodId($1), $3);}
| WHILE '(' expr ')' stmt
{ $$ = nodOper(WHILE, 2, $3, $5); }
| IF '(' expr ')' stmt %prec IFX
{ $$ = nodOper(IF, 2, $3, $5); }
| IF '(' expr ')' stmt ELSE stmt
{ $$ = nodOper(IF, 3, $3, $5, $7); }
| '{' stmt_list '}' { $$ = $2; }
;

Proiectarea unui interpreter cu flex i yacc


stmt_list:
stmt
{ $$ = $1; }
| stmt_list stmt
{ $$ = nodOper(';', 2, $1, $2); }
;
expr:
INT
{ $$ = nodCon($1); }
| VAR
{ $$ = nodId($1); }
| '-' expr %prec UMINUS
{ $$ = nodOper(UMINUS, 1, $2); }
| expr '+' expr
{ $$ = nodOper('+', 2, $1, $3); }
| expr '-' expr
{ $$ = nodOper('-', 2, $1, $3); }
| expr '*' expr
{ $$ = nodOper('*', 2, $1, $3); }
| expr '/' expr
{ $$ = nodOper('/', 2, $1, $3); }
| expr '<' expr
{ $$ = nodOper('<', 2, $1, $3); }
| expr '>' expr
{ $$ = nodOper('>', 2, $1, $3); }
| expr GE expr
{ $$ = nodOper(GE, 2, $1, $3); }
| expr LE expr
{ $$ = nodOper(LE, 2, $1, $3); }
| expr NE expr
{ $$ = nodOper(NE, 2, $1, $3); }
| expr EQ expr
{ $$ = nodOper(EQ, 2, $1, $3); }
| '(' expr ')'
{ $$ = $2; }
;
%%
#define SIZEOF_NODETYPE ((char *)&p->con - (char *)p)
nodeType *nodCon(int value) {
nodeType *p;
size_t nodeSize;
/* alocare memorie pentru noul nod */
nodeSize = SIZEOF_NODETYPE + sizeof(conNodeType);
if ((p = malloc(nodeSize)) == NULL)
yyerror("out of memory");
/* copiere valoare constanta */
p->type = typeCon;
p->con.val = value;
}

return p;

nodeType *nodId(int i) {
nodeType *p;
size_t nodeSize;
/* alocare memorie pentru noul nod */
nodeSize = SIZEOF_NODETYPE + sizeof(idNodeType);
if ((p = malloc(nodeSize)) == NULL)
yyerror("out of memory");

117

118

Analiza semantic
/* copiere valoare indice */
p->type = typeId;
p->id.i = i;
return p;

}
nodeType *nodOper(int oper, int nops, ...) {
va_list ap;
nodeType *p;
size_t nodeSize;
int i;
/* allocare memorie pentru noul nod */
nodeSize = SIZEOF_NODETYPE + sizeof(oprNodeType) +
(nops - 1) * sizeof(nodeType*);
if ((p = malloc(nodeSize)) == NULL)
yyerror("out of memory");
/* copiere informatii functie de nops */
p->type = typeOper;
p->opr.oper = oper;
p->opr.nops = nops;
va_start(ap, nops);
for (i = 0; i < nops; i++)
p->opr.op[i] = va_arg(ap, nodeType*);
va_end(ap);
return p;
}
void freeNode(nodeType *p) {
int i;

if (!p) return;
if (p->type == typeOper) {
for (i = 0; i < p->opr.nops; i++)
freeNode(p->opr.op[i]);
}
free (p);

void yyerror(char *s) {


fprintf(stdout, "%s\n", s);
}
int main(void) {
yyparse();
return 0;
}

Conform regulilor urmtoare (din fiierul tocmai prezentat):


function: function stmt
{ interpret($2); freeNode($2); }
| /* NULL */
;

un program n limbajul surs descris este un ir de instruciuni (stmt). Fiecare din aceste
instruciuni este interpretat: apelul funciei interpret pentru argumentul $2 care este
valoarea atributului lui stm, adic arborele construit pentru stmt, face ca aceast
instruciune s fie executat. Prin freeNode($2) se elibereaz memoria alocat la
construirea arborelui amintit. Funcia interpret() este prezentat mai jos :

Proiectarea unui interpreter cu flex i yacc


/* Functia interpret(nodeType *p)
#include <stdio.h>
#include "glang.h"
#include "glang.tab.h"

119
*/

int interpret(nodeType *p) {


if (!p) return 0;
switch(p->type) {
case typeCon:
return p->con.val;
case typeId:
return sym[p->id.i];
case typeOper:
switch(p->opr.oper) {
case WHILE:
while(interpret(p->opr.op[0]))
interpret(p->opr.op[1]); return 0;
case IF:
if (interpret(p->opr.op[0]))
interpret(p->opr.op[1]);
else if (p->opr.nops > 2)
interpret(p->opr.op[2]);
return 0;
case PRINT: printf("%d\n", interpret(p->opr.op[0]));
return 0;
case ';':
interpret(p->opr.op[0]);
return interpret(p->opr.op[1]);
case '=':
return sym[p->opr.op[0]->id.i] =
interpret(p->opr.op[1]);
case UMINUS: return -interpret(p->opr.op[0]);
case '+':
return interpret(p->opr.op[0]) +
interpret(p->opr.op[1]);
case '-':
return interpret(p->opr.op[0])
interpret(p->opr.op[1]);
case '*':
return interpret(p->opr.op[0]) *
interpret(p->opr.op[1]);
case '/':
return interpret(p->opr.op[0]) /
interpret(p->opr.op[1]);
case '<':
return interpret(p->opr.op[0]) <
interpret(p->opr.op[1]);
case '>':
return interpret(p->opr.op[0]) >
interpret(p->opr.op[1]);
case GE:
return interpret(p->opr.op[0]) >=
interpret(p->opr.op[1]);
case LE:
return interpret(p->opr.op[0]) <=
interpret(p->opr.op[1]);
case NE:
return interpret(p->opr.op[0]) !=
interpret(p->opr.op[1]);
case EQ:
return interpret(p->opr.op[0]) ==
interpret(p->opr.op[1]);
}
}
return 0;
}

n urma lansrii comenzilor bison d glang.y i flex glang.l se obin fiierele lexyy.c
(analizorul lexical) i glang.tab.c (analizorul sintactic) care, mpreun cu interpreterul din
fiierul interpret.c, vor fi compilate cu un compilator de limbaj C i se obine astfel un
interpreter pentru limbajul surs descris. Dac lansm acest interpreter pentru
programul prezentat la nceputul seciunii, se obine rezultatul 6.

Bibliografie

121

Bibliografie
[AnG97] Andrei t., Grigora Gh. : Tehnici de compilare. Lucrri de laborator. Editura
Universitii Al.I.Cuza, Iai, 1995
[App97] Appel, A., Modern Compiler Implementation in C, Cambridge University Press, 1997
[ASU86] Aho, A.V., Sethi, R., Ullman, J.D.: Compilers: Principles, Techniques, and Tools.
Addison-Wesley Publishing Company, U.S.A., 1986
[Gri86] Grigora, Gh.: Limbaje formale i tehnici de compilare. Editura Universitii
Al.I.Cuza, Iai, 1986
[Gri05] Grigora, Gh.: Constructia compilatoarelor - Algoritmi fundamentali, Editura
Universitatii "Al. I. Cuza" Iasi, ISBN 973-703-084-2, 274 pg., 2005.
[JuA97] Jucan, T, Andrei, t.: Limbaje formale i teoria automatelor. Culegere de probleme.
Editura Universitii Al.I.Cuza, Iai, 1997
[Juc99] Jucan, T., Limbaje formale i automate, Editura MatrixRom, 1999.
[er97] erbnai, L.D., Limbaje de programare i compilatoare, Editura Academiei, Bucureti,
1987.

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