Etapa 2 - Transformarea Regex-urilor la AFD-uri

Update legat de upload: incarcati o arhiva ce contine atat fisierele sursa, cat si directorul de testare tests/ si script-ul checker.py.

Checker si teste: Checker proiect LFA.

Transformarea Regex-AFD ne permite operationalizarea expresiilor regulate, iar aceasta este o parte importanta atat dintr-un lexer cat si dintr-un parser.

In etapa 2, vom implementa aceasta transformare in doi pasi:

  • transformarea RegexAFN
  • transformarea AFNAFD

Implementarea transformarilor se bazeaza integral pe procedurile algoritmice prezentate la curs.

Pentru a realiza prima transformare, este necesar sa parsam o expresie regulata si sa construim arborele acesteia. 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 finala. 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 o expresie regulata in forma prenex.
  • Exemple:
UNION a b
UNION CONCAT a b STAR c
CONCAT UNION a b UNION c d
CONCAT STAR UNION a b UNION b c
STAR UNION CONCAT a b CONCAT b STAR d
CONCAT PLUS c UNION a PLUS b 

Implementarea voastra va primi un fisier de input <testxy.in> si un fisier de output <testxy.out>, va construi AFD-ul asociat expresiei regulate, iar apoi va afisa automatul in fisierul de output conform formatului de mai jos:

<alfabet>
<numar_stari>
<stare_initiala>
<lista_stari_finale>
<tranzitie_1>
<tranzitie_2>
...
<tranzitie_n>

Atentie: Implementarea va fi punctata doar in masura in care respecta cerinta (se construieste un AFD folosind algoritmii prezentati la curs).

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 cream o noua stare initiala, si una finala
  • starile AFN-ului pentru 0 trebuie sa fie disjuncte fata de cele ale lui 1
  • 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.

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.

Pentru a testa AFD-urile generate de voi, veti avea nevoie de o modalitate de afisare a acestora in text. Va sugeram sa adaugati la aceasta si o modalitate grafica. Atasam un script Python3 (draw_fa.zip) care poate fi folosit pentru a desena AFD-uri:

  • script-ul primeste in linia de comanda un fisier CSV ce contine un AFD, si deseneaza AFD-ul respectiv.
  • va fi necesar sa instalati anumite module pentru a rula scriptul (e.g. networkx, numpy).
  • trebuie sa scrieti o procedura de afisare a unui AFD sub forma de CSV. Atasam un exemplu (AFD-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 AFD
  • tranzitiile sunt codificate pe linii, sub forma: stare initiala, caracter, stare finala
  • pentru legibilitate, starile finale au fost precedate de simbolul f
  • Folositi clase pentru reprezentarea interna a unei expresii regulate. Vom refolosi aceasta reprezentare in etapa 3.
  • 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.
  • In desfasurarea procesului de constructie al regex-urilor, nu toate expresiile sunt disponibile. Aveti mai multe alternative pentru a construi expresii mai complicate, atunci cand parti (continute) ale acestora nu sunt cunoscute inca:
    • 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, definiti LConcat :: 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.
    • adaugati un constructor pentru expresia regulata Unknown care codifica o expresie ce urmeaza sa fie calculata

———————————————————–

  • Interactiunea cu stiva se face cel mai natural folosind pattern matching (si este mult mai simplu de implementat in Haskell).
  • Pentru codificarea de AFN-uri:
    • Adaugati clasei FA, definita la etapa anterioara, metoda relabel (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 functiei relabel?
  • 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]}

Odata generate AFD-urile de catre implementarea voastra si afisate in cadrul fisierlor de output, checker-ul etapei 2 compara rezultatele fata de o suita de AFD-uri de referinta, verificand pentru fiecare caz in parte daca automatele accepta acelasi limbaj. Mai multe detalii legate de checker gasiti pe pagina Checker proiect LFA.