Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
lfa:2025:proiect:etapa2 [2025/11/20 11:48]
pdmatei
lfa:2025:proiect:etapa2 [2025/11/25 20:48] (current)
ldaniel
Line 1: Line 1:
 Deadline etapa 1: 26 nov 2025 23:55 Deadline etapa 1: 26 nov 2025 23:55
  
-Deadline etapa 2: ian 2026 23:55+Deadline etapa 2: 16 ian 2026 23:55
  
 Link etapa 1 [[lfa:​2025:​proiect:​etapa1| Etapa 1]] Link etapa 1 [[lfa:​2025:​proiect:​etapa1| Etapa 1]]
  
 +Schelet de cod: {{:​lfa:​2025:​proiect:​lfa2025_skel_etapa2.zip|}}
  
-===== Etapa 2 =====+ 
 +====== Etapa 2 ======
  
 **Etapa 2** a proiectul consta in implementarea unui Lexer in Python si, pe baza acestuia, implementarea unui Parser elementar pentru expresii lambda. **Etapa 2** a proiectul consta in implementarea unui Lexer in Python si, pe baza acestuia, implementarea unui Parser elementar pentru expresii lambda.
Line 13: Line 15:
  
  
-=== Ce este un Lexer ===+===Lexer ====
 Un lexer este un program care imparte un sir de caractere in subsiruri numite //lexeme//, fiecare dintre acestea fiind clasificat ca un //token//, pe baza unei specificatii. Specificatia contine o secventa de perechi $math[(token_i,​ regex_i)] in care fiecare token este descris printr-o expresie regulata. Ordinea perechilor in secventa este importanta, iar acest aspect va fi discutat ulterior. Un lexer este un program care imparte un sir de caractere in subsiruri numite //lexeme//, fiecare dintre acestea fiind clasificat ca un //token//, pe baza unei specificatii. Specificatia contine o secventa de perechi $math[(token_i,​ regex_i)] in care fiecare token este descris printr-o expresie regulata. Ordinea perechilor in secventa este importanta, iar acest aspect va fi discutat ulterior.
  
Line 68: Line 70:
  
  
-=== Parser ===+==== Parser ​====
  
 Un parser foloseste output-ul produs de lexer pentru etapa de analiza sintactica a textului. ​ Un parser foloseste output-ul produs de lexer pentru etapa de analiza sintactica a textului. ​
  
-Construim un parser pe baza unei gramatici care contine reguli ce descriu sintaxa valida a inputului. O astfel de gramatica va folosi ​lexemele ​generate de analiza lexicala in rolul de terminali.+Construim un parser pe baza unei gramatici care contine reguli ce descriu sintaxa valida a inputului. O astfel de gramatica va folosi ​categoriile lexicale ​generate de analiza lexicala in rolul de terminali.
  
-<<MP: explica sintaxa gramaticilor in implementarea noastra, inaintea exemplului, pt ca nu are sens regula din gramatica. Inlocuieste 3 cu 1 + 2, si elaboreaza pe exemplu>>​+=== Sintaxa pentru gramatici ===
  
-Exemplu: pentru textul "int x = 3" analiza lexicala produce {(TYPE, "​int"​),​ (ID, "​x"​),​ (EQUAL, "​="​),​ (NUMBER"​3"​)}. ​ si o gramatica ​pentru ​aceasta expresie de atribuire:+In implementarea noastrasintaxa folosita ​pentru ​descrierea gramaticilor in format text va fi urmatoarea:
 <​code>​ <​code>​
- ​assignTYPE ID EQUAL NUMBER+sa b 
 +s: b a 
 +a: A 
 +b: B
 </​code>​ </​code>​
 +unde a si b sunt neterminali si A si B sunt terminali. Preferam sa notam neterminalii cu litere mici si terminalii cu litere mari deoarece terminalii nostri vor fi tokenii proveniti de la analiza lexicala. De asemenea, putem scrie simplificat primele 2 reguli astfel, folosind o alternativa:​
 +<​code>​
 +s: a b|b a
 +</​code>​
 +E important ca inainte si dupa simbolul pentru alternativa sa nu existe spatii, pentru citirea corecta a fisierului. In mod automat simbolul care introduce prima regula din gramatica este considerat simbolul de start, indiferent de denumirea lui.
  
-Parserul nostru va lucra cu gramatici simple, in parserele ​folosite in scrierea compilatoarelor se pot aplica ​reguli asemanatoare celor de la regex-uri si peste gramatici, cum ar fi wildcard-urile *, ?, ++Gramaticile ​folosite ​de noi vor fi intotdeauna ​in Forma Normala Chomsky, pentru a putea aplica ​algoritmul care valideaza apartenenta unui cuvant la limbajul generat ​de gramatica.
  
-Algoritmul pe care il putem implementa pentru a verifica daca un sir este acceptat de o gramatica este CYK, care foloseste o strategie de programare dinamica peste o gramatica in Forma Normala Chomsky(FNC). ​O gramatica este in FNC daca are doar reguli de tipul AB C (un neterminal produce doi neterminali) sau A: a (un neterminal produce un singur terminal).  +O gramatica este in FNC daca are doar reguli de tipul ab c (un neterminal produce doi neterminali) sau a: A (un neterminal produce un singur terminal). 
-Este important ca gramatica sa fie in FNC pentru a putea imparti problema generarii sirului prin derivari succesive in doua subprobleme mai mici, pentru a construi solutia printr-o strategie Bottom-up.+ 
  
-Algoritmul CYK a fost studiat la curs, puteti citi mai multe despre el aici. (hint pentru implementare:​ folositi o matrice ​de dictionare in care pentru fiecare neterminal sa aveti ca valoare un nod de parseTree ca la final sa obtinem arborele de parsare).+=== Verificarea acceptarii unui cuvant ​de catre gramatica ===
  
-Dupa aplicare etapei de parsare se obtine ​un arbore ​de derivare, pe care se pot face prelucrari ulterioare ​in functie ​de comportamentul dorit+Pentru a verifica daca un cuvant apartine limbajului descris ​de o gramatica in FNC folosim algoritmul CYK. 
 +Algoritmul CYK poate fi consultat ​in materialele ​de la curs
  
-Cerinta: +=== Arbore ​de parsare ===
-Implementati un parser general, care primeste o gramatica in FNC si returneaza un arbore ​de derivare.+
  
-In clasa Grammar completati metoda "​cykParse"​, care primeste output-ul unui lexer (tupluri ​de (tokenlexema)) ​si intoarce ​arborele de derivare.+In plus fata de algoritmul CYK implementat la curs, care ne spune daca un text este acceptat ​de gramaticane dorim sa avem si secventa de derivari care a produs cuvantul, sau echivalent: arborele de parsare pentru textul nostru. In scheletul temei aveti implementata o clasa numita ParseTree, care reprezinta ​arborele de parsare.
  
-Scrieti o gramatica in FNC in fisierul "​grammar_lambda.txt",​ pornind ​de la gramatica din fisierul "​grammar.txt"​. Neterminalii adaugati artificial ​in procesul de normalizare trebuie sa inceapa ​cu +Arborele ​de parsare va fi afisat ca un arbore ​in care copiii au o indentare ​cu 2 spatii mai la dreapta decat parintele
-"​int_",​ pentru a nu fi afisati in arborele final de derivare.+
  
-In clasa Parser completati metoda "​parse",​ pentru a scrie un parser general care citeste ​o gramatica in FNC din fisierul primit ca parametru la initializare si returneaza arborele ​de derivare.+Atunci cand doreste sa foloseasca ​o gramatica ​independenta de context arbitrara pentru parsare, programatorul trebuie intai sa o converteasca (manual) ​in FNC. Pentru a usura vizualizarea arborilor ​de parsare, convenim sa denumim toti non-terminalii creati in procesul de conversie cu **int_**. Vom folosi aceasta conventie pentru a nu afisa regulile ce au in stanga un non-terminal de forma **int_**, ci direct copii. Astfel, arborii de parsare sunt mai usor de citit. Pentru regulile simple, de tipul neterminal produce un terminal (a: TOKEN) se va afisa doar categoria lexicala si lexemul corespunzator,​ fara numele regulii. Aceste lucruri sunt deja implementate in metoda str() a lui ParseTree.
  
-Clasa ParseTree ​este implementata deja.+Pentru a obtine un arbore de parsare ca rezultat, in implementarea algoritmului CYK va recomandam sa folositi o matrice de dictionare in care pentru fiecare neterminal sa aveti ca valoare un ParseTree ​cu informatiile despre derivarile bottom-up care au dus la obtinerea acelui neterminal. La final, in caz de acceptare, arborele de parsare afisat va fi arborele avand ca radacina simbolul de start.
  
 +=== Cerinta: ===
 +Implementati un parser general, care primeste o gramatica in FNC si returneaza un arbore de parsare.
  
-Fiind data urmatoarea ​gramatica pentru expresii lambda, ​scrieti o gramatica ​in FNC pe care sa o folositi ​in clasa Parser:+1. In clasa Grammar completati metoda "​cykParse",​ care primeste output-ul unui lexer (tupluri de (token, lexema)) si intoarce arborele de parsare. 
 + 
 +2. Scrieti o gramatica in FNC in fisierul "​**grammar_lambda.txt**",​ pornind de la gramatica pentru expresii lambda ​din fisierul "​**parser_grammar.txt**"​. Aplicati voi transformarile prezentate la curs pentru a obtine FNC. Toti neterminalii creati in acest proces trebuie sa inceapa cu 
 +"​int_"​pentru a fi distinsi de ceilalti neterminali,​ astfel incat sa ii omitem cand afisam arborele de parsare. Astfel outputul este mult mai usor de urmarit. 
 + 
 +3. Completati specificatia pentru Lexerul de expresii lambda ​in fisierul "​**lexer_spec.json**",​ adaugand regex-ul potrivit pentru fiecare Token. 
 + 
 +4. In clasa Parser completati metoda "​parse", ​care primeste un text de intrare si returneaza arborele de parsare. Folositi-va de campurile lexer si grammar ale parserului pentru analiza lexicala si apoi analiza sintactica a textului. Dupa analiza lexicala va trebui ​sa ignoratii tokenii SPACE inainte de a trece la analiza sintactica. 
 + 
 + 
 +=== Exemplu de parsare === 
 +Pentru textul "int x = 1 + 2" analiza lexicala a produs tokenii [(TYPE, "​int"​),​ (ID, "​x"​),​ (EQUAL, "​="​),​ (NUMBER, "​3"​),​ (PLUS, "​+"​),​ (NUMBER, "​2"​)] (tokenii SPACE au fost ignorati). 
 + 
 +Parserul e configurat cu urmatoarea gramatica (care in exemplu nu e in FNC, dar inainte de a fi interpretata de Parser ​a fost convertita la FNC):
 <​code>​ <​code>​
-<​e> ​::= <var> | '​('​ <e> '​)'​ | <e> <op> <e> | '​\'<​var>​.<​e>​ + ​assignTYPE ID EQUAL sum 
-<op> ::= (+|-|/) + sumNUMBER PLUS NUMBER
-<var> ::= [a-zA-Z0-9]++
 </​code>​ </​code>​
  
-=== Exemplu === +Arborele de parsare obtinut va fi:
 <​code>​ <​code>​
-\x.(x * (x + 2)-> Lambda ​(var "​x"​-> Mul (var "​x"​) (Parant (Plus (var "​x"​) (val 2)))+assign 
 +  ​(TYPE: int) 
 +  ​(ID: x) 
 +  ​(EQUAL: =) 
 +  sum 
 +    ​(NUMBER: 1) 
 +    ​(PLUS: +) 
 +    ​(NUMBER: ​2)
 </​code>​ </​code>​
- 
-<note important>​ 
-Pentru moment, exista doar testele pentru lexer! 
-Zilele acestea vor aparea si cateva teste pentru parser 
-</​note>​ 
- 
  
  
Line 126: Line 149:
  
 Verificarea corectitudinii implementarii voastre se va face automat, printr-o serie de teste unitare, teste care vor verifica comportamentul fiecarei functii obligatorii de implementat si ii va testa output-ul pe o diversitate de input-uri. Verificarea corectitudinii implementarii voastre se va face automat, printr-o serie de teste unitare, teste care vor verifica comportamentul fiecarei functii obligatorii de implementat si ii va testa output-ul pe o diversitate de input-uri.
- 
-Un alt check preliminar care se va face pe fiecare DFA construit va fi unul care verifica integritatea lui d.p.d.v. structural (starea initiala si starile finale sunt incluse in multima de stari, nu are tranzitii definite pe un caracter dintr-o anume stare). 
  
 ==== Python ==== ==== Python ====
Line 148: Line 169:
 <​code>​ <​code>​
 . .
-── src +├── grammar_lambda.txt 
-    ├── __init__.py +├── lexer_spec.json 
-    ├── DFA.py +── src 
-    ├── NFA.py +│   ​├── __init__.py 
-    ├── Regex.py +│   ​├── DFA.py 
-    ├── Lexer.py +│   ​├── NFA.py 
-    ├── Parser.py +│   ​├── Regex.py 
-    ... (alte surse pe care le folositi)+│   ​├── Lexer.py 
 +│   ​├── Parser.py 
 +│   ├── Grammar.py 
 +│   ├── ParseTree.py 
 +|   ... (alte surse pe care le folositi)
 ├── ID.txt ├── ID.txt
 </​code>​ </​code>​