Documente Academic
Documente Profesional
Documente Cultură
Proiectarea Compilatoarelor 2007 PDF
Proiectarea Compilatoarelor 2007 PDF
FACULTATEA DE INFORMATIC
DEPARTAMENTUL DE NVMNT LA DISTAN
Gheorghe Grigora
PROIECTAREA
COMPILATOARELOR
2006 2007
Adresa autorului:
Prefa
Cuprins
1.
LIMBAJE..................................................................................................................2
1.1
1.1.1
1.1.2
1.1.3
1.2.1
1.2.2
1.2.3
1.2.4
1.2
ANALIZA LEXICAL...........................................................................................23
2.1
2.2
2.3
2.4
Generatorul Lex.......................................................................................................................... 32
3.1
3.2
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
4.1
4.2
4.3
4.4
4.5
4.6
ii
Prefa
5.1
5.2
6
6.1
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
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
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
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
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:
6
Prefa
Un exemplu de program n minilimbajul de programare descris mai sus este urmtorul:
begin a = b + c ; d = 5 - a end
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;
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.
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.
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*).
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
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
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 .
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
if
if
if
I if e then I else I
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
; . :
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>
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
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.
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
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;
}
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>
%}
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;
}
35
36
Analiza lexical
st
dr
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.
37
~
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.
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
calculul (uv#, #, ) d* (v#, )#, ), atunci n gramatica G are loc derivarea u),
st
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
Corolarul 3.3.1 Dac n parserul descendent are loc (w#, S#, ) d (#, #, ) atunci n
+
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
||, din u1)1, conform ipotezei inductive are loc (u1v1#, #, ) d* (v1#, )1#, 1)
st
=
=
Corolarul 3.3.2 Dac n G are loc derivarea S w atunci n parserul descendent are
st
S uA u1 ux
st
st
st
*
S uA u2 uy
st
st
st
st
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
S uA u1 uau1v
st
st
st
*
S uA u2 uau2v
st
st
st
st
st
S uA u1 uau1v
st
st
st
st
st
S uA u2 u uau2
st
st
42
Analiza lexical
precedentul.
st
st
1 , 2 i atunci a FOLLOW(A).
st
st
st
st
S uA u1 u uau2
st
st
S uA u2 u uau2
st
st
S uA u1 uav i
st
st
st
S uA u2 uav
st
st
st
)
st
if((FIRST(Xi+1) FIRST(A)){
43
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;
+
) {
st
5. if(i == n && Xn
6.
)
st
FIRST() = FIRST( ) 4 { } ;
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
44
Analiza lexical
S trecem acum la determinarea mulimilor FOLLOW(A), A V. Reamintind c
*
FOLLOW(S) pentru c S S.
st
st
st
{ } FOLLOW (B). Acest aspect, de fapt, se poate exprima mai simplu prin:
Dac A B P atunci FIRST()-{ } FOLLOW (B).
st
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
45
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.
46
Analiza lexical
S
S
E
B
E
B
5. B begin SC end
6. C
7. C ;SC
FIRST(X)
FOLLOW(X)
a begin
a begin
end ;
end ;
end ;
end
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
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
+
49
FIRST(X)
( a + -
( a
* /
( a
FOLLOW(X)
)
)
+ - )
+ - )
*/+ - )
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
+
51
S S Au u = u
dr
dr
S S Au u = v = v
dr
dr
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 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.
dr
Derivarea corespunztoare
S A aAa abAba
S A aAa abAba abaAaba
S A aAa abAba abbAbba
SA
S *A
A *bAb S A bAb
SAc
A *c
53
S Au 1B2u = B2u
dr
dr
dr
dr
dr
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)
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
dr
54
Analiza lexical
*
dr
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
u
v
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).
S Au u = u
dr
dr
dr
dr
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
pentru c S S.
dr
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
dr
dr
dr
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.
Lema 5.2.3 Algoritmul 5.2.2 descris mai sus este corect: automatul M obinut este
determinist i 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
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*.
61
Intrare
a1
ai
an
Stiva
tm
TABELA
DE
PARSARE
CONTROL
tm-1
.
.
t0
p1
p2
Ieire
Figura 5.3.1
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,
a
5
(
4
S
1
E
2
T
3
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.
dr
'
'
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
~
'
dr
dr
'
'
Corolarul 5.3.2 Dac n gramatica G, care este LR(0), avem S w, atunci n parserul
dr
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
S Au u = u
dr
dr
S Bv v = v
dr
dr
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
numai dac S w.
dr
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
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).
71
S Au 12u
dr
dr
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
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.
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, #)
(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
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);
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
INTRARE
**aa#
*aa#
aa#
a#
INTRARE
ACIUNE
deplasare D4
deplasare D4
deplasare D5
eroare
ACIUNE
IEIRE
IEIRE
**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
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*, #)
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
a
D5
ACIUNE
*
D4
S
1
GOTO
L
2
R
3
77
acceptare
R5
R2
D6
D5
D4
R4
D5
R4
D4
R3
R5
R3
R5
R1
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).
79
80
82
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;}
84
%}
%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:
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
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.
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
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; }
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
+
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
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
Z.Rspuns =
Z begin D ; I
end
D1 D2 ; type V
D type V
I1 I2 ; use V
I use V
V1 V2, v
Gramatici cu atribute
95
V2.Loc = V1.Loc
V.Lista = {v}
Vv
Fie acum programul:
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.
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
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
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.
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};
}
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};
}
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
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
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
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
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)
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.
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
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
...
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
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
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):
111
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
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)
Cod
(1)
(2)
(3)
(4)
(5)
112
Analiza semantic
(2)
(3)
(4)
(5)
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);
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;
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
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]
[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; }
.
yyerror(" Caracter ilegal!\n");
%%
int yywrap(void) {
return 1;
}
/* valoarea constantei */
115
/* 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 */
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 */
{ 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; }
;
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);
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 :
119
*/
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.