Documente Academic
Documente Profesional
Documente Cultură
Universul Universul
problemei calculatorului
Simulare
Elaborare
program Compilare
Universul limbajului
de programare
Figura 1. Universurile unui limbaj de programare [Şerbănaţi
1987].
Orice limbaj are 3 aspecte caracteristice:
- aspectul sintactic
- aspectul semantic
- aspectul pragmatic
Sintaxa unui limbaj conţine ansamblul regulilor prin care pornind de la
simbolurile de bază care alcătuiesc alfabetul limbajului, se construiesc structuri
compuse [Niţchi 2005].
Mulţimea regulilor sintactice care descriu ansamblul propoziţiilor sau a
formulelor corecte din cadrul limbajului formează gramatica.
Deci, sintaxa şi gramatica ne ajută să identificăm modul în care putem combina
simbolurile de bază ale limbajului pentru a produce elemente acceptate de
limbaj. Ele reprezintă imperative riguroase, care pot fi formalizate matematic.
Sintaxa se descrie teoretic cu ajutorul sistemelor formale. Următoarele
elemente se utilizează pentru descrierea sintaxei unui limbaj:
- diagramele sintactice
- arbori de analiză
- metalimbaje (BNF – Backus Naur Form, EBNF – Extended Backus Naur
Form, Asn.1)
Prin semantică se înţelege sensul construcţiilor sintactice. Ea reprezintă un set
de reguli ce determină semnificaţia propoziţiilor dintr-un limbaj. Este vorba de
reguli de evaluare a acestor propoziţii în termenii unor mulţimi de valori
cunoscute de limbajul respectiv. Astfel, semantica reprezintă înţelesul fiecărei
formule corecte admise de gramatică.
Pragmatica se referă la capacitatea de a utiliza construcţiile sintactice şi
semantice. Referitor la înţelegerea aspectului pragmatic al limbajelor, putem să
12 Capitolul 1. Introducere în limbaje de programare
Limbaj de nivel
superior
Limbaj de
Compilator
asamblare
Interpretor
asamblor
Microprocesor
Cod masina
Traducerea în două faze presupune traducerea codului din cod sursă într-un
cod intermediar care apoi este rulat pe o maşină virtuală. Limbajele care
funcţionează pe acest principiu se numesc limbaje pseudo-compilative.
Exemple de limbaje pseudo-compilative sunt Java şi limbajele din familia .NET.
prin care o clasă este caz particular (specializat) al unei superclase. Astfel, prin
compoziţie şi moştenire se asigură reutilizarea codului.
Principalul avantaj al programării obiectuale faţă de paradigmele anterioare
este realizarea polimorfismului. Prin polimorfism se înţelege proprietatea unui
nume de a fi utilizat cu înţelesuri diferite. Cea mai importantă formă de
polimorfism este polimorfismul de tip, prin care tipul referinţei se adaptează la
tipul obiectului.
Smalltalk este limbajul care a introdus paradigma obiectuală. Acest limbaj a
fost dezvoltat în limbajele Smalltalk80 şi Smalltalk86. C++ reprezintă extensia
obiectuală a limbajului structurat C. De asemenea Java implementează
caracteristicile paradigmei obiectuale.
1
Rapid Application Development (RAD) environments, în lb. Engleză
2
Markup Languages, în lb. Engleză
1.2. Clasificarea limbajelor 19
3
si înseamnă specificare şi implementare, iar s înseamnă numai specificare
1.3. Istoricul şi evoluţia limbajelor de programare 21
Fortran
CPL
PL/1
BCPL
Algol68 Simula67
1970 Pascal
Prolog
C
Modula-2
Ada
1980 C cu clase Smalltalk
Objective
C
ANSI C
C++
Modula-3
1990
Ada9X
Instructiuni Date
4
COmmon Business Oriented Language, în lb. Engleză
5
Syntactic sugar, în lb. Engleză
24 Capitolul 1. Introducere în limbaje de programare
6
United States Department of Defense
1.3. Istoricul şi evoluţia limbajelor de programare 25
7
Object Management Group
8
Unified Modeling Language
26 Capitolul 1. Introducere în limbaje de programare
2.1.1. Gramatici
- R . O regulă de deducţie
mulţimea finită a regulilor de deducţie (inferenţă)
de aritate n + 1 este o relaţie din mulţimea F × F , care asociază o
n
2.1.2.1. BNF
Pentru descrierea limbajelor de programare se folosesc meta-limbaje. Acestea
furnizează reguli prin care se pot specifica şirurile acceptate de un limbaj de
programare.
BNF (Backus Naur Form) reprezintă cel mai utilizat limbaj de specificare a
limbajelor de programare. În fond, BND descrie o gramatică independentă de
context. În BNF, simbolurile neterminare se scriu între paranteze unghiulare şi
se definesc recursiv, prin meta-formule. De asemenea, semnul ::= face parte
din limbajul de specificare. El se poate traduce prin: „se defineşte astfel”.
Simbolul | înseamnă alegere, în sensul că permite utilizarea uneia din cele 2
alternative alăturate simbolului. Astfel, în BNF, simbolurile <, >, ::=, | se
numesc meta-simboluri.
Pentru a exemplifica utilizarea BNF, vom descrie în acest limbaj de specificare
sintaxa de compunere a unei propoziţii simple într-un limbaj informal. Astfel,
avem următoarea definiţie BNF:
<sentence>::=<subject><verb><object>.
<subject>::=<article><noun>|<subject pronoun>
<verb>::=sees|hits
<object>::=<article><noun>|<object pronoun>
<article>::=a|the
<subject pronoun>::=he|she
<object pronoun>::=him|her
2.1.2.2. EBNF
De multe ori, specificarea BNF este greoaie din punct de vedere al lizibilităţii,
mai ales atunci când un simbol neterminal se poate repeta, de un număr finit
sau infinit de ori, într-o construcţie. De exemplu, putem considera definirea unui
string ca fiind un şir de una sau mai multe cifre:
<string>::=<digit>|<digit><string>
<string>::=<digit>{<digit>}
<numer>::=<sign><unsigned>|<unsigned> în BNF
sau
<number>=[<sign>]<unsigned> în EBNF
<sentence>::=<expression>=
<expression>::=<number>[<operator><expression>]
<number>::=[<sign>]<unsigned>
<operator>::=*|+|-|/
<sign>::=+|-
<unsigned>::=<string>[.<string>]|.<string>
<string>::=<digit>{<digit>}
<digit>::=0|1|2|3|4|5|6|7|8|9
<sentence> <expression> =
<digit> 0
1
2
3
4
5
6
7
8
9
<string> <digit>
Asemenea automate sunt definite prin grafuri. Nodurile grafului reprezintă stări
ale automatului, iar arcele reprezintă tranziţii între stări. Arcele sunt marcate cu
condiţii, cu semnificaţia că, dacă automatul se află într-o anumită stare, şi se
îndeplineşte condiţia de pe un arc care iese din starea respectivă, atunci
automatul va trece în starea de la celălalt capăt al arcului selectat.
Automatele pot fi:
- deterministe, dacă dintr-o stare se poate realiza cel mult o singură mişcare
- nedeterministe, dacă dintr-o stare există mai multe mişcări posibile.
Automatele nedeterministe corespund gramaticilor cu producţii cu
alternative.
Un şir x este acceptat de un automat U dacă, pornind de la configuraţia
iniţială, prin mişcările automatului se parcurge întregul şir de intrare şi
automatul ajunge într-o configuraţie finală.
Pentru exemplificare, vom considera automatul finit din figura 6.
b
a
a
q0 q1
a
a b
0 1 3
b b
λ
2
a b λ
0 {0,1} - {2}
1 - {2,3} -
2 {2} {3} -
3 - - -
Tabelul 3. Matricea automatului finit din figura 7
Se poate stabili o relaţie între automatele finite deterministe şi cele
nedeterministe. Astfel, pentru orice automat finit nedeterminist există un
automat finit determinist care acceptă acelaşi limbaj. Vom prezenta succint în
cele ce urmează algoritmul de calcul al automatului finit determinist echivalent
din punct de vedere al limbajului acceptat cu un automat finit nedeterminist.
Considerăm un automat finit nedeterminist care conţine mulţimea de stări Q.
Fie ∑ mulţimea de simboluri de intrare acceptate de automat.
Procedure AFN2AFD
Q1 ← {q0 }
while (mai există stări nemarcate în Q1
q ← [q1 , q2 ,..., qk ] o stare nemarcată din Q1
Se marchează q
for a ∈ ∑ do
M ← 0/
For i = 1, k
M ← M ∪ δ ( qi , a )
End for
Deci, starea iniţială q0 este nodul 0. Închiderea tranzitivă a acestui nod conţine
nodul 0 împreună cu toate nodurile în care se poate ajunge din acest nod
considerând doar tranziţii vide. Deci q0 este [0,2]. Acesta va reprezenta primul
nod din noul automat determinist. Marcăm acest nod, şi încercăm să
determinăm alte noduri ale automatului determinist. Pentru aceasta vom
considera, pe rând, fiecare simbol de intrare acceptat de automat. Fie pentru
început simbolul a. Construim mulţimea M ca fiind mulţimea tuturor nodurilor
destinaţie, pornind de la noduri din q0 şi considerând tranziţii acceptate de
automatul nedeterminist prin simbolul a. Obţinem M = {0,1,2} . Considerăm
închiderea tranzitivă a lui M , adică toate nodurile la care se poate ajunge din
noduri din M prin tranziţii vide succesive. După considerarea acestei operaţii,
mulţimea nou formată q' conţine aceleaşi noduri ca şi M . Extindem mulţimea
de noduri ale noului automat cu acest nou nod q ' = [0,1,2] . În automatul
q ' . Continuăm algoritmul prin considerarea celui de-al doilea simbol de intrare,
b. Considerând toate nodurile care urmează din noduri ale lui q0 prin b, vom
dezvolta mulţimea M = {3} . Închiderea tranzitivă a lui M este mulţimea
vidă. Deci, vom adăuga starea mulţime vidă în automatul determinist şi vom
lega această stare de starea iniţială prin tranziţia pe simbolul de intrare b.
Continuăm algoritmul prin marcarea unei noi stări, [0,1,2] , şi reluarea
aceloraşi paşi. Figura 9 conţine automatul finit determinist obţinut.
Transformarea unui automat finit nedeterminist într-un automat finit determinist
este importantă deoarece lucrul cu automatele nedeterministe este dificil,
datorită traiectoriilor paralele pe care le poate lua calculul în aceste automate.
Dar noi trebuie să privim aceste automate în contextul studiului gramaticilor,
care definesc limbajele de programare. Astfel, pornind de la o gramatică, se
poate genera un automat finit care să accepte gramatica respectivă. Dacă
automatul finit obţinut pentru o gramatică este nedeterminist, vom aplica
procedura din figura 8 pentru a construi automatul finit determinist echivalent.
Automatele deterministe ne indică modul în care trebuie să tratăm un şir de
intrare pentru a identifica dacă acesta este acceptat sau nu de automat. Astfel,
la construirea compilatoarelor, analizorul sintactic foloseşte logica automatului
pentru a spune dacă o construcţie de intrare e validă sau nu, şi în caz afirmativ,
consideră mai departe această construcţie.
38 Capitolul 2. Fundamentele limbajelor de programare
[2]
a
a
b
[0,1,2] [2,3]
b
b
a
[0,2] [3]
b
a,b
[0]
2.2. Compilatoare
Tratarea Gestiunea
erorilor tabelelor