Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
lfa:2022:proiect_etapa2 [2022/11/19 13:46] alex.ilie |
lfa:2022:proiect_etapa2 [2022/11/27 13:43] (current) alex.ilie |
||
---|---|---|---|
Line 7: | Line 7: | ||
====== Proiect ====== | ====== Proiect ====== | ||
- | Proiectul consta in implementarea unui lexer in python sau scala. | + | /* Proiectul consta in implementarea unui lexer in python sau scala. */ |
+ | /* | ||
===== Ce este un 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. | 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. | ||
Line 30: | Line 31: | ||
Lexer-ul are ca output o lista de forma : ''[(lexema1, TOKEN_LEXEMA_1), (lexema2, TOKEN_LEXEMA_2), ...]'', unde ''TOKEN_LEXEMA_N'' este numele token-ului asociat lexemei n, pe baza specificatiei. | Lexer-ul are ca output o lista de forma : ''[(lexema1, TOKEN_LEXEMA_1), (lexema2, TOKEN_LEXEMA_2), ...]'', unde ''TOKEN_LEXEMA_N'' este numele token-ului asociat lexemei n, pe baza specificatiei. | ||
- | ===== Etapa 2 ===== | + | */ |
- | Datorita dificultatii lucrului direct cu regex-uri pentru verificarea apartenentei unui cuvant in limbaj, lexerele reale trec prin cateva etape intermediare inainte de inceperea analizei textului. | + | |
- | **Etapa 2** consta in conversia unui Regex in forma prenex. | + | ===== Etapa 2 ===== |
+ | /* | ||
+ | Datorita dificultatii lucrului direct cu regex-uri pentru verificarea apartenentei unui cuvant in limbaj, lexerele reale trec prin cateva etape intermediare inainte de inceperea analizei textului. */ | ||
- | Forma prenex a fost prezentata in etapa precedenta. In aceasta etapa ne vom ocupa de parsarea Regexurilor scrise in forma prezentata la curs. | + | **Etapa 2** consta in **parsarea** unei expresii regulate (regex) scrisa in maniera conventionala, si conversia acesteia in forma prenex. ((Forma prenex, intermediara in proiectul nostru intre cea conventionala si arborele de parsare (AST) construit de programul vostru, nu este standard pentru o astfel de implementare. De altfel, ea nici nu este folosita in mod curent. Insa cum limbajul expresiilor regulate valide este independent de context (din cauza parantezelor), este dificil de implementat parsarea regexurilor fara cunostinte despre gramatici si in special APD-uri. Tocmai de aceea aceasta etapa de parsare, care in mod natural ar fi prima, a fost amanata. Acest lucru a fost posibil introducand forma prenex care nu contine paranteze.)) |
==== Forma standard a expresiilor regulate ==== | ==== 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 trei 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 | ||
+ | |||
+ | Aceste operatii noi nu contribuie la expresivitatea regex-urilor, insa ajuta foarte mult utilizatorii sa scrie regex-uri compacte si usor de citit. In implementarea voastra, este recomandat sa //preprocesati// regexurile, adica sa eliminati operatorii nou-introdusi si sa ii inlocuiti cu cei standard. Operatorii standard sunt cei prezentati la curs (concatenare, reuniune si star). | ||
+ | |||
+ | Spre exemplu: $math[e+ = ee*] sau $ [0-9] = 0 \cup 1 \cup 2 \cup \ldots \cup 9 $. | ||
+ | |||
+ | In felul acesta, AST-ul va avea un numar minimal de **tipuri** de noduri, iar algoritmul Thompson cat mai putine cazuri diferite de tratat. | ||
+ | |||
+ | ===== Caractere de control sau obisnuite? ===== | ||
+ | |||
+ | O problema care a aparut deja inclusiv la etapa 1 are legatura cu rolul caracterelor intr-un regex. Caracterele pot fi //de control// (precum ''()*|'' dar si //whitespace//) sau obisnuite. Insa dorim sa folosim caractere de control si cu rolul de caractere obisnuite. In acest caz, acestea trebuie intotdeauna //escapate// folosind ghilimele. Spre exemplu, nu putem folosi spatii albe intr-un regex decat //escapat// - ''' '''. | ||
+ | |||
+ | In acelasi timp, in cadrul parsarii, este important sa stim rolul pe care il are un caracter citit (de control sau obisnuit). Pentru a reprezenta aceasta diferenta in Python, aveti in schelet doua clase: ''Character'', ''Operator''. In Scala, puteti folosi tipul de date ''Either[A,B]'' avand constructorii ''Left(v:A)'' si ''Right(v:B)''. | ||
+ | |||
+ | ===== Precedenta ===== | ||
Avantajul formei Prenex este ca folosirea parantezelor nu mai este necesara pentru a specifica prioritatea operatiilor. In forma standard trebuie insa sa avem grija la prioritatea operatiilor pentru a evalua corect o expresie regulata. | Avantajul formei Prenex este ca folosirea parantezelor nu mai este necesara pentru a specifica prioritatea operatiilor. In forma standard trebuie insa sa avem grija la prioritatea operatiilor pentru a evalua corect o expresie regulata. | ||
- | Facem o scurta analogie cu ordinea operatiilor aritmetice: + si -, * si /, respectiv paranteze pentru a intelege mai usor ordinea operatiilor din Regex-uri. | ||
- | Prioritatea operatiilor aritmetice este: paranteze (), inmultiri sau impartiri (* sau /), adunari sau scaderi (+ sau -). Astfel, expresia a + b*c - (d + e*f)/g se va transforma in urmatorul AST: | + | Facem o scurta analogie cu ordinea operatiilor aritmetice: ''+'' si ''-'', ''*'' si ''/'', respectiv paranteze pentru a intelege mai usor ordinea operatiilor din Regex-uri. |
+ | |||
+ | Prioritatea operatiilor aritmetice este: paranteze ''()'', inmultiri sau impartiri (''*'' sau ''/''), adunari sau scaderi (''+'' sau ''-''). Astfel, expresia ''a + b*c - (d + e*f)/g'' se va transforma in urmatorul AST: | ||
{{:lfa:2022:ast.png?500}} | {{:lfa:2022:ast.png?500}} | ||
Line 49: | Line 95: | ||
Putem observa ca paranteza se evalueaza inaintea inmultirilor, iar inmultirile inaintea adunarilor. | Putem observa ca paranteza se evalueaza inaintea inmultirilor, iar inmultirile inaintea adunarilor. | ||
- | Similar, prioritatea pentru Regex-uri este paranteze (), star *, concat (nu are un simbol asociat) si union |. | + | Similar, prioritatea pentru Regex-uri este paranteze ''()'', star ''*'', concat (nu are un simbol asociat) si union ''|''. |
- | De exemplu (a|b)*c|d, care va genera arborele: | + | De exemplu ''(a|b)*c|d'', care va genera arborele: |
{{:lfa:2022:ast2.png?350}} | {{:lfa:2022:ast2.png?350}} | ||
- | Din arbore putem genera usor forma prenex: UNION CONCAT STAR UNION a b c d. | + | Din arbore putem genera usor forma prenex: ''UNION CONCAT STAR UNION a b c d''. |
- | Nu este necesar sa implementati per se AST-ul, insa prin ierarhia de clase acesta este intrisec. | + | |
===== Implementare ===== | ===== Implementare ===== | ||
- | Implementarea consta in parsarea unui Regex si transformarea sa in forma Prenex. Pentru acest lucru se recomanda folosirea unui AST - Abstract Syntax Tree. | + | Implementarea consta in parsarea unui Regex si transformarea sa in forma Prenex. Pentru acest lucru se recomanda folosirea unui AST - Abstract Syntax Tree. Puteti folosi exact AST-ul implementat la Etapa 1, adaugand o metoda de afisare (eventual chiar ''toString'') pentru a obtine forma prenex). |
===== Parsarea expresiilor prenex ===== | ===== Parsarea expresiilor prenex ===== | ||
Pentru a parsa un Regex, avem nevoie de o stiva care sa retina parti ale expresiei / operatii parsate deja. Vom interactiona in doua feluri cu stiva: | Pentru a parsa un Regex, avem nevoie de o stiva care sa retina parti ale expresiei / operatii parsate deja. Vom interactiona in doua feluri cu stiva: | ||
- | |||
* reducerea expresiilor (sau cooling): | * reducerea expresiilor (sau cooling): | ||
* Exemplul 1: daca pe stiva avem: ''0 | Star(?) | …'' , atunci vom inlocui cele doua expresii cu : ''Star(0) | …'' | * Exemplul 1: daca pe stiva avem: ''0 | Star(?) | …'' , atunci vom inlocui cele doua expresii cu : ''Star(0) | …'' | ||
Line 74: | Line 118: | ||
Implementarea voastra trebuie sa combine in mod eficient adaugarea cu reducerea. | Implementarea voastra trebuie sa combine in mod eficient adaugarea cu reducerea. | ||
- | |||
- | ===== Preprocesarea Regex-urilor ===== | ||
- | |||
- | Pe langa caracterele alfa-numerice si operatiile de baza star, concat si union, in aceasta etapa vom testa 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 | ||
- | - escaparea anumitor caracter de tipul '\n', '\t'. In general 'c', unde c este un caracter va fi interpretat drept c. | ||
- | |||
- | Pentru a facilita diferenta intre caractere si operatori aveti in schelet doua clase (Character, Operator in Python, respectiv Left, Right in Scala). | ||
- | |||
- | Operatiile + si ? pot fi transformate direct in echivalentul lor. De exemplu a+ va fi transformat in aa*, iar a? in a|eps. | ||
===== Testare ===== | ===== Testare ===== | ||
Testarea este similara cu cea de la etapa 1. Mai mult, este necesara implementarea intregii etape 1, deoarece vom testa comportamentul corect al DFA-ului construit si nu rezultatul transformarii. | Testarea este similara cu cea de la etapa 1. Mai mult, este necesara implementarea intregii etape 1, deoarece vom testa comportamentul corect al DFA-ului construit si nu rezultatul transformarii. | ||
- | Astfel, la etapa 2 veti obtine o forma prenex dintr-un Regex: Regex -> Prenex, pe care o sa il dam mai departe in transformarea facuta la etapa 1 Prenex -> NFA -> DFA (-> MinDFA eventual). La final testam daca DFA-ul construit accepta/respinge un set de cuvinte. | + | Astfel, la etapa 2 veti obtine o forma prenex dintr-un Regex: Regex -> Prenex, pe care o sa il dam mai departe in transformarea facuta la etapa 1 Prenex -> NFA -> DFA (-> MinDFA eventual). La final testam daca DFA-ul construit accepta/respinge un set de cuvinte. |
Verificarea corectitudinii implementarii voastre se va face automat, printr-o serie de teste unitare, o parte punctate si o parte nepunctate. | Verificarea corectitudinii implementarii voastre se va face automat, printr-o serie de teste unitare, o parte punctate si o parte nepunctate. |