Universitatea de Vest Timişoara Facultatea de Matematică şi Informatică, Specializarea Informatică

Lucrare de diplomă Generarea de cod executabil pe platforma .NET

Profesor coordonator Conf. Dr. Drăgan Mircea

Absolvent Rişcuţia Vlad

Timişoara, 2008

Universitatea de Vest Timişoara Facultatea de Matematică şi Informatică, Specializarea Informatică

Lucrare de diplomă Generarea de cod executabil pe platforma .NET

Profesor coordonator Conf. Dr. Drăgan Mircea

Absolvent Rişcuţia Vlad

Timişoara, 2008

Cuprins

1. Introducere........................................................................................................................ 1 1.1 Motivaţie...................................................................................................................... 1 1.2 Sumar........................................................................................................................... 1 2. Structura compilatorului ................................................................................................. 3 2.1 Analiza lexicală ........................................................................................................... 4 2.2 Analiza sintactică........................................................................................................ 6 2.3 Syntax directed translation...................................................................................... 13 2.4 Reprezentări intermediare....................................................................................... 14 2.5 Analiza semantică ..................................................................................................... 17 2.6 Optimizări independente de maşină ....................................................................... 19 2.7 Generarea codului şi optimizări dependente de maşină ....................................... 20 3. Generarea de cod pentru platforma .NET ................................................................... 24 3.1 Platforma .NET......................................................................................................... 24 3.2 Structura unei aplicaţii ............................................................................................ 27 3.3 Generarea de cod ...................................................................................................... 30 4. Descrierea aplicaţiei ....................................................................................................... 34 4.1 Limbajul sursă .......................................................................................................... 34 4.2 Analiza lexicală şi analiza sintactică ....................................................................... 39 4.3 Reprezentarea intermediară.................................................................................... 40 4.4 Analiza semantică şi optimizări independente de maşină .................................... 47 4.5 Generarea codului şi optimizări dependente de maşină ....................................... 53 5. Concluzii .......................................................................................................................... 60 5.1 Contribuţie personală .............................................................................................. 60 5.2 Direcţii de dezvoltare................................................................................................ 60 Bibliografie .......................................................................................................................... 62

Această oportunitate permite şi cunoaşterea detaliilor arhitecturale ale platformei . Introducere Lucrarea îşi propune să descrie atât din punct de vedere teoretic cât şi din punct de vedere al implementării.NET. precum şi posibilele căi de evoluţie ulterioară. arbori abstracţi de sintaxă şi elemente ale analizei semantice – evaluarea şi modificarea dinamică a arborilor de sintaxă.). lucrarea îşi propune să detalieze principiile teoretice şi paşii necesari pentru a dezvolta o astfel de aplicaţie. algoritmică. 1. a platformei pentru care se generează cod. Delphi etc. Sunt expuse elementele de bază ale analizei lexicale – token-uri. algoritmi de parsing. Cu toate că sunt utilizate numeroase limbaje de programare de nivel înalt pentru a scrie aplicaţii pentru platformă (C++. prezintă fundamentele teoretice necesare pentru implementarea unui compilator. automate finite – şi elemente ale analizei sintactice – gramatici. expresii regulate. Capitolul se încheie cu optimizări 1 .1 Motivaţie Construirea compilatoarelor este unul din domeniile informaticii ce necesită o arie largă de cunoştinţe – structuri de date. limbajul de asamblare pus la dispoziţie permite evidenţierea procesului de execuţie. procesul de compilare al unui text sursă în cod executabil pe platforma . trecând prin toţi paşii necesari pentru efectuarea transformării. C#. Structura compilatorului. Capitolul 2. Atât pentru consolidarea şi aprofundarea materiei predate la cursurile de limbaje formale şi teoria automatelor şi tehnici de compilare cât şi pentru a înţelege modul de lucru intern al unui compilator. a capabilităţilor maşinii virtuale (setul de instrucţiuni). Visual Basic. a aplicaţiei şi a concluziilor la care s-a ajuns în urma implementării.2 Sumar Următoarele capitole cuprind descrierea unui compilator din punct de vedere teoretic. acţiuni semantice.1.NET precum şi modul de funcţionare a maşinii virtuale. arhitectura calculatoarelor. precum şi observarea instrucţiunilor sintetizate de majoritatea compilatoarelor pentru diferite construcţii de limbaj de nivel înalt uzuale. 1. structurile de date utilizate şi paşii procesului de compilare. Se continuă cu descrierea reprezentărilor intermediare utilizate – tabele de simboluri.

prezintă implementarea compilatorului. insistând asupra detaliilor de implementare relevante precum structurile de date şi algoritmii utilizaţi – se reiau subiectele prezentate în capitolul 2. începe cu o descriere a platformei. privite acum din perspectivă practică – generarea analizatorului lexical şi al parser-ului. setul de instrucţiuni precum şi tehnicile utilizate de aplicaţia prezentată în lucrare. Capitolul 5. 2 .NET.NET. implementarea arborelui abstract de sintaxă. evaluarea diferitelor tipuri de noduri. Sunt prezentate şi instrumentele puse la dispoziţie pentru a facilitata generarea codului. optimizări pe arbore. generarea de cod . Concluzii. maşina virtuală pentru care aplicaţia generează cod executabil. Descrierea aplicaţiei. atât din punct de vedere arhitectural cât şi din punct de vedere funcţional. Capitolul 3. Generarea de cod pentru platforma . expune concluziile la care s-a ajuns în urma dezvoltării aplicaţiei şi direcţiile în care aplicaţia va fi extinsă în viitor. Capitolul 4. tehnici şi generarea codului pentru o maşină abstractă.efectuate asupra reprezentării intermediare şi asupra codului final – teorie.

Componenta de sinteză operează pe forma intermediară. optimizări independente de maşină – optimizări la nivel logic aplicate reprezentării intermediare.2. Componenta de analiză preia textul sursă. 3 . Aceste procese pot fi executate pe rând sau simultan. Componenta de sinteză efectuează. îl procesează. majoritatea compilatoarelor efectuând în acelaşi timp cel puţin analiza lexicală şi analiza sintactică. Analiza este împărţit în mai multe procese: analiză lexicală. analiză sintactică şi analiză semantică. în primul rând. făcând optimizări şi generând cod. apoi generează codul final şi efectuează optimizări dependente de maşină – optimizări a codului în funcţie de sistemul pe care acesta va rula. Structura compilatorului Un compilator este format din două componente – o componentă de analiză (numită şi front-end) şi o componentă de sinteză (numită şi back-end). validează şi îl aduce într-o formă intermediară.

2 Expresii regulate Cu toate că mulţimea categoriilor de token-uri este finită. numărul efectiv de tokenuri dintr-o categorie poate fi infinit. eventual.1 Analiza lexicală Analiza lexicală presupune împărţirea textului sursă în cuvinte individuale. Date fiind două expresii regulate M şi N. Orice limbaj are o mulţime finită de categorii de token-uri: simboluri. operatorul | (de alternare) crează o nouă expresie regulată M | N unde un şir de caractere este în limbajul M | N dacă este în M sau în N. De exemplu token-ul pentru constanta număr întreg “12” este definit ca având tipul “constantă număr întreg” şi valoarea “12”. Cu toate că limbajul de expresii regulate este extins în majoritatea implementărilor. posibil infinită. 2. cifre sau simboluri “_”. operatorul * denotă închiderea Kleene a mulţimii M. operatorul · (de concatenare) crează o nouă expresie regulată M·N unde un şir de caractere este în limbajul M·N dacă este compus din alăturarea a două şiruri de caractere α şi β astfel încât α face parte din M şi β face parte din N. 3. cuvinte cheie etc. 4 . şi. toate făcând parte din M[8]. şi ignoră elementele irelevante pentru sensul programului – comentariile şi spaţiile albe din text. Aceasta primeşte un şir de caractere şi întoarce un şir de tokens – nume. alte informaţii asociate. Un şir de caractere face parte din M dacă este compus din alăturarea a oricâtor şiruri de caractere. Expresia regulată ε reprezină limbajul format din şirul de caractere gol (şirul de caractere format din zero caractere). numite tokens. definind limbajul identificatorilor ca un şir de caractere ce începe cu o literă sau simbolul “_” urmat de oricâte litere. cuvinte cheie. Date fiind două expresii regulate M şi N. Componenta compilatorului care efectuează analiza lexicală se numeşte scanner sau lexer. la baza sa stau 5 proprietăţi: 1. O expresie regulată defineşte o mulţime.1. este evident că mulţimea astfel definită este infinită. Pentru a specifica limbaje de dimensiune infinită cu o descriere finită. Pentru orice simbol a din alfabetul limbajului. 4.1. se folosesc expresiile regulate. Un token este definit prin tipul său (categoria din care face parte). de şiruri de caractere. 5. identificatori. 2. Dată fiind expresia regulată M. Token-urile sunt specificate cu ajutorul expresiilor regulate iar analizatoarele lexicale. expresia regulată a defineşte limbajul ce conţine doar şirul de caractere a. pe baza acestor expresii. De exemplu. constante numerice etc. sunt implementate folosind automate finite deterministe. 2.1 Token Un token este o secvenţă de caractere ce reprezintă o singură entitate în gramatica limbajului.2.

la terminarea citirii şirului de caractere automatul se află într-o stare finală. şirul de caractere este acceptat. O stare este identificată ca stare de start iar un număr de stări sunt identificate ca stări finale. în funcţie de input-ul primit. defineşte mulţimea şirurilor de caractere compuse din alăturarea oricâtor şiruri de caractere. Automatele finite nondeterministe (NFA) se deosebesc de cele deterministe prin εtranziţii – muchii etichetate cu simbolul ε – care pot schimba starea automatului fără a 5 . simbolurile ce etichetează muchiile sunt caractere şi automatul are proprietatea că.1. cuvintele cheie respectă şi definiţia identificatorilor. îşi schimbă starea trecând într-o stare adiacentă – legată de starea curentă prin muchia etichetată cu un simbol ce corespunde input-ului. pentru orice pereche de muchii ce pleacă din aceaşi stare. operatorul + aplicat expresiei regulate M. automatul devine blocat. respingând pe loc şirul de caractere. dar cel puţin unul. făcând parte din M. muchii ce pornesc dintr-o stare şi merg în altă stare şi simboluri ce etichetează muchiile. Un automat finit porneşte din starea de start şi. este necesar un model practic de implementare pentru a se putea efectua analiza lexicală. şirul de caractere este respins. se va considera token identificatorul form. Se va considera ca token identificat cel mai lung subşir de caractere ce face parte din limbajul definit de o expresie regulată: pentru şirul de caractere form. Se observă că expresia M+ poate fi exprimată folosind proprietăţile enumerate ca M·M*. pentru majoritatea limbajelor. Limbajul recunoscut de un automat este mulţimea de şiruri de caractere pe care o acceptă. În cazul în care automatul ajunge într-o stare de unde. cu toate că şirul for este un cuvânt cheie şi face parte din gramatica lexicală a limbajului. 2.3 Automate finite Cu toate că expresiile regulate pot descrie gramatica lexicală a unui limbaj din punct de vedere teoretic. pe baza input-ului. 2. Un automat finit are o mulţime finită de stări. dar şi ele pot fi exprimate în funcţie de cele 5 proprietăţi de mai sus. De exemplu. Cu ajutorul expresiilor regulate se pot descrie în întregime elementele unui limbaj de programare – mulţimea tuturor token-urilor – numită şi gramatica lexicală. 1. Pe lângă descrierea limbajului mai sunt necesare două reguli de rezolvare a ambiguităţilor pentru ca un compilator să poată efectua analiza lexicală a unui text. ordinea fiind relevantă. nu poate trece în altă stare (nu există muchia etichetată cu un simbol corespunzător).Extensiile limbajului au fost adăugate pentru a uşura scrierea expresiilor regulate. ştiind structura automatului şi citind caractere unul câte unul. Expresiile regulate se vor aplica într-o anumită ordine asupra textului. Cu alte cuvinte. Pentru aceasta se folosesc automatele finite. Scanner-ele utlizate de compilatoare folosesc astfel de automate pentru a efectua analiza lexicală. Dacă. în caz că automatul nu se află într-o stare finală. astfel automatul acceptă sau respinge un şir de caractere. De exemplu. simbolurile care le etichetează sunt diferite. Într-un automat finit determinist (DFA). se poate stabili la fiecare pas în ce stare se află automatul. astfel că un şir de caractere precum for trebuie identificat corect ca un cuvânt cheie şi nu un identificator. Input-ul este reprezentat de un şir de caractere.

2 Analiza sintactică Analiza sintactică presupune identificarea frazelor din textul sursă. Exemple de terminale sunt cuvinte cheie precum if. astfel. se raportează o eroare în textul sursă (caracter invalid). se raportează sfârşit de fişier neaşteptat (unexpected end of file). Aceste generatoare primesc lista de expresii regulate ce defineşte toată gramatica lexicală a limbajului de programare şi generează automatul finit ce recunoaşte limbajul. 2. folosit de compilatorul prezentat în această lucrare.1 Gramatici Gramaticile sunt formate din patru elemente: 1.1. Sa demonstrat că pentru orice NFA se poate construi un DFA echivalent (care acceptă acelaşi limbaj) şi pentru orice DFA se poate construi un NFA echivalent[8]. Mulţimile simbolurilor definite 6 . then sau simboluri precum “(” sau “)”. precum şi programul ce încapsulează acest automat. pentru un şir de caractere. Pentru a determina stările prin care trece automatul trebuie să se cunoască apriori tot şirul de caractere de intrare. 2. automatul nu se află într-o stare finală. Nonterminale – sunt variabile sintactice ce definesc seturi de simboluri terminale şi/sau alte simboluri nonterminale. Acest tip de automate se numesc nondeterministe deoarece. la epuizarea caracterelor din textul sursă. de fapt. Terminale – sunt simbolurile de bază ce formează construcţiile.consuma caractere input şi prin faptul că permit existenţa a două muchii ce pleacă din aceaşi stare etichetate cu acelaşi simbol. 2. Automatele finite nondeterministe definite de expresiile regulate nu pot fi implementate direct pe calculator însă pot fi transformate în automate finite deterministe. Analizatorul sintactic. primeşte un şir de token-uri şi le grupează în construcţii corecte din punct de vedere sintactic.2. citind caractere unul câte unul de la intrare. automatul se blochează. astfel de automate. o expresie regulată poate fi implementată ca un DFA. echivalente cu token-urile identificate de analizatorul lexical. numit parser. Expresiile regulate definesc. astfel că s-au dezvoltat numeroase generatoare automate de analizatoare lexicale precum celebrul Lex sau Coco/R.4 Generarea automată a analizatoarelor lexicale Transformarea expresiilor regulate în DFA este o sarcină simplă pentru calculator. nu se poate determina starea în care un astfel de automat se află într-un anumit moment. Sintaxa unui limbaj este definită de o gramatică sintactică. 2. Analizatoarele lexicale astfel generate primesc ca input şiruri de caractere şi returnează token-uri. În cazul în care. În cazul în care. Automatele finite pot fi legate în paralel (analogul operaţiei de alternare a expresiilor regulate) sau legate în serial (analog operaţiei de concatenare a expresiilor regulate) putându-se astfel crea un singur automat finit care să accepte tot limbajul descris de utilizator.

Simbol de start – într-o gramatică. Simbolul . Nonterminalele impun o structură ierarhică limbajelor. acestea au fost introduse ca şi extensii ale notaţiei făcute pentru a uşura citirea gramaticilor de către oameni[1]. adică absenţa terminalelor şi a nonterminalelor. pe care se bazează analiza sintactică. acestea sunt separate de simbolul |.2. c. Fiecare producţie este formată din: a. 3. N  digit N | digit poate fi scris ca N  digit { digit } 7 . pentru a face distincţia dintre elemente. nonterminalele unei gramatici sunt de obicei reprezentate cu font italic iar terminalele sunt reprezentate cu font bold. şirul respectiv este încadrat de simbolurile “{” şi “}”.2 BNF şi extensii Deşi specificaţia iniţială a notaţiei BNF nu include toate convenţiile prezentate mai jos. Un corp (body) sau right side. un nonterminal este diferenţiat de celelalte ca fiind simbol de start. format din zero sau mai multe terminale şi nonterminale. Ca şi convenţie de notaţie. 4. Pentru mai multe posibile corpuri asociate unui nonterminal head. Componentele corpului descriu diferite feluri în care simbolurile corespunzătoare nonterminalului head pot fi construite[3].astfel determină limbajul generat de gramatică. Producţii – producţiile unei gramatici specifică modul în care terminalele şi nonterminalele pot fi combinate pentru a forma seturile de simboluri. 2. O formă de reprezentare a gramaticilor este notaţia BNF (Backus-Naur Form) şi extensiile ei. EE+T EE–T poate fi scris ca EE+T |E–T Pentru a reprezenta 0 sau mai multe repetări ale unui şir de terminale şi/sau nonterminale. mulţimea de simboluri definită de acesta fiind chiar limbajul definit de gramatică. Simbolul ε denotă şirul vid. Un nonterminal numit head sau left side b.

toate nodurile frunză vor fi terminale.Pentru a reprezenta un şir de terminale şi/sau nonterminale ce poate să apară o dată sau niciodată (şir opţional). Nodurile frunză pot fi terminale sau nonterminale în timpul construirii arborelui iar în momentul în care acesta a fost determinat în întregime (s-a ajuns la un şir format exclusiv din terminale). de fapt. Pentru şirul a+b*c arborele de derivare final este: 8 . determinarea arborelui de derivare corespunzător unei mulţimi de token-uri pe baza producţiilor ce definesc gramatica.3 Arbori de derivare Un arbore de derivare (arbore de parsing) este o reprezentare a derivării simbolului de start într-o mulţime formată exlusiv din terminale. Analiza sintactică presupune. şirul respectiv este încadrat de simbolurile “[” şi “]”. Un nonterminal head este reprezentat de un nod având ca şi copii nodurile corespunzătoare mulţimilor de terminale şi nonterminale dintr-un set body. Rădăcina arborelui este simbolul de start. păstrând ordinea elementelor de la stânga la dreapta.2. N  – digit { digit } | digit { digit } poate fi scris ca N  [ – ] digit { digit } 2.

2. ordinea corectă a operaţiilor a fost păstrată (înmulţirea înaintea adunării). În funcţie de textul analizat. e “construit” la nivel abstract. 9 . un parser trebuie. şiruri de terminale şi nonterminale body reducându-se astfel în nonterminalele head asociate până când singurul nonterminal rămas este simbolul de start. caz în care derivarea se numeşte rightmost derivation. se porneşte de la simbolul de start şi se repetă derivarea nonterminalelor până când şirul la care se ajunge este un şir de simboluri exclusiv terminale.2. 2.Se observă că. la fiecare iteraţie. datorită gramaticii. Astfel. Există două metode caracteristice implementării parserelor pentru a determina arborele. Un alt mod de a trata producţiile este dat de către reduceri. pentru a analiza tot textul unui program. să “construiască” arborele de parsing corespunzător şirului de token-uri primite ca input.4 Derivări şi reduceri Pentru a determina arborele de derivare ce corespunde unui text utilizând o gramatică există două abordări: derivarea şi reducerea. se aplică iterativ inversul operaţiei de derivare. În exemplul de mai sus. caz în care derivarea se numeşte leftmost derivation sau ultimul nonterminal de la stânga spre dreapta. pentru şirul a+b*c şi gramatica EE+T |T TT*F|F F  ( E ) | id avem următoarele derivări pornind de la simbolul de start: E  E + T  T + T  F + T  id + T  id + T * F  id + F * F  id + id * F  id + id * id Nonterminalul ales la fiecare pas pentru rescriere poate fi primul nonterminal de la stânga spre dreapta din corpul producţiei. corespunzătoare derivărilor şi reducerilor: construirea arborelui top-down. Pornindu-se de la un şir de terminale. Arborele de derivare este rareori construit cu adevărat în practică ca o structură arborescentă de date dar se spune că. Reducerile sunt opusul derivărilor. derivarea este de tip leftmost. Derivarea presupune tratarea producţiilor ca reguli de rescriere[3].2.5 Top-down şi bottom-up parsing Pentru a realiza analiza sintactică. în esenţă. Notând o derivare cu simbolul “”. un nonterminal de tip head este înlocuit cu un şir de terminale şi nonterminale body care îi sunt asociate. în urma determinării derivării.

deci nu se poate decide cum va fi construit 10 . Parsing-ului topdown îi corespund gramaticile LL(k). o gramatică poate să fie ambiguă. parser-ul efectuând derivări sau reduceri în funcţie de token-urile citite până într-un moment. Un exemplu pentru necesitatea utilizării procedurii de lookahead este. parser-ul nu ar mai efectua reducerea şi ar continua citirea simbolurilor. Acest procedeu se numeşte lookahead.7 Ambiguităţi De multe ori. pentru a lua decizia corectă de a deriva sau reduce în funcţie de un şir de token-uri citite. Ţinând cont însă de faptul că următorul token e “*”. Din acest motiv.6 Lookahead În practică.respectiv construirea arborelui bottom-up. cu atât gramatica poate fi mai complexă. făcând întâi reducerea T * F la T şi abia apoi E + T la E. Continuând astfel. Gramaticile LR(k) sunt gramaticile unde simbolurile sunt citite de la stânga spre dreapta. se citesc înainte (lookahead) k simboluri. pe când parsing-ului bottom-up îi corespund gramaticile LR(k).2. Un parser automat nu poate decide în astfel de condiţii ce derivare să facă. pentru şirul a+b*c şi gramatica EE+T |T TT*F|F F  ( E ) | id producţia E  E + T | T. Utilizând un bottom-up parser şi citind token-ul “b”. Gramaticile LL(k) sunt gramatici unde simbolurile sunt citite de la stânga spre dreapta. În implementări se folosesc de obicei gramatici LL(1) şi LR(1). Un parser top-down construieşte arborele începând de la nodul rădăcină şi adăugând noduri în preordine (depth-first). acestea fiind citite unul câte unul. 2. se produc derivări leftmost şi. Desigur. 2.2. rezultatul nu ar mai respecta prioritatea înmulţirii faţă de adunare din moment ce este considerarată ca operaţie “a + b”. Cu cât numărul de simboluri lookahead este mai mare. Un parser bottom-up construieşte arborele începând de la nodurile frunză şi adaugă noduri părinte. dacă parserul nu ar ţine cont că următorul token e “*” ar putea decide să facă reducerea corpului E + T la nonterminalul E (desigur. token-urile nu sunt citite deodată. neputându-se determina o derivare leftmost sau rightmost unică pentru un anumit şir de simboluri. pentru a se determina derivarea corectă. reducerea începe de la id la F). în unele situaţii trebuie să se ţină cont şi de token-urile ce urmează a fi citite. nu orice gramatică e potrivită pentru orice tip de parsing. se produc reduceri rightmost şi se citesc înainte k simboluri.

Analog pentru β. un şir ce începe cu terminalul a. Pentru nici un terminal a α şi β să nu poată deriva.. dacă α derivează ε în oricâţi paşi[3].arborele de parsing. 3. O gramatică este LL(1) dacă pentru orice A  α | β. O ambiguitate întâlnită la majoritatea limbajelor de programare este ambiguitatea if-else. atunci α nu derivează nici un şir ce începe cu un terminal care se poate găsi după A în orice producţie a gramaticii.8 Gramatici LL(1) Mulţimea gramaticilor LL(1) este suficient de mare pentru a include majoritatea limbajelor de programare[3]. ambele. Pentru a soluţiona astfel de probleme. fie se introduc reguli suplimentare de decizie. fără a fi urmată de un alt simbol “else“.2. Totuşi. ifstmt  if ( expr ) then stmt [ else stmt ] unde nonterminalul stmt poate fi derivat atât în ifstmt cât şi în alte terminale/nonterminale şi considerând expr ca un nonterminal ce poate fi derivat într-o expresie de egalitate. fie gramatica este rescrisă astfel încât ambiguităţile să fie eliminate. Dacă β derivează în oricâţi paşi ε. următoarele condiţii sunt îndeplinite: 1. opţional. stmt  ifstmt | . partea else. Considerând parte a unei gramatici ce descrie propoziţia condiţională if şi.. eliminând instanţele de recursivitate-stânga şi ambiguităţile. unde α şi β sunt două producţii distincte a gramaticii. Această ambiguitate este rezolvată cu ajutorul regulii ce consideră că simbolul “else” aparţine întotdeauna ultimei propoziţii if citite. anumite gramatici trebuiesc reformulate pentru a deveni LL(1). De exemplu gramatica operaţiilor aritmetice EE+T |T TT*F|F 11 . parser-ul nu poate decide dacă simbolul “else” aparţine primei propoziţii if sau celei de a doua. 2. există următorul text: if ( a == b ) then if (b == c) then b++ else c++ Atât timp cât propoziţia se încheie aici. Anumite elemente ale unor gramatici LR(1) trebuiesc rescrise pentru a respecta aceste proprietăţi. Cel mult una dintre producţiile α şi β pot deriva şirul vid ε. cu alte cuvinte propoziţia de mai sus se reduce la if ( a == b) then ifstmt şi nu la if (a == b) then ifstmt else c++ 2.

Pornind de la funcţia asociată simbolului de start. Citind simbolul “a” este apelată recursiv funcţia T() care apelează funcţia F() unde simbolul este consumat. parser-ul face derivări leftmost apelând recursiv funcţiile corespunzătoare nonterminalilor. De exemplu pentru gramatica E  T E' E'  + T E' T  F T' T'  * F T' F  ( E ) | id sunt create funcţiile E(). Se revine astfel în funcţia E() şi se apelează E1() care consumă “+” şi apelează funcţia T() şamd. T1() şi F() şi pentru textul a+b*c analiza decurge astfel: întâi este apelată funcţia E().9 Recursive descent parser Un recursive descent parser este un parser top-down ce asociază o funcţie fiecărei producţii a gramaticii. 12 . Se observă că procesul nu poate avea loc pentru o gramatică stânga-recursivă deoarece pentru producţia E  E + T | T funcţia E() s-ar putea apela recursiv pe ea însăşi la infinit. atunci textul sursă este considerat incorect din punct de vedere sintactic şi se raportează o eroare de sintaxă.2. Parserul utlizat de compilatorul prezentat în această lucrare este un recursive descent parser. T().F  ( E ) | id nu este o gramatică LL(1) deoarece are recursivitate-stânga pentru derivările E  E + T şi T  T * F. 2. Gramatica LL(1) echivalentă este E  T E' E'  + T E' T  F T' T'  * F T' F  ( E ) | id Pentru această clasă de gramatici există numeroşi algoritmi de parsing. În cazul în care şirul de simboluri input se întrerupe într-un moment în care nu se poate finaliza derivarea simbolului de start în şirul de simboluri exclusiv terminale sau în cazul în care într-un anumit moment simbolul citit nu permite efectuarea niciunei derivări din toate derivările posibile. în funcţie de token-urile citie. E1().

3. 2. în urma efectuării parsing-ului asupra unui şir a + b * (c + d) pe ecran va fi afişată expresia matematică în formă poloneză postfixă abcd+*+ 2. Considerând gramatica E  T E' E'  + T E' T  F T' T'  * F T' F  ( E ) | id extinsă cu acţiunile semantice (în pseudocod) E  T E' E'  + T E' { write “+” } T  F T' T'  * F T' { write “*” } F  ( E ) | id { write id.value } unde id.1 Acţiuni semantice Acţiunile semantice sunt instrucţiuni scrise în limbajul de programare în care este implementat parser-ul. următorul pas fiind aducerea textului sursă la o reprezentare intermediară. În cazul în care textul sursă este incorect din punct de vedere sintactic. este necesară introducerea unor reguli de translaţie.2 Generarea automată a analizatoarelor sintactice 13 .3 Syntax directed translation Un recursive descent parser ca cel prezentat mai sus este capabil să producă toate derivările necesare pornind de la simbolul de start şi finalizând cu un şir de simboluri terminale. adăugate producţiilor gramaticale. Algoritmul este bun dar insuficient deoarece nu procesează mai departe textul sursă – practic singurul rezultat al execuţiei este confirmarea că textul sursă este corect formulat.value semnifică valoarea token-ului. Regulile de translaţie extind definiţia producţiilor gramaticale cu instrucţiuni de procesare ulterioară a datelor. acest parser raportează erorile întâlnite. Pentru a continua procesarea.2.3.

când cantitatea de memorie RAM disponibilă era limitată. introducând în această funcţie acţiunile semantice specificate în definiţia gramaticii. împreună cu acţiuni semantice. chiar şi cele ce nu crează reprezentări intermediare complete. fără a păstra o reprezentare abstractă completă a textului în memorie. Pentru fiecare producţie gramaticală. 2. Pentru compilatorul prezentat în lucrare s-a folosit versiunea ce produce cod C#. 1 Există versiuni ale generatorului Coco/R şi pentru alte limbaje. adresa din memorie etc. La fel. Acest artificiu a fost introdus deoarece compilatorul era nevoit să codifice apelul de funcţie înainte de a cunoaşte funcţia ce trebuia apelată. În compilatoarele moderne se preferă utilizarea mai multor paşi. pentru a apela o funcţie înainte de a o defini. compilatoarele generau codul final citind fragmente din textul sursă şi transformând fiecare fragment în cod. cu codul sursă în limbajul C#1. 14 .1 Tabele de simboluri Tabelele de simboluri sunt prezente în toate compilatoarele. VB. având nevoie de o singură parcurgere a textului. YACC generează pe baza descrierii unei astfel de gramatici un parser de tip bottomup cu codul sursă scris în limbajul C. diferenţa de performanţă fiind nesemnificativă comparativ cu avantajele aduse [4]. La nivelul interpretării textului sursă.Există numeroase aplicaţii care. tipul ei. Principalul dezavantaj al acestor compilatoare era lipsa unei “vederi” de ansamblu asupra programului. pe baza descrierii unei gramatici într-o formă BNF extinsă (forma utilizată depinde de aplicaţie). o eventuală valoare iniţială. Astfel de compilatoare erau numite one-pass compilers. funcţiile sunt definite prin nume. clase. variabilele şi funcţiile. recursive descent. Tabelele de simboluri sunt completate pe măsură ce textul este analizat sintactic. se crează o intrare în tabela de simboluri ce conţine numele variabilei. Coco/R generează un parser de tip top-down. Simboluri sunt. simboluri sunt şi declaraţiile de tipuri. producerea unei reprezentări complete şi parcurgerea reprezentării pentru a emite cod. În funcţie de limbaj. În trecut. textul sursă este tradus într-o reprezentare intermediară pentru a fi procesat ulterior. În momentul în care în textul sursă se face o referire la variabilă (prin numele ei). interfeţe etc. de exemplu.4. parametri şi valoare returnată) înaintea apelului cu cuvântul cheie forward. aceasta trebuia declarată (cu nume. structuri. iar sinteza codului se face utilizând informaţiile din aceste tabele.4 Reprezentări intermediare Urmând acţiunile semantice. Coco/R construieşte o funcţie asociată. simboluri sunt considerate construcţiile specifice limbajului din textul sursă sau alte surse referite. 2.NET sau C++. parametri şi valoare returnată. căutând în tabela de simboluri se poate determina locaţia ei în memorie sau tipul. în limbajul Pascal. În momentul în care o variabilă este declarată. precum Java. generează codul sursă al parser-ului asociat gramaticii. De exemplu.

i se caută intrarea corespunzătoare în tabela de simboluri activă. fie se parcurge fără rezultat tabela de simboluri rădăcină. Variabilele locale ce aparţin unei funcţii sunt introduse într-o tabelă de simboluri ce corespunde funcţiei şi care păstrează o legătură către tabela de simboluri care conţine definiţia funcţiei. situaţie în care referinţa este invalidă (se încearcă referirea unui simbol inexistent). compilatorul prezentat utilizează doar arborii abstracţi de sintaxă. Datorită acestei organizări nu se poate referi o variabilă locală unei funcţii în afara funcţiei (deoarece este out of scope – tabela activă nu este tabela ce conine variabila i nici copil al tabelei ce conine variabila) şi tot de aceea. Expresia a + b * (c + d) 15 . declaraţiile de funcţii globale etc. sunt introduse într-o tabelă. Tabelele de simboluri sunt completate cu ajutorul acţiunilor semantice introduse în parser. organizate ierarhic astfel: variabilele globale. 2.2 Arbori abstracţi de sintaxă Deşi există mai multe tipuri de reprezentări intermediare a expresiilor – grafuri aciclice direcţionate.Oricărui program îi corespund mai multe tabele de simboluri. cvadruple. referirea după nume se face întotdeauna la variabila locală (deoarece intrarea ei este găsită prima). care nu are părinte. adică zonele de vizibilitate pentru diferite variabile. În momentul în care este referită o variabilă (sau orice alt simbol). Arborii abstracţi de sintaxă reprezintă expresiile ca un nod părinte ce conţine operatorul şi unul sau mai multe noduri copii ce conţin operanzii. În cazul în care nu se găseşte nicio intrare. Tabela de simboluri activă este tabela corespunzătoare blocului de cod procesat la un moment dat. triplete etc. Procesul se repetă până când fie este găsită intrarea. se caută în tabela de simboluri părinte (tabela spre care tabela activă păstrează o legătură). când există o variabilă globală ce are acelaşi nume cu o variabilă locală. Astfel sunt delimitate scope-urile.4..

structurile repetitive precum while sau for şi chiar blocurile. Rădăcina unei propoziţii reprezintă o acţiune specifică iar nodurile reprezintă alte propoziţii sau expresii. 2. nodurile mai pot conţine diferite atribute construite deodată cu nodul sau în timpul analizei semantice. Pe lângă operatori sau operanzi. Textului sursă if (a == b) then begin a = a + 1 b = b – 1 end îi corespunde arborele 16 . Această reprezentare păstrează doar semantica (înţelesul) textului sursă.este reprezentată într-un arbore abstract de sintaxă astfel: Folosind tabelele de simboluri şi arbori abstracţi de sintaxă. independentă de limbajul în care textul a fost scris. se poate produce o reprezentare intermediară completă a unui text sursă. spre deosebire de expresii. nu sunt reprezentate prin operatori şi operanzi. acestea din urmă fiind reprezentate printr-o rădăcină ce are atâţia copii câte propoziţii se găsesc în interiorul blocului. renunţând la sintaxă.4. Exemple de propoziţii sunt structurile condiţionale if-else.3 Propoziţii Pe lângă subarborii de tip expresie se mai distinge cel puţin o categorie de subarbori corespunzătoare construcţiilor din textul sursă: propoziţiile. Propoziţiile.

5 Analiza semantică Analiza semantică poate fi efectuată deodată cu analiza sintactică sau ca un pas separat. expresiile returnează o valoare (rezultatul evaluării) pe când propoziţiile nu returnează nimic. iar subarborii reprezentaţi de textele “if expresie propoziţie”. asupra reprezentării intermediare. putem avea în textul sursă expresia ‘text’ + 14 care. Întrun astfel de caz. ele reprezentând doar acţiuni precum atribuirea. Principala diferenţă dintre expresii şi propoziţii este că. pentru gramatica EE+T |T TT*F|F F  ( E ) | id dacă considerăm că operatorul pentru concatenarea şirurilor de caractere este tot “+”. 2. luând în considerare “semnificaţia” lor. “begin listă_propoziţii end” şi “variabilă = expresie” sunt propoziţii. Analiza semantică validează expresiile limbajului corecte din punct de vedere sintactic. deşi corectă din punct de vedere sintactic. în urma evaluării. în funcţie de limbajul utilizat. “a + 1” şi “b – 1” sunt expresii. fie se semnalează o eroare datorată 17 . De exemplu. nu are sens din punct de vedere logic.if == a b a + bloc b - a 1 b 1 unde subarborii reprezentaţi de textele “a == b”.

fie. ceea ce presupune inserarea unei operaţii de conversie implicită în număr în virgulă mobilă a rezultatului operaţiei de adunare: 1 Conversia între tipuri este tot o expresie. având ca operator tipul în care se doreşte conversia şi ca unic operand expresia care se doreşte a fi convertită.5 * (10 + 12) se poate stabili că se încearcă înmulţirea unui număr întreg cu un număr în virgulă mobilă.incompatibilităţii între tipuri. rezultatul fiind ‘text14’1. dacă expresia prezentată la punctul 2.2 Modificarea dinamică a arborelui Există numeroase cazuri în care. se determină diferite atribute a nodurilor care nu erau cunoscute înaintea analizei. În momentul parcurgerii şi evaluării arborelui abstract de sintaxă. este determinat doar în timpul analizei semantice.5.5.5. 18 . adică tipul nodului operator “+”. se constată necesitatea inserării unor noduri în interiorul arborelui. ca 2.1 este o sub expresie. De exemplu. Operatorului i se atribuie tot tipul număr întreg. Pentru nodurile se cunoaşte încă din momentul în care au fost create că nodurile ce reprezintă operatorii “10” şi “12” sunt noduri de tip număr întreg dar rezultatul operaţiei. 2. de exemplu.1 Atribute semantice Componentă a analizei semantice este şi sinteza atributelor semantice. 14 este transformat implicit în şirul de caractere ‘14’. 2. în timpul analizei semantice.

având toate atributele specifice fiecărui nod determinate.1 Constant folding Constant folding presupune transformarea expresiilor cu operanzi exclusiv constanţi într-o singură valoare constantă. de multe ori aceste optimizări contând cel mai mult când se compară diferite compilatoare deoarece de ele depinde performanţa codului produs. se încarcă doar o valoare.6.6 Optimizări independente de maşină Optimizările independente de maşină sunt optimizările la nivel logic efectuate pe reprezentarea intermediară a programului. există o mulţime de tehnici de optimizare a diferitelor aspecte ale unui program. 19 . Rezultatul analizei semantice este un arbore abstract de sintaxă validat din punct de vedere logic (în cazul în care textul sursă este corect).Unde în timpul analizei semantice se determină tipul nodurilor operand (adică a rezultatului expresiilor) şi se introduce nodul de conversie. Totuşi. 2. pentru orice text sursă. Compilatorul prezentat efectuează două astfel de optimizări: constant folding şi dead code elimination. produce codul optim[3]. 2. cu alte cuvinte nu există niciun algoritm care să garanteze că. expresia “10 + 12” formată din operatorul “+” şi operanzii “10” şi “12” şi reprezentată în arborele abstract de sintaxă prin trei noduri poate fi redusă la valoarea “22” şi reprezentarea în arborele abstract de sintaxă printr-un singur nod. Problema optimizării este o problemă NPcompletă (imposibil de soluţionat cu un algoritm având ordinul de complexitate polinomial). Astfel. a le aplica operatorul “+” şi a stoca rezultatul operaţiei. Avantajul unei astfel de optimizări este evident în momentul generării codului: în loc de a încărca două valori.

Avantajul acestui tip de optimizare este atât reducerea în dimensiune a codului produs cât şi. în acelaşi bloc de cod. renunţându-se atât la codul ce corespunde evaluării expresieicondiţie cât şi la cealaltă ramură. Astfel de instrucţiuni pot să apără dacă în textul sursă. se poate renunţa la introducerea în codul generat al ramurii else.LD x încarcă variabila x pe stivă . evident. Practic. se poate renunţa la introducerea ramurii if. Codul este generat parcurgând arborele abstract de sintaxă în postordine. 1 20 . în caz că expresia este adevărată.SUM adună primele 2 variabile din vârful stivei şi le înlocuieşte cu suma lor . operând pe reprezentarea intermediară. comparaţiile etc. 2. în cazul transformării instrucţiunii if. pentru o propoziţie condiţională if în care expresia-condiţie este constantă sau devine constantă în urma constant folding-ului. O astfel de maşină nu foloseşte regiştri. se pot determina la nivel abstract reprezentarea expresiilor uzuale. De exemplu.2 Dead code elimination Dead code elimination presupune eliminarea din codul generat a segmentelor de instrucţiuni ce nu vor fi executate niciodată. pentru o maşină bazată pe o stivă1. ştiind că limbajul de asamblare este format din următoarele instrucţiuni: . 2. Astfel. pentru arborele abstract Acesta este modelul folosit de unele calculatoare vechi şi adoptat de obicei de maşinile virtuale. strâns legată de maşina pentru care se generează codul respectiv. chiar în timpul compilării se poate determina că nu se va ajunge niciodată la propoziţiile respective. Sinteza codului este. propoziţie care părăseşte imediat blocul.6. Analog.7 Generarea codului şi optimizări dependente de maşină Ultimul pas efectuat de un compilatorul este sinteza codului pe baza reprezentării intermediare şi optimizările dependente de maşină.2.MUL înmulţeşte primele 2 variabile din vârful stivei şi le înlocuieşte cu produsul lor. dacă expresia este constant falsă. reducerea numărului de operaţii efectuate. La fel. întreaga expresie if este înlocuită cu una din ramurile sale. instrucţiuni pentru a opera asupra valorilor şi instrucţiuni pentru stocarea valorilor. O maşină bazată pe stivă încarcă şi scoate valori pe stivă şi execută operaţii pe elementele din vârful stivei.1 Expresii unare şi binare În categoria expresiilor unare şi binare intră operaţiile aritmetice şi logice. există propoziţii după o propoziţie return.7. Luând în considerare faptul că majoritatea maşinilor pot fi programate cu ajutorul unui limbaj de asamblare ce pune la dispoziţie instrucţiuni pentru încărcarea valorilor.

2 Structuri condiţionale Structurile condiţionale sunt reprezentate de structuri precum IF-ELSE sau SWITCH. Extinzând limbajul de asamblare cu instrucţiunile .EQ verifică egalitatea primelor două valorile din vârful stivei şi pune pe stivă 1 dacă acestea sunt egale. secvenţa de instrucţiuni 1:LD a 2:LD b 3:LD c 4:MUL 5:SUM În urma execuţiei secvenţei de instrucţiuni. stiva va conţine rezultatul expresiei. parcurgând arborele în postordine.JMP x mută necondiţionat controlul la instrucţiunea x .7. care.ST x scoate valoarea din vârful stivei şi o stochează în variabila x .se generează. 2. în funcţie de rezultatul unei expresii. decid ce instrucţiuni urmează a fi executate. 0 în caz contrar pentru textul sursă IF (a == b) a = a + 1 reprezentat ca arbore abstract de sintaxă prin 21 .JZ x mută controlul la instrucţiunea x dacă pe vârful stivei se află valoarea zero .

Pentru secvenţa de text sursă WHILE (a == b) a = a + 1 reprezentată ca arbore abstract de sintaxă prin 22 . REPEAT-UNTIL etc.. 2.se generează secvenţa de instrucţiuni 1:LD a 2:LD b 3:EQ 4:JZ 9 5:LD a 6:LD 1 7:SUM 8:ST a 9: .7.3 Structuri repetitive Structurile repetitive sunt reprezentate de propoziţii precum WHILE. Unde instrucţiunea numărul 9 reprezintă instrucţiunea care urmează în textul sursă după secvenţa de mai sus. FOR. DO-WHILE..

se generează secvenţa de instrucţiuni 1:LD a 2:LD b 3:EQ 4:JZ 10 5:LD a 6:LD 1 7:SUM 8:ST a 9:JMP 1 10: . Un exemplu de astfel de optimizare – care nu se aplică la compilatoarele ce produc cod pentru platforma .4 Optimizări dependente de maşină Optimizările dependente de maşină sunt optimizările care nu se pot realiza luând în considerare doar reprezentarea intermediară ci necesită cunoaşterea maşinii pe care codul produs de compilator va rula. în mod abstract.NET. ca cel prezentat – ar fi. 2. Unde instrucţiunea numărul 10 reprezintă instrucţiunea care urmează în textul sursă după secvenţa de mai sus. în momentul în care se remarcă faptul că o variabilă este intens referită. să se aleagă opţiunea de a stoca variabila respectivă într-un registru al procesorului în loc de a o stoca în memoira RAM..7. Astfel de optimizări nu pot fi prezentate din punct de vedere teoretic. 23 . deoarece depind direct de arhitectura maşinii pentru care este generat codul..

algoritmi numerici şi comunicaţii prin reţea[9].NET Componenta compilatorului ce se ocupă de generarea şi optimizarea codului (backend-ul compilatorului) este strâns legată de platforma pentru care compilatorul generează cod – în acest caz platforma .1 Common Language Runtime Maşina virtuală (CLR) asigură un “strat” operaţional între aplicaţiile . Mai mult.NET Framework) este o componentă software pusă la dispoziţie de către Microsoft şi integrată în sistemele de operare începând cu Windows 2003 Server. . Compilatoarele ce produc cod pentru platformă reprezintă acest cod într-o formă intermediară abstractă. accesul la date. Un alt mare avantaj îl constituie interoperabilitatea extinsă între limbajele de programare care produc aplicaţii pentru platformă – interoperabilitate realizată nu doar la nivel de apel reciproc al funcţiilor ci la nivel de clase. dezvoltarea de aplicaţii web.NET (. Organizarea internă a tipurilor de date este pur orientată obiect. Pentru a putea emite un executabil valid.NET. independentă de limbajul de programare sursă folosit şi 24 .NET Common Language Runtime şi o bibliotecă de clase (Base Class Library) ce acoperă o arie largă de necesităţi precum interfaţa cu utilizatorul.NET Compact Framework) precum şi o portare pentru sistemele de operare Linux (Mono Project).1 Platforma .NET Platforma .. Generarea de cod pentru platforma .NET şi sistemul de operare. criptografie. 3. o versiune a platformei există şi pentru dispozitive mobile (. Platforma este compusă din două mari componente: maşina virtuală . trebuie cunoscută structura unui fişier executabil precum şi setul de instrucţiuni puse la disponibile de maşina pe care codul va fi executat. intenţionându-se ca aceasta să fie folosită de către majoritatea noilor aplicaţii create pentru platforma Windows[9].3. conectivitatea la baze de date. 3.1. un program scris într-un limbaj putând moşteni clase scrise în alt limbaj şi compilate de un alt compilator.NET Framework este o ofertă cheie a firmei.

Loader-ul citeşte metadata şi crează în memorie o reprezentare internă a claselor executând în acelaşi timp verificări pentru a confirma integritatea metadatelor referite. Un modul este compus din două mari componente. loader-ul citeşte metadata asociată clasei. Compilatorul acestui limbaj nu va putea decide care din cele două clase este referită. O aplicaţie .NET sau chiar compilatorul prezentat în această lucrare). Folosind un alt limbaj. păstrând în memorie codul nativ în aşteptarea execuţiilor ulterioare. De exemplu un limbaj poate fi case-sensitive. Printre altele. compilatorul JIT transformă codul IL în cod nativ. utlizatorul încearcă să utilizeze una din clase. Codul executat de CLR se numeşte managed code. pe scurt IL. runtime-ul asigurând managementul codului. CLR nu este un interpretor[6].1. oferind astfel o performanţă mult mai ridicată faţă de un interpretor. IL conţine instrucţiunile specifice maşinii virtuale. pot apărea diferite incompatibilităţi datorate diferenţei dintre limbaje. Un assembly poate fi format din unul sau mai multe fişiere. elemente globale etc. Pentru a rezolva astfel de probleme s-a impus stabilirea unui set de reguli care să asigure o interoperabilitate consistentă. Atât loader-ul cât şi compilatorul JIT lucrează doar la cerere – când o clasă este referită. Reprezentarea intermediară pusă la dispoziţie asigură interoperabilitatea extinsă dintre limbaje. respectiv compilatorul JIT. CLR se aseamănă altor run-time-uri pentru limbaje interpretate. când o metodă este apelată. clasele compilate într-un assembly putând fi utilizate citind metadata ce le descrie. declarând două clase cu acelaşi nume şi diferenţiidu-le doar prin litere mici/mari. un loader şi un compilator just-intime (JIT). denumite module. nu le vor procesa. Metadata este un sistem de descriptori pentru toate elementele structurale ale unei aplicaţii – clase.NET sunt generate de diferite compilatoare (cum ar fi Microsoft Visual C#. loader-ul. tratarea excepţiilor. Acest set de reguli poartă denumirea de Common Language Specification (CLS) şi limitează elementele limbajelor precum convenţiile de 25 . Microsoft Visual Basic . chiar dacă toate compilatoarele produc metadata şi cod IL corect. Modelul de execuţie utilizat de CLR foloseşte două componente. Dar asemănarea este doar principială. În caz că o clasă nu este referită niciodată dea lungul execuţiei unei aplicaţii sau o metodă nu este apelată niciodată.independentă de sistemul de operare pe care codul va fi executat. În principiu. cum ar fi GBasic.2 Common Language Specification Având în vedere că aplicaţiile . Codul nativ astfel compilat este executat de către sistemul de operare. case-insensitive. acesta asigură garbage collection (obiectele neutilizate sunt eliberate automat din memorie). Compilatorul JIT transformă metodele din IL în cod nativ maşinii pe care este rulată aplicaţia. – şi a relaţiei dintre ele. 3. Acest sistem stă la baza interoperabilităţii.NET poartă numele de assembly. mebri şi atributele claselor. verificarea şi conversia automată a tipurilor în timpul execuţiei etc. metadata şi limbajul intermediar. numit Microsoft Intermediate Language (MSIL) sau Common Intermediate Language (CIL) sau. reprezentat în formă binară abstractă.

În cazul în care un compilator produce cod ce nu respectă CLS. de o metodă etc. claselor System. 3. de o clasă.NET pune la dispoziţie un set de clase grupate sub denumirea de System. de un modul. compilatorul poate completa mult mai uşor informaţiile metadata folosind clasele specifice decât scrierea directă a fişierului binar.NET. ce poate fi executat de către CLR. sau să consume date din stivă.denumire.Emit.pdf 26 . tipurile de date. utilizate pentru a genera dinamic assembly-uri şi cod executabil. Trebuie avut în vedere că aceste specificaţii sunt doar recomandări pentru a asigura interoperabilitatea aplicaţiilor . folosind chiar platforma .1. diferă în funcţie de sistemul de operare).3 Reflection Reflection este numele generic dat capabilităţilor specifice maşinilor virtuale de a inspecta (citi) cod compilat şi de a pune la dispoziţie programatorilor unelte necesare pentru generarea de cod executabil la runtime. Maşina nu foloseşte regiştri şi nu permite adresarea 1 http://www. Este pus la dispoziţie şi un set de clase. Astfel se oferă un plus de portabilitate – compilatorul poate fi utilizat pe orice sistem de operare pe care este instalat . grupate sub denumirea de System.org/publications/files/ECMA-ST/Ecma-335. În acest fel.NET. CLS este detaliat în partea a 2-a a standardului ECMA 335 . compilatorul poate să nu ţină cont de structura efectivă a unui executabil şi header-ele specifice acestuia (care. Proprietăţile acestor clase sunt extrase întotdeauna din assembly-uri compilate.NET pentru construirea unui compilator sunt numeroase: compilatorul nu trebuie să cunoască toate clasele puse la dispoziţie de . acest cod s-ar putea să nu poată fi utilizat de către alte aplicaţii dar el poate să fie cod valid. Sunt puse astfel la dispoziţie clase ce încapsulează informaţiile conţinute de un assembly. Avantajul utilizării capabilităţilor Reflection oferite de . clase şi assembly-uri pe care le pot ulterior salva şi/sau executa. nu prin dezasamblare. Aici. Platforma .Reflection. desigur.4 Intermediate Language Cu toate că utilizarea capabilităţilor Reflection uşurează compilarea informaţiilor metadata.Reflection pentru a inspecta codul compilat – acest lucru făcându-se citind metadata.1. În momentul generării unui executabil. compilatorul poate folosi Reflection pentru a identifica şi valida entitatea externă.NET.NET pentru a produce aplicaţii .când se doreşte apelarea unei entităţi externe programului.ecma-international.Common Langauge Infrastructure 4th Edition din iunie 20061.Reflection le corespund clase ce pot construi entităţi precum metode.NET sau de alte aplicaţii . Maşina virtuală este bazată pe o stivă – o instrucţiune IL poate să adauge date pe stivă. 3. codul executabil trebuie scris utilizând corect limbajul intermediar şi facilităţile puse la dispoziţie de maşina virtuală. funcţiile acceptate şi altele.

). este suficient un singur modul. ele nu pot fi executate independent. Astfel. Referinţele nu sunt pointeri deoarece ele nu adresează o locaţie fixă din memorie – mecanismul de garbage collection poate muta instanţele stocate în heap dintr-o zonă de memorie în alta. fişierele componente. acesta este căutat în directorul aplicaţiei. Pentru fişiere executabile există întotdeauna un modul principal. eventual. cu toate că modulele conţin cod IL şi metadata. Desigur. un strong name este format din cel puţin patru componente: un nume de fişier (fără extensie). fie referiri la metadata (metadata token). menţinând astfel 1 Se pune la dispoziţie un context (unsafe) care permite manipularea directă a memoriei şi lucrul cu pointeri dar utilizarea acestui context nu este încurajată din motive evidente – CLR nu poate asigura managementul memorie.. 3. Tipurile valoare sunt încărcate direct pe stivă (cum ar fi întregii. tratarea excepţiilor şi securitatea pe care le asigură în modul normal de lucru (context safe) 27 . Pentru majoritatea aplicaţiilor. în momentul referirii unui assembly. semnătura digitală etc.2 Structura unei aplicaţii O aplicaţie pentru platformă (un assembly) este format din patru componente: un assembly manifest ce conţine informaţii despre assembly precum versiunea. Aceste componente se regăsesc într-unul sau mai multe module. Platforma permite executarea aplicaţiilor ce menţin module pe alte calculatoare. assembly-urile sunt identificate printr-un strong name. este indicată folosirea modulelor multiple. modulele fiind copiate pe calculatorul pe care rulează aplicaţia doar în momentul în care sunt referite – în această situaţie. numerele în virgulă flotantă. cultura. care conţine punctul de intrare al aplicaţiei. cod Intermediate Language. Tipurile referinţă sunt încărcate în memoria heap. Parametrii pot fi fie value types. Setul de instrucţiuni IL specific versiunii 2. pentru un assembly de dimensiuni mari. structurile etc. cu mai multe variante pentru diferite regiuni/limbi) şi un public key token utilizat pentru validarea integrităţii. 3. De exemplu apelarea unei metode se poate face folosind instrucţiunea call urmată nu de un pointer la metoda apelată ci de o referinţă la metadata metodei. De menţionat că.NET numără 220 de instrucţiuni.0 a platformei . în afara contextului assembly-ului.1 Referirea unui assembly În modelul . tabele de metadata şi resurse. o versiune.2. O instrucţiune IL este reprezentată de un opcode (codul operaţiei) şi.directă a memoriei1. pe stivă punându-se doar o referinţă către aceste tipuri. unul sau mai mulţi parametri. o aplicaţie poate apela cod dintr-un assembly extern – la fel cum o aplicaţie pentru platforma Windows poate apela funcţii din bibliotecile sistemului sau biblioteci scrise de utilizatori.NET. Tipurile de date utilizate se împart în două categorii: tipuri valoare (value types) şi tipuri referinţă (reference types). nu printrun nume de fişier şi printr-o cale. fie referinie. Astfel. o cultură (pentru a identifica versiunea unui assembly în cazul aplicaţiilor internaţionalizate. în subdirectoarele acesteia şi în Global Assembly Cache.

3. Diferite instrucţiuni IL primesc ca parametru un metadata token de care mediul de execuţie se foloseşte pentru a regăsi informaţiile necesare în tabela corespunzătoare.).2. Intrarea corespunzătoare unui tip (majoritatea tipurile sunt clase datorită modelului orientat obiect al platformei) în tabelul de metadata conţine. printre altele.dll. definiţia de bază a metodei (în caz că metoda este moştenită). Intrarea corespunzătoare unui membru al unei clase conţine numele membrului. 3. printre altele. numele tipului. la lansarea oricărei aplicaţii.) necesare oricărei aplicaţii. Maşina virtuală utilizează o stivă care. Utilizarea token-urilor metadata reprezintă un mod unificat de a referi atât elementele interne aplicaţiei precum şi elementele externe (definite într-o bibliotecă referită).NET nu suportă moşteniri multiple). tipul returnat.).2 Global Assembly Cache Uzual. există biblioteci utilizate de mai multe aplicaţii (întreg Base Class Library este format din biblioteci puse la dispoziţia oricărei aplicaţii).) Intrarea corespunzătoare unei metode conţine numele metodei. Pentru acestea există spaţiul virtual numit Global Assembly Cache (GAC). un şir binar de opcode-uri şi parametri dintr-un modul. readonly etc. string etc. Instrucţiunile pot pune elemente pe stivă – de exemplu ldstr urmat de un şir de caractere pune respectivul şir de caractere pe stivă. printr-un token. pot consuma elemente de pe stivă – instrucţiunea pop “aruncă” elementul aflat în vârful stivei. tipurile parametrilor. O bibliotecă parte a Base Class Library este mscorlib. bibliotecă ce trebuie referită de orice assembly deoarece aceast conţine. Orice apel de metodă sau referire la un tip sau un membru al unui tip se face utilizând tabelele de metadata.4 Setul de instrucţiuni IL Instrucţiunile adresate maşinii virtuale sunt citite dintr-un instruction stream.3 Metadata Tabelele metadata conţin descrieri a tuturor elementelor logice dintr-o aplicaţie. static etc. Platforma suportă method overloading – metode cu acelaşi nume. clasa pe care o moşteneşte (. private etc. bibliotecile utilizate de o aplicaţie şi numai de o aplicaţie sunt distribuite în acelaşi director cu aplicaţia sau într-unul din subdirectoare.2. interfeţele pe care le implementează şi informaţii despre membri.restricţiile de securitate (nu se poate specifica un director arbitrar) şi integritatea aplicaţiilor. declarate în aceiaşi clasă. abstract. declaraţiile tipurilor de bază (object. tipul său. vizibilitatea. vizibilitatea (public. 28 . Totuşi. alţi modificatori (virtual.2. care diferă doar prin numele şi/sau tipul argumentelor. este goală. vizibilitatea şi alţi modificatori (static. Locaţia pe disc este irelevantă pentru aplicaţiile . 3.NET deoarece o referire printr-un strong name asigură că assembly-urile referite vor fi căutate şi în GAC.

clt – verifică dacă al doilea element din vârful stivei este mai mic decât primul şi le înlocuieşte cu 1 dacă este.add – înlocuieşte primele două elemente din vârful stivei cu suma lor .rem – determină restul împărţirii celui de al doilea element din vârful stivei la primul şi le înlocuieşte cu rezultatul operaţiei Operaţii pe biţi: xor – înlocuieşte valoarea din vârful stivei cu complementul său binar - Comparaţii: .div – împarte al doilea element din vârful stivei la primul şi le înlocuieşte cu rezultatul operaţiei . În cazul în care o instrucţiune încearcă să consume mai multe elemente decât sunt pe stivă sau.ceq – verifică dacă primele două elemente din vârful stivei sunt egale şi le înlocuieşte cu 1 dacă sunt.sau ambele – un apel de metodă scoate de pe stivă un număr de elemente egal cu numărul de parametri aşteptaţi şi depune valoarea returnată de metodă. la ieşirea dintr-o metodă. Astfel.mul – înlocuieşte primele două elemente din vârful stivei cu produsul lor .sub – scade primul element din vârful stivei din al doilea şi le înlocuieşte cu rezultatul operaţiei .cgt – verifică dacă al doilea element din vârful stivei este mai mare decât primul şi le înlocuieşte cu 1 dacă este. Cu toate că setul de instrucţiuni este relativ mare (220 de instrucţiuni) compilatorul prezentat utlizează doar 36 de instrucţiuni: Operaţii aritmetice: . cu 0 dacă nu . cu 0 dacă nu . identificăm o proprietate a instrucţiunilor numită delta-stack – diferenţa dintre numărul de elemente aflate pe stivă înainte şi după executarea instrucţiunii[6]. aceasta lasă pe stivă mai multe elemente decât valoarea returnată.neg – înlocuieşte valoarea din vârful stivei cu negativul ei (produsul ei cu -1) . cu 0 dacă nu Încărcare pe stivă: ldc_i4 i – încarcă constanta întreagă i pe stivă ldc_i4_0 – încarcă constanta întreagă 0 pe stivă ldc_i4_1 – încarcă constanta întreagă 1 pe stivă ldc_i4_2 – încarcă constanta întreagă 2 pe stivă ldc_i4_3 – încarcă constanta întreagă 3 pe stivă ldc_i4_4 – încarcă constanta întreagă 4 pe stivă ldc_i4_5 – încarcă constanta întreagă 5 pe stivă ldc_i4_6 – încarcă constanta întreagă 6 pe stivă ldc_i4_7 – încarcă constanta întreagă 7 pe stivă ldc_i4_8 – încarcă constanta întreagă 8 pe stivă ldc_r8 r – încarcă constanta în virgulă mobilă cu precizie dublă r pe stivă - 29 . stiva este considerată dezechilibrată şi CLR termină aplicaţia considerând-o invalidă.

numele instrucţiunilor este înlocuit cu o valore reprezentată de 1 sau 2 octeţi (în funcţie de instrucţiune) urmată de eventualii parametri – spre exemplu instrucţiunea ldc_i4 primeşte ca parametru un întreg reprezentat pe 4 octeţi (32 de biţi).blt x – face un salt la x dacă prima valoare de pe stivă este mai mică decât a doua . care facilitează atât inspectarea assembly-urilor externe – pentru un apel de funcţie de exemplu se verifică dacă funcţia se regăseşte întradevăr într-o bibliotecă sau apelul din codul sursă este incorect – precum şi scrierea 30 . 0 corespunzând primului argument. În format binar.3 Generarea de cod Pentru generarea codului.bgt x – face un salt la x dacă prima valoare de pe stivă este mai mare decât a doua - Apeluri de funcţii - – primeşte ca parametru un metadata token al unei metode pe care o apelează pasându-i ca argumente elementele aflate în vârful stivei newobj <token> – primeşte ca parametru un metadata token al unui constructor şi instanţiază un obiect de acel tip. după ordinea în care au fost declarate.- ldstr s – încarcă constanta şir de caractere s pe stivă ldloc x – încarcă pe stivă variabila locală cu numărul x ldarg x – încarcă pe stivă argumentul cu numărul x al funcţiei ldsfld <token> – primeşte ca parametru un metadata token al unui atribut static al unei clase şi îl încarcă pe stivă - Mutare de pe stivă: pop – scoate elementul din vârful stivei stloc x – mută valoarea din vârful stivei în variabila locală cu numărul x stsfld <token> – primeşte ca parametru un metadata token al unui atribut static al unei clase şi mută valoarea din vârful stivei în el Instrucţiuni de branching: ret – părăseşte necondiţionat funcţia br x – face un salt necondiţionat la instrucţiunea x brfalse x – face un salt la instrucţiunea x dacă valoarea din vârful stivei este 0 brtrue x – face un salt la instrucţiunea x dacă valoarea din vârful stivei este diferită de 0 . 1 celui de al doilea argument etc. compilatorul prezentat se foloseşte de capabilităţile Reflection puse la dispoziţie de platformă. punând pe stivă o referinţă la el call <token> Argumentele unei metode sunt numerotate în ordinea în care sunt primite. Variabilele locale (declarate în corpul unei metode) sunt numerotate în acelaşi mod. 3.

Reflection. modulele punând la rândul lor la dispoziţie o metodă ce extrage din metadata definiţiile de tipuri – GetTypes().dll este încărcată implicit pentru orice text sursă. O instanţă a clasei Assembly conţine informaţii despre un assembly . valoare returnată şi argumente pentru metode etc. manifest pentru CLR.) şi metode care permit inspectarea ulterioară a membrilor tipului (ce atribute şi ce metode conţine). FieldBuilder şi ILGenerator. extrase din tabela de metadata (ce clasă moşteneşte.Load. Pe lângă acestea. Fiecărui tip de date îi corespunde o instanţă a clasei Type. folosind un model orientat obiect. Module. MethodBuilder. ModuleBuilder. vizibilitatea etc. unui atribut conţinut de un tip îi corespunde un obiect FieldInfo pe când unei metode (conţinută sau nu într-un tip.NET.Emit Clasele puse la dispoziţie sub numele de System. Aşa cum unui tip îi corespunde un obiect Type. La fel ca pentru tipuri. care conţine informaţii relevante despre tip. Este pusă la dispoziţie o metodă pentru a extrage modulele ce compun assembly-ul – GetModules() – care returnează o listă de module. ConstructorBuilder. deoarece . Clasele AssemblyBuilder. Compilatorul se foloseşte de aceste clase pentru a valida referirile la atribute şi apeluri de metode externe. compilatorul primeşte. În momentul în care se doreşte compilarea unui text sursă. În momentul în care se identifică o referire externă. opţional. Metoda returnează o listă de obiecte Type pentru toate tipurile definite în modul. Reflection utilizează deseori clasa Type.NET suportă metode globale) îi corespunde un obiect MethodInfo. precum tipurile pe care acesta le conţine.3.2 System.Reflection.3. care se regăseşte în biblioteca System. TypeBuilder. De exemplu.vizibilitate. Folosindu-se metoda statică Assembly.Reflection Printre clasele puse la dispoziţie sub numele de System. pentru a construi un modul.fişierului executabil – header pentru sistemul de operare. se crează o instanţă a clasei Assembly corespunzătoare assembly-ului referit. 3.Reflection. entrypoint-ul şi manifestul. acesta trebuie să 31 . ModuleBuilder. care primeşte ca parametru un strong name ce corespunde unui assembly. tip pentru atribute. tabele de metadata şi instruction stream. TypeBuilder. ConstructorBuilder. creând instanţe Assembly pentru fiecare dintre ele. parametri compatibili). esenţiale pentru un compilator sunt clasele Assembly. de exemplu către o metodă. compilatorul se foloseşte de listă pentru a căuta o metodeă care se potriveşte cu semnătura apelului (acelaşi nume. 3. Biblioteca mscorlib. MethodBuilder şi FieldBuilder permit declararea structurată a intrărilor metadata. ca argumente şi o listă de strong names care identifică assembly-urile a căror componente sunt referite în textul sursă. FieldInfo şi MethodInfo. Se determină astfel în timpul compilării dacă apelul este corect sau se încearcă apelarea unei metode inexistente. modificatori. aceste obiecte conţin datele ce se regăsesc în tabelele de metadata corespunzătoare atributelor şi metodelor .1 System.Emit şi utilizate de compilator sunt AssemblyBuilder.

Pentru a marca punctul etichetat. inclusiv header-ele necesare sistemului de operare. se foloseşte ca parametru obiectul Label corespunzător.Reflection permit doar inspectarea (citirea) metadatelor. opţional. un header COFF (Common Object File Format) şi altele. care primeşte ca parametru un obiect label şi marchează ca punct de salt următoarea instrucţie creată printr-un apel Emit. se foloseşte instrucţiunea ldc. astfel că nu este necesară scrierea octetului corespondent instrucţiunii. Folosind reflection. diferenţa fiind că. în timp ce clasele System.Emit permit construirea metadatelor. astfel că AssemblyBuilder pune la dispoziţie metoda DefineDynamicModule. o semnătură PE (Portable Executable) care identifică fişierul ca executabil. există instrucţiunile IL 32 . Pe lângă clasele ce facilitează scrierea metadata. în loc de adresa binară a instrucţiunii. metoda Save pusă la dispoziţie de clasa AssemblyBuilder scrie executabilul. în momentul scrierii codului. Instrucţiunile sunt construite apelând metoda Emit a clasei. Pentru parametrii instrucţiunilor de salt este pusă la dispoziţie clasa Label astfel că. se crează o instanţă Label. clasele System. instanţe ILGenerator sunt create apelând metoda GetILGenerator a claselor MethodBuilder sau ConstructorBuilder. scrierea acestor header-e nu mai trebuie implementată în compilator.) şi că optimizările ce depind efectiv de sistemul pe care aplicaţia rulează sunt realizate de către compilatorul JIT al platformei. care primeşte. Utilizând Reflection. parametrii aferenţi opcode-urilor. La fel.3. în schimb este folosit numele opcode-ului. Compilatorul se foloseşte de aceste clase pentru a construi toate tabelele de metadata aferente aplicaţiei compilate. transformarea în formă binară fiind efectuată intern. Un set de astfel de optimizări este reprezentat de instrucţiunile prescurtate de încărcare a valorilor pe stivă. primul octet reprezentând tipul metadatei. un header MS DOS. memoria nu este manipulată direct etc. printre altele.i4 urmată de valoarea constantei. Totuşi.Reflection – de fapt sunt derivate din clasele corespunzătoare. 3. Se observă că aceste clase corespund claselor System. Se folosesc în schimb obiecte Label astfel: apelând metoda DefineLabel a clasei ILGenerator. instrucţiuniile ce necesită token-uri ca parametru primind în schimb obiecte de tip MethodInfo. nu trebuie ţinut cont de numărul instrucţiunii la care se doreşte saltul.3 Optimizări dependente de maşină Având în vedere că arhitectura maşinii virtuale este relativ simplă (nu există regiştri.aparţină unui Assembly. clasa ILGenerator este pusă la dispoziţie pentru scrierea instrucţiunilor IL. Pentru instrucţiunile de salt. se apelează metoda MarkLabel. Un metadata token care identifică o metodă sau un atribut este o valoare numerică pe patru octeţi. Cum instrucţiuni IL pot să apară doar în corpul metodelor. există puţine optimizări specifice CLR-ului de care un compilator poate să ţină cont. Opcode-urile sunt puse la dispoziţie în enumerarea OpCodes.Reflection. un tip trebuie să aparţină unui modul. astfel că ModuleBuilder pune la dispoziţie metoda DefineType. nu este necesară construirea manuală a acestor token-uri. ceilalţi trei intrarea din tabelul de metadata corespunzător. metode şi atribute. FieldInfo etc. Pentu a încărca o constantă număr întreg. În acelaşi mod se pot defini constructori. În momentul în care se încheie compilarea şi se doreşte salvarea assembly-ului. Un executabil Windows conţine.

. ldc. instrucţiuni ce nu aşteaptă niciun parametru. instrucţiunea ldc.i4.3 cu diferenţa că prima instrucţiune ocupă 5 octeţi (1 octet pentru opcode şi 4 pentru constantă) în timp ce a doua ocupă doar 1 octet (pentru opcode).i4.1.i4.ldc. Astfel. 1.i4 3 este echivalentă instrucţiunii ldc.. respectiv 8.. ldc.8 care încarcă pe stivă constantele întregi 0. 33 ...0. De menţionat că astfel de optimizări nu au un impact critic asupra performanţei aplicaţiilor. .i4.

Analiza semantică şi optimizări independente de maşină (aplicate asupra formei intermediare) 3. Interoperabilitatea cu alte assembly-uri. numere în virgulă mobilă cu precizie dublă (double). Descrierea aplicaţiei Compilatorul preia un text sursă şi îl procesează. Întregul procesul este efectuat în trei paşi: 1. care marchează sfârşitul unei declaraţii.NET corespunzător. program exemplu. Analiza lexicală şi sintactică (făcute cu ajutorul unui scanner şi a unui parser generate automat) 2. şiruri de caractere (string) şi valori logice booleene (bool) – precum şi vectori şi matrici (în oricâte dimensiuni. NET se limitează la apelul de metode externe statice precum şi referirea atributelor externe statice.1 Declaraţii Orice aplicaţie începe cu cuvântul cheie program urmat de un identificator ce reprezintă numele programului (numele pe care îl va avea executabilul generat) şi simbolul “. atât timp cât tipurile de date sunt suportate de limbaj. aducându-l întâi la o formă intermediară.NET. Generarea de cod şi optimizări dependente de maşină (care sintetizează executabilul final) Aplicaţia rezultată poate fi rulată pe orice maşină pe care este instalată platforma . analizând şi modificând reprezentarea intermediară şi generând în final un executabil . 4.1. 4.”. 34 . Sunt suportate declaraţii de funcţii precum şi declaraţii de variabile. globale sau locale funcţiilor în care apar. iniţializate static) ce conţin tipuri primitive de date.4.1 Limbajul sursă Limbajul sursă procesat de compilator este un limbaj procedural simplu ce utilizează patru tipuri primitive de date – numere întregi (int).

begin end Funcţia entrypoint a aplicaţiei nu este declarată.1. opţional. argumentele funcţiei. double argument2) var int locala. doar corpul ei este încadrat de cuvintele cheie begin şi end.Console astfel: 35 . double. operaţii unare şi operaţii binare. după celelalte declaraţii de variabile şi funcţii. Constantele literare de tip logic sunt cuvintele cheie true şi false. care conţin dimensiunile separate prin virgule – şi unul sau mai mulţi identificatori separaţi prin virgule. 10] matrice. declaraţii de variabile locale şi corpul funcţiei delimitat de cuvintele cheie begin şi end. 4. referiri de variabile. se utilizează atributul static CapsLock al clasei System. function exemplu(int argument1.Declaraţiile de variabilele încep cu cuvântul cheie var.”. 12 sau "string" sunt astfel de constante. apeluri de funcţii. string sau bool. Aceasta apare întotdeauna ultima în textul sursă. double[10. urmat de numele funcţiei şi o pereche de paranteze rotunde care conţin. În cazul în care funcţia returnează o valoare. pentru a vedea dacă CapsLock este activat.NET au de obicei numele compus din mai mulţi identificatori separaţi prin punct). Funcţiile sunt declarate utilizând cuvântul cheie function. Constante literare sunt valori constante declarate în limbajul sursă. De exemplu. Pentru interoperabilitatea cu biblioteci externe – referirea atributelor statice a unei clase declarată într-un alt assembly – referirea se face utilizând un număr oricât de mare identificatori separaţi prin simbolul “. identificate prin cuvintele cheie int. Aceştia reprezintă numele clasei (clasele . care poate fi urmat de paranteze pătrate (care denotă un vector sau o matrice).”. pe stivă rămâne o valoare). typecast-uri. urmaţi de simbolul “. Expressile sunt de mai multe tipuri: constante literare. Urmează. int variabila2. parantezele sunt urmate de simbolul “:” şi tipul returnat.2 Expresii Expresiile sunt construcţiile pentru care se emite cod şi care returenează o valoare (în urma execuţiei. var bool variabila1. opţional. indexări. Referirile de variabile sunt reprezentate printr-un identificator (numele variabilei referite). Argumentele sunt formate dintr-un tip şi un identificator şi sunt separate prin virgulă. variabila3. Urmează oricâte declaraţii formate dintr-un tip – tipul variabilelor este unul din tipurile primitive.

Operatorii binari sunt operatori de egalitate (==. -12 !false Operaţiile binare sunt formate dintr-un operator şi doi operanzi. Operatorii unari sunt minus unar şi negaţie logică. sau exclusiv logic 36 . *.System. Singurul cast explicit implementat este transformarea unei valori double într-o valoare întreagă: {int} 20. urmată de oricâte expresii ce returnează valori întregi (indecşi) încadrate de paranteze pătrate şi separate prin virgule. doar că.CapsLock Apelurile de funcţii au aceiaşi formă ca referirile de variabile. –. una sau mai multe expresii separate prin virgulă. apel de funcţie 2. 10). după ultimul identificator.1] Typecast-urile sunt reprezentate printr-un tip primitiv încadrat de paranteze acolade şi o expresie (expresia pentru care se face casting). identificaţi prin cuvinte cheie (and. Înmulţire. Minus unar. /. în caz că funcţia acceptă argumente. Operatorul este aplicat asupra valorii rezultate în urma evaluării expresiei. %) şi operatori logici. Typecast. sau logic. O indexare este formată dintr-o expresie ce trebuie să returneze o valoare de tip matrice sau vector. Şi logic. negaţie logică 4. >. indexare 3.1 Operaţiile unare sunt formate dintr-un operator şi un operand. >=. <=.Console. Indexările returenează valoarea dintr-o matrice aflată la indecşii specificaţi (sau matricea cu mai puţine dimensiuni conţinută în matricea dată la indecşii specificaţi). împărţire. or. Adunare. i + j a > b c or d Precedenţa operaţiilor este următoarea: 1. se utilizează expresia Math. Referire de variabilă. operatori aritmetici (+. urmează o pereche de paranteze rotunde ce conţin. xor). Pentru a apela funcţia Pow (care primeşte ca argument două valori double şi returnează rezultatul ridicării primei valori la puterea celei dea doua) din clasa Math pentru a calcula 210. <. !=). test[0.Pow(2. modulo 5. scădere 6.

3 Propoziţii Propoziţiile sunt construcţiile pentru care se emite cod şi care. Toate propoziţiile ce formează corpul unei funcţii sunt conţinute de o propoziţie bloc. Aceste propoziţii sunt formate din două expresii separate prin simbolul “=” şi încheiate de simbolul “. Structurile condiţionale sunt structurile unde se decide. propoziţii apel. return 5. Pe lângă aceasta.”. Propoziţiile conţin expresii. System. Expresia din stânga trebuie să fie o expresie care poate fi atribuită – fie o referire de variabilă. else System. propoziţia poate fi urmată de cuvântul cheie else şi o altă propoziţie ce va fi executată în cazul în care expresia este evaluată ca falsă. Pentru a modifica ordinea de executare a operaţiilor se pot utliza paranteze rotunde. Propoziţiile atribuire sunt utilizate pentru a atribui expresiei din stânga valoarea returnată în urma evaluării expresiei din dreapta. propoziţii atribuire. test[0.Console. 37 .1.Console. pe baza rezultatului evaluării unei expresii.0] = 5. inegalitate şi operaţii de comparaţie Operaţiile cu aceaşi precedenţă sunt evaluate în ordine de la stânga spre dreapta. propoziţi return.7. fie o indexare. structuri condiţionale şi structuri repetitive.”. O propoziţie apel arată din punct de vedere sintactic ca o expresie apel. Propoziţiile bloc (block statements) sunt propoziţii care conţin o listă de propoziţii internă. Limbajul suportă propoziţii bloc. propoziţiile bloc pot să apară oriunde în textul sursă unde sunt aşteptate propoziţii. urmat opţional de o expresie a cărei valoare este returnată şi simbolul “. if (i == 1) System. urmat de o expresie încadrată în paranteze şi o propoziţie ce va fi executată doar în cazul în care expresia este evaluată ca adevărată (având o valoare diferită de 0 sau true pentru expresiile logice). Expresia este opţională datorită posibilităţii de a declara funcţii ce nu întorc nicio valoare. nu lasă nimic pe stivă în umra execuţiei. care este următoarea propoziţie ce va fi executată. Propoziţiile apel sunt apeluri de funcţie a căror valoare returnată este ignorată. Opţional. spre deosebire de expresii. doar că este urmată de simbolul “. 4. O propoziţie return începe cu cuvântul cheie return. Acestea încep cu cuvântul cheie if. Propoziţiile return obligă părăsirea necondiţionată a funcţiei executate.WriteLine("Hello World!"). Acestea sunt încadrate de cuvintele cheie begin şi end.Console.WriteLine("i diferit de 1"). Egalitate.” care marchează sfârşitul propoziţiei.WriteLine("i egal cu 1").

Spre deosebire de structura while. ident } .4 Descriere formală Gramatica limbajului exprimată utilizând notaţia Backus-Naur:  program ident .1. { Type ident { . unul din cuvintele cheie to sau downto (to pentru a incrementa valoarea. for i = 10 downto 1 do System. while (i < 10) i = i + 1. ident } . a doua expresie. Limbajul suportă trei tipuri de astfel de structuri repetitive: while.WriteLine(i). se execută propoziţia apoi parametrul este incrementat respectiv decrementat. Cât timp a doua expresie este evaluată ca mai mare sau egală respectiv mai mică sau egală cu parametrul. 4. Structura while începe cu cuvântul cheie while urmat de o expresie încadrată în paranteze şi o propoziţie ce va fi executată atât timp cât expresia este evaluată ca adevărată. downto pentru a decrementa valoarea). while (i < 10). { FunctionDeclaration | VariableDeclarations } ProgramBody FunctionDeclaration  function ident ( [ Type ident [. cuvântul cheie do şi propoziţia ce urmează a fi repetată. Type ident]] ) [: Type] { VariableDeclarations } BlockStatement VariableDeclarations  var Type ident { . } ProgramBody  BlockStatement Type  Primitive [ [ intCon { . do-while şi for. Structura for utilizează un parametru şi două expresii pentru a determina numărul de iteraţii. simbolul =.Structurile repetitive sunt structurile ce permit executarea unei propoziţii de mai multe ori. Parametrului i se atribuie întâi valoarea primei expresii. în funcţie de rezultatul evaluării unei expresii. intCon } ] ] Primitive  bool | double | int | string Start 38 . Structura do-while începe cu cuvântul cheie do urmat de o propoziţie şi cuvântul cheie while urmat de o expresie încadrată în paranteze. Structura începe cu cuvântul cheie for urmat de o expresie ce poate fi atribuită. do i = i + 1.Console. prima expresie. garantând astfel cel puţin o execuţie a propoziţiei. această structură evaluează expresia doar după ce propoziţia a fost executată.

respectiv constante întregi. Expression }] ] | ( Expression )) EqExpr  LogExpr { ( == | != | > | < | >= | <= ) UnaryExpr | LogExpr } LogExpr  AddExpr { ( and | or | xor ) UnaryExpr | AddExpr } AddExpr  MulExpr { ( – | + ) UnaryExpr | MulExpr } MulExpr  { ( * | / | % ) UnaryExpr } Statement Unde token-urile ident.2 Analiza lexicală şi analiza sintactică Procesele de analiză lexicală şi analiză sintactică sunt efectuate simultan (într-un singur pas). IfStatement  if ( Expression ) Statement [ else Statement ] WhileStatement  while ( Expression ) Statement DoWhileStatement  do Statement while ( Expression ) ForStatement  for Expression = Expression ( to | downto ) Expression do Statement Expression  UnaryExpr | EqExpr ConstantExpression  intCont | realCon | stringCon | true | false UnaryExpr  (– UnaryExpression | ! UnaryExpression | ConstantExpression | { Type } UnaryExpression | ident { .[0-9]+((e|E)(+|–)?[0-9]+)?)|(e|E) (+|–)?[0-9]+(d|D)?|d|D) “.*” Celelalte terminale sunt luate ca atare.* /\*(. realCon şi stringCon sunt identificatori. Comentariile ignorate de analizatorul lexical sunt identificate după următoarele expresii regulate: singleLine multiLine //. BlockStatement | CallOrAssgStatement | ReturnStatement | IfStatement | WhileStatement | DoWhileStatement | ForStatement BlockStatement  begin { Statement } end CallOrAssgStatement  Expression [ = Expression ] . constante reale şi constante de tipul şir de caractere. ident } [ ([Expression { . Limbajul este case-sensitive. Componentele scanner şi parser sunt generate automat cu ajutorul aplicaţiei Coco/R pe baza unei descrieri formale a gramaticii şi a unor acţiuni semantice. intCon.[0-9]+((e|E)(+|–)?[0-9]+)?)|([0-9](\. identificate după următoarele expresii regulate: ident intCon realCon stringCon ([a-zA-Z]|_) ([a-zA-Z0-9]|_ )* [0-9]+|(0x|0X)([0-9a-fA-F]+ (\. Expression }] )] [ [Expression {. 39 . ReturnStatement  return [ Expression ] .|\n)*\*/ 4.

4.3 Acţiuni semantice Analizatorul lexical pune la dispoziţie propoziţiilor de cod inserate în descrierea gramaticii (acţiuni semantice) un obiect t de tipul Token. Coco/R permite un lookahead de k simboluri pentru a simplifica rezolvarea eventualelor ambiguităţi – de multe ori un limbaj necesită un lookahead mai mare de 1 doar pentru un număr foarte mic de producţii ce pot genera ambiguităţi. Acţiunile semantice se folosesc de valoarea unui Token pentru a determina. de exemplu.2. valoarea token-ului (şirul de caractere corespunzător din textul sursă). numele unui identificator. Tabelele de simboluri conţin noduri declaraţie. Această ambiguitate este rezolvată prin regula suplimentară (implicit adăugată de generator) de a considera token-ul else ca aparţinând propoziţiei corespunzătoare ultimului token if apărut. Clasa Token reţine. printre altele. declaraţiile de funcţii conţin noduri propoziţie iar nodurile propoziţie conţin atât alte noduri propoziţie precum şi noduri expresie. derivate din clasa abstractă Scope. Institut für Systemsoftware. Acesta generează automatul finit ce identifică toate token-urile corespunzătoare gramaticii lexicale şi un recursive-descent parser pentru o gramatică LL(k) corespunzătoare gramaticii sintactice.1 Tabelele de simboluri Compilatorul utilizează trei tipuri de tabele de simboluri.Cu ajutorul acestor acţiuni semantice se generează reprezentarea intermediară ce va fi evaluată la următorul pas. 4. 4.7 – problema interpretării unui token else ce apare după un token if dintr-o construcţie ce începe cu un alt token if. Compilatorul prezentat nu necesită un lookahead mai mare de 1.2.3 Reprezentarea intermediară Pentru reprezentarea în formă intermediară se utlizează un arbore abstract de sintaxă şi o mulţime de tabele de simboluri. parentScope. care va menţine legătura spre o tabelă de simboluri superioară şi un număr de metode abstracte pentru identificarea 40 .3. Clasa pune la dispoziţie un atribut.2. precum şi linia şi coloana unde a fost identificat.1 Coco/R Coco/R este dezvoltat la Universitatea Johannes Kepler din Linz. Deşi implicit se utilizează un lookahead cu un singur simbol. Linia şi coloana sunt memorate pentru raportarea erorilor.2. 4. 4.2 Gramatica pentru generator Gramatica definită pentru Coco/R conţine o singură ambiguate prezentată şi în secţiunea 2.

Tabela de simboluri superioară acestei instanţe este întotdeauna instanţa GlobalScope. o instanţă a acestei clase fiind atribuită fiecărei funcţii. GlobalScope nu are nicio tabelă superioară (parentScope e null). Aceasta implementează metodele abstracte pentru a căuta simbolurile în declaraţiile reţinute. precum şi două metodă pentru a emite cod pentru o referire de variabilă şi o atribuire de variabilă. Tabela de simboluri superioară unui LocalScope este instanţa ProgramScope. astfel că. căutarea acestuia este delegată la tabela de simboluri GlobalScope. unde sunt delegate căutările de variabile în caz că acestea nu sunt identificate în listele menţinute intern. ProgramScope poate fi considerat rădăcina arborelui abstract de sintaxă. Utilizând Reflection. folosind Reflection.Emit. Există zero sau mai multe obiecte LocalScope.funcţiilor şi variabilelor. Cele trei tipuri de tabele de simboluri sunt GlobalScope. Apelurile de funcţii sunt delegate automat deoarece limbajul nu permite declararea de funcţii în interiorul funcţiilor. ProgramScope şi LocalScope. ProgramScope generează obiectele 41 . precum şi toate declaraţiile de funcţii globale (inclusiv funcţia entrypoint). O instanţă GlobalScope reţine toate assembly-urile externe referite şi implementează metodele abstracte pentru a căuta. O instanţă ProgramScope reţine toate declaraţiile de variabilele globale. atribute şi metode în acele assembly-uri. Un LocalScope reţine o listă de variabile locale şi o listă de argumente şi implementează metodele abstracte pentru a căuta simbolurile în aceste liste. GlobalScope Assembly-uri externe ProgramScope Variabile globale Declaraţii de funcţii LocalScope Variabile locale Argumente LocalScope LocalScope Din moment ce toate declaraţiile ce împreună compun un program sunt conţinute de instanţa ProgramScope şi instanţele LocalScope. Atât clasa ProgramScope cât şi clasa LocalScope pun la dispoziţie metode pentru emiterea declaraţiilor. dacă un simbol nu a fost găsit.

La fel. pe când mutarea într-un atribut static al unei clase se face utilizând stsfld (store static field) şi o instanţă FieldInfo. 4. iar LocalScope apelează metoda DeclareLocal a clasei ILGenerator pentru a declara variabilele locale şi argumentele. Cele două funcţii abstracte ce emit cod pentru referirile şi atribuirile de variabile sunt necesare deoarece instrucţiunile IL diferă în funcţie de tipul variabilei astfel: . Supraîncărcarea funcţiilor permite declararea mai multor funcţii cu acelaşi nume. La cel mai simplu nivel. Apelurile de funcţii nu neceseită o astfel de încapsulare deoarece o funcţie este apelată întotdeauna cu instrucţiunea call urmată de o instanţă MethodInfo. care să reţină atributele şi metodele corespunzătoare claselor. care diferă doar prin numărul şi/sau tipul argumentelor. O metodă ce emite o referire sau o atribuire la o variabilă caută variabilele printre simbolurile menţinute intern şi. doar o funcţie de căutare ce întoarce un astfel de obiect.3. în acel punct nu se mai ţine cont de tipul variabilei (locală. Problema apare în 42 .Un atribut static al unei clase este încărcat pe stivă folosind instrucţiunea ldsfld (load static field) urmată de o instanţă FiledInfo. respectiv EmitVariableAssignement a tabelei de simboluri active. în momentul în care se emite cod pentru alte construcţii care includ o referire sau o atribuire de variabilă. dacă o regăseşte. Identificarea metodelor ridică însă o altă problemă: signature matching. astfel că. Aceste diferenţe sunt încapsulate în clasele derivate din Scope. ClassScope. se poate crea un nou tip de scope. pentru a muta valoarea din vârful stivei într-o variabilă locală se utlizează instrucţiunea stloc (store locale) şi numărul de ordine. . emite codul. argument. astfel că tabelele de simboluri nu conţin metode diferite de a emite cod. În momentul în care se apelează o funcţie în textul sursă (printr-un nume şi un set de expresii pasate ca argumente).FieldBuilder şi MethodBuilder corespunzătoare declaraţiilor de variabile globale şi metode.Un argument este încărcat pe stivă folosind instrucţiunea ldarg (load argument) urmată de numărul de ordine al argumentului . dacă nu face delegarea emiterii la tabela de simboluri superioară. pentru o funcţie test care poate primi ca argument fie un întreg – test(int) – fie un şir de caractere – test(string) – pentru un apel de tipul test(10) trebuie identificată corect funcţia test(int) ca cea apelată. câmp) – se apelează doar metoda EmitVariableReference. Modelul are avantajul de a fi extensibil – dacă se doreşte de exemplu extinderea limbajului cu suport pentru programare orientată obiect.O variabilă locală este încărcată pe stivă folosind instrucţiunea ldloc (load locale) urmată de numărul de ordine a variabilei. Semnătura unei funcţii este dată de numele acesteia şi lista tipurilor argumentelor pe care aceasta le primeşte. trebuie determinată funcţia ce urmează a fi apelată dintr-o posibilă mulţime de funcţii omonime.2 Signature matching Signature matching este numele generic dat procedeului necesar pentru regăsirea funcţiei corecte la limbajele care permit supraîncărcarea funcţiilor.

Se revine la pasul 2 cât timp mai există în tabela de simboluri funcţii cu acelaşi nume. double) şi test(double. b. identificându-se următoarele cazuri: a. Se verifică dacă tipul argumentelor este compatibil – fie identic. c.momentul în care limbajul suportă şi typecasting implicit. int) la apelul test(10. Se caută în tabela de simboluri o funcţie cu acelaşi nume. 5. că nu se poate determina funcţia dintre două funcţii cu semnăturile test(int. La acelaşi apel însă. int) nu se poate determina funcţia apelată. se adaugă semnătura analizată la listă. se alege test(int) pentru apelul test(10). 3. se revine la pasul 2. Se reţine o listă de semnături candidat care reprezintă cele mai bune semnături identificată până în prezent. se adaugă semnătura la listă. cunoscându-se semnătura apelului: 1. apelul este considerat ambiguu şi se semnalează o eroare semantică. double) deoarece aceasta nu necesită un typecast la un argument pentru care cealaltă semnătură ar avea nevoie de typecast – semnătura este “mai apropiată” de semnătura apelului. 4. b. se revine la pasul 2. 43 . În această situaţie. Se observă. Ambele necesită typecasting la argumente diferite. Se compară semnătura cu fiecare semnătură din lista de candidaţi. Dacă nu. este evident că apelul nu se referă la această funcţie şi se revine la pasul 2. cu toate că una din funcţii implică un singur typecast pe când cealaltă implică două. int. Dacă nu. double) şi test(double. 30). 2. Se verifică dacă numărul argumentelor coincide. dintre funcţiile test(int. 6. dintre funcţiile test(int) şi test(double). 7. După ce se elimină toţi candidaţii mai puţini buni. Dacă se poate decide că semnătura analizată în prezent este mai bună decât o semnătură din listă. 20) şi semnăturile test(int. Lista are un singur element – s-a identificat funcţia ce trebuie apelată. Regula după care se identifică semnăturile este următoarea: o semnătură este considerată “mai bună” decât alta dacă necesită typecasting la aceleaşi argumente ca o altă semnătură. În cazul în care există mai multe argumente. c. pentru apelul test(10. Dacă se poate decide că semnătura analizată este mai puţin bună decât o semnătură din listă. 20. Motivul este că ambele necesită typecast-uri la argumente diferite. double. Se caută astfel semnătura pentru care se poate face un typecast implicit – test(double). double) şi test(double. a. Lista e goală – nu s-a putut determina nicio funcţie – apel invalid. Identificarea funcţiilor se face în următorii paşi. utilizând această regulă. Lista are mai multe elemente – nu s-a putut determina o singură funcţie – apel ambiguu. Dacă nu se poate decide pentru niciun candidat dacă semnătura este mai bună sau mai puţin bună. se elimină din listă candidatul. fie există un typecast implicit. mai puţin unul. double) se alege test(int. Pentru funcţiile test(double) şi test(string) şi apelul test(10) nu este suficientă verificarea egalităţii tipurilor deoarece 10 este de tip int. Totuşi. Se verifică lista de semnături candidat. Se începe cu lista goală.

utilizată pentru a instanţia un vector. Aceasta conţine tipul primitiv. se utilizează un vector ArrayType ce conţine tipul ArrayType (a doua dimensiune). Aceasta presupune apelul unei funcţii cu un argument fiind o instanţă a unei clase derivate din tipul acceptat ca parametru. trebuie luat în considerare. nu doar unul primitiv. Clasa ArrayType conţine şi o metodă EmitInstance. pe lângă typecasting. Clasa ArrayType reprezintă un vector sau matrice de tipuri primitive. Metodele sunt necesare deoarece un tip vector . precum şi două metode utilizate pentru a întoarce. PrimitiveType reprezintă un tip de bază recunoscut de limbaj (boolean.3. int sau string). precum şi mărimea fiecărei dimensiuni. numărul de dimensiuni.NET ce reprezintă tipurile interne utilizate de CLR şi este necesară în momentul emiterii codului cu Reflection. O alternativă la modul de reprezentare al compilatorului este de a reprezenta matricile ca având o singură dimensiune dar conţinând orice tip de date. double. La baza reprezentării interne a tipurilor de date se află clasa abstractă BaseType. tipul void. pentru limbajele orientate obiect. pentru a reprezenta o matrice bidimensională de exemplu.De notat că. respectiv stoca o valoare pe o anumită poziţie – EmitGet şi EmitSet. Astfel. Limbajul prezentat nu este un limbaj orientat obiect. şi posibilitatea upcasting-ului. Clasele ce reprezintă tipuri concrete sunt PrimitiveType şi ArrayType.3 Tipuri de date Clasele corespunzătoare tipurilor de date utilizate de limbaj nu apar ca noduri în arborele abstract de sintaxă dar sunt conţinute de majoritatea nodurilor. utilizat la interoperabilitatea dintre o aplicaţie compilată şi un assembly extern în momentul în care apare un tip ce nu este unul din tipurile primitive recunoscute. 4. deci nu necesită verificările aferente.Emit. Aceasta pune la dispoziţie metode abstracte pentru a determina compatibilitatea tipurilor (egalitate sau existenţa unui typecast implicit) precum şi conversia tipurilor din BaseType în Type şi invers – Type este clasa pusă la dispoziţie de . abia acesta conţinând tipul primitiv. utilizat de către funcţiile ce nu returnează valori şi tipul unsupported.NET 44 .

fiecare întreg semnificând mărimea dimensiunii corespunzătoare şi depune pe stivă o referinţă la obiectul vector. De fapt.3. în . precum şi două metode pentru generarea codului: EmitDeclaration şi EmitBody. acesta fiind stocat în memoria heap. Tipurile referinţă trebuiesc întotdeauna instanţiate. tabelele de simboluri conţin instanţe a obiectelor ce corespund acestor declaraţii. conţine numele variabilei şi tipul ei (BaseType). Constructorul primeşte ca argumente (consumă de pe stivă) un număr de întregi egal cu dimensiunea sa.4 Declaraţii Declaraţiile sunt de două tipuri – declaraţii de funcţii şi declaraţii de variabile. 4. astfel că metoda EmitInstance construieşte un vector pe stivă apelându-i constructorul pus la dispoziţie de platformă. tipul returnat (BaseType). Funcţiile EmitGet şi EmitSet sunt necesare deoarece. spre deosebire de limbaje precum C unde indexarea într-un vector se face adunând indexul la pointerul de început al vectorului.este de fapt un reference type. Baza arborelui abstract de sintaxă arată astfel: FunctionDeclaration pune la dispoziţie o metodă Evaluate care apelează metoda Evaluate corespunzătoare instanţei BlockStatment pentru a efectua recursiv analiza semantică şi optimizările independente de maşină. respectiv Set ale instanţei vectorului. corpul funcţiei – un obiect BlockStatement şi o instanţă LocalScope unde sunt reţinute variabilele locale şi argumentele funcţiei (ca instanţe VariableDeclaration). O declaraţie de variabilă (clasa VariableDeclaration). indexarea fiind efectuată de către CLR.NET se utilizează metodele Get. pe stivă fiind ţinută doar o referinţă la acest tip. Instrucţiunea corespunzătoare instanţierii oricărui obiect este newobj urmat de un obiect ConstructorInfo. O declaraţie de funcţie (clasa FunctionDeclaration) conţine numele acesteia. 45 .

un obiect Statement ce corespunde ramurii if a propoziţiei şi. WhileStatement şi DoWhileStatement conţin un obiect expresie de tip Expression ce corespunde condiţiei de repetare a ciclului şi un obiect Statement ce reprezintă corpul ciclului. un obiect Statement ce corespunde ramurii else (dacă aceasta există).Metoda EmitDeclaration generează metadata asociată funcţiei folosind Reflection. Aceasta pune la dispoziţie două metode abstracte – Evaluate pentru evaluarea recursivă şi EmitCode pentru emiterea recursivă a codului. DoWhileStatement. Clasele corespunzătoare expresiilor sunt ConstantExpression. IfStatement. respectiv ultima valoarea pe care variabila o va lua înainte de terminarea ciclului. IfStatement conţine un obiect expresie de tip Expression ce corespunde expresiei condiţionale. Metoda EmitBody apelează metoda EmitCode corespunzătoare instanţei BlockStatement pentru a genera recursiv cod IL. CastExpression.3. UnaryExpression. lasă o valoare pe stivă. 4. ForStatement şi ReturnStatement. Pe lângă acestea. AssignementStatement.6 Expresii Expresiile sunt nodurile ce moştenesc clasa abstractă Expression şi corespund construcţiilor din limbaj care. CallExpression. unul de tip AssignableExpression ce reprezintă partea din stânga a atribuirii şi unul de tip Expression ce reprezintă partea din dreapta a atribuirii.Emit şi construieşte instanţele necesare pentru generarea de cod (obiecte MethodBuilder şi ILGenerator). ReturnStatement conţine. metodele abstracte Evaluate şi EmitCode. Clasele derivate din clasa Statement corespund tipurilor de propoziţii suportate de limbaj: BlockStatement. la fel ca şi clasa Statement. un obiect de tip Expression care este valoarea întoarsă de funcţie (dacă aceasta există). CallStatement. Clasa abstractă pune la dispoziţie. 4. AssignementStatement conţine două obiecte. opţional. ConstantExpression reprezintă o constantă literală din cod.5 Propoziţii Nodurile propoziţie ale arborelui abstract de sintaxă sunt reprezentate din clase ce moştenesc clasa abstractă Statement. ForStatement conţine un obiect expresie de tip AssignableExpression ce reprezintă variabila ce variază la fiecare iteraţie.3. opţional. WhileStatement. două obiecte expresie de tip Expression ce reprezintă valoarea atribuită iniţial variabilei. există şi atributul ReturnType (de tip BaseType) ce reprezintă tipul valorii returnate de expresie. BlockStatement conţine o listă de obiecte tip Statement şi reprezintă propoziţiile conţinute între o pereche de cuvinte cheie begin şi end. CallStatement conţine un obiect expresie de tip CallExpression şi reprezintă un apel de funcţie. BinaryExpression şi AssignableExpression. 46 . în urma executării.

CallExpression reprezintă un apel de funcţie şi conţine numele funcţiei apelate şi o listă de obiecte Expression ce reprezintă argumentele pasate funcţiei. CastExpression reprezintă un typecast şi conţine un obiect Expression asupra cărui rezultat va efectua cast-ul precum şi tipul (BaseType) în care se va face transformarea. UnaryExpression reprezintă o expresie cu un operator (unar) şi un operand de tip Expression. BinaryExpression reprezintă o expresie cu un operator (binar) şi doi operanzi de tip Expression. AssignableExpression reprezintă expresiile cărora le pot fi atribuite valori. Din clasa AssignableExpression sunt derivate două clase, VariableReferenceExpression şi IndexerExpression. VariableReferenceExpression reprezintă o referire la o variabilă şi conţine numele variabilei. IndexerExpression conţine o listă de obiecte Expression care, în urma evaluării, reprezintă indecşii unui vector sau unei matrici, precum şi un obiect Expression al cărui tip returnat trebuie să fie ArrayType (expresii care pot retura acest tip sunt VariableReferenceExpression şi CallExpression). Secţiunea din arborele abstract de sintaxă corespunzătoare secvenţei de cod
if (a == b) then System.Console.WriteLine("a egal cu b"); else System.Console.WriteLine("a diferit de b");

este

4.4 Analiza semantică şi optimizări independente de maşină

47

Analiza semantică şi optimizările independente de maşină sunt efectuate tot într-un singur pas, pas care presupunerea parcurgerea recursivă în postordine a arborelui abstract de sintaxă pornind de la nodurile BlockStatement ce reprezintă corpurile funcţiilor. Nodurile de tip Statement pot conţine doar alte noduri de tip Statement sau noduri de tip Expression iar nodurile Expression pot conţine doar alte noduri de tip Expression. Atât clasa Statement cât şi clasa Expression pun la dispoziţie o metodă Evaluate ce primeşte ca parametru o instanţă de tip Scope (ce reprezintă tabela de simboluri activă) şi returnează o instanţă de tip Statement, respectiv Expression. 4.4.1 Analiza expresiilor Procesarea unei expresii presupune determinarea tipului întors de aceasta (setarea atributului ReturnType) şi verificarea compatibilităţii tipurilor operanzilor şi a operatorului. Evaluarea efectivă diferă de la expresie la expresie astfel: o expresie constantă literală (ConstantExpression) are stabilit tipul în momentul în care este introdusă în arbore. De exemplu se ştie că 10 este de tip int şi 10.5 este de tip double. Similar, CastExpression are stabilit tipul în momentul în care este creată – o conversie de la int la double returnează un double. Pentru VariableReferenceExpression, IndexerExpression şi CallExpression, tipul este determinat interogând tabela de simboluri activă. Pentru UnaryExpression şi BinaryExpression, tipul este determinat în funcţie de tipul operanzilor şi a operatorului. Determinarea tipului returnat de o expresie este esenţială pentru a verifica corectitudinea expresiilor şi pentru a regăsi funcţiile apelate după semnătură. Astfel, dacă operandul stâng al unei expresii binare ce reprezintă o operaţie de adunare este de tip int iar operandul drept este de tip string, se semnalează o incompatibilitate între tipuri. Dacă în schimb operandul stâng este de tip int iar operandul drept este de tip double, se sintetizează un CastExpression pentru conversia implicită suportată de compilator (de la int la double). La fel şi pentru apelurile de funcţie, în caz că semnătura găsită în tabela de simboluri nu are tipul argumentelor identic cu tipul argumentelor pasate dar tipurile sunt totuşi compatibile. Secţiunea din arbore

va arăta în urma evaluării astfel:

48

Se verifică şi dacă operanzii unei expresii sunt compatibili cu operatorul – de exemplu operanzilor ce returnează un tip boolean nu li se poate aplica operaţia de adunare la fel cum operanzilor ce returnează un tip int nu li se poate aplica operaţia şi logic. Deoarece parcurgerea nodurilor se face în postordine, întotdeauna când este evaluat un nod, se cunosc tipurile returnate de operanzii săi. 4.4.2 Constant folding Optimizarea se face tot în procesul de evaluare pentru nodurile de tip CastExpression, UnaryExpression şi BinaryExpression. Dacă operandul unui nod CastExpression este de tip ConstantExpression, atunci nodul CastExpression este înlocuit cu un ConstantExpression sintetizat ce conţine valoarea nodului ConstantExpression în tipul în care se dorea conversia. Abstract, expresia {double}10 este înlocuită astfel cu 10.0. Dacă operandul unui nod UnaryExpression este de tip ConstantExpression, nodul UnaryExpression este înlocuit cu un nod ConstantExpression sintetizat. În urma optimizării, expresia !false este înlocuită la nivel logic cu true. Similar şi la nodurile BinaryExpression. Parcurgerea în postordine asigură efectuarea operaţiei de folding de câte ori este posibil, fără a fi necesare mai multe apeluri. Pentru expresia

49

nodul BinaryExpression nu este încă evaluat.. Expresia se reduce la pentru ca apoi.Evaluate(scope). în urma evaluării nodului BinaryExpression. // Verificarea compatibilităţii tipurilor şi // eventual adăugarea de cast-uri implicite . rightOperand = rightOperand..Evaluate(scope). să fie redusă din nou la Analiza şi optimizările expresiilor pentru clasa BinaryExpression ia următoarea formă: public Expression Evaluate(Scope scope) { leftOperand = leftOperand. 50 .când se evaluează nodul CastExpression.

return this.returnType = leftOperand. Tipul returnat de expresie este egal cu tipul operanzilor doar în cazul în care expresia nu este o comparaţie.IsComparison()) { // În cazul în care este. 4. Desigur.returnType = PrimitiveType. Limbajul permite orice tip de date în cadrul expresiilor condiţionale.3 Analiza propoziţiilor Din moment ce propoziţiile nu au un ReturnType. tipul returnat de expresie este boolean this.BOOL) { // Eroare semantică } 51 .// Se verifică dacă operaţia este o comparaţie. } } return new ConstantExpression(result).BOOL.. tipul este acelaşi ca cel returnat de operanzi // Ambii operanzi au aici acelaşi returnType this. egalitate sau // inegalitate if (operator. } else { // Altfel. Acest lucru este valabil şi la propoziţiile WhileStatement şi DoWhileStatement. Dacă s-ar fi dorit limitarea expresiilor la expresii de tip boolean. Orice valoare diferită de zero este considerată adevărată.Evaluate(scope).4.returnType. } if ((leftOperand is ConstantExpression) && (rightOperand is ConstantExpression)) { // Se calculează static rezultatul expresiei (result) . codul este mult simplificat. if (condition.. analiza propoziţiilor presupune apelarea metodei de evaluare a obiectelor Expression pe care acestea le conţin pentru a se asigura astfel parcurgerea în totalitate a arborelui. egalitate sau inegalitatea – acestea returnează un tip boolean chiar dacă ambii operatori au alte tipuri.returnType != PrimitiveType. astfel încât expresia conţinută de o propoziţie IfStatement nu trebuie să aibă ReturnType de tip boolean. verificarea s-ar fi făcut în cadrul metodelor de evaluare corespunzătoare propoziţiilor: condition = condition.

Dacă da. înseamnă că în timpul execuţiei propoziţiei. este înlocuit cu propoziţia de pe ramura else în caz că aceasta există. Nodurile DoWhileStatement au Returns adevărat dacă nodul Statement conţinut are atributul Returns adevărat. nodul este înlocuit cu propoziţia conţinută.4 Dead code elimination Optimizarea se aplică asupra nodurilor propoziţiilor în diferite moduri şi presupune reducerea propoziţiilor la forme mai simple dacă acest lucru este posibil. Dacă obiectul BlockStatement asociat corpului funcţiei are atributul Returns fals şi funcţia returnează o valoare. Dacă funcţia are atributul Returns fals dar nu se presupune întoarcerea niciunei valori. 52 . în urma evaluării. se sintetizează o propoziţie ReturnStatement şi se adaugă la sfârşitul listei de propoziţii conţinute de bloc (platforma . Dacă nu. CallStatement şi AssignementStatement nu pot avea Returns niciodată ca adevărat deoarece ele nu pot conţine alte propoziţii.Toate clasele ce reprezintă propoziţii conţin un atribut boolean Returns.4. valoarea sa devine true. IfStatement este înlocuit cu propoziţia de pe ramura if. Valoarea atributului este verificată în urma evaluării integrale a corpului unei funcţii. Acestea pot fi eliminate din listă. Nodurile BlockStatement atribuie valoarea true atributului Returns dacă una din propoziţiile conţinute are.Evaluate(scope). Acesta are iniţial valoarea false iar dacă. Analiza pentru nodurile IfStatement ia următoarea formă: public Statement Evaluate(Scope scope) { // Se evaluează întâi nodurile conţinute condition = condition. atributul Returns cu valoarea true. se întâlneşte pe toate (posibilele) ramuri de execuţie o propoziţie ReturnStatement. În caz că o ramură else nu există. Pentru nodurile DoWhileStatement. Pentru nodurile BlockStatement. se poate determina static dacă propoziţia condiţională va fi executată sau nu. în urma evaluării. Pentru nodurile IfStatement. se semnalizează o eroare semantică – nu toate căile de execuţie returnează o valoare. IfStatement are Returns adevărat doar dacă propoziţia conţine şi o ramură else şi atât propoziţia de pe ramura if cât şi cea de pe ramura else au Returns adevărat. 4. în cazul în care expresia condiţională este de tip ConstantExpression şi este evaluată static ca falsă.NET necesită o instrucţiune de ieşire din funcţie – ret – să apară întotdeauna pentru a permite părăsirea corectă a funcţiei). rezultă că propoziţii care urmează acea propoziţie nu vor mai fi executate niciodată. ReturnStatement are întotdeauna atributul Returns adevărat. IfStatement este eliminat cu totul din arbore. în cazul în care una din propoziţiile din listă are atributul Returns true. ForStatement şi WhileStatement nu pot avea Returns niciodată ca adevărat din moment ce nu se garantează execuţia propoziţiei conţinute. în cazul în care expresia condiţională este (sau devine în urma evaluării) de tip ConstantExpression.

Emiterea unei declaraţii persupune instanţierea obiectului Reflection. // În cazul în care nu s-au efectuat optimizări.5. 53 .3.1 Generarea metadata Tabelele de metadata sunt populate parcurgând tabela de simboluri ProgramScope şi apelând metodele EmitDeclaration ale declaraţiilor de variabile şi de funcţii. MethodBuilder) şi procesarea informaţiilor conţinute (adăugarea tipului pentru variabile.Evaluate(scope).returns && elseBranch.returns = ifBranch.Emit asociat (FieldBuilder. nodul rămâne // neschimbat return this. 4.5 Generarea codului şi optimizări dependente de maşină Ultimul pas efectuat de compilator este generarea codului şi optimizările dependente de maşină. else // Se elimină nodul din arbore return null. } // Se verifică dacă condiţia poate fi evaluată static pentru dead // code elimination if (condition is ConstantExpression) // Se verifică dacă expresia este întotdeauna adevărată if (condition. else // Se verifică dacă există o ramură else if (elseBranch != null) // Se înlocuieşte nodul cu ramura else return elseBranch. } 4. Singura optimizare dependentă de maşină efectuată este utilizarea formelor prescurtate.3.returns. adăugarea argumentelor pentru funcţii). // Se determină valoarea Returns a propoziţiei this. // Se verifică dacă există şi un branch else if (elseBranch != null) { // Se evaluează propoziţia else elseBranch = elseBranch. Generarea codului se face într-un pas separat faţă de evaluarea arborelui deoarece structura arborelui poate fi modificată în timpul analizei semantice.Evaluate(scope).IsTrue()) // Se înlocuieşte nodul cu ramura if return ifBranch. detaliată în secţiunea 3.ifBranch = ifBranch.

Mul: // Se emite opcode-ul mul ilGen. pentru expresiile de tip ConstantExpression sau VariableReferenceExpression.. Practic. necesitând stabilirea dimensiunilor în momentul declarării. // Se emite cod pentru operanzi leftOperand. Astfel. Scope scope) { // Se verifică tipul operaţiei . 54 . // Alte situaţii . rightOperand. generarea codului se rezumă la încărcarea unei constante pe stivă sau apelarea metodei EmitReference a tabelei de simboluri. dar limbajul nu permite acest lucru.. scope).5. ilGen. } Metoda primeşte ca parametri un obiect ILGenerator (referinţa este propagată în jos pe arbore începând de la FunctionDeclaration care crează obiectul) şi un obiect Scope ce reprezintă tabela de simboluri activă. emiterea codului ia următoarea formă: public void EmitCode(ILGenerator ilGen. // Se determină opraţia . 1 Constructorul privat al unei clase statice este apelat automat prima dată când se face o referire la un membru al clasei. se emite automat cod pentru instanţiaţierea vectorilor.Compilatorul depune toate variabilele globale într-o clasă statică intitulată Data. Celelalte expresii presupun emiterea instrucţiunilor corespunzătoare operanzilor (sau argumentelor pentru CallExpression) şi emiterea opcode-ului corespunzător operaţiei.EmitCode(ilGen.Mul).EmitCode(ilGen. scope). rightOperand.EmitCode(ilGen. scope). căreia îi adaugă şi un constructor privat1 unde se emite cod pentru instanţierea vectorilor.NET.EmitCode(ilGen... Pentru o expresie binară de înmulţire de exemplu. 4. // În cazul în care operaţia este o operaţie de înmulţire case BinaryOperator.Emit(OpCodes.Emit(OpCodes.Mul).. emiterea codului pentru expresii este relativ simplă. CLR permite instanţierea dinamică a vectorilor (oriunde în cod).. scope). singurele comenzi relevante sunt leftOperand. Pentru a evita riscul de a indexa un vector neiniţializat. Astfel de constructori sunt utilizaţi pentru a garanta iniţializarea membrilor.2 Generarea expresiilor simple Datorită arhitecturii bazate pe o stivă a maşinii virtuale .

rightOperand.Ldc_I4_0). dacă o expresie este mai mică sau egală cu o altă expresie (leftOperand <= rightOperand). în vârful stivei se va găsi valoarea acestuia. // Se verifică dacă operandul drept este stric mai mic decât // operandul stâng ilGen. 4.5. în urma execuţiei codului corespunzător primului operand. astfel că s-ar înregistra pierderi de performanţă dacă s-ar încerca simularea acestui lucru. cele două valori din vârful stivei sunt înlocuite cu produsul lor – produs care este valoarea lăsată pe stivă de această expresie. În momentul întâlnirii unei operaţii de adunare. Adunarea utilizează opcode-ul add în mod similar înmulţirii prezentate mai sus. >=). <=.Emit(OpCodes. apoi codul operaţiei astfel încât. orice alte valori fiind considerate adevărate.Emit(OpCodes. În momenul executării instrucţiunii mul. Acestea consumă două valori de pe stivă şi depun pe stivă valoarea 1 dacă propoziţia matematică este adevărată. Se emite recursiv cod pentru operanzi. nodurile BinaryExpression consideră valide expresiile având ca operator adunarea şi ca operanzi două şiruri de caractere. Pentru a determina.4 Generarea comparaţiilor O parte din operaţiile de comparaţie trebuiesc construite cu ajutorul a două instrucţiuni deoarece IL pune la dispoziţie doar opcode-urile ceq (check equal). // Se încarcă 0 pe stivă ilGen. se va folosi: // Se emite cod pentru operanzi leftOperand. pe stivă se va adăuga şi valoarea acestuia. CLR.5.Clt). se verifică tipul operanzilor şi se decide dacă se va emite instrucţiunea add sau instrucţiunea call urmată de obiectul MethodInfo corespunzător metodei Concat. La nivel IL.3 Generarea concatenării şirurilor de caractere Pentru a permite concatenarea şirurilor de caractere cu operatorul “+”. valoarile egale cu 0 sunt considerate false. Negarea se face verificând egalitatea dintre valoarea de pe stivă şi constanta întreagă 0. Utilizând opcode-urile puse la dispoziţie. Emiterea codului pentru concatenare însă nu este identic cu acela pentru adunare. 55 . scope). ca alte maşini virtuale sau procesoare reale. cgt (check greater than) şi clt (check less than). de exemplu. se neagă rezultatul uneia dintre aceste operaţii. Concatenarea funcţiilor se face însă apelând metoda statică Concat a clasei System. Pentru celelalte operaţii (!=.String ce consumă de pe stivă două valori string şi depune pe stivă rezultatul concatenării. se poate genera cod pentru verificarea egalităţii sau inegalităţilor stricte. scope). nu biţi singulari.EmitCode(ilGen. nu suportă la nivel de limbaj de asamblare tipuri logice datorită faptului că procesoarele sunt construite să manipuleze numere reprezentate pe mai mulţi biţi.EmitCode(ilGen.acestea reprezentând emiterea codului pentru operaţia de înmulţire. 4. După execuţia codului corespunzător celui de al doilea operand. 0 dacă este falsă.

// Se marchează falseLabel ilGen.Br. dacă valoarea de pe stivă e diferită de 0. se face un salt la falseLabel.DefineLabel().// Se verifică dacă rezultatul comparaţiei este egal cu 0 ilGen. operandul drept nu mai este evaluat. egalitatea e falsă şi valoarea este înlocuită cu 0. Label endLabel = ilGen. pentru operaţia and comenzile arătând astfel: // Se definesc două label-uri pentru salturi Label falseLabel = ilGen. Negarea funcţionează astfel: dacă valoarea de pe stivă e 0. scope). dacă operandul stâng al unei operaţii or este evaluat ca adevărat.Brfalse. falseLabel). astfel că se încarcă pe stivă constanta 0 care este şi rezultatul întregii operaţii. În cazul în care primul operand este evaluat ca fals.5.EmitCode(ilGen. Avem următoarele echivalenţe: a != b a >= b a <= b !(a == b) !(a < b) !(a > b) 4.DefineLabel().Ceq).MarkLabel(falseLabel). // Se emite cod pentru operandul drept rightOperand. // Se face salt la falseLabel dacă primul operand e evaluat ca fals ilGen. // Se emite cod pentru operandul stâng leftOperand.Emit(OpCodes.5 Generarea operaţiilor logice Operaţiile logice and şi or sunt construite în mod diferit faţă de alte operaţii pentru a respecta convenţiile întâlnite la majoritatea limbajelor de programare: dacă operandul stâng al unei operaţii and este evaluat ca fals.Emit(OpCodes.Ldc_I4_0).Emit(OpCodes. Implementarea presupune utilizarea a două instrucţiuni de salt condiţionat.EmitCode(ilGen. // Se marchează endLabel ilGen. operandul drept nu mai este evaluat.MarkLabel(endLabel). // Se încarcă 0 pe stivă ilGen. Instrucţiunea brfalse consumă elementul din vârful stivei. egalitatea e adevărată şi valoarea este înlocuită cu 1. 56 . scope). endLabel). // Salt necondiţionat la endLabel ilGen.Emit(OpCodes.

MarkLabel(loopLabel).Emit(OpCodes. emiterea codului pentru propoziţia conţinută şi instrucţiunile de salt aferente: // Se definesc două label-uri pentru salturi Label loopLabel = ilGen. Generarea expresiilor or este similară. astfel că valoarea trebuie eliminată de pe stivă pentru a nu o dezechilibra). se face un salt la endLabel ilGen. Structuri asemănătoare au şi metodele de generare a codului corespunzătoare nodurilor IfStatement şi DoWhileStatement. Label endLabel = ilGen.DefineLabel(). De exemplu emiterea codului pentru un BlockStatement presupune doar emiterea codului pentru fiecare din propoziţiile conţinute. // Se face un salt necondiţionat la începutul ciclului ilGen. // Se emite cod pentru expresia condition ce determină dacă se va // mai executa ciclul condition.5. acolo rămânând valoarea de adevăr a întregii expresii – aceasta depinde aici doar de operandul drept (true and rightOperand e echivalent cu rightOperand).Emit(OpCodes.DefineLabel().MarkLabel(endLabel).EmitCode(ilGen. CallStatement presupune adăugarea unei instrucţiuni pop după codul emis pentru obiectul CallExpression conţinut dacă funcţia apelată întoarce o valoare (CallStatement sunt apelurile de funcţii a căror valoare returnată este ignorată. 57 .Brfalse. endLabel).i4. // Se marchează începutul ciclului (loopLabel) ilGen. scope). scope). doar că saltul condiţionat se face în cazul în care operandul stâng e evaluat ca adevărat (brtrue) şi pe stivă se încarcă valoarea 1 (ldc.6 Generarea propoziţiilor Pentru generarea majorităţii propoziţiilor este suficientă generarea codului pentru propoziţiile şi expresiile pe care acestea le conţin şi utilizarea instrucţiunilor de salt.1) 4.În cazul în care primul operand este evaluat ca adevărat. se evaluează al doilea operand şi se face un salt necondiţionat la sfârşitul expresiei. Emiterea codului pentru un WhileStatement presupune emiterea codului pentru condiţia de repetare.EmitCode(ilGen. // Dacă expresia este evaluată ca falsă. // Se emite cod pentru corpul ciclului body. // Se marchează sfârşitul ciclului ilGen. loopLabel). Instrucţiunea br nu consumă valoarea din vârful stivei.Br.

endLabel). "1"). Label endLabel = ilGen. scope). // Se emite cod pentru valoarea finală pe care variabila o va lua final. initial).DefineLabel(). // Se verifică "direcţia" (Up pentru to.Up) // Dacă variabila e incrementată se face un salt la endLabel în // cazul în care variabila e mai mare decât valoarea finală ilGen.7 Generarea propoziţiei ForStatement Propoziţiile ForStatement au cele mai complexe metode de emis cod. // Se atribuie variabilei parametru valoarea expresiei iniţiale (variable as AssignableExpression).MarkLabel(loopLabel). // Operandul stâng e întregul 1 step. operaţia e adunare.DefineLabel(). dacă // parametrul e decrementat.Bgt.Emit(OpCodes. // Se emite atribuirea rezultatului expresiei la variabilă 58 .op = BinaryOperator.Int.EmitAssignement(ilGen. // Se încarcă variabila parametru pe stivă variable. endLabel). Down pentru downto) if (direction == ForDirection. else // Dacă variabila e decrementată se face un salt la endLabel în // cazul în care variabila e mai mică decât valoarea finală ilGen. scope. Generarea codului pentru o propoziţie ForStatement se face astfel: // Se definesc două label-uri pentru salturi Label loopLabel = ilGen. // Se marchează începutul ciclului ilGen.4. operaţia e scădere if (direction == ForDirection. S-a ales implementarea propoziţiilor după modelul limbajului Pascal – există o singură variabilă parametru pentru ciclu iar cuvintele cheie to şi downto determină dacă variabila este incrementată sau decrementată.5.op = BinaryOperator. scope). scope).EmitCode(ilGen.Add.Emit(OpCodes. // Se emite cod pentru a incrementa/decrementa variabila: // Se crează o nouă expresie binară BinaryExpression step = new BinaryExpression(variable). else step. // Dacă parametrul este incrementat.Up) step.rightOperand = new ConstantExpression(Primitive.Blt.Sub.EmitCode(ilGen.EmitCode(ilGen. // Se emite cod pentru corpul ciclului body.

// Se emite un salt necondiţionat la începutul ciclului ilGen.MarkLabel(endLabel).Emit(OpCodes.(variable as AssignableExpression). step). // Se marchează sfârşitul propoziţiei ilGen. loopLabel). paşii de generare dinamică a expresiei puteau fi înlocuiţi cu emiterea codului pentru expresiile arbitrare. Se observă introducerea în momentul emiterii codului a unei expresii binare generate dinamic pentru incrementarea/decrementarea variabilei parametru. 59 . În cazul în care se dorea introducerea unor expresii arbitrare de modificare a variabilei (asemănător limbajului C).Br. scope.EmitAssignement(ilGen.

a fost proiectată modular. 60 . Concluzii Construirea unui compilator necesită cunoştinţe din mai multe domenii ale informaticii. acesta este relativ uşor de extins cu noi construcţii. Noi noduri de tipul declaraţie. 5. propoziţie sau expresie pot fi construite şi introduse în arborele abstract de sintaxă prin extensii aduse descrierii gramaticii.1 Contribuţie personală S-a dorit o implementare originală a tuturor componentelor aplicaţiei. Componenta de analiză semantică ce operează pe arbore a fost implementată având în vedere numeroasele tipuri de erori semantice ce pot să apară într-un text sursă. au fost implementate toate nodurile necesare unei reprezentări intermediare robuste şi complete. în locul unor grafuri aciclice direcţionate[3]. Algoritmul de signature matching implementat asigură identificarea corectă a funcţiilor apelate în funcţie de semnătura apelului şi semnăturile funcţiilor omonime. La fel. având în vedere posibile dezvoltări ulterioare.). metodele de evaluare a arborelui pot fi îmbogăţite cu noi tipuri de procesări. 5. algoritmică. structuri de date. inginerie software. astfel încât atât structurile de date cât şi algoritmii utilizaţi nu au fost refolosiţi ci au fost implementaţi de către autor.5. arbori – producând în final acelaşi rezultat. Arhitectura compilatorului. atât teoretice cât şi practice – limbaje formale şi teoria automatelor. limbaje de nivel înalt şi limbaje de nivel jos. tehnici de compilare. C# etc.2 Direcţii de dezvoltare Datorită arhitecturii modulare a compilatorului. urmând regulile de signature matching aplicate de limbaje de programare cunoscute (C++. Algoritmii de optimizare utilizaţi au fost rescrişi pentru a utiliza. Pornind de la o descriere a tipurilor de noduri ce ar putea să apară într-un arbore abstract de sintaxă[4]. arhitectura calculatoarelor. deşi urmând modelul clasic (front-end – backend). aceasta asigurând corectitudinea codului generat. Compilatorul va fi extins în viitor în două direcţii: interoperabilitate şi optimizare. facilitând astfel integrarea de noi expresii în limbaj. Generarea de cod pentru diferite tipuri de expresii şi propoziţii a fost implementată pentru a permite construirea prin apeluri recursive.

1 Interoperabilitate completă cu assembly-uri . funcţii cu număr variabil de argumente.2. short. 5.2 Optimizare Se vor adăuga de asemenea şi alte tehnici de optimizare ce vin să completeze optimizările efectuate în prezent (constant folding şi dead code elimination) – tehnici precum constant propagation şi utilizarea identităţilor algebrice – care vor asigura generarea de cod echivalent mai rapid (cu un număr mai redus de instrucţiuni) pentru numeroase texte sursă. suport pentru programare orientată obiect – declararea de tipuri şi instanţierea de obiecte – precum şi alte extensii necesare (suport Unicode. byte. cu alte cuvinte un limbaj care să se conformeze în totalitate la Common Language Specification – setul de recomandări puse la dispoziţie de către dezvoltatorii platformei. vizibilităţi diferite la tipuri. 61 . Componenta de optimizare a compilatoarelor este şi va rămâne o problemă deschisă. metode şi atribute etc.2.).5. Scopul final este o interoperabilitate completă cu alte assembly-uri . float etc.).NET. int64.NET Se va adăuga suport pentru toate tipurile primitive oferite de CLR (char.

NET Common Language Runtime (CLR). Build Your Own . Compilers: Principles. Backus-Naur Form. Serge Lidin.ro/~dragan/Limbaje2. Apress. Modern Compiler Implementation in Java 2nd Edition.0 IL Assembler. Ştefan Măruşter. Microsoft Press.NET 2. Aho. Ullman.ro/~dragan/PrComp. Expert . 2006 6. 2006 4.pdf 12. 2006 11. Alfred V. http://en.uvt. Mircea Drăgan. http://www. Addison Wesley. Prentice Hall. 2004 3.Bibliografie 1. Techniques.info.NET Language and Compiler.uni-linz. Apress. Nilges. 2006 7.info. Standard ECMA-335.wikipedia. Hanspeter Mössenböck.ssw. Common Language Infrastructure 4th Edition. John Gough.NET Framework.at/coco/UserManual. Lam. Cambridge University Press. Ecma International.uvt. Mircea Drăgan. http://web. Jeffrey Richter. Ravi Sethi. Andrew W. The Compiler Generator Coco/R User Manual. . 2002 9. Jeffrey D. and Tools 2nd Edition.pdf 62 . Monica S. http://www. Compiling for the . Wikipedia. Jens Palsberg.wikipedia. CLR via C#.ac. Edward G.pdf 8.org/wiki/Backus-Naur_form 2. http://web. 2001 5. Tehnici de Compilare. Limbaje Formale.NET 10.org/wiki/Microsoft_. Wikipedia. Appel.

Sign up to vote on this title
UsefulNot useful