Parser top-down Top-Down Parsing Il parse tree è creato dalla radice alle foglie. Il parser puo essere realizzato Recursive-Descent Parsing Il Backtracking è necessario (se una scelta di una regola non risulta quella corretta) È una tecnica di parsing generale ma di norma non viene utilizzata. Non è efficente Predictive Parsing no backtracking Efficente Recursive Predictive Parsing è una forma speciale di parser discendente ricorsivo senza backtracking. Sono necessarie forma particolari di grammatiche 1
Una grammatica left-recursive può portare a un loop infinito un parser discendente ricorsivo (anche con backtracking) Per poter applicare parsing predittivo (senza backtracking) in molti casi basta scrivere una grammatica con attenzione, eliminarne la leftrecursion e applicare il left-factoring Recursive-Descent Parsing (uso del backtracking) È necessario il Backtracking. Cerca di trovare una left-most derivation. S abc S S B bc b a B c a B c input: abc b c Fallisce e esegue un backtrack b 2
Predictive Parser (esempio) istr if... while... begin... for... Quando troviamo il non-terminale istr se il token è uguale ad if scegliamo la prima regola se il token è uguale ad while scegliamo la seconda regola etc Grammatiche LL(1) Un grammatica si dice LL(1) se Scorre la frase da sinistra (Left) a destra produce derivazioni Leftmost usa un solo simbolo di lookahead ad ogni passo del parsing Tutti i parser LL(1) operano in un tempo lineare utilizzando un spazio lineare. LL(k): LL(1) possono essere estese con l utilizzo di k simboli di look-ahead, in modo che attraverso l utilizzo di più simboli le grammatiche restino predittive.. 3
LL(1) Parser input buffer Stringa di cui effettuare l analisi sintattica. Assumeremo che essa termini con un simboli di fine stringa $. stack Contiene i simboli terminali del linguaggio Alla fine della pila c è il simbolo speciale di fine stringa $. Inizialmente lo stack coontiene il simbolo speciale $ e l assioma S. Quando lo stack è vuoto il parsing è completato. output Una produzione rappresenta un passo della sequenza di derivazioni (left-most derivation) della stringa in un input buffer parsing table È un array bidimenzionale M[A,a] Ogni riga contiene un simbolo non-terminalel Ogni colonna un simbolo terminale o $ Ogni entry contiene una produzione. Detti LL(1) Parser Parser Actions X Il simbolo in cima alla pila (nota che X {T V $ a il simbolo di ingresso Sono possibili quattro diverse azioni: 1. X = $ ansd a = $ parser termina (successful completion) 2. X = a a X $ pops() e a = nexttoken() 3. X V il parser esamina M[X,a]. 1. If M[X,a] contiene una produzione X Y 1 Y 2...Y k, 2. {pops(); 3. pushes Y k,y k-1,...,y 1 into the stack. 4. outputs la produzione X Y 1 Y 2...Y k per rappresentare un passo di derivazione. 4. non dei casi sopra error Tutte le caselle vuote della parser table corrispondono ad errori X T e X a, error 4
S aba LL(1) Parser Example1 B bb ε stack input output $S abba$ S aba $aba abba$ $ab bba$ B bb $abb bba$ $ab ba$ B bb $abb ba$ $ab a$ B ε $a a$ $ $ S a S aba b $ B B ε B bb LL(1) Parser Esempio1 Outputs: S aba B bb B bb B ε Derivation(left-most): S aba abba abbba abba S parse tree a B a b B b B ε 5
LL(1) Parser Example2 E TE E +TE ε T FT T *FT ε F (E) id id + * ( ) $ E E TE E TE E E +TE T T FT T FT T T ε T *FT E ε T ε E ε T ε F F id F (E) LL(1) Parser Example2 stack input output $E id+id$ E TE $E T id+id$ T FT $E T F id+id$ F id $ E T id id+id$ $ E T +id$ T ε $ E +id$ E +TE $ E T+ +id$ $ E T id$ T FT $ E T F id$ F id $ E T id id$ $ E T $ T ε $ E $ E ε $ $ accept 6
Costruzione della Parser Table LL(1) Abbiamo definito le funzioni FIRST FOLLOW FIRST(α) è l insieme dei simboli terminali che possono occupare la prima posizione nella stringa che si ottime dalla derivazione di α dove α è una stringa di simboli di T e V. Se α deriva ε, allora ε FIRST(α). FOLLOW(A) è l insieme dei simboli terminale che si trovano immediatamente dopo (follow) il non-terminale A nelle stringhe derivate dall assioma a FOLLOW(A) iff $ FOLLOW(A) iff S * αaaβ S * αa FIRST Example E TE E +TE ε T FT T *FT ε F (E) id FIRST(F) = {(,id FIRST(TE ) = {(,id FIRST(T ) = {*, ε FIRST(+TE ) = {+ FIRST(T) = {(,id FIRST(ε) = {ε FIRST(E ) = {+, ε FIRST(FT ) = {(,id FIRST(E) = {(,id FIRST(*FT ) = {* FIRST(ε) = {ε FIRST((E)) = {( FIRST(id) = {id 7
FOLLOW Example E TE E +TE ε T FT T *FT ε F (E) id FOLLOW(E) = { $, ) FOLLOW(E ) = { $, ) FOLLOW(T) = { +, ), $ FOLLOW(T ) = { +, ), $ FOLLOW(F) = {+, *, ), $ Algoritmo per la costruzione della Parser Table LL(1) A α della grammatica G a FIRST(α) aggiungere A α in M[A,a] If ε FIRST(α) a FOLLOW(A) aggiungere A α in M[A,a] If ε FIRST(α) e $ FOLLOW(A) Aggiungere A α in to M[A,$] 8
Definizione di Predict di una produzione E possibile definire un insieme detto insieme dei predict di una produzione come l insieme dei look-ahead tokens che indicano che una produzione deve essere applicata. Data una grammatica non contestuale G(V,T,P,S) si definisce Predict(P) il seguente insieme di terminali: Data una P: A α First(α) se non esiste α ε Predict(A α) = {First(α) Follow(A) se esiste α ε Esempio E TE FIRST(TE )={(,id E TE into M[E,(] and M[E,id] E +TE FIRST(+TE )={+ E +TE into M[E,+] E ε FIRST(ε)={ε none but since ε in FIRST(ε) and FOLLOW(E )={$,) E ε into M[E,$] and M[E,)] T FT FIRST(FT )={(,id T FT into M[T,(] and M[T,id] T *FT FIRST(*FT )={* T *FT into M[T,*] T ε FIRST(ε)={ε none but since ε in FIRST(ε) and FOLLOW(T )={$,),+ T ε into M[T,$], M[T,)] and M[T,+] F (E) FIRST((E) )={( F (E) into M[F,(] F id FIRST(id)={id F id into M[F,id] 9
T = {a, b, c V = {S, A, B P = { (1) S ABc Predict(1) = First(Abc) = {a, b,c (2) A a Predict(2) = First(a) = {a (3) A ε Predict(3) = Follow(A) = {b,c (4) B b Predict(4) = First(b) = {b (5) B ε Predict(5) = Follow(B) = {c S = S T = {a, b V = {S, A, B P = { 1. S Ab Predict(1) = First(Abc) = {a, b 2. A a Predict(2) = First(a) = {a 3. A B Predict(3) = First(B) = {b 4. A ε Predict(4) = Follow(A) = {b 5. B b Predict(5) = First(b) = {b 6. B ε Predict(6) = Follow(B) = {b S = S 10
Grammatiche LL(1) Una grammaticha che ha una parse table semza entry multiple è una grammatica LL(1). Condizione necessaria e sufficiente perché una grammatica sia LL e che l insieme dei predict delle produzioni che hanno la stessa parte sinistra sia disgiunto Ogni grammatica LL(1) è non ambigua Grammatiche non LL(1) Eliminazione ricorsioni sinistre Fattorizzazione 11
Grammatiche non LL(1) Una grammatica ricorsiva sinistra (left recursive) non pui essere LL(1) A Aα β x FIRST(β) anche x FIRST(Aα) poichè Aα βα. If β = ε, x FIRST(α) anche x FIRST(Aα) e x FOLLOW(A). Una grammatica non puo essere fattorizzata (not left factored) se A αβ 1 αβ 2 x FIRST(αβ 1 ) anche x FIRST(αβ 2 ). Una grammatica ambigua non può esssere LL(1). T = {a, b, c, d, e, V = {S, B, C P = {(1) S ase Predict (S ase) = First(aSe) = {a (2) S B Predict (S B) = First(B) = {b, c, d (3) B bbe Predict (B bbe) = First(bBe) = {b (4) B C Predict (B C) = First(C) = {c, d (5) C cce Predict (C cse) = First(cCe) = {c (6) C d Predict (C d) = First(d) = {d S = S 12
Esempio a + b * c Simboli Terminali: T = {a, b, c,..., z {+, -, *, $, (, ) Simboli Non-terminali: V = {E, T, F, P (E: espressione, T: termine, F: fattore, P: primary) Assioma: E Rules: E E + T T T T * F F F P $ F P P (E) -P a... z // left associative // left associative // right associative Nota che noi possiamo codificare le precedenze (P ha la massima precedenza, seguita da F, T e E) e le associatività nella grammatica. Esempio Predict (E E + T) = First(E + T) = First(T) = First(F)) = { (, -, a,. Predict (E T) = First(T)= First(F)) = { (, -, a,. Predict (T T * F) = First(T* F) = First(F)) = { (, -, a,. Predict (T F) = First(F) = { (, -, a,. Predict (F P $ F) = First(P $ F) = First(P) = { (, -, a,. Predict (F P) = First(P) = { (, -, a,. Predict (P (E) = First((E)) = {( Predict (P -P) =First(-P) = {- Predict (P a) = First(a) = {a 13
E E + T T // left associative T T * F F // left associative F P $ F P // right associative P (E) -P a... z E T E' E' + T E' ε T F T' T' * F T' ε F P $ F P P (E) - P a... z Parser LL(1) Discesa ricorsiva cablato sulla grammatica Parsetable codice indipendente dalla grammatica 14
L'implementazione di un parser TopDown a discesa ricorsiva è costituito da un insieme di procedure, una per ogni non terminale. Ogni procedura è responsabile dell'analisi di una sequenza di token derivabili dai non-terminali. Ad esempio, una procedura di analisi, A, quando invocata dovrebbe chiamare lo scanner e far corrispondere una sequenza di token derivabile da A. Partrndo dall assioma si dovrebbe far corrispondere l'intero input, che deve essere derivabile Questo approccio è chiamato a discesa ricorsiva perché le procedura di parsing sono in genere ricorsive Algoritmo per costruire una parsing table predittiva per una grammatica G 1. Per ogni produzione A α della grammatica, ripetere i passi 2 e 3 2. Per ogni terminale a in FIRST( α ) inserire nella tabella come elemento M[ A, a] la produz. A α 3. Se ε è in FIRST( α ) inserire nella tabella come elemento M[ A, b] la produz. A α per ogni terminale b che appartiene a FOLLOW( A) Se ε è in FIRST( α ) inserire nella tabella come elemento M[ A,$] la produz. A α se $ appartiene a FOLLOW( A) ogni entry viene contrassegnata con error 15
Recursive Predictive Parsing Ogni non-terminale corrisponde ad una produzione Esempio A abb proc A { 1. matching del token corrente con a 2. Leggi il token successivo 3. B; 4. Matching del token corrente con b Recursive Predictive Parsing (cont.) A abb bab proc A { case token corrente { a : { matching del token corrente con a Leggi il token successivo B; Matching del token corrente con b b : { matching del token corrente con b Leggi il token successivo A; B; 16
Recursive Predictive Parsing (cont.) A aa bb ε Se tutte le produzioni falliscono noi possiamo applicare produzioni ε. Se ad esempio il token non è ne a ne b, possiamo applicare la produzione ε. Noi dobbiamo applicare la produzione ε per il non-terminale A quando il token corrente è uno dei terminali che puo seguire A Recursive Predictive Parsing (Esempio) A abe A cbd A C B bb B ε C f boolean A(){ switch (symbol) { case = 'f' : return C(); case = 'a' : {symbol = scanner(); if (B()) if (symbol == 'e') { symbol = scanner(); return true; else return false; return false; case = 'c' : {symbol = scanner(); if (B()) if (symbol == 'd'){ symbol = scanner(); return true; else return false; return false; default : return false; 17
Recursive Predictive Parsing (Esempio) A abe cbd C B bb ε C f boolean B() { switch (symbol) { case = 'b': {symbol = scanner(); return B(); case = 'd' 'e': return true; default return false; Recursive Predictive Parsing (Esempio) A abe cbd C B bb ε C f boolean C() { switch (symbol) { case = 'f': {symbol = scanner(); return true; default : return false; 18
Grammatica LL(1) S A a {b,d,a A B D {b, d, a B b { b B ε {d, a D d { d D ε { a boolean S(){ switch (symbol) { symbol in {b,d,a : { if (A()) { symbol = scanner(); if (symbol == 'a') return true; else return false; else return false; default : return false; boolean A(){ switch (symbol) { symbol in {b,d,a : { if (B()) if (D()) return true; else return false; else return false; default : return false; 19
boolean B() { switch (symbol) { symbol = 'b' : {symbol = scanner(); return true; symbol in {d, a : return true; default :return false; boolean D() { switch (symbol) { symbol = 'd' : {symbol = scanner(); return true; symbol = 'a' : return true; default :return false; Prog { Stmts Eof { { Stmts Stmt Stmts {id,if Stmts ε { Stmt id = Expr ; { id Stmt if ( Expr ) Stmt { if Expr id Etail { id Etail + Expr { + Etail -Expr - { - Etail ε { ), ; 20
Parser LL(1) Data la seguente grammatica : E T E' E' + T E' ε T F T' T' * F T' ε F P $ F P P (E) - P a... z si possono scrivere un insieme di procedure mutuamente ricorsive, una per ogni non terminale, che consentono di analizzare una frase e segnalare gli errori. Esempio T = {+, *, -, ^, (, ), a,..., z V = {E, E', T, T', F, P P = { 1. E T E' 2. E' + T E' 3. E' ε 4. T F T' 5. T' * F T' 6. T' ε 7. F P $ F 8. F ε 9. P (E) 10. P -P 11. P a b... z S = E 21
Una grammatiche che non è LL(1) S i C t S E a FOLLOW(S) = { $,e E e S ε FOLLOW(E) = { $,e C b FOLLOW(C) = { t FIRST(iCtSE) = {i FIRST(a) = {a FIRST(eS) = {e FIRST(ε) = {ε FIRST(b) = {b S E C a S a b C b e E e S E ε i S ictse t $ E ε 22
Svantaggi dei parser discendenti ricorsivi Sono castomizzati rispetto alla grammatica Utilizzo della ricorsione che non è efficiente Algoritmo di parsing LL(1) public boolen parser (){ Tabella ParseTable; Pila P; Produzione Pro; Simbolo C; P.push(S); C=scanner(); While (!p.empty()&&c!= $ ){ X = P.pop(); if (x == C) C = scanner() else {(if C.terminale) return false Pro = ParseTable.get(C,X); if (Pro.vuota()) return false p.push(pro); if (p.empty()&&c= $ ) return true else return false 23