01. Introduction to Flex

Introducere

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.

Nice to read

Prezentare teoretică

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.

Instalare flex

Î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

Utilizare 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:

  • -d - activarea modului debugging; variabila globală yy_flex_debug trebuie să fie setată pe o valoare nenulă, implicit având valoarea 0
  • -i - analizorul generat va fi case-insensitive
  • -o - schimbă numele implicit al fișierului de ieșire lex.yy.c

Structura fișierului de specificații

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.

Secțiunea de definiții

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ă:

  • opțiuni
  • stări de start
  • secvențe de cod C

Asocierea de nume cu expresiile regulate se face prin construcții de forma:

name definition

unde:

  • name - cuvânt compus din litere, cifre, - și/sau _; primul caracter trebuie să fie literă sau _;
  • definition - expresie regulată (până la sfârșit de rând);

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.

Opțiuni

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.

  • %option main - generează o funcție main care doar apelează funcția de analiză yylex;
  • %option debug - activează modul debugging;
  • %option case-insensitive - analizatorul va fi case insensitive;
  • %option yylineno - activează variabila globală yylineno ce va conține numărul liniei corespunzătoare din codul sursă;

Stări de start

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:

  • condiție inclusivă (declarată cu %s) - regulile acceptate sunt atât cele neetichetate cât și cele etichetate cu numele condiției;
  • condiție exclusivă (declarată cu %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.

Secvențe de cod C în secțiunea de definiții

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

Secțiunea de reguli este folosită pentru a asocia acțiuni semantice expresilor regulate folosind construcții de forma:

template action

unde,

  • template - expresie regulată al cărei prim caracter trebuie să se afle pe prima poziție a liniei;
  • action - secvență formată din una sau mai multe instrucțiuni C; trebuie să înceapă pe aceeași linie cu template; pentru mai multe instrucțiuni se va folosi definirea unui bloc de instrucțiuni (adică linii de cod împrejmuite de acolade); action poate fi inclusiv instrucțiunea vidă;

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.

Expresii regulate

Ș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

Secvențe de cod C în secțiunea de reguli

Î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

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.

Variabile și macro-uri utile

Î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.

  • char *yytext - adresa zonei în care sunt reținute caracterele ce compun atomul curent;
  • int yyleng - lungimea atomului curent;
  • FILE *yyin - pointer către fișierul deschis ce conține textul curent analizat;
  • FILE *yyout - pointer către fișierul deschis în care se poate scrie cu ajutorul macro-ului ECHO;
  • ECHO - realizează scrierea conținutului yytext în yyout;
  • BEGIN(start_state) - comută analizorul în start_state;
  • REJECT - caută următoarea regulă al cărei șablon se potrivește cu atomul în curs de prelucrare (sau cu un prefix) și execută acțiunea asociată regulii găsite;
  • YY_START - starea curentă (un număr);
  • yyterminate() - termină procesul de analiză și întoarce valoarea 0 către apelantul funcției yylex;
  • yymore() - concatenează următorul atom la atomul existent în yytext;
  • yyless(int n) - lasă în 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;
  • unput(char c) - forțează plasarea în șirul de intrare a caracterului c;
  • input() - citește și returnează următorul caracter din șirul de intrare;

Funcția yylex

  • se aplică acțiunile corespunzătoare din specificație (secțiunea de reguli) care pot genera text specific în fișierul yyout sau pot întoarce coduri lexicale;
  • textul din fișierul sursă care nu se potrivește cu niciun șablon este copiat automat în yyout;
  • dacă pentru un text se potrivesc mai multe șabloane, se alege potrivirea cea mai lungă;
  • dacă pentru un text se potrivesc mai multe șabloane de aceeași lungime, se alege prima potrivire în ordine textuală din specificație;
  • textul corespunzător potrivirii este copiat în yytext, iar lungimea acestuia este reținută în yyleng;
  • când analizorul întâlnește marcajul 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.

Exerciții de laborator (10p)

În rezolvarea laboratorului folosiți arhiva de sarcini lab01_num_lines.zip

Exercițiul 0

Înscrieți-vă pe Lista de discuții de CPL.

Exercițiul 1 - num lines (3p)

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.

Exercițiul 2 - num words (3p)

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.

Exercițiul 3 - names (2p)

Scrieți un fișier de specificație .lex care identifică toate cuvintele care încep cu majusculă.

Exercițiul 4 - appearances (2p)

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.

cpl/labs/01.txt · Last modified: 2016/10/03 21:56 by bogdan.nitulescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0