Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
lfa:2023:proiect [2023/11/01 09:43] pdmatei |
lfa:2023:proiect [2024/12/12 12:40] (current) atoader old revision restored (2023/12/07 13:21) |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | Deadline etapa 1: 20 nov 2023 23:59 | + | Deadline etapa 1: 22 nov 2023 23:59 |
+ | |||
+ | Deadline etapa 2: 6 dec 2023 23:59 | ||
+ | |||
+ | Deadline etapa 3: 10 jan 2024 23:59 | ||
+ | |||
+ | Schelet etapa 1 {{:lfa:2023:skel-etapa1.zip|}} | ||
+ | |||
+ | Schelet etapa 2 {{:lfa:2023:skel-etapa2.zip|}} | ||
+ | |||
+ | Schelet etapa 3 {{:lfa:2023:skel-etapa3.zip|}} | ||
- | Schelet etapa 1 {{:lfa:2023:lfa_2023-skel-1.zip|}} | ||
====== Proiect ====== | ====== Proiect ====== | ||
Line 37: | Line 46: | ||
In scheletul temei veti gasi 2 clase: **NFA** si **DFA**. In fiecare veti avea de implementate metodele necesare pentru verificarea comportamentului lor si pentru a realiza conversia ceruta de tema. | In scheletul temei veti gasi 2 clase: **NFA** si **DFA**. In fiecare veti avea de implementate metodele necesare pentru verificarea comportamentului lor si pentru a realiza conversia ceruta de tema. | ||
- | ===Clasa DFA=== | + | === Clasa DFA === |
Un DFA va fi descris de urmatoarele campuri: | Un DFA va fi descris de urmatoarele campuri: | ||
Line 54: | Line 63: | ||
Spre exemplu, daca am avea automatul de mai jos: | Spre exemplu, daca am avea automatul de mai jos: | ||
+ | |||
+ | {{:lfa:2023:remap_before.png?400|}} | ||
+ | /* | ||
<code> | <code> | ||
> (0) -a,b-> (1) ----a----> ((2)) | > (0) -a,b-> (1) ----a----> ((2)) | ||
Line 60: | Line 72: | ||
\-a,b-/ | \-a,b-/ | ||
</code> | </code> | ||
+ | */ | ||
Am putea aplica functia ''x -> 'q' + str(x+2)'', care ar creea un DFA cu urmatoarele stari: | Am putea aplica functia ''x -> 'q' + str(x+2)'', care ar creea un DFA cu urmatoarele stari: | ||
+ | |||
+ | {{:lfa:2023:remap_after.png?400|}} | ||
+ | /* | ||
<code> | <code> | ||
> (q2) -a,b-> (q3) ----a----> ((q4)) | > (q2) -a,b-> (q3) ----a----> ((q4)) | ||
Line 68: | Line 84: | ||
\-a,b-/ | \-a,b-/ | ||
</code> | </code> | ||
+ | */ | ||
===Clasa NFA=== | ===Clasa NFA=== | ||
Line 80: | Line 97: | ||
Functiile ''epsilon_closure'' si ''subset_construction'' **sunt obligatoriu** de implementat, iar functia ''remap_states'' **nu** este. | Functiile ''epsilon_closure'' si ''subset_construction'' **sunt obligatoriu** de implementat, iar functia ''remap_states'' **nu** este. | ||
+ | |||
+ | ===== Etapa 2 ===== | ||
+ | |||
+ | **Etapa 2** a proiectului consta in conversia Regex - NFA (folosind Algoritmul Thompson prezentat la curs) | ||
+ | |||
+ | ====Structura scheletului==== | ||
+ | |||
+ | In scheletul temei veti gasi pe langa cele 2 clase de la etapele anterioare (**NFA** si **DFA**) inca o clasa, **Regex**, si o metoda ''parse_regex''. | ||
+ | |||
+ | === Forma standard a expresiilor regulate === | ||
+ | |||
+ | Forma standard a regex-urilor poate fi descrisa in forma [[https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form | BNF]] astfel: | ||
+ | <code> | ||
+ | <regex> ::= <regex><regex> | | ||
+ | <regex> '|' <regex> | | ||
+ | <regex>'*' | <regex>'+' | <regex>'?' | | ||
+ | '(' <regex> ')' | | ||
+ | "[A-Z]" | | ||
+ | "[a-z]" | | ||
+ | "[0-9]" | | ||
+ | "eps" | <character> | ||
+ | </code> | ||
+ | |||
+ | In descrierea de mai sus, elementele dintre parantezele angulare <> sunt **non-terminali** care trebuie generati, caracterele sunt intotdeauna plasate intre ghilimele simple, iar sirurile intre ghilimele duble. | ||
+ | |||
+ | ''<character>'' se refera la orice caracter obisnuit care nu face parte din caractele de //control// (precum ''*'' sau ''|''), sau la orice sir de lungime doi de forma ''\c'', unde ''c'' poate fi orice caracter inclusiv de control. | ||
+ | |||
+ | "eps" reprezinta caracterul Epsilon. | ||
+ | |||
+ | ==== Preprocesarea Regex-urilor ==== | ||
+ | |||
+ | In descrierea de mai sus, pe langa caracterele alfa-numerice si operatiile de baza star, concat si union, veti gasi si: | ||
+ | - doua operatii noi: | ||
+ | - plus ''+'' - expresia asupra careia este aplicat apare de 1 data sau mai multe ori. | ||
+ | - semnul intrebarii ''?'' - expresia asupra careia este aplicat apare o data sau niciodata. | ||
+ | - 3 syntactic sugars: | ||
+ | - ''[a-z]'' - orice caracter litera mica din alfabetul englez | ||
+ | - ''[A-Z]'' - orice caracter litera mare din alfabetul englez | ||
+ | - ''[0-9]'' - orice cifra | ||
+ | |||
+ | <note important> | ||
+ | Daca un regex contine spatii albe, acestea sunt ignorate. Pentru a nu fi ignorate, acestea vor fi precedate de un backslash "\ ". | ||
+ | Similar, pentru a nu incurca caracterele '*', '+', ')', '(', '|', '?' care reprezinta si operatori din regex-uri, cand fac propriu zis parte din regex, vor fi precedate de backslash. | ||
+ | </note> | ||
+ | |||
+ | ===Clasa Regex=== | ||
+ | |||
+ | In aceasta clasa veti avea de implementat metoda ''thompson'', metoda care primeste un obiect de tip regex si intoarce un NFA (cu starile de tipul ''int'' ca si conventie). Regexul primit ca input va avea forma prezentata mai sus. | ||
+ | |||
+ | Concatenarea nu va fi reprezentata printr-un caracter anume, vom considera ca constructiile de forma ''ab'' se traduc automat in "caracterul a concatenat cu caracterul b". Concatenarea oprindu-se astfel la intalnirea **unei paranteze** sau **a unei uniuni**. Spre exemplu: | ||
+ | - ''ab|c'' se traduce in ''(ab)|c'' | ||
+ | - ''abd*'' se traduce in ''ab(d)*'' | ||
+ | - ''ab+'' se traduce in ''a(b)+'' | ||
+ | |||
+ | === Hint de implementare === | ||
+ | |||
+ | Pentru a va usura lucrul cu expresiile regulate, va puteti creea mai multe clase care extind din clasa regex (ex ''Character'', ''Star'', ''Union'', ''Concat'', etc...) iar fiecare clasa va avea propria implementare a metodei ''thompson''. | ||
+ | |||
+ | |||
+ | ===== Etapa 3 ===== | ||
+ | |||
+ | **Etapa 3** a proiectul consta in implementarea unui lexer in python. | ||
+ | |||
+ | === Structura scheletului=== | ||
+ | |||
+ | In scheletul temei veti gasi pe langa cele 3 clase de la etapele anterioare (**Regex**, **NFA** si **DFA**) inca o clasa, **Lexer**. | ||
+ | |||
+ | === Ce este un 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. | ||
+ | |||
+ | Exista mai multe modalitati prin care puteti implementa un lexer. Abordarea conventionala (si cea pe care o recomandam) consta in urmatoarele etape: | ||
+ | - fiecare regex este convertit intr-un AFN, pastrand totodata informatia despre token-ul aferent si pozitia la care acesta apare in spec. | ||
+ | - se construieste un AFN unic, introducand o stare initiala si epsilon-tranzitii de la aceasta catre toate starile initiale ale AFN-urilor de mai sus. Astfel, acest AFN va accepta oricare dintre tokenii descrisi in specificatie. Starea finala vizitata va indica token-ul gasit. | ||
+ | - AFN-ul este convertit la un AFD (care optional poate fi minimizat). In acest automat: | ||
+ | - cand vizitam un grup de stari ce contine (AFN-)stari finale, inseamna ca unul sau mai multe token-uri corespunzatoare au fost identificate. | ||
+ | - cand vizitam un sink-state (daca acesta exista), inseamna ca subsirul curent nu este descris de nici un token. In acest caz trebuie sa intoarcem cel mai lung prefix acceptat si sa continuam lexarea cuvantului ramas | ||
+ | - cand vizitam o stare non-finala si care nu e sink-state, continuam prin trecerea in urmatoarea stare a AFD-ului consumand un caracter din cuvant | ||
+ | |||
+ | Scopul unui lexer este identificarea **celui mai lung subsir** care satisface un regex din specificatia data. Daca un cel mai lung subsir satisface **doua sau mai multe regex-uri**, va fi raportat primul token aferent, in ordinea in care acestea sunt scrise in specificatie. | ||
+ | |||
+ | Pentru a identifica **cel mai lung subsir** folosind un AFD precum cel descris in sectiunea anterioara, trebuie sa observam faptul ca: | ||
+ | - vizitarea unui grup de stari ce contine o (AFN-)stare finala, **nu indica** in mod necesar faptul ca am gasit cel mai lung subsir acceptat. | ||
+ | - daca un grup de stari ce contine o (AFN-)stare finala a fost vizitata **anterior**: | ||
+ | - vizitarea unui grup de stari ce nu contine stari finale, **nu indica** in mod necesar faptul ca am gasit cel mai lung subsir (automatul poate accepta in viitor) | ||
+ | - vizitarea sink-state-ului AFD-ului (daca acesta exista), indica faptul ca automatul nu va mai accepta in viitor. | ||
+ | - daca in AFD nu exista un sink state, atunci analiza lexicala trebuie sa continue pana la epuizarea inputului, pentru a decide asupra celui mai lung subsir. | ||
+ | |||
+ | Odata ce subsirul cel mai lung a fost identificat: | ||
+ | - AFD-ul va fi //resetat// - adus in starea initiala pentru a relua analiza lexicala. | ||
+ | - analiza lexicala va continua de la pozitia unde subsirul cel mai lung s-a terminat, iar aceasta poate preceda cu **oricate pozitii**, pozitia curenta unde a ajuns analiza. | ||
+ | |||
+ | === Clasa Lexer === | ||
+ | Clasa lexer are un constructor care primeste ca parametru o **specificatie** care are urmatoarea structura: | ||
+ | <code python> | ||
+ | spec = [(TOKEN_LEXEMA_1, regex1), (TOKEN_LEXEMA_2, regex2), ...] | ||
+ | </code> | ||
+ | unde primul element din fiecare tuplu este un nume dat unui token, iar al doilea element din tuplu este un regex ce descrie acel token. Puteti imagina aceasta specificatie ca un //fisier de configurare// care descrie modul in care va functiona lexerul pe diverse fisiere de text. | ||
+ | |||
+ | In plus, clasa lexer contine functia ''lex'' care va primi un cuvant ''str'' ca input si va intoarce rezultatul lexarii lui sub forma ''list[tuple[str, str]]''. Metoda va intoarce o lista de tupluri ''(token, lexem_cuvant)'' in cazul in care lexarea reuseste. In caz de eroare, se va intoarce o lista cu un singur element de forma ''("", "No viable alternative at character _, line _")'' (//Mai multe despre cazurile in care un lexer poate esua mai jos//). Astfel, metoda are ca output o lista de forma : ''[(TOKEN_LEXEMA_1, lexema1), (TOKEN_LEXEMA_2, lexema2), ...]'', unde ''TOKEN_LEXEMA_N'' este numele token-ului asociat lexemei n, pe baza specificatiei. | ||
+ | |||
+ | === Exemplu === | ||
+ | |||
+ | Fie specificatia urmatoare: | ||
+ | <code> | ||
+ | spec = [("TOKEN1", "abbc*"), ("TOKEN2", "ab+"), ("TOKEN3", "a*d")] | ||
+ | </code> | ||
+ | si input-ul ''abbd''. Analiza lexicala se va opri la caracterul ''d'' (AFD-ul descris anterior va ajunge pe acest caracter in sink state). Subsirul ''abb'' este cel mai lung care satisface atat ''TOKEN1'' cat si ''TOKEN2'', iar ''TOKEN1'' va fi raportat, intrucat il preceda pe ''TOKEN2'' In specificatie. Ulterior, lexerul va devansa cu un caracter pozitia curenta in input, si va identifica subsirul ''d'' ca fiind ''TOKEN3''. | ||
+ | |||
+ | Pentru lamuriri ulterioare si mai multe exemple ce includ cel mai lung subsir, revizitati cursul aferent lexerelor. | ||
+ | |||
+ | === Erori de lexare === | ||
+ | |||
+ | Erorile de lexare sunt in general cauzate o configuratie gresita / incompleta sau de un cuvant invalid. Informatiile care trebuie transmise in acest caz trebuie sa ajute programatorul sa isi dea seama unde un cod s-a intamplat eroarea si care este tipul erorii. Din acest motiv vom afisa linia si coloana unde lexarea a esuat si tipul erorii. Eroarea este echivalenta cu ajungerea in starea ''SINK_STATE'' a lexerului fara a trece in prealabil printr-o stare finala. In acest caz vom afisa un mesaj de eroare in formatul | ||
+ | |||
+ | No viable alternative at character ..., line ... | ||
+ | |||
+ | In primul loc liber vom pune indexul caracterului unde s-a oprit lexarea (am ajuns in ''SINK_STATE'') indexat de la 0, iar in an doilea spatiu liber vom pune linia unde s-a intamplat asta (indexata de la 0). | ||
+ | |||
+ | Daca lexarea a ajuns la finalul cuvantului fara a accepta in prealabil un lexem, iar lexerul nu a ajuns in sink state, insa nici intr-o stare finala, vom afisa un mesaj de eroare in formatul: | ||
+ | |||
+ | No viable alternative at character EOF, line ... | ||
+ | | ||
+ | Ca un mic rezumat: prima eroare apare atunci cand caracterul la care am ajuns este invalid si nu avem cum sa acceptam, iar a doua apare atunci cand lexerul ar mai accepta, insa cuvantul este incomplet si nu mai are ce. | ||
===== Testare ===== | ===== Testare ===== | ||
Line 90: | Line 230: | ||
Versiunea de python pe care o vom folosi pentru aceasta tema este ''python3.12''. Un ghid de instalare a acestei versiuni poate fi gasita [[https://aruljohn.com/blog/install-python/|aici]] | Versiunea de python pe care o vom folosi pentru aceasta tema este ''python3.12''. Un ghid de instalare a acestei versiuni poate fi gasita [[https://aruljohn.com/blog/install-python/|aici]] | ||
+ | |||
+ | <note important> | ||
+ | Este recomandat sa parcurgeti [[lfa:2023:lab_python_extras|documentul extra]] pentru descrierea unor feature-uri folosite in schelet si a unora utile in implementarea proiectului, mai ales topic-urile: | ||
+ | * [[lfa:2023:lab_python_extras#dictionaries_sets_and_hashable_objects | hashing]] | ||
+ | * [[lfa:2023:lab_python_extras#python_312_generics | genericitate in python 3.12]] | ||
+ | * [[lfa:2023:lab_python_extras#dataclasses | decoratorul dataclass]] | ||
+ | </note> | ||
Pentru rularea testelor folositi comanda ''python3.12 -m unittest''. | Pentru rularea testelor folositi comanda ''python3.12 -m unittest''. | ||
Line 103: | Line 250: | ||
├── __init__.py | ├── __init__.py | ||
├── NFA.py | ├── NFA.py | ||
+ | ├── Regex.py | ||
... (alte surse pe care le folositi) | ... (alte surse pe care le folositi) | ||
</code> | </code> |