====== Etapa 3 - Lexer complet si Limbajul Imperative ======
**Checker si teste**: [[lfa:proiect:checker|Checker proiect LFA]].
Etapa 3 consta la randul ei in trei parti, dintre care ultima este bonus.
==== Punctaj ====
* Punctajul pentru 3.1. este de **0.7p** (din totalul de 1p al etapei)
* Punctajul pentru 3.2. este de **0.3p** (din totalul de 1p al etapei)
* Punctajul pentru 3.3. este de **0.3p** (bonus la Etapa 3)
Le descriem in continuare pe fiecare dintre ele:
===== 3.1. Lexer complet =====
Plecand de la implementarile voastre pentru etapele 1 si 2, veti realiza un lexer complet care, spre deosebire de lexer-ul de la [[lfa:proiect:etapa1 | Etapa 1]], primeste **o specificatie de tokens ce contine expresii regulate** in loc de AFD-uri.
Daca nu ati citit pana acum cerinta de la [[lfa:proiect:etapa1 | Etapa 1]], este bine sa o faceti pentru a intelege mai bine Etapa 3.
==== Structura fisierului cu tokens ====
Fisierul va contine un **token** si expresia lui regulata, cate una pe fiecare linie, sub urmatoarea forma:
;
unde:
* un '''' este o secventa de caractere alfabetice (de obicei uppercase)
* un '''' este o expresie regulata
Token-ul este separat printr-un spatiu alb de expresia regulata. Fiecare linie se va termina prin caracterul special '';''.
Expresia regulata are urmatoarea sintaxa:
::= | # concatenare
| | # reuniune (intre regex-uri folosim caracterul '|' pt a desemna reuniunea)
* | # Kleene star
+ | # ee*
() | # paranteze
[a-z]+ | # alias pt expresia a | b | c ... | z, avand numai caractere alfabetice lowercase
[0-9]+ | # alias pt expresia 0 | 1 | 2 | ... | 9
| # orice caracter alfanumeric
'' # orice caracter ASCII, nu doar unul alfabetic
**Spatiile albe pot aparea liberal in corpul unei expresii regulate, fara a influenta sensul acesteia.** Expresiile regulata ce contin caracterul whitespace se regasesc incadrate intre '' ' ' ''.
Exemplu de specificatie:
KEYWORD def | while | if; # concatenare de caractere
REGISTER R[0-9]+; # orice sir ce incepe cu caracterul R urmat de unul sau mai multe cifre
EXPR [a-z]+ (('+' | '*') [a-z]+)*; # o posibila codificare a expresiilor aritmetice
SPACES ' ' | '\t'; # spatii albe
==== Implementarea lexerului complet ====
Aceasta poate fi organizata astfel:
* **Citirea si parsarea specificatiei:** aceasta va fi contributia cea mai substantiala a acestei etape. Expresiile regulate nu mai sunt in forma PRENEX, asadar, pentru parsarea lor va trebui sa implementati un APD (Automat Push-Down) simplu. Aceasta va construi un arbore al expresiei (poate fi aceeasi structura de date folosita la [[lfa:proiect:etapa2|Etapa 2]]).
* **Generarea de AFD-uri**, cate unul pt fiecare expresie regulata. Pentru aceasta veti prelua implementarea voastra a etapei [[lfa:proiect:etapa2|Etapa 2]].
* **Implementarea procedurii de analiza lexicala:** veti citi un fisier, si veti construi o lista de lexeme, exact ca la [[lfa:proiect:etapa2|Etapa 1]]. Puteti folosi direct implementarea voastra anterioara.
==== Output si testare ====
Output-ul va fi scris intr-un fisier, fiind unui sir de tokens si lexeme identificat (la fel ca la [[lfa:proiect:etapa1|Etapa 1]]). El va avea forma:
...
unde fiecare pereche '''' se afla pe cate o linie separata, iar intre fiecare ''token'' si ''lexem'' se afla cate un singur spatiu alb (**neincluzand spatiile albe ce se pot afla in compozitia lexemului**).
Testarea va fi efectuata intr-o maniera identica ca cea din cadrul [[lfa:proiect:etapa1|Etapei 1]], prin compararea output-ului generat de voi cu cel aflat in fisierele de referinta. Mai multe detalii legate de script-ul de testare si cum sa il rulati gasiti pe pagina dedicata [[lfa:proiect:checker|Checker-ului]].
==== Sugestii de implementare pt Lexer complet ====
* Inainte de a scrie cod, construiti o gramatica ne-ambigua pentru limbajul expresiilor regulate. **Atentie la precedenta operatorilor**.
* Folositi ideile din aceasta gramatica pentru a construi un APD (Automat Push-Down).
* Comportamentul APD-ului scris de voi este cel mai bun //road-map// pentru implementare. Stiva APD-ului poate fi folosita inclusiv pt a stoca referinte la expresiile regulate partiale pe care le-ati parsat.
===== 3.2. Parser pentru un limbaj de programare (Imperative) =====
Folosind lexer-ul scris de voi, implementati un parser simplu pentru limbajul **Imperative** descris mai jos.
Folositi aceeasi abordare ca in implementarea parserului pentru expresii regulate.
}
==== Input ====
Input-ul va fi un program a carui descriere sintactica se regaseste mai jos (**//Limbajul Imperative//**):
::= '='
| begin end
| while () do od
| if () then else fi
::= | '\n'
::= '+' | '-' | '*' | '>' | '==' | |
* Urmariti structura fisierelor de test pentru a intelege mai bine gramatica precum si semnificatia **variabilelor** si a **intregilor**.
* Acordati atentie modului in care sunt folosite ''\n'' in sintaxa limbajului (atunci cand delimiteaza intre instructiuni si atunci cand doar formateaza).
Pentru implementare, trebuie sa:
* scrieti o specificatie pentru analiza lexicala a programelor;
* folosind prima parte a etapei 3, implementati un parser pentru programe **Imperative**. Scopul vostru este sa realizati o parsare corecta si o afisare a acesteia. Pentru aceasta din urma, puteti folosi urmatorul schelet de clase, care vor reprezenta AST (Abstract Syntax Tree-ul) pentru programe **Imperative** si care au deja implementata pentru voi procedura de afisare: {{:lfa:proiect:ast.zip|ast.zip}}
==== Output ====
Output-ul va fi redat sub forma unui fisier ce contine AST-ul rezultat in urma parsarii programului. Un exemplu de instantiere si afisare se gaseste in comentariile din scheletul de clase ''ast.py'' (atentie: **subblocurile sunt identate cu cate doua spatii** fata de blocurile parinte).
===== 3.3. Interpretor pentru limbajul Imperative =====
Folosind parserul scris anterior, realizati un interpretor pentru programe **Imperative**. Interpretorul va folosi AST-ul scris de voi.
Aceasta parte nu va avea tester dedicat si se va baza pe fisierele de testare ale partii **3.2**. In timpul prezentarii veti ilustra asistentului vostru modalitatea de functionare.
In termeni generici, un **interpretor** mentine si gestioneaza un **store**, adica o mapare intre nume de variabile intalnite in program, si **valorile** la care sunt legate acestea:
* anumite instructiuni adauga noi legari **variabila**-**valoare** la store
* anumite instructiuni modifica legari existente
* anumite instructiuni pot doar verifica valorile respective pentru a lua o decizie.
* Interpretorul va incepe intotdeauna executia cu un **store** vid.
* Output-ul unui interpretor va fi continutul store-ului final, mai exact, maparea ce contine toate variabilele folosite in program alaturi de valorile lor.
Mai jos aveti un exemplu de actualizare a unui **store** in timpul interpretarii unui program scurt (**store-ul** este reprezentat in comentarii drept un **dictionar** ce retine maparile dintre o variabila si valorea sa curenta):
begin // {} - store vid
a = 1; // {a : 1} - s-a adaugat o mapare in store
r = 0; // {a : 1, r : 0}
if (a == 2) then // {a : 1, r : 0} - expresiile booleene nu modifica store-ul
r = a + 3; // nu se ajunge aici
else
r = a; // {a : 1, r : 1} - valoarea lui r a fost actualizata
fi
end
==== Sugestii de implementare pentru limbajul Python ====
* Cel mai indicat mod de implementare a unui interpretor intr-un limbaj Orientat-Obiect este folosind design pattern-ul **Visitor**, in care:
* **AST**-ul este structura vizitata (**Visitable**)
* **Interpretorul** este vizitatorul (**Visitor**)
==== Sugestii de implementare pentru limbajul Haskell ====
* Cel mai indicat mod de implementare a unui interpretor intr-un limbaj **functional** este folosind **Monade**; Intrucat acestea nu au fost discutate inca, puteti folosi o implementare direct-recursiva, care sa construiasca o functie cu signatura '' Program -> Store '', unde ''Store'' trebuie definit de catre voi.
==== Citirea unui fisier in limbajul Haskell ====
IO (Input / Output) in limbajul Haskell se bazeaza pe design pattern-ul **Monad**, ne-discutat inca. Puteti totusi folosi cu usurinta urmatorul exemplu de cod pentru citirea dintr-un fisier, si afisarea unui mesaj:
test = do
in <- readFile "filename.extension"
putStrLn $ show $ f in
In codul de mai sus:
- ''in'' este o variabila de tip ''String'' ce va retine continutul fisierului indicat
- ''putStrLn'' este o functie care are ca efect afisarea
- ''f :: (Show a) => String -> a'' este o functie pe care o puteti defini voi sub ce forma doriti, si care materializeaza intreaba prelucrare pe care o faceti asupra continutului fisierului. Ea poate intoarce orice obiect, cu conditia sa fie afisabil (astfel incat apelul ''show'' sa fie unul valid).