560 likes | 676 Views
Implementazione di Linguaggi 2 PARTE 4. Massimo Ancona DISI Università di Genova Testo: A.V. Aho, R. Sethi, J.D.Ullman Compilers Principles,Techniques and Tools, Addison Wesley. PARSER LR. Algoritmo di Parsing LR. Il parser usa uno stack su cui impila stringhe della forma:
E N D
Implementazione di Linguaggi 2PARTE 4 Massimo Ancona DISI Università di Genova Testo: A.V. Aho, R. Sethi, J.D.Ullman Compilers Principles,Techniques and Tools, Addison Wesley
Algoritmo di Parsing LR Il parser usa uno stack su cui impila stringhe della forma: s0X1s1X2s2…Xmsm consm sul top dove XiN mentre si rappresenta uno stato del parser. Il parser e’ di tipo shift-reduce e il suo comportamento e’ deciso dall’input corrente ai e dall top dello stack sm. Le tavole del parser sono formate da due funzioni dette action function f e goto function g. Il parser consulta [sm,ai] che puo’ valere: • shift • reduce by A • accept • error
Algoritmo di Parsing LR La funzione goto g mappa stati e simboli grammaticali in stati g: SVS ed e’ la funzione di transizione di un DFA che accetta prefissi accessibili (PrAc) della grammatica G. Un PrAc di una grammatica G e’ un prefisso di una forma sentenziale canonica destra (FSCD) che puo’ comparire sul top dello stack. Piu’ formalmente un PrAc di G e’ una stringa tale che: S*rmAwrmw e PREF() (*) Una maniglia/chiave (handle) di una FSCD di G e’ una coppia formata da una produzione e una posizione nella FSCD. Nell’ esempio (*) (A,) o (A,n) dove n=|+1|. Lo handle esprime che la stringa
Algoritmo di Parsing LR Canonico alla posizione specificata dallo handle puo’ essere sostituita da A per ottenere la FSCD precedente in una derivazione canonica destra. Il DFA ha come stato iniziale quello impilato dal parser all’inizio. Una configurazione del parser e’ una coppia formata da: • il contenuto dello stack • l’input non ancora letto (s0X1s1X2s3…Xmsm,ajaj+1…an) La mossa corrente del parser e’ determinata da aj, da sm e dal valore di f[sm,aj] (action[sm,aj]) come segue:
Algoritmo di Parsing LR Canonico • Se f[sm,aj]=shift allora il parser esegue uno shift e passa alla configurazione (s0X1s1X2s3…Xmsmajs,aj+1…an) dove s=g[sm,aj]. • se f[sm,aj]=reduce A allora il parser esegue una azione reduce passando alla configurazione (s0X1s1X2s3…Xm-rsm-rAs,ajaj+1…an) dove s=g[sm-r,A] ed r=||. Cioe’ il parser spila 2r simboli dallo stack fino ad esporre sul top sm-r; quindi impila A seguito da s=g[sm-r,A]. • Se f[sm,aj]=accept allora il parser termina l’analisi con successo. • Se f[sm,aj]=error allora attiva una routine di errore.
Struttura delle tavole di Parsing LR Si consideri l’esempio (1) EE+T, (2) ET,(3) TT*F (4) TF, (5) F(E), (6) Fid. La prima slide mostra le tavole canoniche. Il loro difetto e’ di avere la tavola f sparsa e spreca-spazio. Per questo la slide successiva sposta la parte delle azioni goto sui terminali nella tavola f. Ad esempio se f[s,a]=shift e g[s,a]=n allora f[s,a] diviene action[s,a]=shift n, mentre la funzione g(a) scompare restando solo g(A). Di conseguenza il parser va ridescritto come segue (le nuove funzioni f e g vengono chiamate action e goto):
Algoritmo di Parsing LR libro • Se action[sm,aj]=shift s allora il parser esegue uno shift e passa alla configurazione (s0X1s1X2s3…Xmsmajs,aj+1…an) • se action[sm,aj]=reduce A allora il parser esegue una azione reduce passando alla configurazione (s0X1s1X2s3…Xm-rsm-rAs,ajaj+1…an), s=goto[sm-r,A] e r=||. Cioe’ il parser spila 2r simboli dallo stack fino ad esporre sul top sm-r; quindi impila A seguito da s=goto[sm-r,A]. • Se action[sm,aj]=accept allora il parser termina l’analisi con successo. • Se action[sm,aj]=error allora attiva una routine di errore.
Algoritmo di Parsing LR Driver Routine PROC ParserLR; DO/* s TOS state, a next input and ip pointer to it */ IF action[s,a]=shift s’ THEN push a; push s’; ip++ ELSIF action[s,a]=reduce A THEN pop 2|| symbols; /* s’ becomes TOS */ push A; push goto[s’,A]; out “A” ELSIF action[s,a]=accept THEN RETURN ELSE Error() FI OD END.
Esempio di Parsing LR Step stack input action output 1 0 id*id+id$ shift 2 0id5 *id+id$ reduce Fid Fid 3 0F3 *id+id$ reduce TF TF 4 0T2 *id+id$ shift 5 0T2*7 id+id$ shift 6 0T2*7id5 +id$ reduce Fid Fid 7 0T2*7F10 +id$ reduce TT*F TT*F 8 0T2 +id$ reduce ET ET 9 0E1 +id$ shift 10 0E1+6 id$ shift 11 0E1+6id5 $ reduce Fid Fid
Esempio di Parsing LR cont. Step stack input action output 12 0E1+6F3 $ reduce TF TF 13 0E1 +6E9 $ reduce EE+T EE+T 14 0E1 $ accept Da un punto di vista pratico sono state usate le grammatiche LR(0) ed LR(1) ed alcune sottoclassi di queste uktime: SLR(1) e LALR(1) benche’ le potenzialita’ dei processori di oggi permetta di prendere in considerazione le LR(1) ed anche le LR(k) con k>1 senza grossi problemi.
Tavole di Parsing Canoniche Stato id + * ( ) $ E T F id + * ( ) $ 0 s s 1 3 5 2 4 s 1 A 6 2 s 2 2 7 2 3 4 4 4 4 4 s s 8 2 3 5 4 5 6 6 6 6 6 s s 9 3 5 4 s 7 s 10 5 4 8 s s 6 11 9 1 s 1 1 7 10 3 3 3 3 11 5 5 5 5
Tavole di Parsing del Libro Stato id + * ( ) $ E T F 0 s5 s4 1 3 2 1 A s6 2 s7 2 2 2 3 4 4 4 4 4 s5 s4 8 2 3 5 6 6 6 6 6 s5 s4 9 3 s4 7 s5 10 8 s6 s11 9 1 s7 1 1 10 3 3 3 3 11 5 5 5 5
Costruzione dell Tavole di Parsing LR Per questo vedremo in dettaglio le tavole LR(0), SLR(1) ed LR(1) trattando le LALR(1) marginalmente solo come meccanismo di semplificazione delle tavole LR. Item LR(0). Un item LR(0) e’ una produzione con un dot che ne marca una posizione nella parte destra. La produzione AXYZ origina quattro item: A.XYZ AX.YZ AXY.Z AXYZ.; Mentre A genera il solo item A. . L’idea centrale alla base del metodo consiste nel costruire dalla grammatica un automa finito che riconosca i PrAc della grammatica G. Alla base dei metodi piu’ semplici (SLR LALR) vi e’ la Collezione Canonica di Stati LR(0) (CCS0).
Tavole LR(0): funzione closure Definizione. Data G=(N,T,P,S) definiamo grammatica estesa G’=(NS’,T,PS’S,S’) dove S’N e’ un nuovo simbolo iniziale. Scopo: determinare esattamente la terminazione del parser. Definizione. Chiusura di un insieme di item I. • Inizialmente Closure(I)=I. • Se A.BClosure(I) e B alllora aggiungere B.a closure(I). Il significato intuitivo di Closure e’ il seguente: se A.BClosure(I) allora durante il parsing e’ possibile che si analizzi una sottostringa derivata da B. Se inoltre B allora e’ anche possibile analizzare una sottostringa derivabile da .
Tavole LR(0): chiusura Esempio di chiusura. Data la grammatica estesa G’ (0)E’E; (1)EE+T; (2)ET; (3)TT*F; (4)TF; (5)F(E); (6)Fid. Calcolo della chiusura di un insieme di item I. • Se inizialmente I={E’ .E} • Allora Closure(I)={E’.E, E.E+T, E.T, T.T*F, T.F, F.(E), F.id}.
Tavole LR(0): routine di chiusura FUNC Closure(I: ItemSet); VAR J:ItemSet; BEGIN J:=I; DO FOR each itemA.BJ & each B DO J:=J{B.} OD UNTIL J unchanged; RETURN J END.
Tavole LR(0): kernel item e item validi Definizione: Un kernel item e’: • S’.S • Un item con il dot chenon precede la parte destra della produzione Definizione Valid item. Un item A1.2e’ valido per per un PrAc 1 se esiste una derivazione S’rm*Awrm*12w Sapere che un item A1.2e’ valido per un PrAc 1 permette di decidere tra un’azione shift e reduce quando 1 e’ al top dello stack. In particolare se 2 allora lo handle non si trova ancora sullo stack e l’azione e’ shift. Se invece 2= allora A1e’ lo handle e l’azione sara’ reduce. E’ chiaro che due item validi diversi possono suggerire due azioni contrastant per lo stesso PrAc.
Tavole LR(0): funzione closure Definizione. Data G=(N,T,P,S) definiamo grammatica estesa G’=(NS’,T,PS’S,S’) dove S’N e’ un nuovo simbolo iniziale. Scopo: determinare esattamente la terminazione del parser. Definizione. Chiusura di un insieme di item I. • Inizialmente Closure(I)=I. • Se A.BClosure(I) e B alllora aggiungere B.a closure(I). Il significato intuitivo di Closure e’ il seguente: se A.BClosure(I) allora durante il parsing e’ possibile che si analizzi una sottostringa derivata da B. Se inoltre B allora e’ anche possibile analizzare una sottostringa derivabile da .
Tavole LR(0): funzione closure Definizione. Data G=(N,T,P,S) definiamo grammatica estesa G’=(NS’,T,PS’S,S’) dove S’N e’ un nuovo simbolo iniziale. Scopo: determinare esattamente la terminazione del parser. Definizione. Chiusura di un insieme di item I. • Inizialmente Closure(I)=I. • Se A.BClosure(I) e B alllora aggiungere B.a closure(I). Il significato intuitivo di Closure e’ il seguente: se A.BClosure(I) allora durante il parsing e’ possibile che si analizzi una sottostringa derivata da B. Se inoltre B allora e’ anche possibile analizzare una sottostringa derivabile da .
Tavole LR(0): funzione goto Definizione. Funzione goto(I,X) definita su insiemi di item esimboli grammaticali: goto(I,X)=Closure({ AX.| A.XI} Intuitivamente se I e’ l’insieme degli item validi per un PrAc allora goto(I,X) e’ l’insieme degli item validi per il PrAc X. Esempio: goto({E’E.,EE.+T},+)=Closure({EE+.T})= {EE+.T, T.T*F, T.F, F.(E), F.id}
Tavole LR(0): funzione goto cont. Per ogni G la funzione goto definisce un DFA che riconosce PrAc di G. Il DFA e’ costruibile tramite un NFA che usa gli insiemi di item come stati e transizioni da A.X a AX. etichettate X e da A.B a B. etichettate . Ne segue che Closure(I) coincide con la -closure di un insiemi di stati di un NFA come visto a suo tempo. La funzione goto(I,X) realizza la transizione da da I tramite l’input X nel DFA ottenuto dal NFA tramite la costruzione basata sui sottoinsiemi di stati gia’ vista.
Insiemi di item canonici LR(0) Algoritmo: Costruzione della Collezione Canonica degli item LR(0) per una grammatica (augmented) G’. PROC Items(G’); BEGIN C=Closure({s’.s}); DO FOR (I) IC & (X) XV: goto(I,X) DO C:=C goto(I,X) UNTIL C unchanged END;
Insiemi di item canonici LR(0) di G’ G’: (0)E’E;(1)EE+T;(2)ET;(3)TT*F;(4)TF; (5)F(E); (6)Fid. I0: E’.E I3: TF. T.F I11: F(E). E .E+T I4: F(.E) F.(E) goto(I0,E)=I1 E .T E.E+T F.id goto(I0,T)=I2 T .T*F E.T I7: TT*.F goto(I0,F)=I3 T .F T.T*F F.(E) goto(I0,()=I4 F .(E) T.F F.id goto(I0,id)=I5 F.id F.(E) I8: F(E.) goto(I1,+)=I6 I1: E’ E. F.id E E.+T goto(I2,*)=I7 E E.+T I5: Fid. I9: EE+T. goto(I4,+)=I8 I2: E T. I6: EE+.T TT.*F goto(I4,T)=I2 T T.*F T.T*F I10: TT*F. goto(I4,F)=I3
Insiemi di item canonici LR(0) di G’ G’: (0)E’E;(1)EE+T;(2)ET;(3)TT*F;(4)TF; (5)F(E); (6)Fid. I0: E’.E I3: TF. T.F I11: F(E). E .E+T I4: F(.E) F.(E) goto(I4,()=I4 E .T E.E+T F.id goto(I4,id)=I5 T .T*F E.T I7: TT*.F goto(I6,T)=I9 T .F T.T*F F.(E) goto(I6,F)=I3 F .(E) T.F F.id goto(I6,()=I4 F.id F.(E) I8: F(E.) goto(I6,id)=I5 I1: E’ E. F.id E E.+T goto(I7,F)=I10 E E.+T I5: Fid. I9: EE+T. goto(I7,()=I4 I2: E T. I6: EE+.T TT.*F goto(I7,id)=I5 T T.*F T.T*F I10: TT*F. goto(I8,))=I11
Insiemi di item canonici LR(0) di G’ G’: (0)E’E;(1)EE+T;(2)ET;(3)TT*F;(4)TF; (5)F(E); (6)Fid. I0: E’.E I3: TF. T.F I11: F(E). E .E+T I4: F(.E) F.(E) goto(I8,+)=I46 E .T E.E+T F.id goto(I9,*)=I7 T .T*F E.T I7: TT*.F T .F T.T*F F.(E) F .(E) T.F F.id F.id F.(E) I8: F(E.) I1: E’ E. F.id E E.+T E E.+T I5: Fid. I9: EE+T. I2: E T. I6: EE+.T TT.*F T T.*F T.T*F I10: TT*F.
Tavole di parsing SLR(1) Si costruisce a partire dalla collezione C di insiemi di item LR(0) di G’ calcolando action e goto come segue: • Costruire C={I0,I1,…In} Collezione di insiemi di item LR(0) di G’ • La tavola (stato) i deriva da Ii con azioni: • A.aIi & goto(I,a)=Ij action[i,a]=“shift j” • A.Ii aFOLlOW(A) &AS’ action[i,a]=“reduce A” • S’SIi action[I,$]=“accept” Se le regole precedenti generano azioni in conflitto allora G’ non e’ SLR(1).
Tavole di parsing SLR(1) • AN goto[Ii,A]=Ij goto(i,A)=j • Ogni entry non definita in 1. 2. 3. e’ posta a error • Lo stato iniziale del parser e’ costruito da I0={S’.S,…} Una grammatica che possieda una tavola di parsing SLR(1) e’ detta SLR(1). Ogni grammatica SLR(1) e’ non ambigua. Esistono grammatiche non ambigue che non sono SLR(1), Es: SL=R SR L*R Lid RL
Tavole di Parsing Stato id + * ( ) $ E T F id + * ( ) $ 0 s s 1 3 2 s 1 A 2 s 2 2 2 3 4 4 4 4 4 s s 8 2 3 5 6 6 6 6 6 s s 9 3 s 7 s 10 8 s s 9 1 s 1 1 10 3 3 3 3 11 5 5 5 5
Item LR(1) Un item LR(1) e’ una coppia [A.,a] con aT{$} e A. Il secondo componente di un item e’ chiamato stringa di lookahead. Essa non si usa quando β≠. Se invece β=la stringa di lookahead a specifica una riduzione A solo quando il token in input è a. Definizione. Un item LR(1) [A.,a] è valido per un PrAc a se esiste una derivazione canonica destra S’rm*Awrm*w, con = e w=aw’ o w= & a=$. In relazione alle grammatiche SLR(1) va notato che l’ insieme di stringhe di lookahead a che formano gli item LR(1) [A.,a]validi per un prefisso verifica FOLLOW(A) ma in generale FOLLOW(a)
Tavole LR(1): funzione closure Definizione. Chiusura di un insieme di item I per una G’ estesa: • Inizialmente Closure(I)=I. • Se [A.B,a]Closure(I) e B alllora aggiungere [B.,b]a closure(I) per ogni bFIRST(a) Il significato intuitivo di Closure e’ il seguente: se A.BClosure(I) allora durante il parsing e’ possibile che si analizzi una sottostringa derivata da B. Se inoltre B allora e’ anche possibile analizzare una sottostringa derivabile da per tutti gli input b tali che bFIRST(a)
Tavole LR(1): routine di chiusura FUNC Closure(I: ItemSet); VAR J:ItemSet; BEGIN J:=I; DO FOR each item [A.B,a]J & each B & each bFIRST(a) DO J:=J{[B.,b]} OD UNTIL J unchanged; RETURN J END.
Tavole LR(1): funzione goto Definizione. Funzione goto(I,X) definita su insiemi di item esimboli grammaticali: goto(I,X)=Closure({ [AX.,a]| [A.X,a]I} Ancora se I e’ l’insieme degli item validi per un PrAc allora goto(I,X) e’ l’insieme degli item validi per il PrAc X.
Insiemi di item canonici LR(1) Algoritmo: Costruzione della Collezione Canonica degli item LR(1) per una grammatica (augmented) G’. PROC Items(G’); BEGIN C=Closure({[s’.s,$]}); DO FOR each IC & each XV such that goto(I,X) DO C:=C goto(I,X) /* INCL(C,goto(I,X)) */ UNTIL C unchanged END;
Insiemi di item canonici LR(1) di G2’ G2’: (0)S’S;(1)SCC;(2)CcC;(3)Cd; I0: [S’.S,$] I3: [Cc.C,c] [C.d,$] goto(I2,d)=I7 [S.CC,$] [Cc.C,d] I7: [Cd.,$] goto(I3,C)=I8 [C.cC,c] [C.cC,c] I8: [CcC.,c] goto(I3,c)=I8 [C.cC,d] [C.cC,d] [CcC.,d] goto(I3,d)=I4 [C.d,c] [C.d,c] I9: [Cc.,$] goto(I6,C)=I9 [C.d,d] [C.d,d] goto(I0,S)=I1 goto(I6,c)=I6 I1: [S’S.,$] I4: [Cd.,c] goto(I0,C)=I2 goto(I6,d)=I7 I2: [SC.C,$] [Cd.,d] goto(I0,c)=I3 [C.cC,$] I5: [SCC.,$] goto(I0,d)=I4 [C .d,$] I6: [Cc.C,$] goto(I2,C)=I5 [C.cC,$] goto(I2,c)=I6
Costruzione delle Tavole LR(1) Input: una grammatica estesa G’. Output: tavole canoniche LR(1) di G’. • Costruire C={I0,I1,...,In} CCII LR(1) • La tavola i deriva da Ii con action seguente: • se [A.a,b]Iie goto(Ii,a)=Ijallora action[i,a]=shift j • se [A.,a]Iie A≠S allora action[i,a]=reduce A • se [S’S,$]Iiallora action[i,$]= accept • Se goto(Ii,A)=Ij allora goto[i,$]=j • Porre tutte le entry non definite in 1),2) e 3) a error • Lo stato iniziale del parser è quello contenente [S’.S,$] Se le tavole non presentano conflitti shift-reduce o reduce-reduce allora la grammatica G è LR(1).
Tavole di Parsing LR(1) esempio Stato c d $ S C 0 s3 s4 1 2 G2’: (0)S’S;(1)SCC;(2)CcC;(3)Cd; 1 A s6 s7 5 2 s4 s3 8 3 4 r3 r3 5 r1 s6 s7 9 6 7 r3 8 r2 r2 r2 9
Tavole di Parsing LR(1) esempio Stato c d $ S C 0 s3 s4 1 2 G2’: (0)S’S;(1)SCC;(2)CcC;(3)Cd; 1 A goto (I0,c)=I3 goto(I0,d)=I4 goto(I0,S)=I1 goto(I0,C)=I2 goto(I2,c)=I6 goto(I2,d)=I7 goto(I2,C)=I5 goto(I3,c)=I3 goto(I3,d)=I4 goto(I3,C)=I8 goto(I6,c)=I6 goto(I6,d)=I7 goto(I6,C)=I9 s6 s7 5 2 s4 s3 8 3 4 r3 r3 5 r1 s6 s7 9 6 7 r3 8 r2 r2 r2 9
Costruzione delle Tavole LALR(1) Definiamo core(I)={[A.] | [A.,u]I} Poichè il core di goto(I,X) dipende dal core di I, ne segue che goto(HIi)= Hgoto(Ii). Quindi i goto di stati immersi sono a loro volta tra loro immergibili. Le tavole ottenute in questo modo, come ottimizzazione della collezione di stati canonici LR(1) sono tuttavia anche ottenibili a partire dalla collezione canonica di stati LR(0). Questo ne fa un algoritmo molto efficiente.
Costruzione delle Tavole LALR(1) Se nella collezione canonica di insiemi di item LR(1) di G2’ uniamo gli stati I4 e I7 in un nuovo stato I47 con 3 item [Cd.,c/d/$] allora il parser ha azione reduce_Cd su ogni input, anche su quelli su cui l’originale dichiara error (es. ccd cdcdc) . La primo token in input errato. L’ottimizzazione consiste nell’ immergere gli stati con lo stesso core, cioè stati che differiscono solo per il token di lookahead (idem per I3-I6 e I8-I9).
Grammatiche LL ed LR Teorema (decidibilita’): date due grammatiche LL(k), G1 e G2, il problema L(G1)=L(G2) e’ decidibile. La seguente grammatica: SA|B, AaAb|0, BaBbb|1 e’ LR(0) ma non LL(k) per ogni k0. Teorema. Ogni grammatica LL(k) e’ anche LR(k), k0. Definizione. Um linguaggio CFG e’ deterministico se esite un DPDA (Det, PushDown Automaton) che lo accetta.
Linguaggi LL ed LR Definizione. Un linguaggio L tale che nessuna wL e’ della forma w=xy con xL e’ detto avere la proprieta’ del prefisso (prefix-free). Teorema. L linguaggio deterministico: GLR(1): L=L(G). Se inoltre L e’ prefix-free allora: GLR(0): L=L(G). Altrimenti detto LT* CFG deterministico e TGLR(0): L=L(G).
Linguaggi LL ed LR cont. Teorema. Dato un linguaggio L per cui esiste una grammatica G, LL(k) e priva di -produzioni, tale che L=L(G) e k>1, allora esiste una grammatica G’, LL(k-1) tale che L=L(G’). Teorema. Per ogni k 0 la classe dei linguaggi LL(k) e’ propriamente inclusa in quella dei linguaggi LL(k+1).