Contenuti del corso Parsing: esempio Data una grammatica: Continuazione del corso di linguaggi. E T F • Nuovi paradigmi di computazione • logico: Prolog • logic - funzionale: Curry • Teoria base dei compilatori • parsing • grammatiche ad attributi • type checking • generazione codice intermedio • Descrizione formale comportamento dei linguaggi dichiarativi • paradigma funzionale: sistemi di riscrittura, lambda calcolo, pattern matching • paradigma logico: unificazione, SLD-derivations • paradigma logico funzionale: narrowing e una stringa: id + (id ∗ id) Costruire l’albero di derivazione per la stinga E T + E F id 24 + 48 ore. P. Di Gianantonio (Udine) T F id Parsing T | T +E F | F ∗T id | (E ) → → → 1 / 77 P. Di Gianantonio (Udine) ∗ T F Parsing 2 / 77 id Parsing Cosa aggiungiamo in questo corso Stadio compilazione di un programma: Descrizione dei metodi, algoritmi effettivamente utilizzati per il parsing. • analisi lessicale: riconoscimento token, linguaggi regolari, lexer, lex • Linguaggi: strumenti software senza specificare i metodi utilizzati (come invece fatto per l’analisi lessicale). • analisi sintattica: costruzione dell’albero di derivazione, linguaggi liberi da contesto, parser, yacc bison • Fondamenti: algoritmi poco efficienti. • generazione codice intermedio Nel dettaglio: • ... • presenteremo vari algoritmi di parsing; Argomento gia` visto in altri corsi: • teoria, Fondamenti dell’Informatica: linguaggi liberi da contesto, automi a pila non deterministici, pumping lemma, teoremi di ` decidibilita; • diverso grado di generalita` - efficienza; • pratica, Linguaggi: produzioni e regole semantiche in strumenti software. • algoritmi utilizzati negli strumenti software. P. Di Gianantonio (Udine) Parsing • diverse sottoinsiemi dei linguaggi liberi da contesto che possono essere analizzati in maniera efficiente, tempo lineare; 3 / 77 P. Di Gianantonio (Udine) Parsing 4 / 77 Vari algoritmi di parsing Parsing top-down non-deterministico Costruisco l’albero di parser: • Parser top-down: costruiscono l’albero di derivazione a partire dalla radice • non deterministici, con backtracking: generali poco efficienti • predittivi: possibili solo per certi linguaggi • Parser bottom-up: albero di derivazione a partire dalle foglie: deterministici piu` complessi dei precedenti ma anche piu` generali (possibili per un insieme piu ampio di linguaggi), usati nella pratica. • a partire dalla radice, simbolo iniziale, • espando sempre il non-terminale piu` a sinistra • uso in maniera parallela tutte le produzioni • uso le produzioni in un certo ordine, faccio backtracking in caso di fallimento • se creo un terminali a sinistra, controllo che questi coincidano con quelli presenti nella stringa, se no fallimento. Stesse idee degli automi a pila non-deterministici. P. Di Gianantonio (Udine) Parsing 5 / 77 P. Di Gianantonio (Udine) Parsing Esempio Esempio Grammatica: Grammatica: E T F → → → E T F T | T +E F | F ∗T id | (E ) → → → T | T +E F | F ∗T id | (E ) Stringa: Stringa: (id + id) id ∗ id P. Di Gianantonio (Udine) 6 / 77 Parsing 7 / 77 P. Di Gianantonio (Udine) Parsing 7 / 77 Pseudo-codice Pseudo-codice Per ogni non-terminale A definiamo: function parse_A () { choose a production parse_X1 (); . . parse_Xn (); } Programma: A -> X1 ... Xn parse () { parse_S (); match (eof) } Come implemento l’istruzione Per ogni terminale t definiamo choose a production function parse_t () { x = read_token (); if (x=t) then return () else error } P. Di Gianantonio (Udine) A -> X1 ... Xn • Esecuzione parallela: automi a pila non deterministici • Backtracking: puo` portare a tempi esponenziali. Parsing 8 / 77 P. Di Gianantonio (Udine) Parsing Trasformazioni di grammatiche 9 / 77 Ricorsione sinistra Il parser con backtracking e` sensibile al modo in cui e` definita la grammatica del linguaggio Una grammatica e` ricorsiva a sinistra se esiste una derivazione del tipo • lo stesso linguaggi definito da grammatiche diverse A →+ A α • per grammatiche ambigue il parsing non ha soluzione unica Esempio: • con alcune grammatiche il parsing puo` non terminare (ricorsione sinistra) E T F • modificando la grammatica si riducono le scelte da svolgere (left factoring) Vedremo come trasformare la grammatiche di un linguaggio per ottenere un parser-top down predittivo, nessuna scelta, complessita` lineare (non piu` potenzialmente esponenziale) P. Di Gianantonio (Udine) Parsing 10 / 77 → → → T | T +E F | F ∗T id | (E ) non ricorsiva a sinistra P. Di Gianantonio (Udine) Parsing 11 / 77 Ricorsione sinistra Problemi ricorsione sinistra Una grammatica e` ricorsiva a sinistra se esiste una derivazione del tipo A →+ A α L’algoritmo di parsing (con backtracking) puo` divergere se la grammatica ha ricorsione sinistra. Esempio: E T F → → → E+T T | F | T ∗F id | (E ) Esempio: id + id ricorsiva a sinistra Le due grammatiche associano + e ∗ in maniera differente. Non sono esattamente equivalenti. P. Di Gianantonio (Udine) Parsing 11 / 77 P. Di Gianantonio (Udine) Parsing Eliminare la ricorsione immediata a sinistra 12 / 77 Esempio La grammatica ricorsiva a sinistra Caso base se A ha produzioni A → Aα | β Viene aggiunto un non terminale A 0 e le produzioni sostituite da A A0 → → βA 0 αA 0 | Parsing → → → T | E+T F | T ∗F id | (E ) E E0 T T0 F → → → → → T E0 + T E0 | F T0 ∗ F T0 | id | (E ) diventa Entrambe le grammatiche da A derivano βα∗ P. Di Gianantonio (Udine) E T F 13 / 77 P. Di Gianantonio (Udine) Parsing 14 / 77 Ricorsione sinistra immediata Eliminazione ricorsione sinistra In generale, se le produzioni per A sono: L’algoritmo descritto nel dragon-book. Si definisce un ordine lineare ≤ tra i non terminali e in maniera incrementale si riscrivono le produzioni A → A α1 | . . . | A αm | β1 | . . . | βn Viene aggiunto un non-terminale A 0 e le produzioni sostituite da A A0 → → A → Bβ β1 A 0 | . . . | βn A 0 α1 A 0 | . . . | αm A 0 | P. Di Gianantonio (Udine) Parsing se B ≤ A 15 / 77 P. Di Gianantonio (Udine) Parsing Parsing top-down predittivo 16 / 77 Fattorizzazione sinistra Si elimina il non-determinismo del parser top-dow non deterministico. Con un lookahead di n caratteri (osservando gli n caratteri successivi della stringa non ancora analizzata) si determina la produzione top-down da utilizzare. Non tutte le grammatiche ammettono un parser top-down predittivo, con lookahead di n simboli. LL(n) e` classe di grammatiche che lo ammettono Per un parsing predittivo produzioni nella forma A → αβ1 | αβ2 | γ risultano problematiche, se α produce una stringa di m terminali devo avere un lookahead di almeno m + 1 caratteri per decidere la produzione da utilizzare. Possono essere eliminate riscrivendo le produzioni: • esamina la stringa Left to right • si cerca la Leftmost derivation, a partire dal simbolo iniziale. A A0 • si usano n simboli di lookahead. → → αA 0 | γ β1 | β2 si ha che: LL (1) ⊂ LL (2) ⊂ . . . ⊂ grammatichelibere Considereremo principalmente le grammatiche LL(1). P. Di Gianantonio (Udine) Parsing 17 / 77 P. Di Gianantonio (Udine) Parsing 18 / 77 Esempio Funzione FIRST Un parser predittivo deve scegliere tra le produzioni di un non-terminale La grammatica per le espressioni aritmetiche: E T F viene riscritta come: E E0 T T0 F A → α1 | . . . | αn T | T +E F | F ∗T id | (E ) → → → → → → → → e` utile sapere con quali terminali iniziano le stringhe derivate da αi . FIRST(a ) FIRST(A ) FIRST() FIRST(X1 . . . Xn ) FIRST(X1 . . . Xn ) TE 0 | +E FT 0 | ∗T id | (E ) ::= ::= ::= ::= ::= {a } S a terminale A non-terminale A {} stringa vuota FIRST(X1 ) < FIRST(X1 ) FIRST(X1 ) ∪ FIRST(X2 . . . Xn ) ∈ FIRST(X1 ) → α FIRST(α) Definizione ricorsiva, soluzione minima. P. Di Gianantonio (Udine) Parsing 19 / 77 P. Di Gianantonio (Udine) Funzione FOLLOW I piu` piccoli insiemi tali che: eof ∈ FOLLOW(S ) S FOLLOW(A ) ::= B S simbolo iniziale FIRST(β) → αA γ FOLLOW(B ) ∈ FIRST(γ) S→ αA β ossia l’insieme FOLLOW(A ) formato dai terminali b per cui esiste una derivazione S →∗ βAb γ, ∪ B −{} possono calcolati in maniera incrementale assegnando, inizialmente, per ogni non-terminale B FOLLOW(B ) = ∅ e aggiungendo in seguito nuovi elementi in base alle produzioni. Infatti, se b ∈ FOLLOW(A ) ed in un parsing devo espandere A con un lookahead b, allora la produzione A → α e` un possibile candidato. Parsing 20 / 77 Definizione ricorsiva di FOLLOW Nel caso A → α, ∈ FIRST(α), e` utile conoscere quali terminali possono seguire A in una derivazione, P. Di Gianantonio (Udine) Parsing 21 / 77 P. Di Gianantonio (Udine) Parsing 22 / 77 Esempio: Definizione di grammatiche LL(1) La grammatica E E0 T T0 F → → → → → T E0 + T E0 | F T0 ∗ F T0 | id | (E ) Una grammatica G e` LL(1) se per ogni coppia di produzioni A → α | β FIRST(α) ∩ FIRST(β) = ∅ porta a: ∈ FIRST(α) ⇒ FIRST(β) ∩ FOLLOW(A ) = ∅ FIRST(E ) = FIRST(T ) = FIRST(F ) = { (, id } FIRST(E 0 ) = { +, } FIRST(T 0 ) = { ∗, } Ogni grammatica LL(1) ammette un parser predittivo con un simbolo di lookahead. FOLLOW(E ) = FOLLOW(E 0 ) = { ), $ } FOLLOW(T ) = FOLLOW(T 0 ) = { +, ), $ } FOLLOW(F ) = { +, ∗, ), $ } P. Di Gianantonio (Udine) Parsing 23 / 77 P. Di Gianantonio (Udine) Parsing Parser predittivo Parser predittivo Nella funzione function parse_A () { choose a production parse_X1 (); . . parse_Xn (); } 24 / 77 Con singolo di lookahead l scelgo la produzione A → α A -> X1 ... Xn tale che: l ∈ FIRST(α) o ∈ FIRST(α) ∧ l ∈ FOLLOW(A ) La condizione LL(1) assicura che ci sia al piu` una tale produzione. posso scegliere la produzione in base al simbolo di lookahead. P. Di Gianantonio (Udine) Parsing 25 / 77 P. Di Gianantonio (Udine) Parsing 26 / 77 Parsing table Esempio di parsing table consideriamo la grammatica Una matrice M che mi indica le produzioni da utilizzare nel parsing top-down. In base al E E0 T T0 F • non-terminale da espandere A • il terminale di lookahead a Descrive quale produzione A → α utilizzare. → → → → → T E0 + T E0 | F T0 ∗ F T0 | id | (E ) M [A , a ] := A → α Se • a ∈ FIRST(α) oppure • a ∈ FOLLOW(A ) e ∈ FIRST(α) La condizione LL(1) assicura che, per ogni coppia A , a ci sia al piu` una produzione possibile. P. Di Gianantonio (Udine) Parsing 27 / 77 P. Di Gianantonio (Udine) Parsing Esempio di parsing table Esempio di parsing table consideriamo la grammatica E E0 T T0 F P. Di Gianantonio (Udine) 28 / 77 consideriamo la grammatica → → → → → T E0 + T E0 | F T0 ∗ F T0 | id | (E ) Parsing E E0 T T0 F 28 / 77 P. Di Gianantonio (Udine) → → → → → T E0 + T E0 | F T0 ∗ F T0 | id | (E ) Parsing 28 / 77 Parser automa a pila Parser automa a pila ` Ad ogni passo l’automa puo: • accettare un token della stringa di input, se presente in testa all input e alla pila • modificare la pila mediante la produzione indicata dalla tabella di parsing • generare errore, se non esiste nessuna produzione nella tabella di parsing per gli input attuali Input: • la stringa ancora da analizzare, • una pila contenente la parte finale della stringa derivata, mediante leftmost produzione, P. Di Gianantonio (Udine) Parsing • un tabella di parsing. • terminare con successo se pila e stringa di ingresso sono entrambe vuote. 29 / 77 P. Di Gianantonio (Udine) Parsing Predictive parsing program repeat begin let X be the top stack symbol and a the next input; if X is a terminal or $ then if X = a then pop X from the stack and remove a from the input else ERROR( ) else /* X is a nonterminal */ if M[X,a] = X -> Y1, Y2, ... , Yk then begin pop X from the stack; push Yk, Yk-1, ... ,Y1 onto the stack, Y1 on top end else ERROR( ) end until stack is empty P. Di Gianantonio (Udine) Parsing 31 / 77 30 / 77 Esempio parsing P. Di Gianantonio (Udine) Parsing 32 / 77 Parser LL(1) Parser LL(K) Riassumendo: • parsing in tempo lineare, esamino una volta sola la stringa d’ingresso; • relativamente semplice costruire, a mano, la tabella di parsing; • algoritmo non troppo difficile da comprendere e motivare; • la funzioni FIRST e FOLLOW restituiscono insiemi di stringe lunghe fino a k caratteri, • svantaggio principale: poche grammatiche rientrano nella classe LL(1). • la condizione LL(K) per una grammatica e` una variazione piu` complessa della condizione LL(1) Esercizio. Costruire la tabella di parsing per la grammatica: S L R P. Di Gianantonio (Udine) L =R | R ∗R | id → → → La metodologia precedente puo` essere estesa per costruire un parser LL(K) Principalmente ci si limita alle grammatiche LL(1) L Parsing 33 / 77 P. Di Gianantonio (Udine) Parsing Error recovering 34 / 77 Gestione dell’errore Se un compilatore trova degli errori • genera un messaggio di diagnostica, • prosegue nella compilazione per cercare altri errori Quindi. in caso di errore, il parser deve cercare di continuare l’analisi della restante stringa di ingresso. • panic mode: salto l’input sino a quando non trovo dei token di sincronizzazione, • se in cima alla pila c’e` A , considero i token FOLLOW(A ), caratteri di sincronizzazione e faccio pop di A ; • spesso anche token in FIRST(A ) sono considerati di sincronizzazione: gestisco quei casi dove caratteri sono stati inseriti per errore; • se esiste la produzione A → , scelgo quella produzione e proseguo nel parsing • si considerano caratteri di sincronizzazione • i caratteri tipici di terminazione dei comandi ; , • le parentesi per le espressioni ) • i token di chiusura blocchi } • se in cima alla pila c’e` un terminale a, lo elimino (implicitamente lo aggiungo alla stringa di input P. Di Gianantonio (Udine) Parsing 35 / 77 • phrase-level recovering: piu` sofisticato, introduco nelle caselle vuote della tabella di parsing dei rimandi a procedure che gestiscono la condizione di errore, andando a modificare opportunamente pila e input. P. Di Gianantonio (Udine) Parsing 36 / 77 Parser bottom-up Parsing shift-reduce Anche un parser bottom-up puo` essere visto come un automa a pila. Operano nel verso opposto rispetto ai parser top-down. A partire da una stringa di non terminali cerco una serie di riduzioni (produzioni applicate al contrario) che mi portano al simbolo iniziale. id ∗ id ← F ∗ id ← T ∗ id ← T ∗ F ← T ← E La derivazione che ottengo e` la rightmost (espando il non-terminale piu` a destra). Dalla derivazione posso calcolare l’albero di parsing. P. Di Gianantonio (Udine) Stack e input ancora da esaminare definiscono un antecedente, rispetto alle rightmost derivation, della stringa di input. (forma sentenziale destra) Parsing 37 / 77 P. Di Gianantonio (Udine) Parsing Esempio 38 / 77 Esempio, continua Consideriamo la grammatica (ricorsiva a sinistra) delle espressioni: E T F → → → E+T | T T ∗F | F (E ) | id Stack Input Action $ $ id $F $T $E $E+ $ E + id $E+F $E+T $E+T ∗ id + id ∗ id $ +id ∗ id $ + id ∗ id $ + id ∗ id $ + id ∗ id $ id ∗ id $ ∗ id $ ∗ id $ ∗ id $ id $ shift reduce by F reduce by T reduce by E shift shift reduce by F reduce by T shift shift P. Di Gianantonio (Udine) Parsing → → → E T F → id → F → T E+T | T T ∗F | F (E ) | id Stack Input Action $ E + T ∗ id $E+T ∗F $E+T $E $ $ $ $ reduce by F → id reduce by T → T ∗ F reduce by E → E + T accept → id → F 39 / 77 P. Di Gianantonio (Udine) Parsing 40 / 77 Decisioni Decisioni Un parser shift-reduce ad ogni passo deve decidere se: In alcuni casi casi ci possono essere piu riduzioni possibili: • inserire un nuovo simbolo in input nella pila: operazione di shift, o • ridurre la testa della pila applicando una riduzione (produzione al contrario) operazione reduce, Le decisioni sono prese in base al contenuto della pila, e in base allo prossimo token in input. $E+T ∗ id $ shift $E+T $ reduce by E → E + T P. Di Gianantonio (Udine) $E+T ∗F $E+T $ reduce by T → T ∗ F $E+T ∗F $E+T ∗T $ reduce by T → F Solo una e` corretta. Parsing 41 / 77 P. Di Gianantonio (Udine) Parsing Desiderata 42 / 77 Tre tipi di parser Per motivi di efficienza, il parser prende la decisione considerando solo il simbolo in testa alla pila. Metodo. Nella pila introduco, oltre al simboli della grammatica, un’informazione aggiuntiva che • riassume la parte significativa dei dati contenuto della pila, Tre tipi di informazioni aggiuntiva che posso inserire nella pila di un parser shift-reduce. Tre tecniche per dirimere i conflitti. Tre tipi di parser. • parser SLR (Simple LR), meno informazione, piu` semplici da costruire, trattano un insieme ristretto di linguaggi. • parser LR: piu` informazione, piu` complessi da costruire, trattano un insieme piu` ampio di linguaggi. • parser LALR (LookAhead LR): un buon compromesso tra semplicita` e ` generalita. • indica quali sono le riduzioni possibili, • nel caso ci siano piu riduzioni indicando quella da scegliere. Linguaggi LR(n) Chiamo questa informazione stato della pila. • L esamino la stringa from Left to right • R costruisco la Rightmost derivation • n con n simboli di lookahead. P. Di Gianantonio (Udine) Parsing 43 / 77 P. Di Gianantonio (Udine) Parsing 44 / 77 Parsing SLR [DeRemer 1969] Esempio Con la grammatica Lo stato, l’informazione aggiuntiva: un insieme di riduzioni E T F Una riduzione appartiene allo stato se: • e` la prima riduzione, di una catena di riduzioni; • la catena trasforma: • la stringa presente in pila, eventualmente completata con altri simboli; • nel simbolo iniziale della grammatica. • La catena di riduzione e` l’inverso di una rightmost derivation (leftmost reduction); • la riduzione viene applicata alla testa pila e/o a un gruppo di simboli immediatamente a destra di essa. → → → E+T | T T ∗F | F (E ) | id se la pila contiene i simboli E + T , lo stato e` formato dalle riduzioni: • E → E + T ovviamente; • T → T ∗ F, si applica se la pila e` seguita dalla stringe ∗F. non devo inserire la riduzione E → T infatti se applicata porta alla stringa E + E e impedisce la riduzione al simbolo iniziale. Non ci sono altre riduzioni nello stato. P. Di Gianantonio (Udine) Parsing 45 / 77 P. Di Gianantonio (Udine) Parsing Esempio 46 / 77 Item Con la solita grammatica E T F → → → Piu` precisamente, lo stato contiene, oltre all’insieme di riduzioni, un dato su quanta parte della parte destra di ciascuna riduzione e` gia` presente in pila. Questo dato viene rappresentato con un punto che separa la parte presente in pila da quella ancora da inserire. E+T | T T ∗F | F (E ) | id se la pila contiene E + lo stato contiene: Definizione Un item di una grammatica G e’ un espressione A → α · β con A → αβ produzione in G. • E → E + T , nel caso la pila sia seguita da T ; • T → F, nel caso la pila sia seguita da F; Se A → XY e` una produzione allora A → ·XY , A → X · Y , A → XY ·, sono i tre item generati. • T → T ∗ F, nel caso la pila sia seguita da T ∗ F; • e le riduzioni F → (E ) e F → T . P. Di Gianantonio (Udine) Parsing 47 / 77 P. Di Gianantonio (Udine) Parsing 48 / 77 Esempio Prefisso ammissibile Definizione formale: Con la grammatica E T F → → → E+T | T T ∗F | F (E ) | id Definizione Una stringa αβ e` un prefisso ammissibile (viable prefix) per un item A → β · γ se esiste una stringa di terminali w e una rightmost derivation se la pila contiene E + lo stato contiene gli item: tali che. S →∗rm αAw →rm αβγw • E → E + ·T , nel caso la pila sia seguita da T ; • T → ·F, nel caso la pila sia seguita da F; Definizione • T → ·T ∗ F, nel caso la pila sia seguita da T ∗ F; Lo stato che descrive una pila contenente la stringa αβ e` l’insieme degli item A → β · γ per cui αβ e` un prefisso ammissibile. • e le riduzioni F → ·(E ) e F → ·T . P. Di Gianantonio (Udine) Parsing 49 / 77 P. Di Gianantonio (Udine) Parsing Closure 50 / 77 Esempio Sulla grammatica: Non tutti gli insiemi di item possono formare uno stato. Se uno stato contiene un item, lo stesso stato deve contenere anche altri item. Definizione → → → E+T | T T ∗F | F (E ) | id La chiusura di { E → E + ·T } e` l’insieme: Dato un insieme I di item, la sua chiusura CLOSURE(I) e` il piu` piccolo insieme tale che CLOSURE({ E → E + ·T }) = { E → E + ·T , T → ·T ∗ F , T → ·F , F → ·(E ), F → ·id } • I ⊆ CLOSURE(I ) • se (A → α · B β) ∈ CLOSURE(I ) e B → γ allora B → ·γ ∈ CLOSURE(I) Se in testa alla pila e` presente la stringa E +, ed e` corretto applicare la riduzione E → E + T (nel caso la pila sia seguita dal simbolo T ) allora e` lecito anzi necessario applicare la riduzione T → F (nel caso la pila sia seguita dal simbolo F). Un insieme e’ chiuso se coincide con la sua chiusura. Gli stati dell’automa sono sottoinsiemi chiusi di item. P. Di Gianantonio (Udine) E T F Parsing 51 / 77 P. Di Gianantonio (Udine) Parsing 52 / 77 Grammatica aumentata Funzione GOTO Per semplificare la condizione di terminazione del parser LR, si definisce la semantica aumentata caratterizzata da: • un nuovo simbolo iniziale S0 • con una singola produzione S 0 → S, dove S e` simbolo iniziale originale. Lo stato successivo e` definito dalla funzione GOTO Definizione La grammatiche aumentata delle espressioni aritmetiche e` E0 E T F P. Di Gianantonio (Udine) → → → → Dato uno stato I, che descrive una pila con α, posso calcolare lo stato che descrive la pila αX . Dato un insieme chiuso I di item e un simbolo della grammatica X , definiamo GOTO(I, X ) come E E+T | T T ∗F | F (E ) | id CLOSURE({A → αX · β | A → α · X β ∈ I }) Parsing 53 / 77 P. Di Gianantonio (Udine) Esempio Parsing 54 / 77 Collezione canonica Sia I lo stato: { E → E + ·T , T → ·T ∗ F , T → ·F , F → ·(E ), F → ·id } L’insieme stati di un automa SLR per una grammatica G e` data dalla collezione canonica di insieme di item, insieme ottenuto GOTO(I, T ) e` lo stato: { E → E + T ·, T → T · ∗F } • partendo dallo stato iniziale CLOSURE({S 0 → ·S }) All’inizio del parsing, la pila vuota, e ci sia aspetta che la stringa in input, dopo una catena di riduzioni, permetta le riduzione S 0 → S. GOTO(I, F ) e` lo stato: { T → F· } • aggiungendo tutti gli stati di item ottenibili applicando la funzione GOTO. GOTO(I, ( ) e` lo stato: CLOSURE({ F → (·E ) }) L’insieme generato dalla stato iniziale e dalla funzione GOTO GOTO(I, id) e` lo stato: { F → id· } Con gli altri simboli Y la funzione GOTO(I, Y ) genera l’insieme vuoto e segnala una condizione di errore. P. Di Gianantonio (Udine) Parsing 55 / 77 P. Di Gianantonio (Udine) Parsing 56 / 77 Automa SRL (non compattato) Linguaggio SRL Parser shift-reduce: • che oltre ad un simboli della grammatica, inserisce di volta in volta nella pila uno stato (collezione di item) La costruzione precedente puo` creare conflitti: • usa l’informazione dello stato I, ed un simbolo di lookahead a, per decidere l’azione da svolgere (shift o reduce) • conflitti shift-reduce: per una coppia I , a, e` ammissibile sia un azione di shift, che una reduce. I contiene due item, uno nella forma A → α · a β, e l’altro nella forma A 0 → α0 ·, con a ∈ FOLLOW(A 0 ) Shift • Quando: GOTO(I , a ) , ∅, ossia I contiene un item nella forma A → α · aβ • Come: consumo il simbolo a dall’input, inserisco in pila in simbolo a e lo stato GOTO(I, a ) Reduce, con la riduzione A → α • Quando: I contiene l’item A → α·, a ∈ FOLLOW(A ) • conflitti reduce-reduce: per una coppia I , a, ci sono due riduzioni possibili. Se non ci sono esistono conflitti, la grammatica si definisce SRL, puo` essere riconosciuta da un automa SRL. • Elimino dalla pila |α| simboli e stati e inserisco il simbolo A e GOTO(I, A ), se in I e` lo stato in testa alla pila dopo l’eliminazione. P. Di Gianantonio (Udine) Parsing 57 / 77 P. Di Gianantonio (Udine) Parsing Automa SRL (compatto) ACTION Per motivi efficienza, l’automa SRL e` implementato in modo diverso rispetto alla descrizione precedente Tabella con • Uno stato I descrive anche i simboli presenti in testa alla pila. Se in testa alla pila c’e` I e A → α · β ∈ I, allora in testa alla pila deve esserci la stringa di simboli α. Nella pila posso inserire solo stati, simboli ridondanti. • righe con indice stato I (codificati da numeri), • colonne con indice simboli non terminali a. Dato lo stato I e il simbolo di lookahead a la tabella indica l’azione da compiere. • Uno stato puo` essere codificato con un numero. Costruisco la collezione canonica, e numero gli stati ottenuti. • shift I 0 , (sI 0 ), vai uno shift e inserisci in pila lo stato I 0 , (I 0 = GOTO(I , a )) • Le azioni che l’automa deve compire possono essere riassunte in due tabelle ACTION GOTO P. Di Gianantonio (Udine) Parsing 58 / 77 59 / 77 • reduce n, (rn), applica un riduzione secondo la produzione n, (anche le produzione vengono numerate) P. Di Gianantonio (Udine) Parsing 60 / 77 GOTO Parsing LR(1) (Donald Knuth 1965) Come il parsing SRL, ma gli stati contengono piu` informazione Debolezza del parsing SRL: Nello stato I, con lookahead a, viene suggerita l’azione di reduce, secondo la produzione A → α se: Applicare una riduzione n corrispondente a A → α bisogna • rimuove |α| stati dalla pila, • A → α· ∈ I • inserire lo stato GOTO(I , A ), dove I stato in testa alla pila dopo rimozione, • a ∈ FOLLOW(A ) FOLLOW(A ) funzione generale: restituisce tutti i simboli che possono Tabella GOTO, associa uno stato ad ogni coppia stato, simbolo non-terminale. seguire A in una qualsiasi derivazione, Ma in nessuna delle derivazioni che portano alla pila A e` seguito da a In nessuna di questa A e` seguita dal simbolo a. Il parsing SRL indica delle riduzioni che possono essere escluse da un analisi piu` accurata. P. Di Gianantonio (Udine) Parsing 61 / 77 P. Di Gianantonio (Udine) Parsing Esempio Item LR(1) Grammatica S L R → → → Un item LR(1) e’ una coppia item LR(0), simbolo terminale. L =R | R ∗R | id L Definizione Un item LR(1) di una grammatica G e’ una coppia (A → α · β, a ) con A → αβ produzione in G e a simbolo terminale in G. Stack Input Action {S 0 id = id $ = id $ = id $ = id $ shift reduce by L → id reduce by R → L deadend → ·S } ” {L → id·} ” {R → L ·, S → L · = R } ” {S → R ·} 62 / 77 Lo stato associato ad una pila α0 α, e` formato dagli item (A → α · β, a ) tali che esiste una stringa di terminali w e una rightmost derivation nella forma S →∗rm α0 Aaw →rm α0 αβaw Il simbolo = puo` seguire solo le istanze di R che appaiono in ∗R Nell’item LR(1) introduco un’informazione esplicita su quali sono i simboli terminali a che posso seguire l’istanza sintetizzata di A . Aggiungendo piu` informazione nello stato risolvo il conflitto shift-reduce nella terza linea. P. Di Gianantonio (Udine) Parsing 63 / 77 P. Di Gianantonio (Udine) Parsing 64 / 77 Closure LR(1) Funzione GOTO Definizione Definizione Dato un insieme I di item, la sua chiusura CLOSURE(I) e` il piu` piccolo insieme tale che Dato uno stato LR(1) I e un simbolo della grammatica X , definiamo GOTO(I, X ) come • I ⊆ CLOSURE(I ) • se (A → α · B β, a ) ∈ CLOSURE(I ) , B → γ , b ∈ FIRST(βa ) allora (B → ·γ, b ) ∈ CLOSURE(I) P. Di Gianantonio (Udine) Parsing CLOSURE({(A → αX · β, a ) | (A → α · X β, a ) ∈ I }) 65 / 77 P. Di Gianantonio (Udine) Stato iniziale Nello stato I con lookahead a esegue • shift se GOTO(I , a ) , ∅ • reduce se esiste (A → α·, a ) ∈ I Lo stato iniziale dell’automa LR(1) e` • success nel caso particolare I = {S 0 → S ·} e a = $ CLOSURE({(S 0 → ·S , $)}) Parsing 66 / 77 Comportamento del parser Anche gli automi LR1 considerano grammatiche aumentate con un nuovo simbolo iniziale S 0 e una nuova produzione S 0 → S. P. Di Gianantonio (Udine) Parsing • error negli altri casi. 67 / 77 P. Di Gianantonio (Udine) Parsing 68 / 77 Parsing LALR [DeRemer 1969] Parser LALR Definiamo core di un LR(1) stato I (TIU) e` l’insieme dei LR(0) item in I Dal punto di vista pratico, i parser LR(1) hanno troppi stati, tabelle ACTION e GOTO troppo ampie. TIU = {A → α · β | ∃a (A → α · β, a ) ∈ I} Preferibile una tecnica di parsing intermedia tra SLR e LR: tabelle di parsing ridotte, insieme sufficientemente ricco di grammatiche trattabili. Definiamo due stati equivalenti Ij ≈ Ik se hanno lo stesso core TIj U = TIk U La funzione GOTO preserva l’equivalenza tra stati. Gli stati di un automa LALR sono ottenuto quozientando gli stati dell’automa LR(1). TIj U = TIk U ⇒ TGOTO(Ij , X )U = TGOTO(Ik , X )U Ij ≈ Ik ⇒ GOTO(Ij , X ) ≈ GOTO(Ik , X ) P. Di Gianantonio (Udine) Parsing 69 / 77 P. Di Gianantonio (Udine) Parsing Proprieta` parser LALR Parser LALR Definizione Proposizione Gli stati dell’automa LALR sono costituiti dall’unione dei stati LR(1) equivalenti tra loro. [ [I] = Ij Quozientare non introduce conflitti shift-reduce. Se l’automa LALR(1), per la grammatica G ha un conflitto shift-reduce, allora anche l’automa LR(1) per G ha un conflitto shift-reduce. Prova ... Quozientare puo` introdurre conflitti reduce-reduce. I j ≈I La funzione GOTO e` data da: Proposizione GOTO([I], X ) = [GOTO(I, X )] Data una grammatica G, gli stati del parser LALR, sono gli stati del parser SLR arricchiti con un informazione sui simboli di follow di una produzione. ` Il comportamento del parser LALR e: nello stato [I] con lookahead a esegue: Segue dal fatto che: TGOTO(I, X )U = GOTO(TIU, X ) • shift se GOTO([I ], a ) , ∅ • reduce se esiste (A → α·, a ) ∈ [I ] P. Di Gianantonio (Udine) Parsing 70 / 77 e dal fatto che l’insieme degli stati e` generato dalla funzione GOTO. 71 / 77 P. Di Gianantonio (Udine) Parsing 72 / 77 Possibili costruzioni parser LALR Costruzione parser LALR • Costruisco l’automa LR(1) e faccio il quoziente. Inefficiente, devo generare un automa con molti stati. Costruisco il parser SLR. • Nella costruzione del parser LR(1) evito la costruzione di stati con lo stesso core. Se la funzione GOTO genera uno stato con I equivalente ad uno stato J gia` generato (I ≈ J), fondo I con J e rifaccio i calcoli della funzione GOTO. Non genere piu` stati del necessario ma si puo` fare meglio. • Costruisco gli stati SLR e aggiungo in seguito l’informazione di prossimo terminale (di lookahead). Costruzione piu` efficiente. P. Di Gianantonio (Udine) Parsing 73 / 77 Devo distinguere tra i simboli di lookahead in GOTO(I, X ) in • propagati, simboli di lookahead in I, che si spostano in GOTO(I , X ). • quelli generati spontaneamente sono presenti indipendentemente dai simboli di lookahead di I, appartengono a qualsiasi GOTO(J , X ) con J≈I P. Di Gianantonio (Udine) Costruzione parser LALR Parsing 74 / 77 Uso di grammatiche ambigue Una grammatica ambigua G genera sempre parser con conflitti. Una stringa ha piu` alberi di parsing, quindi la costruzione dell’albero di parsing deve poter prendere due strade diverse. Aggiungo un nuovo terminale #, lo inserisco come simbolo di lookahead in tutti gli item di SLR e calcolo la funzione GOTO LR(1) su gli stati arricchiti. In questo modo ottengo due informazioni: • L’insieme di lookahead generati spontaneamente. • Un mappa di come si propagano i simbolo di lookahead. Calcolo il punto fisso della propagazione (Dopo aver inserito il simbolo di lookahead $ nello item S ‘ → ·S dello stato iniziale. Grammatiche ambigue piu` convenienti perche´ piu` sintetiche, potenzialmente generano parser piu` compatti e veloci. Nell’uso comune le grammatiche ambigue possono essere disambiguate definendo • un ordine di precedenza tra gli operatori, • delle regole di associativita` (destra o sinistra) per uno stesso operatore. Nei parser lo stesso effetto puo` essere ottenuto specificando come risolvere i conflitti, shift-reduce nel parsing. Senza conflitti di parsing, ad ogni stringa viene associata al piu` ad un albero. P. Di Gianantonio (Udine) Parsing 75 / 77 P. Di Gianantonio (Udine) Parsing 76 / 77 Error recovering I parser LR riconoscono un errore subito. Non appena la parte dell’input letta non ammette nessun completamento corretto, il parser genera errore. Non faccio operazioni di shift senza la possibilita` di terminare con successo. Due gestioni degli errori: • panic mode: scelgo un insieme di non terminali principali (statement, expression, block), nel caso di errore smonto lo stack sino a trovare uno stato s con GOTO(s , A ) per A principale, inserisco lo stato GOTO(s , A ) in pila. scarico l’input sino a trovare un simbolo in FOLLOW(A ), • recupero a livello di frase: si scrivono delle procedure che per ogni coppia stato I, lookahea a che porta a errore, determinano le azioni da compiere. Eventualmente un azione caso particolare dell’azione precedente. P. Di Gianantonio (Udine) Parsing 77 / 77
© Copyright 2024 Paperzz