====== 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).