This is an old revision of the document!
Proiect LFA - Lexer in Python sau Haskell
Etapa 4 - Parsare Regex si limbaj
Serializare lexer (ca sa ne legam direct cu etapa 1).
Apoi parsare. Doua programe.
Limbajul 1: Imperative
<prog> ::= <variable> '=' <expr>; | begin <instruction_list> end | while (<expr>) do <prog> od | if (<expr>) then <prog> else <prog> fi <instruction_list> ::= [<prog>] <expr> ::= <expr> + <expr> | <expr> > <expr> | <expr> == <expr> | <variable> | <integer>
Limbajul 2: Lisp
Limbajul 3: Html ???
Metodologia de testare:
Combinam expresii din fisierul de referinta in specificatii. Mai sunt necesare detalii de clarificat.
Etapa 3 - Conversie AFN AFD
Metodologia de testare:
Asemanator cu etapa 2. Folosim acelasi fisier de input, insa generam, in locul expresiilor PRENEX, nfa-uri, din lista de referinta.
Etapa 2 - Conversie Regex AS la AFN
Obiectivul etapei 2 este conversia unei expresii regulate la un Automat Finit Nedeterminist. Aceasta transformare este un prim pas in conversia unei specificatii (expresia regulata) intr-o implementare (AFN).
Descrierea inputului
- un prim pas in aceasta etapa este parsarea expresiilor regulate, si construirea unui arbore pentru acestea. Spre exemplu, expresia:
ab|c*
are asociat arborele:UNION(CONCAT(a,b),STAR(c))
. Datorita regulilor de precenta ale operatorilor, construirea acestui arbore este mai complicata, iar aceast task va fi amanat pentru etapa 4. Pentru a simplifica problema parsarii, expresiile vor fi prezentate in forma prenex. - In forma prenex, operatorul preceda intotdeauna operanzii. Prezentate astfel, expresiile pot fi parsate mult mai usor. Spre exemplu, expresia de mai sus in forma prenex este
UNION CONCAT a b STAR c
. Observati similaritatea cu reprezentarea de arbore. - Un test consta intr-un fisier
.in
ce contine:- pe prima linie, o expresie regulata in forma prenex
- in continuare, cate un cuvant pe o linie
- Exemplu:
UNION CONCAT a b STAR c abc abcccc ccc ab
Cerinta si descrierea outputului
Implementarea voastra va primi un fisier de test <testxy.in>
, va construi AFN-ul asociat expresiei regulate, apoi va verifica daca acesta accepta cuvintele din fisierul de test. Implementarea va scrie la output un fisier <testxy.out>
ce va contine, pe cate o linie ACCEPT
sau REJECT
, pentru fiecare din cuvintele de la input. Exemplu de fisier de output pentru input-ul anterior:
REJECT REJECT ACCEPT ACCEPT
Atentie: Implementarea va fi punctata doar in masura in care respecta cerinta (se construieste un AFN folosind algoritmul prezentat la curs).
Identitatea starilor
Implementarea algoritmului lui Thomson este complicata de faptul ca starile sunt reprezentate ca intregi. Spre exemplu, AFN-urile pentru expresiile 0
si 1
vor avea fiecare cate doua stari, cel mai probabil numerotate de la 0 la 1. Pentru a construi AFN-ul pentru 0|1
, trebuie sa renumerotam starile tinand cont de:
- faptul ca trebuie sa creem o noua stare initiala, si una finala
- starile AFN-ului pentru
0
trebuie sa fie disjuncte fata de cele ale lui1
- logica AFN-urilor (ce cuvinte accepta) nu trebuie sa influentata de numerotare
- renumerotarea trebuie sa fie suficient de generala (vom mai avea nevoie de ea la etapa 3), astfel incat sa se comporte ca un map (o aplicatie de functie peste stari). In aceasta etapa, functia va fi
+x
.
Parsarea formei Prenex
Pentru a parsa forma Prenex, 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).
- Exemplul 1: daca pe stiva avem:
0 | Star(?) | …
, atunci vom inlocui cele doua expresii cu :Star(0) | …
- Exemplul 2: daca pe stiva avem:
Star(0) | Concat(?,?) | …
, rezultatul reducerii va fi:Concat(Star(0),?) | …
- adaugarea expresiilor: vom citi operatorul sau operandul curent, si vom adauga elementele corespunzatoare pe stiva.
Implementarea voastra trebuie sa combine in mod eficient adaugarea cu reducerea.
Developlement si testare
Pentru a testa AFN-urile generate de voi, aveti nevoie de o modalitate de afisare a acestora in text. Va sugeram sa ii adaugati si modalitate grafica. Atasam un script Python3 care poate fi folosit pentru a desena AFN-uri:
- script-ul primeste in linia de comanda un fisier CSV ce contine un AFN, si deseneaza AFN-ul respectiv.
- va fi necesar sa instalati anumite dependinte pentru a rula scriptul (e.g. networkx, numpy).
- trebuie sa scrieti o procedura de afisare a unui AFN sub forma de CSV. Atasam un exemplu (AFN-ul generat pentru expresia
00*
):
from,char,to 0,0,1 1,ε,2 2,ε,3 2,ε,f5 3,0,4 4,ε,3 4,ε,f5
- prima linie este obligatorie si aceeasi pt orice AFN
- tranzitiile sunt codificate pe linii, sub forma:
stare initiala, caracter, stare finala
- pentru legibilitate, starile finale au fost precedate de simbolul
f
- [TODO: link catre script pe github-ul LFA]
Sugestii de implementare pentru Python
- Folositi clase pentru reprezentarea interna a unei expresii regulate. Vom refolosi aceasta reprezentare in etapa 4.
- Alegeti o implementare pentru AFN-uri care sa permita refolosirea (extinderea) ei pentru situatii in care starile au alta structura. (multimi de intregi in loc de intregi).
- In procesul de parsare, instantiati cu
None
expresii a caror parsare este in curs de desfasurare. Ele vor fi modificate ulterior.
Sugestii de implementare pentru Haskell
- In tipul vostru de date care va retine expresii, adaugati constructori pentru expresii a caror parsare este in curs de desfasurare. Acestia vor fi folositi doar in timpul parsarii formei prenex. Spre exemplu, pe langa
Concat :: Expr → Expr → Expr
, definitiLConcat :: Expr → Expr
. Acesta va codifica faptul ca am citit doar partea din stanga a unei concatenari. Asemanator,LRConcat :: Expr
va codifica faptul ca am citit o concatenare si urmeaza sa citim operanzii acesteia. - Interactiunea cu stiva se face cel mai natural folosind pattern matching (si este mult mai simplu de implementat in Haskell).
- Pentru codificarea de NFA-uri:
- Adaugati clasei
FA
, definita la etapa anterioara, metodarelabel
(si implementati-o):
class FA t where fromList :: State s => Set Char -> s -> [(s,Char,s)] -> [s] -> t s states :: (State s) => t s -> [s] relabel :: (State s, State s') => (s -> s') -> t s -> t s' size :: (State s) => t s -> Integer
- Bonus: de ce nu am folosit
fmap
, in locul functieirelabel
?
- Definiti tipul
Nfa a
si inrolati-l in clasa anterioara. O definitie (mai multe sunt posibile) este urmatoarea:
type NDelta a = Map (a,Char) (Set a) data Nfa a = Nfa {nsigma :: Set Char, ninitial :: a, ndelta :: (NDelta a), nfin :: [a]}
Metodologia de testare:
- construim un fisier mare cu expresii regulate de mana, peste diverse alfabete, care sa acopere cat mai multe din toate situatiile posibile, impreuna cu cuvinte acceptate sau nu. Acest fisier va trebui discutat eventual cu fiecare dintre noi. Exemplu:
0 ACCEPT 0 REJECT 01 REJECT 10 00* ACCEPT 0000000000 REJECT 1 REJECT ε
- parsam expresiile cu implementarea noastra, generam forma prenex, si creem mai multe perechi de fisiere separate, input-output - in fiecare fisier input se va gasi o expresie si cuvintele de test. Ficare fisier de output va marca rezultatul asteptat. Exemplu:
- Test0.in
0 0 01 10
- Test0.out
ACCEPT REJECT REJECT
- Test1.in
CONCAT 0 STAR 0 0000000000 1 ε
- Test1.out
ACCEPT REJECT REJECT
- Vom verifica, fiecare, ca outputul este corect, folosind implementarile noastre.