Sunteți pe pagina 1din 24

1. Introducere

1.1. Limbaje formale

În matematică, logică şi în ştiinţa calculatoarelor, un limbaj formal este o mulţime de cuvinte (sau şiruri) de lungime fintă, peste un alfabet finit.

Pentru o prezentare mai intuitivă, se consideră un alfabet format numai din două elemente {a, b}. Un şir posibil peste acest alfabet este"ababba", iar un limbaj tipic peste alfabetul ce conţine acest şir poate fi mulţimea tuturor şirurilor care conţine un număr egal de elemente de tipul "a" şi "b".

Un limbaj admite şi cuvântul vid, notat uzual cu e, ε ori λ.

Deşi alfabetul e finit, iar fiecare şir are o lungime finită, un limbaj poate avea un număr posibil infinit de cuvinte.

Exemple de limbaje formale:

mulţimea tuturor cuvintelor peste alfabetul {a, b},

mulţimea { a n | n număr prim},

mulţimea tuturor programelor corecte din punct de vedere sintactic într-un limbaj de programare.

Un limbaj formal se poate specifica folosind o varietate de metode:

şiruri produse de o gramatică formală;

şiruri produse de o expresie regulată;

şiruri acceptate de un automat.

O proprietate tipică a unui limbaj formal este măsura în

care se poate decide dacă un cuvânt dat aparţine sau nu

limbajului.

O gramatică formală constă dintr-o mulţime de reguli care

descriu un limbaj formal. Ideea de bază este că se pot genera şiruri pornind de la un simbol special de start, folosind reguli care indică felul în care se pot înlocui anumite combinaţii de

simboluri cu altele.

De exemplu, presupunând că simbolul de start este S şi dispunem de regulile (producţiile):

S

S

aSb

ba

simbolul "S" se poate rescrie în forma "aSb" înlocuind pe "S"cu "aSb" (prima regulă), apoi "aSb" se poate rescrie "aaSbb" aplicând din nou aceeaşi regulă. Acest lucru se repetă până când rezultatul conţine numai simbolurile alfabetului, de exemplu "abab" (a doua regulă).

Limbajul peste gramatica dată constă din toate şirurile care se pot genera prin acest procedeu: ba, abab, aababb, aaababbb, etc.

Există clase de gramatici şi limbaje care se pot deriva cu acestea, în care se aplică unele restricţii. Ele au denumiri speciale şi se vor studia separat. Un sistem de clasificare uzual pentru gramatici este ierarhia Chomsky, care este o mulţime de 4 tipuri de gramatici definite de Noam Chomsky în anul 1950. Diferenţa dintre aceste tipuri de gramatici constă în folosirea unor producţii cu restricţii din ce în ce mai stricte care permit exprimarea unui număr mai redus de limbaje formale.

În construcţia translatoarelor se folosesc mai mult două tipuri mai importante de gramatici: gramatici libere de context

2

şi gramatici regulate. Limbajele care se pot descrie folosind aceste gramatici se numesc limbaje libere de context, respectiv limbaje regulate.

În capitolele următoare se vor aborda în detaliu aceste gramatici, precum şi limbajele descrise de ele şi se vor aplica în proiectarea translatoarelor.

1.2. Translatoare

1.2.1. Noţiunea de translator

Limbajele de nivel înalt au o serie de avantaje în raport cu limbajele de asamblare. Pentru a putea însă folosi limbaje de nivel înalt, trebuie să existe posibilitatea de a converti programele scrise în aceste limbaje într-o formă binară. Această necesitate a condus la dezvoltarea translatoarelor şi compilatoarelor, programe care acceptă o reprezentare textuală a unui algoritm exprimat printr-un program sursă şi produc o reprezentare a aceluiaşi algoritm, exprimat într-un alt limbaj:

limbajul obiect sau un limbaj echivalent.

Translatorul este un program care traduce programele scrise de utilizator într-un limbaj, în programe echivalente într- un alt limbaj. Dacă acestea din urmă sunt programe în cod maşină, sau un limbaj apropiat de limbajul calculatorului, translatorul se numeşte compilator.

În figura 1.1 se reprezintă funcţiile principale ale unui translator.

iar

programul în cod maşină obţinut se numeşte program obiect.

Programul

utilizator

se

numeşte

program

sursă,

3

Între cele două programe trebuie să existe o relaţie de echivalenţă în ceea ce priveşte efectul lor asupra calculatorului.

Translator Program utilizator Raportare erori
Translator
Program
utilizator
Raportare
erori
Translator Program utilizator Raportare erori Program scris în alt limbaj Figura 1.1. Funcţiile

Program scris în alt limbaj

Figura 1.1. Funcţiile principale ale unui translator

Un translator poate fi definit formal ca o funcţie pentru care domeniul de definiţie este limbajul sursă, iar mulţimea valorilor este limbajul obiect, ori un limbaj echivalent (destinaţie).

Execuţia unui program în limbaj simbolic are loc în două faze: compilare sau translaţie şi execuţie propriu-zisă.

Program Program Compilator sursă obiect a. Faza de translaţie Program Rezultate Date de obiect intrare
Program
Program
Compilator
sursă
obiect
a. Faza de translaţie
Program
Rezultate
Date de
obiect
intrare
b. Faza de execuţie

Figura 1.2. Execuţia unui program în limbaj simbolic

În faza de translaţie, calculatorul execută programul compilator, iar în faza de execuţie propriu-zisă, calculatorul execută programul obiect, adică citirea datelor iniţiale,

4

prelucrarea lor şi memorarea sau tipărirea rezultatelor (figura

1.2)

Pentru scrierea unui translator, trebuiesc definite foarte bine atât limbajul sursă, cât şi limbajul destinaţie. Aceasta înseamnă că ambele limbaje trebuie să fie formale.

În acest sens, un limbaj are două aspecte principale: sintaxă şi semantică. Sintaxa stabileşte care text este corect din punct de vedere gramatical, iar semantica stabileşte modul în care se poate deriva semnificaţia unui text corect din punct de vedere gramatical.

Pentru a descrie sintaxa unui limbaj formal, s-au dezvoltat numeroase formalisme şi instrumente software accesibile. Pentru descrierea semanticii însă, formalismele şi instrumentele software existente sunt mai complexe şi mai dificil de implementat.

În dezvoltarea translatoarelor, sunt implicate cel puţin trei limbaje: limbajul sursă de translatat, limbajul obiect sau destinaţie şi limbajul gazdă folosit la implementarea translatorului. Dacă translatarea are loc în mai multe etape, pot exista şi alte limbaje intermediare. Desigur, limbajul gazdă şi limbajul obiect nu sunt cunoscute de utilizatorul limbajului sursă.

Totuşi, trebuie făcută observaţia că un compilator nu necesită un limbaj destinaţie real. De exemplu, compilatoarele Java generează cod pentru o maşină virtuală numită "Java Virtual Machine" (JVM), iar interpretorul JVM interpretează apoi instrucţiunile JVM fără nici o translatare ulterioară.

5

1.2.2. Diagrame T

Pentru descrierea programelor şi în particular a translatoarelor, există o reprezentare schematică consacrată, numită diagramă T, introdusă de Bratman în 1961.

O diagramă T pentru un program general este de forma prezentată în figura 1.3, iar o diagramă T pentru un translator general este de forma prezentată în figura 1.4:

Numele programului Date de intrare
Numele programului
Date de intrare

Date de ieşire

Limbajul de

implementare

t e d e i e ş i r e Limbajul de implementare Figura 1.3. Diagramă

Figura 1.3. Diagramă T pentru un program general

Numele translatorului Limbaj sursă
Numele translatorului
Limbaj sursă

Limbaj destinaţie

Limbajul gazdă de implementare a translatorului

ţie Limbajul gazd ă de implementare a translatorului Figura 1.4. Diagramă T pentru un translator general
ţie Limbajul gazd ă de implementare a translatorului Figura 1.4. Diagramă T pentru un translator general

Figura 1.4. Diagramă T pentru un translator general

Cele mai multe compilatoare nu produc cod maşină cu adrese fixe, ci o formă relocabilă. Secţiunile astfel compilate sunt legate cu ajutorul unor editoare de legături (linker), care pot fi privite ca ultima etapă în procesul de translatare. Combinarea acestor secţiuni într-un program executabil se face prin rezolvarea referinţelor externe (înlocuirea numelor simbolice cu adrese de memorie) şi adăugarea eventual a

6

rutinelor din bibliotecile standard. Limbajele care permit compilarea separată a părţilor unui program, depind esenţial de existenţa editoarelor de legături.

arăta

interdependenţa dintre translatoare şi editoare de legături, aşa

Diagramele

T

pot

fi

combinate

pentru

a

cum se prezintă în figura 1.5. COMPILATOR.EXE Program în cod Program în relocabil L2 limbaj
cum se prezintă în figura 1.5.
COMPILATOR.EXE
Program în cod
Program în
relocabil L2
limbaj sursă L1
Limbajul gazdă de
implementare a
compilatorului
Cod relocabil
LINK.EXE
L2
Bibliotecă de
PROG.EXE
programe
Limbajul gazdă
de implementare
a editorului
de legături

Figura 1.5. Interdependenţa dintre un translator şi un editor de legături

1.3. Clasificarea translatoarelor

1.3.1. Asamblorul

Termenul de asamblor este asociat cu translatoarele care transformă instrucţiuni scrise în limbaj de nivel coborât, în cod

7

maşină, care poate fi executat direct. În limbaj de asamblare, de obicei, liniile individuale ale programului sursă corespund cu o instrucţiune la nivel maşină. Rolul asamblorului este să convertească reprezentările simbolice ale instrucţiunilor în configuraţii de biţi, care sunt echivalentele instrucţiunilor în cod-maşină.

Macroasamblorul este un asamblor care permite utilizarea macrodefiniţiilor. El utilizează o primă trecere pentru colectarea macrodefiniţiilor.

Rezultatul asamblării este un text în formă binară, în care referinţele externe sunt păstrate în formă simbolică, în tabele asociate secţiunilor. Codul binar al secţiunilor este însoţit de informaţii care indică locul referinţelor relocabile pentru ca, în momentul încărcării, valorile acestora să se poată transforma în referinţe absolute.

1.3.2. Compilatorul

Compilatorul este un translator care traduce instrucţiuni de nivel înalt în cod maşină, care poate fi executat direct. În cazul limbajelor de nivel înalt, liniile individuale din programul sursă corespund cu mai multe instrucţiuni la nivel maşină. Caracteristica principală a compilatorului este fatul că el translatează o secvenţă întreagă de cod înainte ca orice execuţie să fie posibilă.

1.3.3. Preprocesorul

Preprocesorul este un translator care traduce dintr-un superset al unui limbaj de nivel înalt, în limbajul de nivel înalt original, sau care face simple substituiri de texte înainea procesului de translatare propriu-zis. De exemplu, există

8

numeroase preprocesoare de FORTRAN structurat care translatează din versiuni structurate ale limbalului FORTRAN în FORTRAN obişnuit.

1.3.4. Translatorul de nivel înalt

Translatorul de nivel înalt este un translator care traduce un limbaj de nivel înalt într-un alt limbaj de nivel înalt, pentru care există deja compilatoare sofisticate, destinate unui număr mare de maşini.

1.3.5. Interpretorul

În unele medii interactive, există necesitatea execuţiei unor părţi ale aplicaţiei fără a fi necesară pregătirea acesteia în ansamblu, ori se permite utilizatorului modificarea dinamică a acţiunii următoare. În aceste cazuri, se foloseşte un interpretor.

Interpretorul este un translator care acceptă la intrare un program sursă şi îl execută direct, fără a produce aparent nici un cod obiect.

În fond, interpretorul preia din programul sursă instrucţiunile, una câte una, le analizează, le aduce la o formă mai simplă, numită cod intermediar sau pseudocod, şi apoi le execută una câte una, simulând execuţia în limbaj sursă. Forma intermediară executată de interpretor este de fapt un alt limbaj mai simplu, mai apropiat de limbajele de asamblare.

Evident, pentru ca un astfel de scenariu să poată funcţiona, este necesară impunerea unor restricţii severe programului sursă. Astfel, nu se pot folosi structuri complexe de program, cum ar fi de exemplu proceduri imbricate.

9

Interpretorul execută algoritmul original prin simularea unei maşini virtuale pentru care codul intermediar este efectiv codul maşină.

Distincţia dintre codul maşină şi pseudocod este ilustrată în figura 1.6:

Instrucţiunile

limbajului sursă

în figura 1.6: Instruc ţiunile limbajului surs ă Etapa 1 Cod intermediar (pseudocod) Etapa 2 Interpretor

Etapa 1

în figura 1.6: Instruc ţiunile limbajului surs ă Etapa 1 Cod intermediar (pseudocod) Etapa 2 Interpretor

Cod intermediar

(pseudocod)

Etapa 2 Interpretor Instrucţiuni în Execuţie cod maşină
Etapa 2
Interpretor
Instrucţiuni în
Execuţie
cod maşină

Figura 1.6. Codul maşină şi pseudocodul

Dacă se parcurge numai etapa 1 de translatare, pseudocodul este intrare în interpretor. Dacă se parcurg etapele 1 şi 2, rezultatul este un program obiect cu instrucţiuni în cod maşină, care poate fi lansat în execuţie independent.

Principalul avantaj al folosirii unui interpretor este portabilitatea. Într-adevăr, orice maşină reală poate fi privită ca un interpretor specializat, ce preia din programul sursă instrucţiunile una câte una, le analizează şi le execută una câte una. Într-o maşină reală, această execuţie se face prin hardware. Implementând o astfel de maşină virtuală pe diferite tipuri de maşini, interpretorul devine poartabil, astfel încât el se poate folosi pe oricare dintre acestea.

Suplimentar, se poate conclude că se pot scrie programe care permit unei maşini reale să emuleze orice altă maşină reală, cu dezavantajul reducerii vitezei de execuţie. Aceste programe sunt numite emulatoare şi sunt uzual folosite în proiectarea de noi maşini şi a software-ului care se va executa pe acestea.

10

1.3.6. Compilatoare incrementale

Compilatoarele incrementale îmbină calităţile compilatoarelor cu cele ale interpretoarelor. Programul sursă este divizat de compilator în mici porţiuni numite incremente. Acete incremente prezintă o oarecare independenţă sintactică şi semantică unele faţă de altele. Compilatorul traduce aceste incremente, fiind permisă intervenţia utilizatorului atât în timpul compilării, cât şi în timpul execuţiei.

Una din cele mai cunoscute aplicaţii de compilator incremental portabil este "PascalP" (Zurich 1981) care constă din 3 componente:

Un compilator Pascal, scris într-o variantă foarte completă a limbajului, numită Pascal-P. Scopul acestui compilator este translatarea programelor sursă Pascal-P într-un limbaj intermediar foarte bine definit şi documentat, numit "P- code", care este "codul maşină" pentru un calculator ipotetic, bazat pe stivă, calculator numit "P-machine".

O versiune compilată a primului compilator, astfel încât prima sarcină a compilatorului devine propria sa compilare.

Un interpretor pentru limbajul P-code, scris în Pascal. Acest interpretor a servit în principal ca model pentru scrierea unor programe similare pentru alte maşini, în scopul emulării unei maşini ipotetice P-machine.

1.3.7. Decompilatorul şi dezasamblorul

Decompilatorul şi dezasamblorul sunt translatoare care au ca intrare un cod obiect şi regenerează codul sursă într-un limbaj de nivel mai înalt. În timp ce această regenerare se poate realiza destul de uşor pentru limbaje de asamblare, este mult

11

mai dificil de implementat pentru limbaje de nivel înalt cum ar fi C, Pascal.

1.4. Fazele translaţiei.

Un translator execută o succesiune de operaţii asupra programului sursă, pentru a-l transforma în reprezentări din ce în ce mai apropiate de limbajul destinaţie. Din acest motiv, procesul de translaţie se divide într-o serie de faze.

O fază transformă programul sursă dintr-o reprezentare în

alta.

Înainte de producerea efectivă a codului obiect, este necesar să se verifice dacă codul sursă este corect, adică dacă respectă întocmai restricţiile impuse de limbajul sursă. În acest context, procesul de translatare se divide într-o fază analitică, urmată de o fază sintetică.

În faza analitică se analizează programul sursă şi se determină dacă el corespunde cu restricţiile sintactice şi static semantice impuse de limbajul sursă, iar în faza sintetică se generează efectiv codul obiect.

Componentele translatorului care îndeplinesc aceste faze majore se numesc "front end" şi "back end". Prima este total independentă de maşină, în timp ce a doua este foartedependentă de maşina destinaţie.

ei

componente mai mici. Acestea sunt reprezentate în figura 1.7.

Secţiunea de gestionare caractere comunică exteriorul, prin sistemul de operare, pentru citirea caracterelor din textul sursă. Cum setul de caractere şi gestiunea fişierelor diferă de la un

Fiecare

din

fazele

prezentate,

conţine

la

rândul

12

sistem la altul, această fază este dependentă de maşină sau/şi de sistemul de operare.

1.4.1. Analizorul lexical

Analizorul lexical, cunoscut şi sub denumirea de Scanner, preia textul sursă de la secţiunea de gestionare caractere sub forma unei secvenţe continue de caractere, şi le grupează în cuvinte distincte numite atomi (tokens), identificând totodată tipul atomilor.

Atomii pot avea diferite tipuri, precum identificatori, şiruri, constante numerice, cuvinte cheie, operatori.

De asemenea, analizorul lexical elimină din textul sursă comentariile şi spaţiile (tabulatori, blankuri, CR, LF, etc.).

De exemplu, dacă textul sursă conţine expresia (1.1):

a[index] = 7 + 2

(1.1)

analizorul lexical identifică următorii atomi:

a

identificator

[

paranteză dreaptă deschisă

index

identificator

]

paranteză dreaptă închisă

=

operator de atribuire

7

număr

+

operator de adunare

2

număr

Se impune observaţia că analizorul lexical recunoaşte cuvintele individuale din programul sursă (atomii), dar nu verifică

13

dacă acestea sunt folosite corect, ignorând contextul programului în ansamblu.

Cod sursă

Gestiune caractere Analizor lexical (Scanner) Fază analitică (Front end) Atomi Analizor sintactic (Parser)
Gestiune caractere
Analizor lexical
(Scanner)
Fază analitică
(Front end)
Atomi
Analizor sintactic
(Parser)
Gestiune tabele
Arbore sintactic
Analizor semantic
Arbore sintactic adnotat
Generator de cod
Raportare erori
intermediar
Fază sintetică
(Back end)
Optimizator de cod
Generator de cod
final
Cod obiect

Figura 1.7. Structura unui translator

14

1.4.2. Analizorul sintactic

Analizorul sintactic, cunoscut şi sub denumirea de Parser, verifică dacă programul sursă de la intrare este corect din punct de vedere sintactic, prin gruparea atomilor în structuri sintactice, cum ar fi de exemplu un arbore sintactic.

În esenţă, analizorul sintactic grupează atomii în propoziţii, pentru a verifica dacă secvenţa de atomi identificaţi de analizorul lexical sunt în ordine corectă. Analizorul sintactic analizează contextul fiecărui atom şi grupează atomii în declaraţii, instrucţiuni, instrucţiuni de control etc. Uzual, simultan cu această analiză, el construieşte şi arborele sintactic al structurilor obţinute. Un arbore este o reprezentare potrivită pentru un segment de program, deoarece el facilitează transformări ale programului care se produc în următoarele faze ale compilării.

sintactic produce un

arbore sintactic reprezentat în figura 1.8.

Pentru

exemplul

(1.1),

analizorul

expresie expresie de atribuire expresie = expresie expresie expresie indicială aditivă expresie [ expresie ]
expresie
expresie
de atribuire
expresie
= expresie
expresie
expresie
indicială
aditivă
expresie
[
expresie
] expresie
+
expresie
număr
identificator
identificator
număr
a index
7 2

Figura 1.8. Arborele sintactic pentru expresia (1.1)

15

1.4.3. Analizorul semantic

Pe durata analizei sintactice, de obicei se produce şi o analiză semantică. Aceasta presupune verificări legate de compatibilitatea dintre tipurile datelor şi operaţiile în care ele sunt implicate, precum şi de respectarea domeniilor de vizibilitate impuse de limbajul sursă. Pentru a putea îndeplini această sarcină, compilatorul foloseşte o tabelă de simboluri.

Analizorul semantic produce un arbore sintactic adnotat cu atribute, în care atributele sunt informaţii suplimentare produse de analizorul semantic.

Pentru exemplul considerat, analizorul semantic produce un arbore sintactic adnotat de forma prezentată în figura 1.9.

expresie

de atribuire
de atribuire
expresie indexare expresie aditivă întreg întreg identificator identificator număr număr a index 7 2
expresie indexare
expresie aditivă
întreg
întreg
identificator
identificator
număr
număr
a
index
7 2
tablou de întregi
întreg
întreg
întreg

Figura 1.9. Arborele sintactic adnotat pentru expresia (1.1)

1.4.4. Raportare erori

Compilatorul poate detecta erori doar în fazele de analiză lexicală, sintactică şi semantică. După aceste faze, compilarea se face în mod sigur corect. Aşadar, erorile detectabile de

16

compilator,

sunt

de

3

tipuri,

corespunzător

cu

cele

3

faze

prezentate.

Tratarea unei erori constă în detectarea ei, emiterea unui mesaj de eroare, revenirea din eroare şi continuarea procesului de compilare până la epuizarea programului sursă. Aceasta, în scopul reducerii numărului de compilări necesare pentru detectarea şi eliminarea tuturor erorilor din programul utilizatorului.

Erorile detectabile în faza de compilare sunt următoarele:

Erorile lexicale se produc uzual prin preluarea unor caractere ilegale, ori necunoscute, care se datorează unor greşeli de editare. Exemplu de erori lexicale detectabile sunt: editarea accidentală a secvenţei ";=" în loc de ":=", ori găsirea unui şir de caractere inclus între apostroafe sau ghilimele nefinalizat, precum "alfa.

Faza de analiză sintactică poate detecta cele mai multe erori, numite erori de sintaxă. În această fază însă, este necesară o decizie privitoare la locul din care se va continua analiza sintactică, în urma detectării unei erori. Dacă acesta nu este corect ales, analizorul sintac poate produce o rafală de erori fără nici o legătură cu programul utilizatorului. Din acest motiv, calitatea sistemului de detectare erori este un criteriu important de apreciere al compilatoarelor.

Erorile detectabile în faza de analiză semantică sunt legate de faptul că, unele instrucţiuni pot fi corecte din punct de vedere sintactic, însă nu au sens. Din acest motiv, nu se poate genera nici un cod care să reflecte semnificaţia instrucţiunilor respective. De exemplu, instrucţiunea a := b, este corectă din punct de vedere sintactic, dar dacă a este de tipul boolean, iar b este un tip tablou, instrucţiunea nu are sens şi nu se poate executa.

17

1.4.5. Generatorul de cod intermediar

Este o fază care, uzual se include în fazele precedente, ori se omite, în cazul translatoarelor foarte simple. În această fază, arborele sintactic se transformă într-o secvenţă de instrucţiuni simple, asemănătoare cu macroinstrucţiunile unui limbaj de asamblare, cu diferenţa că nu se specifică registrele folosite. Codul intermediar este folosit deoarece este mai uşor de optimizat decât codul în limbaj maşină.

1.4.6. Optimizatorul de cod intermediar

Optimizatorul de cod intermediar modifică arborele sintactic, ori porţiuni din codul intermediar, astfel încât programul rezultat să îndeplinească unele cerinţe de performanţă.

De exemplu, în limbajul Pascal, o buclă for de tipul:

for i:=1 to 1000 do begin

a:= b + 5; x:= b + i;

end;

conţine o instrucţiune care nu are legătură cu variabila i, şi anume: a:= b + 5; şi această instrucţiune ar trebui repetată în mod inutil de 1000 de ori.

Codul obiect se poate genera de aşa manieră încât această instrucţiune este mutată în afara corpului instrucţiunii for, astfel încât execuţia devine mult mai eficientă.

18

Pentru exemplul (1.1), optimizatorul de cod poate efectua calculul din membrul drept, rezultând astfel un arbore sintactic simplificat, reprezentat în figura 1.9:

În urma fazelor de analiză lexicală, sintactică şi semantică, compilatorul poate crea secvenţe de instrucţiuni cu trei registre. Aceste instrucţiuni, au un număr de 3 operanzi, în contrast cu o instrucţiune de atribuire, care are un sindur operand. În aceste cazuri, se pot face optimizări de cod folosind reguli algebrice abstracte.

expresie de atribuire expresie număr
expresie
de atribuire
expresie
număr
întreg identificator identificator
întreg
identificator
identificator

a index

întreg

tablou de întregi

9

întreg

Figura 1.9. Arborele sintactic simplificat pentru expresia (1.1)

De exemplu, fie o instrucţiune de atribuire de următoarea formă:

x := x0 + a*5;

Translatarea acestei instrucţiuni în cod cu 3 adrese este:

temp1:= inttoreal(5) temp2:= id3 * temp1 temp3:= id2 + temp1 id1:= temp3

19

O translatare optimizată poate produce un cod mai scurt:

temp1:= id3 * 5.0 id1:= id2 + temp1

Pentru exemplul (1.1), secvenţa următoare:

temp = 7 + 2 id1 = temp

se poate optimiza la:

temp = 9 id1 = temp

1.4.7. Generatorul de cod final

Generatorul de cod final produce codul obiect. Generatorul de cod final are sarcina să decidă care anume locaţii de memorie sunt folosite pentru memorarea datelor, să genereze coduri care asigură accesul la aceste date, să specifice registrele folosite pentru calcule intermediare şi indexare etc.

Unele translatoare sofisticate continuă procesul de optimizare cu o fază numită "peephole optimizer" în care se fac încercări de reducere a unor operaţii inutile prin examinarea în detaliu a unor secvenţe din codul generat, în scopul producerii unui cod care se execută mai rapid. Optimizarea se produce local şi foloseşte avantajul oferit de proprietăţile de comutativitate, asociativitate şi distributivitate ale unor operatori.

20

1.5. Translatoare cu una sau mai multe treceri

Deşi din punct de vedere conceptual, procesul de translatare este divizat în faze, de obicei translatoarele sunt divizate fizic în treceri, în care fazele pot fi combinate ori întreţesute.

Uzual, o trecere citeşte programul sursă, ori ieşirea unei treceri anterioare, face unele transformări şi scrie ieşirea sa într-un fişier intermediar, care va fi citit de o trecere ulterioară.

Aceste treceri pot fi gestionate de diferite părţi integrate în acelaşi compilator, ori se gestionează prin execuţia a două sau mai multe programe separate.

Trecerile pot comunica, folosind un limbaj intermediar, ori pot folosi în acest scop fişiere, dar se pot face şi mai multe treceri utilizând acelaşi cod sursă original.

Numărul trecerilor depinde de o varietate de factori. Unele limbaje necesită cel puţin două treceri pentru a genera mai uşor codul obiect. Compilatoarele cu mai multe treceri folosesc de obicei mai puţină memorie şi sunt mai performante în ce priveşte optimizarea codului şi raportarea de erori, dar sunt mai lente decât cele cu o singură trecere.

Un compilator cu mai multe treceri poate executa de exemplu următoarele acţiuni:

prima trecere efectuează analiza lexicală a textului sursă şi memorează rezultatul într-un fişier de ieşire;

a doua trecere citeşte fişierul produs la prima trecere, execută analiza sintactică prin construirea arborelui sintactic şi îl scrie pe acesta într-un fişier de ieşire;

a treia trecere preia arborele sintactic, îl optimizează şi generează codul final.

21

Un compilator cu o singură trecere, citeşte textul sursă o singură dată şi produce direct codul final. Un astfel de compilator este construit în jurul analizorului sintactic care apelează procedurile pentru celelalte funcţii.

Există avantaje şi dezavantaje atât pentru compilatoarele cu mai multe treceri, cât şi pentru cele cu o singură trecere.

Un compilator cu o singură trecere este rapid, deoarece codul compilatorului se încarcă în memorie o singură dată În plus, ieşirea fiecărei treceri, în cazul unui compilator cu mai multe treceri, se memorează pe disc, iar acesta trebuie citit de următoarea trecere.

Pe de altă parte, un compilator cu o singură trecere, impune programului sursă anumite restricţii: constantele, tipurile, variabilele şi procedurile trebuiesc definite înainte de folosire, ceea ce este un inconvenient.

Componentele unui compilator cu o singură trecere sunt mult dependente unele de altele decât cele ale unui compilator cu mai multe treceri. Acest lucru presupune ca toţi programatorii care lucrează la proiectarea unui astel de compilator să cunoască întregul program. Un compilator cu mai multe treceri, se poate descompune în faze, care sunt relativ independente, astfel încât programatorii care lucrează la proiectarea lui au o independenţă mai mare. Fiecare trecere a compilatorului poate fi privită în acest caz ca un mini- compilator, care are la intrare un program sursă scris într-un limbaj intermediar şi produce o ieşire acelaşi program, într-un alt limbaj intermediar.

În practică, cel mai adesea sunt folosite compilatoare cu două treceri, în care prima trecere este un translator de nivel înalt, care converteşte programul sursă în limbaj de asamblare, sau chiar

22

într-un alt limbaj de nivel înalt, pentru care există deja un translator eficient.

1.6. Concluzii

proiectarea

Pentru

formale;

translatoarelor

se

folosesc

limbaje

Un translator are la intrare un cod sursă într-un limbaj şi produce la ieşire un cod într-un alt limbaj;

Principalele faze ale unui proces de compilare sunt: faza analitică şi faza sintetică;

Faza analitică constă din: transformarea codului sursă într-o secvenţă de atomi, efectuarea analizei sintactice pentru a decide dacă programul sursă este corect din punct de vedere gramatical, acestea fiind urmate de analiza semantică pentru verificarea tipurilor;

Faza sintetică produce codul obiect. În această fază se decide care anume variabile şi constante trebuiesc memorate, felul în care se face memorarea lor şi se produce codul obiect.

23