Sunteți pe pagina 1din 21

2.

Analiza lexical
2.1. Funciile unui analizor lexical
Analiza lexical este prima faz a procesului de compilare,
fiind efectuat de analizorul lexical.
Analizorul lexical are la intrare programul surs i produce
la ieire atomi lexicali. (figura 2.1).

Program surs

Analizor
lexical

Atomi lexicali

Figura 2.1. Funcia unui analizor lexical

n multe privine, analiza lexical este faza cea mai simpl a


procesului de compilare. n ciuda acestei simpliti ns, ea are
la baz elemente teoretice i procedee practice necesare pentru
scrierea unui software performant.
n esen, analizorul lexical preia irul continuu de caractere
de la intrare i l divide n cuvinte, care pot fi recunoscute de
analizorul sintactic, care se execut n urmtoarea faz a
procesului de compilare.
Exemple de atomi sunt: identificatori (nume de variabile
folosite de programator), cuvinte cheie (for, while, begin,
end etc.), numere i secvene de caractere speciale.
Se numete lexem, un ir de caractere de la intrare care
este n curs de analizare. Iniial, lexema curent este irul vid.
La ea se adaug pe rnd caractere de la intrare, pn cnd va
corespunde cu un atom al limbajului, ori se va semnala eroare
prin epuizarea tuturor posibilitilor.

Atomii se mpart n clase de atomi, iar pentru fiecare clas


se aloc un cod, astfel nct programul surs se transform n
final ntr-un ir de coduri aranjate n ordinea detectrii atomilor.
n plus, analizorul lexical elimin din textul surs
comentariile i spaiile (blankuri, tabulatori, comentarii, alte
caractere de control). De asemenea, el numeroteaz liniile
textului surs pentru a permite raportarea de erori,detecteaz i
semnaleaz erori lexicale.
Dei funciile analizorului lexical ar putea incluse n
analizorul sintactic, n majoritatea aplicaiilor, ele sunt faze
distincte, n principal datorit faptului c analizorul lexical este
mare consumator de timp. Din acest motiv, de multe ori, el se
implementeaz n limbaj de asamblare, spre deosebire de
celelalte faze ale compilrii care se implementeaz n limbaje
de nivel nalt.
Ieirea analizorului lexical este mult mai condensat,
deoarece irul de caractere de la intrare este divizat n cuvinte,
multe caractere redundante din punctul de vedere al compilrii
sunt eliminate, astfel nct analizorul sintactic va prelucra un
volum de date mai redus.
De asemenea, bazele teoretice ale analizei lexicale sunt mai
simple dect ale analizei sintactice, astfel nct n aceast faz,
se pot folosi soluii software mai puin complexe.
Suplimentar, prin separarea fazelor, compilatorul poate fi
scris modular i realizat n echip, iar capacitatea de reutilizare
a codului crete.
O privire de ansamblu asupra analizei lexicale este ilustrat
n figura 2.2:

Program surs
Gestionare
caractere

begin
x:=1;
..

\n
le
xi
ca
li

\n

..

Analizor
lexical

cuv.cheie

id

atribuire

numar

begin

:=

..

Figura 2.2. Analiza lexical

Pentru a parcurge textul surs, se folosete un bloc de


gestionare caractere, care citete intrarea i o transmite linie cu
linie la intrarea analizorului lexical. n continuare, analizorul
lexical identific atomii lexicali.
2.2. Codificarea atomilor lexicali.
Analizorul lexical mparte atomii n clase de atomi i
atribuie fiecrui atom detectat un cod.
Exemple de clase de atomi sunt: cuvinte cheie,
identificatori, operatori, numere etc. Unele dintre aceste clase
conin un numr cunoscut de elemente (de exemplu clasa
cuvintelor cheie, clasa operatorilor etc.), altele conin un numr

posibil infinit de elemente (de exemplu identificatori, numere


etc.).
Codul conine dou cmpuri: codul lexical i un atribut.
Atributul conine informaii suplimentare:
este valoarea atomului, dac acesta este de tip
numeric

este adresa unde se memoreaz, n cazul n care


este un identificator.

Codul lexical este un numr ntreg care identific atomul i


care este stabilit astfel:
1.

Pentru clase de atomi cu un numr cunoscut de elemente, fiecare


atom este asociat cu un numr distinct, care identific complet
atomul. n aceste cazuri prezena atributului nu este necesar.

De exemplu: pentru "begin"se poate asocia codul 100;


pentru "while" codul 101 etc.
2.

Pentru clase de atomi cu un numr posibil infinit de elemente, codul


este un cmp care identific clasa, el fiind acelai pentru toi atomii
clasei respective. nm aceste cazuri, codul este urmat obligatoriu de
un atribut.

De exemplu:

codul unui identificator se compune din codul clasei


identificatorilor ,(de exemplu 200) i o adres care
pointeaz spre tabela de simboluri;

codul unui numr ntreg se compune din codul clasei


numerelor ntregi, (de exemplu 300) i valoarea numrului.
Exemplul este ilustrat n figura 2.3.

cuvnt cheie
begin
clasa

Cod lexical
100

identificator alfa
clasa
atribut

200
adresa

Atomul numeric 5
clasa
atribut

Pointeaz spre
tabela de
simboluri unde
s-a memorat
atomul alfa

300
5

Figura 2.3. Exemplu de cod lexical

Codificarea atomilor lexicali are avantajul c, la intrarea


analizorului sintactic, n loc de o secven de iruri de caractere
cu lungime variabil, se prezint o codificare compact a
atomilor.
2.3. Tipuri de date folosite pentru definirea atomilor
lexicali.

Atomii sunt definii de obicei folosind un tip enumerativ.


De exemplu:
enum tip_atom {if, then, else, plus, ...};

sau prin folosirea macrodefiniiei #define:


#define if 156
#define then 257
#define else 258

...
5

Deoarece identificatorii i atomii numerici sunt asociai cu


un atribut care poate fi un pointer sau o valoare, se poate
folosi fie o structur, fie o uniune:

struct inregistrare
{
tip_atom valoare; //codul atomului
char * stringval; // atriburul identificatorului
int numval; // atributul unui numr
};
struct inregistrare
{
tip_atom valoare;
union {
char * stringval;
int numval;
} atribute;
};

2.4. Comunicarea analizorului lexical cu celelalte


componente ale compilatorului
Analizorul lexical comunic cu tabela de simboluri i cu
analizorul sintactic.

Comunicaia cu tabela de simboluri presupune operaii de


scriere a codului pentru identificatori.

Pentru comunicaia cu analizorul sintactic, analizorul lexical


poate folosi unul din urmtoarele procedee:

1. Analiza lexical se execut ntr-o trecere separat i el


produce la ieire codul tuturor atomilor din programul surs.
Aceast ieire este de fapt o form codificat a programului
surs, care se poate scrie ntr-un fiier, ori se poate pstra n
memorie (figura 2.4).
6

Program surs

Analizor
lexical

Prima
trecere

Coduri de
atomi
Fiier

Memorie
Analizor
sintactic

Urmtoarea
trecere

Figura 2.4. Trecere separat pentru analiza lexical


3.

Analizorul sintactic apeleaz analizorul lexical n momentul n care


are nevoie de un nou atom. La fiecare apel, analizorul lexical
trimite un singur atom la intrarea analizorului sintactic. Aceast
implementare este mai avantajoas, deoarece nu se mai produce o
form codificat a programului surs (figura 2.5).

Analizor
sintactic
Codul
atomului
urmtor

gettoken()

Program
surs

Analizor
lexical

Figura 2.5. Analizorul sintactic apeleaz analizorul lexical

4.

Cele dou analizoare funcioneaz n regim de corutin, fiind


simultan active.

Soluia preferat este situaia n care analizorul lexical este


apelat de analizorul sintactic, folosind de exemplu o funcie
an_lex. Analizorul lexical poate s returneze codul lexical al
atomululi, iar atributele lui pot fi variabile globale, vizibile n
alte pri ale compilatorului.
De exemplu, dac stringval i numval sunt variabile
globale:
char * stringval; // global
int numval; // global

i presupunnd c n textul surs se afl urmtoarea linie de


program:
a[index] = 4 + 2

dup primul apel al analizorului lexical, variabila global


stringval va conine "a", se returneaz codul clasei
identificatorilor, iar pointerul de intrare va avansa:
a

Pointerul de intrare

Figura 2.6. Poziionarea pointerului pe urmtorul atom

Un fiier header an_lex.h,


lexical, poate conine urmtoarele:

destinat

unui analizor

typedef enum { nume, numar, acol_l, acol_r, par_l,


par_r, atrib,
8

pct_v, plus, minus, eroare } tip_atom;


typedef struct
{
tip_atom tip;
union
{
int valoare; /* tipul numar */
char * nume; /* tipul identificator */
}atribut;
}
atom;
extern atom an_lex(); /*functia an_lex() e definita
altundeva)*/

2.5. Construirea unui analizor lexical.


Analizorul efectueaz urmtoarele aciuni:

recunoate atomii lexicali;

produce la ieire codul atomilor i tributul lor;

genereaz eroare n caz de eec;

scrie datele colectate n aceast faz (identificatori,


constante) n tablela de simboluri.

Un analizor lexical poate fi construit manual, ori folosind


metoda automatelor finite.
Indiferente de metdoa folosit, construirea analizoarelor
lexicale are la baz gramatici regulate.
Construirea manual a analizorului lexical nseamn
scrierea programului propriu zis pe baza unor diagrame de
tranziii, care precizeaz structura atomilor din textul surs.
Aceste diagrame reprezint automate cu stri finite. Metoda
manual asigur creerea unor analizoare lexicale eficiente, dar
scrierea programului e monoton, prezint riscul unor erori, mai
ales dac exist un numr mare de stri.
9

Exist numeroase instrumente software care automatizeaz


proiectarea unui analizor lexical. Acestea se numesc
generatoare de analizoare lexicale. n prezent, rareori se scriu
manual analizoare lexicale.
Un generator de analizoare lexicale este un program care
primete la intrare, ntr-un limbaj de specificare, structura
atomilor lexicali i eventualele aciuni semantice care vor trebui
executate simultan cu analiza lexical. Ieirea unui astfel de
program este un program de analiz lexical.
2.6. Construirea manual a unui analizor lexical
Un analizor lexical manual trebuie s aib n vedere
urmtoarele aciuni:
1. Eliminarea spaiilor ( blankuri, tabulatori, CR, etc.);
2. Eliminarea comentariilor;
3. Colectare atomi i identificarea clasei pentru fiecare atom;
4. Generarea atributelor i scrierea n tabela de simboluri
atunci cnd e cazul.
Dac atomul id reprezint un identificator, iar atomul num
reprezint un numr, analizorul lexical va clasifica o secven
de intrare de forma:
E := M * C ** 2

astfel:
id, pointer la tabela de simboluri pentru E
assign_op, :=
id, pointer la tabela de simboluri pentru M
mult_op, *
id, pointer la tabela de simboluri pentru C
exp_op, **
num, 2
10

i transform aceast secven n urmtorul ir de atomi:


id := id * id ** num

n continuare se prezint un exemplu de pseudocod pentru


un analizor lexical:
function alex
:
integer;
var
lexbuf
:
array [0..100] of char;
c
:
char;
begin
loop begin
citeste un caracter in c;
if c este un blank sau tabulator then
nici o actiune
else if c este newline then
lineno := lineno + 1
else if c este o cifra then begin
set tokenval la valoarea
cifrei si a cifrelor urmatoare;
return NUM
end
else if c este o litera then begin
pune c si urmatoarele litere
sau/si cifre in lexbuf;
p := lookup(lexbuf);
if p = 0 then
p := insert(lexbuf, ID);
tokenval := p;
return adresa din tabel p
end
else /* atomul e un singur caracter */
set tokenval to NONE;
/* nu sunt atribute*/
return codul intregului care
codeaza caracterul c
end
end

Acest pseudocod nu face distinie ntre identificatori i


cuvinte cheie. El se poate completa cu o aciune de cutare ntr11

un tabel al cuvintelor cheie, care s permit recunoaterea


cuvintelor cheie.
2.6.1. Gestionarea textului surs
Textul surs se citete linie cu linie, iar pentru memorarea
unei linii se folosete o zon tampon (buffer), a crui
dimensiune corespunde cu lungimea fizic a liniei de intrare.
Sfritul textului surs este marcat de EOF.
Pentu a localiza un atom lexical (lexema curent), se pot
folosi doi pointeri, numii pointer de nceput pi i pointer de
anticipare pa.
La nceput, ambii pointeri pi i pa indic primul caracter al
lexemei curente. n continuare, pointerul pa avanseaz, pn
cnd analizorul identific atomul. n acest moment, irul de
caractere cuprins ntre cei doi pointeri este chiar atomul lexical
identificat.
Dup detectarea unui atom, pointerul de anticipare se poate
poziiona fie pe ultimul caracter al lexemei curente, fie pe
primul caracter al lexemei urmtoare. n a doua situaie nu mai
este necesar returnarea la intrare a extracaterului necesar
pentru recunoaterea anumitor atomi.
Dup prelucrarea lexemei curente, pi este adus n aceeai
poziie cu pointerul de anticipare i se continu cu analiza unui
nou atom.
n figura 2.7 se reprezint felul n care se folosesc cei doi
pointeri pentru identificarea unui atom.

12

\n

\n

pi
pa

pi
b

pa
e

n
pi

\n

pa
Figura 2.7. Folosirea a doi pointeri pentru identificarea unui atom

2.6.2. Construirea diagramelor de tranziii


Pentru proiectarea unui analizor lexical manual se folosesc
diagrame de tranziii, care conin:

O mulime de stri,reprezentate cu cercuri;

Starea final reprezentat cu linie dubl:

Arce care reprezint tranziiile analizorului dintr-o stare n


alta. Arcele ncep ntr-o stare i se pot termina n aceeai
stare, ori ntr-o alt stare;

Starea final este starea n care s-a recunoscut un atom;

Arcele sunt etichetate cu simboluri, care indic ce caracter


de la intrare determin trecerea analizorului din starea de la
care pornete arcul n starea n care ajunge acel arc.

13

Astfel de diagrame de tranziii sunt asociate cu automate cu


stri finite, care vor fi prezentate n capitolul urmtor. Deoarece
reprezentarea este intuitiv, aceste diagrame se pot construi de
fapt fr a cunoate teoria care st la baza lor. Din acest motiv,
n continuare se exemplific felul n care se construies aceste
diagrame de tranziii n scopul implementrii manuale a unui
analizor lexical, urmnd ca n capitolul 4 s fie reluate unele
noiuni mai n detaliu, pe msur ce se definesc noiuni teoretice
noi.
Exemplu. Se construiesc diagramele de tranziii care
stabilesc funcionarea unui analizor lexical manual care
recunoate urmtorii atomi: numere ntregi, identificatori,
separatori: ";", paranteze:"(", ")", acolade: "{", "}", spaii,
operatori: "+", "-", "=".
Diagramele de tranziii sunt reprezentate n figura 2.8.
Observaii.

Strile iniiale ale fiecrei diagrame sunt de fapt reunite ntro singur stare iniial, referit n continuare ca "stare0".
Aceasta semnific faptul c nu s-a decis nc ce diagram se
va urma. Alegerea diagramei se face pe baza caracterului de
la intrare.

Uneori, de exemplu pentru atomul "{", atomul e recunoscut


imediat prin citirea ultimului caracter din acesta. Pentru ali
atomi ns, de exemplu pentru numar, se cunoate lungimea
atomului numai dup citirea unui extracaracter, care nu
aparine numrului (aceast situaie apare n toate strile
notate cu *). n acest caz, caracterul citit n plus trebuie
returnat la intrare.

Dac se citete un caracter care nu corespunde cu nici o


secven acceptat, se returneaz atomul special eroare.
14

digit

not(digit)

3*

digit
{

letter

23

24

not(letter|digit)

25*

letter|digit
8

sp

10

11

not(sp)

12*

sp
13

17

21

14

18

15

19

22

Figura 2.8. Diagrame de tranziii

15

16
=

20

2.6.3. Scrierea codului pentru diagramele de tranziii


Diagramele de tranziii care descriu funcionarea unui
analizor lexical se pot implementa fie prin instruciunea case,
fie printr-o succesiune de instruciuni if.
Pentru fiecare stare se scrie cte o secven de program
distinct.
Dac starea nu este final, adic exist arce care ies din ea,
se citete urmtorul caracter de la intrare, care va produce o
tranziie spre starea urmtoare. Tranziia este posibil numai n
cazul n care, starea curent conine un arc de ieire etichetat
caracterul citit de la intrare. Dac exist un astfel de arc, se
trece la secvena de program a strii urmtoare. Dac nu exist
un astfel de arc, iar starea curent nu este final, se ncearc
traversarea unei alte diagrame, folosind pointerul de nceput al
lexemei curente. n cazul n care s-au epuizat toate posibilitile,
se apeleaz o procedur de eroare.
Secvena urmtoare prezint o implementare manual
simpl a unui analizor lexical, scris pe baza diagramelor de
tranziii din figura 2.8.
#include
#include
#include
#include
#include

<stdio.h>
<ctype.h>
<stdlib.h>
<string.h>
"alex.h"

static int stare = 0;


#define maxbuf 256
static char buf[maxbuf];
static char
*pbuf;
static char *token_nume[] =
{
"nume", "numar", "acol_l", "acol_r",
16

"par_l", "par_r", "atrib", "pct_v",


"plus", "minus", "error"
};
static TOKEN token;
char *
dup_str ;
/* Acest cod nu e complet. Nu se testeaza depasirea
bufferului, etc*/
TOKEN *alex()
{
char c;
while (1)
switch(stare)
{
case 0: /* pentru unul din
1,4,6,8,10,13,15,17,19,21,23 */
pbuf = buf;
c = getchar();
if (isspace(c))
stare = 11;
else if (isdigit(c))
{
*pbuf++ = c; stare = 2;
}
else if (isalpha(c))
{
*pbuf++ = c; stare = 24;
}
else switch(c)
{
case '{': stare = 5; break;
case '}': stare = 7; break;
case '(': stare = 9; break;
case ')': stare = 14; break;
case '+': stare = 16; break;
case '-': stare = 18; break;
case '=': stare = 20; break;
case ';': stare = 22; break;
default:
stare = 99; break;
}
break;
17

case 2:
c = getchar();
if (isdigit(c))
*pbuf++ = c;
else
stare = 3;
break;
case 3:
token.info.valoare= atoi(buf);
token.type = numar;
ungetc(c,stdin);
stare = 0; return &token;
break;
case 5:
token.type = acol_l;
stare = 0; return &token;
break;
case 7:
token.type = acol_r;
stare = 0; return &token;
break;
case 9:
token.type = par_l;
stare = 0; return &token;
break;
case 11:
c = getchar();
if (isspace(c));
else
stare = 12;
break;
case 12:
ungetc(c,stdin);
stare = 0;
break;
case 14:
token.type = par_r;
stare = 0; return &token;
break;
case 16:
token.type = plus;
18

stare = 0; return &token;


break;
case 18:
token.type = minus;
stare = 0; return &token;
break;
case 20:
token.type = atrib;
stare = 0; return &token;
break;
case 22:
token.type = pct_v;
stare = 0; return &token;
break;
case 24:
c = getchar();
if (isalpha(c)||isdigit(c))
*pbuf++ = c;
else
stare = 25;
break;
case 25:
*pbuf = (char)0;
dup_str= strdup(buf);
token.info.nume =dup_str;
token.type = nume;
ungetc(c,stdin);
stare = 0; return &token;
break;
case 99:
if (c==EOF)
return 0;
fprintf(stderr,"Caracter ilegal: \'%c\'\n",c);
token.type = error;
stare = 0; return &token;
break;
default:
break; /* Nu se poate intampla */
}
}
int main()
19

{
TOKEN *t;
while (((t=alex())!=0))
{
printf("%s",token_nume[t->type]);
switch(t->type)
{
case nume:
printf(":%s\n",t ->info.nume);
break;
case numar:
printf(":%d\n",t ->info.valoare);
break;
default:
printf("\n");
break;
}
}
return 0;
}

Observaie. Pentru scrierea (chiar i manual) a unor


analizoare lexicale performante se folosesc noiuni legate de
expresiile regulate, care se vor prezenta n capitolul urmtor.
Folosind expresiile regulate pentru a specifica cuvintele unui
limbaj, se pot implementa diagramele de tranziii care descriu
funcionarea unui analizor lexical manual.
Pentru generarea automat a analizoarelor lexicale se
folosesc noiuni teoretice suplimentare care vor fi descrise n
capitolul 5.

20

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