De-a lungul acestui semestru, veți studia în cadrul cursului modul de implementare al unui compilator. La nivel conceptual, compilatorul este un program ce face translatarea dintr-un limbaj în altul. Cel mai adesea, compilatorul face translatarea dintr-un limbaj de nivel înalt (C, C++, Java), ce este scris și înteles de un programator, într-un limbaj low level (bytecode, limbaj asamblare, cod mașina) ce poate fi mai apoi executat de procesor (eventual după aplicarea unei noi translatări).
Operația de compilare a unui limbaj se face în general în mai mulți pași. Fiecare pas produce ca output o translatare a datelor de intrare primite, output ce servește ca input pentru pasul următor. Principalii pași ce se disting, în mod conceptual, pentru majoritatea compilatoarelor se pot vedea în figura urmatoare, împreuna cu inputul si outputul aferent, precum și tool-urile pe care le vom folosi în cadrul laboratorului și temelor pentru implementare.
Analiza lexicală este primul pas în înțelegerea de către compilator a fișierelor sursă. Scopul său este de a identifica atomii lexicali (“tokens”). Exemple de atomi lexicali în C sunt constantele întregi (42
, -8l
, 0xff
), șirurile de caractere (“ana are mere”
) sau identificatorii (my_awesome_function
).
flex
este un generator de analizoare lexicale. Acesta primește la intrare un fișier ce conține o specificație a atomilor pe care urmează să-i recunoască analizorul generat, precum și o serie de acțiuni semantice de executat. Specificațiile vor fi scrise într-un fișier text ce poate avea orice denumire/extensie. De regulă se folosește extensia .lex
.
Pe baza spcificațiilor, flex
generează un program C ce conține tabele de analiză și o funcție de analiză numită yylex
. Numele implicit al fișierului astfel generat este lex.yy.c
. Acesta urmează să fie compilat (gcc
sau un alt compilator) pentru a se obține fișierul executabil.
În cadrul acestui laborator vom folosi pachetul flex
standard oferit de distribuție. Pentru mașina virtuală CPL pusă la dispoziție versiunea pachetului flex
este 2.5.35
.
sudo apt-get install flex
flex [OPTIONS] [FILE] ...
Implicit, rulând programul fără nicio opțiune, va aștepta introducerea de specificații de la intrarea standard (stdin).
Comanda dispune de o varietate de parametri și opțiuni (man flex) dintre care amintim:
yy_flex_debug
trebuie să fie setată pe o valoare nenulă, implicit având valoarea 0lex.yy.c
Fișierul de specificații conține expresiile regulate ce descriu atomii limbajului sursă, precum și acțiunile pe care analizorul trebuie să le execute la recunoașterea fiecărui atom.
definitions %% rules %% user_code
Se pot adăuga /* comentarii ca în C */ cât timp sunt precedate de cel puțin un spațiu.
Această secțiune apare înainte de prima secvență de %%
și este utilizată pentru a simplifica scanarea expresiilor prin asocierea de nume expresiilor regulate. În această secțiune pot să apară:
Asocierea de nume cu expresiile regulate se face prin construcții de forma:
name definition
unde:
-
și/sau _
; primul caracter trebuie să fie literă sau _
;
Numele astfel asociate pot fi folosite în definirea altor expresii regulate folosind construcția {name}
. Comportamentul este similar celui obținut cu ajutorul directivei de preprocesor define
din C.
Directivele %option
permit precizarea unor comportamente specifice legate de analizorul generat.
În continuare o să prezentăm câteva dintre cele mai des întâlnite astfel de opțiuni.
main
care doar apelează funcția de analiză yylex
;yylineno
ce va conține numărul liniei corespunzătoare din codul sursă;
flex
este capabil să lucreze simultan cu mai multe seturi de reguli diferite. Acest lucru poate fi util spre exemplu dacă se dorește analiza unui fișier sursă în care apar secvențe de cod scrise în limbaje diferite, cărora le corespund seturi de expresii regulate diferite. Astfel, există un mecanism ce permite activarea dinamică a unui anumit set de reguli în funcție de startea de start.
Stările de start trebuie declarate în secțiunea de definiții, folosind una dintre următoarele două metode:
%s inclusive_start_state1,inclusive_start_state2,...,inclusive_start_stateN %x exclusive_start_states1,exclusive_start_states2,...,exclusive_start_statesN
Numele stărilor de start vor fi atașate anumitor reguli din secțiunea de reguli și vor putea fi folosite ca argumente ale directivelor BEGIN
. Prin execuția unei directive BEGIN
, analizorul trece în starea de start ce apare ca argument al directivei și va folosi regulile ce corespund condiției respective astfel:
%s
) - regulile acceptate sunt atât cele neetichetate cât și cele etichetate cu numele condiției;%x
) - regulile acceptate sunt doar cele etichetate cu numele condiției;O regulă poate fi etichetată cu una sau mai multe stări de start, astfel:
<state1,state2,...,stateN>rule
Un exemplu aici.
Secțiunea de definiții poate conține secvențe de cod C definite de utilizator. Pentru a introduce astfel de secvențe, ele trebuie să fie incluse între perechile de caractere %{
, respectiv %}
. Cele două perechi trebuie să apară pe linii separate și neindentate.
Codul cuprins între %{
și %}
va fi copiat în analizorul generat, în spațiul de vizibilitate extern (global) funcției yylex
.
Secțiunea de reguli este folosită pentru a asocia acțiuni semantice expresilor regulate folosind construcții de forma:
template action
unde,
Analizorul generat va executa secvența descrisă ca acțiune de fiecare dată când va recunoaște în textul sursă un șir care se potrivește cu template. Dacă o acțiune se termină cu instrucțiunea return
, acest lucru va determina ieșirea din funcția yylex
, deci va trece automat la căutarea următoarelor potriviri.
Spre exemplu, dacă ne dorim ca analizorul lexical să prelucreze câte un singur atom la fiecare apel, va trebui să prevedem instrucțiuni de return la toate acțiunile care corespund unor atomi valizi (fără comentarii și whitespaces). Pentru compilatoarele care lucrează în mai multe faze (analiza lexicală fiind o fază distinctă), acțiunile se vor termina prin instrucțiuni de scriere (ex: directiva ECHO
) a codurilor lexicale într-un fișier de ieșire (yyout
) pentru a putea fi prelucrate în fazele ulterioare.
Șablon | Descriere |
---|---|
a | caracterul a |
. | orice caracter mai puțin sfârșitul de linie |
\a | escapează caracterul a sau definește un caracter special (\n, \b, etc.) |
\042 | caracterul cu codul ascii 42 în octal |
\x42 | caracterul cu codul ascii 42 în hexa |
[abc] | oricare dintre caracterele a, b sau c |
[a-z] | oricare dintre caracterele din intervalul a-z |
[a-zA-Z] | orice literă |
[^a] | orice în afară de caracterul a |
[^a-z] | orice în afară de litere mici |
r* | zero sau mai multe apariții succesive ale expresiei regulate r |
r+ | una sau mai multe apariții succesive ale expresiei regulate r |
r? | maxim o apariție a expresiei regulate r |
r{2,5} | 2, 3, 4 sau 5 apariții succesive ale expresiei regulate r |
r{2,} | 2 sau mai multe apariții succesive ale expresiei regulate r |
r{2} | 2 apariții succesive ale expresiei regulate r |
{xxx} | se expandează la expresia regulată căreia i s-a asociat numele xxx în secțiunea de definiții |
(r) | expresia regulată r, evaluarea ei având prioritate |
rs | concatenarea expresiilor regulate r și s |
r|s | una dintre expresiile regulate r sau s |
r/s | expresia regulată r, cu condiția ca aceasta să fie urmată de expresia regulată s |
^r | expresia regulată r, cu condiția ca această să apară la început de linie |
r$ | expresia regulată r, cu condiția ca această să apară la sfârșit de linie |
<<EOF>> | marcaj de sfârșit de fișier |
<s>r | expresia regulată r, dar numai în condiția de start s și în condițiile inclusive |
În secțiunea de reguli, la fel ca în cea de definiții, utilizatorul poate să plaseze secvențe de cod C, altele decât cele care reprezintă acțiuni atașate expresiilor regulate. Sintaxa este aceeași ca în cazul secțiunii de definiții.
Codul din secțiunea de reguli va fi copiat în analizorul generat în interiorul funcției yylex
. Recomandăm plasarea unor astfel de secvențe înainte de prima regulă; acestea vor conține de obicei declarații locale în raport cu funcția yylex
, inițializări etc.
Secțiunea de cod este opțională, iar în cazul în care există are ca efect copierea codului la sfârșitul fișierului lex.yy.c
.
În mod normal, în această secțiune poate să apară funcția main
care să conțină apeluri către funcția yylex
sau alte funcții utile pentru a fi apelate din interiorul funcției yylex
.
În această secțiune vom defini un set de variabile și macro-uri utile oferite de flex
. Acestea pot fi folosite în cadrul secțiunii de cod, dar și în momentul inserării de cod în cadrul secțiunii de definiții sau de reguli.
ECHO
;yytext
în yyout
;start_state
;yylex
;yytext
;yytext
primele n caractere ale atomului curent, restituind în șirul de intrare caracterele de pe pozițiile n+1 până la sfârșit; actualizează yyleng
la n;c
;yyout
sau pot întoarce coduri lexicale;yyout
;yytext
, iar lungimea acestuia este reținută în yyleng
;EOF
, verifică rezultatul funcției yywrap
; dacă rezultatul este fals (zero), înseamnă că analiza trebuie să continue cu noul fișier desemnat de yyin
; dacă rezultatul este nenul, analiza se termină și se întoarce valoarea zero spre apelantul funcției yylex
;
Absența funcției yywrap
poate fi suplinită de opțiunea %option noyywrap
sau prin opțiunea -lfl
la linkare.
În rezolvarea laboratorului folosiți arhiva de sarcini lab01_num_lines.zip
Înscrieți-vă pe Lista de discuții de CPL.
Intrați în directorul 1-num-lines
.
Fișierul example.lex
este un exemplu de fișier de specificație ce definește o variabilă globală, num_lines
, iar de fiecare dată când identifică caracterul \n
(newline) incrementează valoarea variabilei globale. Setul de specificații conține și o zonă definită de cod în care utilizatorul a definit funcția main, apelează funcția de parsare yylex
și afișează numărul total de linii.
Generați cu ajutorul comenzii flex
fișierul C lex.yy.c
. Compilați și generați fișierul executabil. Folosiți drept fișier de input unabom.txt
.
Modificați exercițiul anterior pentru a număra câte cuvinte se află într-un fișier dat. Cuvintele pot conține literali și cratime și sunt separate prin spații albe (enter, spațiu, tab, etc.) sau semne de punctuație.
Scrieți un fișier de specificație .lex
care identifică toate cuvintele care încep cu majusculă.
Scrieți un fișier de specificație .lex
care identifică de câte ori se regăsește un cuvânt dat într-un text.