Corso di Laurea in Informatica A.A. 2015/2016 · genericamente con θ) generata dall’analizzatore...
Transcript of Corso di Laurea in Informatica A.A. 2015/2016 · genericamente con θ) generata dall’analizzatore...
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Linguaggi formali e compilazioneCorso di Laurea in Informatica
A.A. 2015/2016
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
L’input per il parser
◮ Nel contesto della compilazione l’input per il parser ècostituito da una stringa di token (che indicheremogenericamente con θ) generata dall’analizzatorelessicale.
◮ Per semplicità di linguaggio ignoreremo la presenzadello scanner e supporremo che il parser leggadirettamente θ dallo stream di input.
◮ Per individuare la “fine” della stringa di input, neglialgoritmi di parsing supporremo che la stringa stessasia terminata da uno speciale simbolo, non presentefra i token del linguaggio, ad esempio $.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
L’input per il parser
◮ Se S è l’assioma iniziale della grammatica, pertenere conto del simbolo di terminazioneintroduciamo “formalmente” un nuovo assioma S ′,con la sola produzione S ′ → S$.
◮ In questo modo, S ⇒∗ θ se e solo se S ′ ⇒∗ θ$.◮ Nel seguito lasceremo implicita questa “aggiunta”
alla grammatica, a meno che non risulti importanteconsiderarla per comprendere meglio qualche altroconcetto.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Tipi di parser
◮ Una prima classificazione suddivide il parsing inaccordo all’ordine di costruzione del parse tree per θ.
◮ Nel parsing top-down l’albero viene costruito apartire dalla radice, corrispondente all’assiomainiziale.
◮ Equivalentemente, possiamo dire che nel parsingtop-down si cerca una derivazione canonica sinistraper θ$ partendo da S ′.
◮ Si noti che la costruzione top-down dell’albero diderivazione corrisponde in modo naturale alriconoscimento di categorie sintattiche (es, uncomando o una espressione) in termini delle particostituenti.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Tipi di parser (continua)
◮ Nel parsing bottom-up il parse tree per θ vienecostruito procedendo dalle foglie verso la radice.
◮ Equivalentemente, possiamo dire che nel parsingbottom-up si cerca una derivazione canonica (destra)per la stringa θ$ applicando riduzioni successive.
◮ Una riduzione non è nient’altro che l’applicazione “insenso opposto” di una produzione della grammatica.
◮ Si noti che la costruzione bottom-up dell’albero diderivazione corrisponde in modo naturale alriconoscimento di singole porzioni di un programmae alla loro composizione in parti più complesse.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Tipi di parser (continua)
◮ I tipi di parser più diffusi includono:◮ parser a discesa ricorsiva con backtracking,◮ parser a discesa ricorsiva senza backtracking
(parsing predittivi),◮ parser di tipo shift-reduce.
◮ I parser a discesa ricorsiva sono di tipo top-down,mentre i parser shift-reduce sono di tipo bottom-up.
◮ Considereremo sottoinsiemi di grammatiche libereper cui si possono costruire parser efficienti adiscesa ricorsiva (grammatiche LL(1)) o di tiposhift-reduce (grammatiche LR(1))
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Scelta della produzione da usare al genericopasso
◮ In entrambi i tipi di parser (top-down o bottom-up) lascelta della produzione da utilizzare (rispettivamenti,in avanti o all’indietro) viene effettuata in funzionedello stato interno del parser e della prossimaporzione di input (1 o più caratteri).
◮ Come vedremo, lo stato interno del parser saràtipicamente espresso dall’informazione memorizzatanella cima di una struttura dati stack.
◮ Il numero di caratteri di input considerati perprendere la decisione è noto invece comelookahead.
◮ Noi saremo interessati di norma ad un lookahead diun carattere.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Algoritmo generale
◮ Un parser a discesa ricorsiva (d.r.) costruisce ilparse tree (eventualmente non in modo esplicito) apartire dall’assioma ed esaminandoprogressivamente l’input.
◮ Al generico passo, l’algoritmo è idealmente“posizionato” su un nodo x dell’albero.
◮ Se il nodo è una foglia etichettata con un simboloterminale a, l’algoritmo controlla se il prossimosimbolo in input coincide con a.
◮ In caso affermativo fa avanzare il puntatore di input,altrimenti (nel caso più semplice) dichiara errore.
◮ Se invece il nodo è un simbolo non terminale A,sceglie una produzione A→ X1X2 . . .Xk , crea i nodi(figli di A) etichettandoli con X1,X2, . . . ,Xk , e passaricorsivamente ad esaminare tali nodi, da sinistraverso destra.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Algoritmo generale (continua)
◮ Da un punto di vista implementativo, un parser a d.r.può essere realizzato come una collezione diprocedure mutuamente ricorsive, una per ognisimbolo non terminale della grammatica.
◮ Il problema fondamentale consiste nella “scelta” dellaproduzione da applicare, nel caso in cui (per un datonon terminale) ne esista più d’una.
◮ Lo pseudocodice nella diapositiva seguente lasciaaperto questo problema, che andremosuccessivamente a risolvere in almeno due modidiversi.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Procedura per il generico non terminale A
1: Scegli “opportunamente” una produzioneA→ X1X2 . . .Xk (Xj ∈ V)
2: for j = 1, . . . , k do
3: if Xj ∈ N then
4: Xj()5: else
6: x ← READ()7: if Xj 6= x then
8: ERROR() {Include il caso x = EOF}
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio◮ Si consideri la seguente grammatica, che genera il
“solito” linguaggio {anb
m : n > 0,m ≥ 0}:
S → aA
A → aA | B
B → bB | ǫ
◮ Su input ab un parser a d.r. nondeterministicoproduce la seguente derivazione:
S
S
Aa
S
A
B
a
S
A
B
Bb
a
S
A
B
B
ǫ
b
a
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Implementazione concreta
◮ Per eliminare il non determinismo insito nel codiceprecedente, una prima soluzione consistenell’esplorare tutte le possibili produzioni relative algenerico non terminale A prima eventualmente didichiarare errore.
◮ Se una particolare produzione fallisce, ma prima delfallimento sono stati letti simboli di input, ènecessario operare un backtracking sull’input stesso.
◮ Per i parser a discesa ricorsiva ciò può esseresufficientemente agevole (dal punto di vistadell’implementatore), anche se computazionalmentepesante.
◮ Questa prima variante è mostrata nella diapositivasuccessiva.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Procedura con backtracking per A
1: SAVEINPUTPOINTER()2: for all produzione A→ X1X2 . . .Xk (Xj ∈ V) do
3: fail ← False
4: for j ← 1 . . . k do
5: if Xj ∈ N and Xj() then
6: continue
7: if Xj ∈ T then
8: x ← READ()9: if Xj = x then
10: continue
11: RESTOREINPUTPOINTER()12: fail ← True
13: break
14: if not fail then
15: return True
16: return False
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Procedura con backtracking per S)
◮ La procedura precedente va modificata nel casodell’assioma S che deve dichiarare il successocomplessivo o il fallimento
◮ Per questo è sufficiente rimpiazzare la riga 16 con ilseguente codice:
16: if not EOF() then
17: FAIL()18: else
19: SUCCESS()
dove FAIL() e SUCCESS() sono due opportuneprocedure che “informano” il main program sull’esitodel parsing
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio◮ Riconsideriamo la grammatica
S → aA
A → aA | B
B → bB | ǫ
◮ Su input ab un parser a d.r. con backtrackingpotrebbe produrre la seguente derivazione:
S
S
Aa
S
A
Aa
a
S
A
B
a
S
A
B
Bb
a
S
A
B
Bb
a
S
A
B
Bb
a
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Parsing a discesa ricorsiva (continua)◮ Se analizziamo attentamente il codice del parser a
d.r., comprendiamo perché una grammatica conricorsioni a sinistra sia inadatta al parsing a discesaricorsiva.
◮ Supponiamo, infatti, che ad un determinato passo ilparser sia “posizionato” su un nodo (etichettato con ilnon terminale) A dell’albero.
◮ Supponiamo inoltre che la prima produzione cheviene (tentativamente) applicata abbia una ricorsionea sinistra, sia cioè del tipo A→ Aα (dove α è unaqualunque stringa di terminali e/o non terminali).
◮ Accade allora che il codice relativo al non terminaleA:
◮ consideri il primo simbolo della parte sinistra dellaproduzione, che è ancora A;
◮ chiami ricorsivamente la procedura per A,innescando così un ciclo infinito.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio
◮ Per la grammatica con precedenza di operatori (cheabbiamo già incontrato):
E → E + T | T
T → T × F | F
F → number | (E)
le procedure per i non terminali E e T innescanopotenzialmente un ciclo infinito.
◮ Ad esempio, su input number + number, laproduzione corretta da applicare inizialmente èE → E + E (se si applica E → number laderivazione fallisce e bisogna operare backtrackingsull’input), ma questa innesca un ciclo infinito.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio (continua)
E
E
T+E
E
T+E
T+E
E
T+E
T+E
T+E ......
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Parsing a discesa ricorsiva (continua)◮ Una soluzione migliore rispetto all’esplorazione
esaustiva delle possibili derivazioni consistenell’usare un certo numero di caratteri di lookaheadper decidere la prossima produzione da utilizzare.
◮ Naturalmente, se questa strada possa esserepercorsa con successo dipende dalla grammatica.
◮ Consideriamo la semplice grammatica:
A → aA | bB
B → ǫ | bB
◮ Per entrambi i non terminali, la scelta dellaproduzione da usare può essere fatta guardandosolo il prossimo simbolo x in input.
◮ Per A: se x = a, usa la produzione A→ aA; sex = b, usa la produzione A→ bB.
◮ Per B. se x = $ (end of input), usa la produzioneB → ǫ; se x = b, usa la produzione B → bB;
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Grammatiche LL(1)
◮ Un parser predittivo può essere realizzatoagevolmente nel caso di grammatiche cosiddetteLL(1).
◮ La doppia L sta per Left-Left, ad indicare che l’input èletto da sinistra verso destra e che la derivazioneprodotta è canonica sinistra.
◮ Il “parametro” 1 indica che un carattere di lookaheadè sufficiente per decidere correttamente laproduzione da utilizzare.
◮ Più in generale, si possono considerare grammaticheLL(k), dove sono sufficienti (e, in generale,necessari) k caratteri di lookahead.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Grammatiche “non” LL(k)
◮ Nessuna grammatica con ricorsioni a sinistra puòessere LL(k), per nessun k .
◮ Anche nel caso in cui esistano produzioni conprefissi comuni la quantità di lookahead necessarianon è limitabile a priori.
◮ Un esempio grammatica con prefissi comuni è datadal più volte esaminato caso del “dangling else”.
S → I | A
I → if B then S | if B then S else S
A → a, B → b
◮ Infatti, la quantità di codice che può essere presentefra le keyword then e else non è limitabile a priori.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Grammatiche “non” LL(k) (continua)
◮ Un altro esempio è dato dalla grammatica:
A → aB | aC
B → aB | b
C → aC | c
◮ Su input anb è necessario un lookahead di n + 1
caratteri per decidere inizialmente che la produzionecorretta è A→ aB.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Grammatiche LL(1)
◮ Analizziamo ora in dettaglio le caratteristiche chedeve avere una grammatica G per potersi qualificareLL(1).
◮ Consideriamo un generico non terminale per il qualeesistano almeno due produzioni, poniamo
A → α | β
◮ Se vale simultaneamente che α∗
⇒ aα′ e β∗
⇒ aβ′,cioè se da α e da β si possono derivare stringhe cheiniziano con lo stesso simbolo non terminale, alloraG non è LL(1).
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempi◮ Il caso più semplice è quando α e β iniziano con lo
stesso simbolo terminale.◮ Ad esempio, se la grammatica contiene le produzioni
S → aA | aB non può essere LL(1).◮ Tuttavia il problema può non essere di così
immediata evidenza.◮ Ad esempio, la grammatica
S → aA | B
A → aA | ǫ
B → b | C
C → aB | c
non è LL(1) perché B ⇒ C ⇒ aB e dunque laprocedura per il non terminale S, su input a . . . nonpuò decidere quale produzione usare (S → aA
oppure S → B) guardando solo il primo simbolo diinput.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Grammatiche LL(1) (continua)
◮ La situazione appena descritta non è l’unica chedeve essere evitata affinché la grammatica sia LL(1).
◮ Un secondo vincolo è che, sempre considerandouna coppia di produzioni A→ α | β, se risulta α
∗
⇒ ǫ
allora non può accadere che β∗
⇒ ǫ (e viceversa).◮ Infatti, in caso contrario, qualunque sia il prossimo
simbolo di input entrambe le produzioni potrebberoteoricamente essere valide.
◮ Anche questo non basta ancora; è infatti necessarioche sia verificata un’ultima condizione.
◮ Se α∗
⇒ ǫ e β∗
⇒ aβ′, allora non deve accadere cheS
∗
⇒ γ1Aaγ2, dove β′, γ1, γ2 ∈ V∗.
◮ In altri termini a non deve apparire subito dopo A inalcuna forma di frase.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio◮ Si consideri la grammatica
S → A | B
A → aA | ǫ
B → Aab
◮ Su input la stringa “a” la procedura per il nonterminale S non può decidere correttamente laproduzione da usare.
◮ Risulta infatti:
S ⇒ A⇒ aA⇒ a
S ⇒ A⇒ aA⇒ a
e
S ⇒ B ⇒ Aab⇒ ab
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
FIRST e FOLLOW◮ Per esprimere in modo compatto le condizioni che
qualificano come LL(1) una data grammatica,introduciamo le funzioni FIRST e FOLLOW .
◮ Data G = (N ,T ,P,S) e data una stringaα ∈ (N ∪ T )∗, si definisce FIRST (α) l’insieme deisimboli terminali con cui può iniziare una frasederivabile da α, più eventualmente ǫ se α⇒∗ ǫ:
FIRST (α) = {x ∈ T |α⇒∗ xβ, β ∈ T ∗}
∪ {ǫ} , se α⇒∗ ǫ.
◮ Per ogni non terminale A ∈ N , FOLLOW (A) èl’insieme dei terminali che si possono trovareimmediatamente alla destra di A in una forma difrase di una qualche derivazione canonica (destra osinistra):x ∈ FOLLOW (A) se S ⇒∗ αAxβ, con α, β ∈ V∗.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Calcolo di FIRST (α)
◮ Definiamo innanzitutto come si calcola FIRST (α) nelcaso in cui α sia un singolo simbolo dellagrammatica, cioè α = X con X ∈ N ∪ T .
◮ Se X è un terminale, si pone naturalmenteFIRST (X) = {X};
◮ se X è un non terminale il calcolo procede per passi,con l’inizializzazione FIRST (X) = {}.
◮ Se esiste la produzione X → X1 . . .Xn, e risultaǫ ∈ FIRST (Xj), j = 1, . . . , k − 1, poniamoFIRST (X ) = FIRST (X ) ∪ {x} per ogni simbolox ∈ FIRST (Xk).
◮ Infine, se esiste la produzione X → ǫ oppureǫ ∈ FIRST (Xj), j = 1, . . . , k , poniamoFIRST (X ) = FIRST (X ) ∪ {ǫ}.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempi
◮ Si riconsideri la grammatica per le espressioni che“forza” la precedenza di operatori:
E → E + T | T
T → T × F | F
F → number | (E)
◮ Per questa grammatica risulta◮ FIRST (F ) = {( ,number};◮ FIRST (T ) = FIRST (F );◮ FIRST (E) = FIRST (T ).
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempi◮ La seguente grammatica genera lo stesso linguaggio
della precedente
E → (E)E ′ | number E ′
E ′ → +E E ′ | × E E ′ | ǫ
◮ Risulta◮ FIRST (E) = {( ,number}◮ FIRST (E ′) = {+,×, ǫ}
◮ Si consideri ora la grammatica per anb
mc
k
A → aA | BC
B → bB | ǫ
C → cC | ǫ
◮ Per questa grammatica risulta◮ FIRST (C) = {c, ǫ}◮ FIRST (B) = {b, ǫ}◮ FIRST (A) = {a,b,c, ǫ}
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Calcolo di FIRST (α) (continua)
◮ Il calcolo di FIRST (α), dove α = X1 . . .Xn è unagenerica stringa di terminali e nonterminali, può oraessere svolto nel modo seguente
◮ a ∈ FIRST (α) se e solo se, per qualche indicek ∈ 1, . . . n, risulta a ∈ FIRST (Xk) e ǫ ∈ FIRST (Xj),j = 1, . . . , k − 1 (si suppone sempre ǫ ∈ FIRST (X0)).
◮ Se ǫ ∈ FIRST (Xj), j = 1, . . . ,n, allora ǫ ∈ FIRST (α).◮ Ad esempio, nel caso della seconda grammatica del
lucido precedente
A → aA | BC
B → bB | ǫ
C → cC | ǫ
risulta: FIRST (aA) = {a} e FIRST (BC) = {b,c, ǫ}.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Calcolo di FOLLOW (A)
◮ Il calcolo di FOLLOW (A), per un generico nonterminale A, può essere svolto in questo modo.
◮ Se esiste la produzione B → αAβ, tutti i terminali inFIRST (β) si inseriscono in FOLLOW (A).
◮ In particolare, poiché (almeno implicitamente) esistesempre la produzione S ′ → S$, il simbolo speciale $sta sempre nel FOLLOW (S).
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Calcolo di FOLLOW (A)
◮ Se esiste la produzione B → αA, tutti i terminali chestanno in FOLLOW (B) si inseriscono inFOLLOW (A).
◮ Infatti, se esiste una derivazione S ⇒∗ βBγ, allorausando la produzione B → αA abbiamo anche:
S ⇒∗ βBγ ⇒ βαAγ
◮ Dunque ciò che segue B in una forma di frase (cioè ilFIRST (γ)) può anche seguire A.
◮ Si arriva alla stessa conclusione anche nel caso incui B → αAδ e ǫ ∈ FIRST (δ).
◮ Infatti:
S ⇒∗ βBγ ⇒ βαAδγ ⇒∗ βαAγ
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio
◮ Consideriamo ancora la grammatica
E → (E)E ′ | number E ′
E ′ → +E E ′ | × E E ′ | ǫ
◮ Possiamo subito stabilire che FOLLOW (E) include ilsimbolo $ e il simbolo ); inoltre contiene i simboli inFIRST (E ′) (eccetto ǫ) e cioè + e ×.
◮ FOLLOW (E ′) include FOLLOW (E), a causa (adesempio) della produzione E → numberE ′.
◮ La produzione E ′ → +EE ′, unitamente a E ′ → ǫ,stabilisce che vale anche il contrario, e cioè cheFOLLOW (E) include FOLLOW (E ′).
◮ Mettendo tutto insieme si ottieneFOLLOW (E) = FOLLOW (E ′) = {$, ) ,+,×}.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio
◮ Per la grammatica
A → aA | BC | ǫ
B → bB | ǫ
C → cC | ǫ
risulta FOLLOW (A) = FOLLOW (C) = {$} eFOLLOW (B) = {c, $}.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio
◮ Per la grammatica con precedenza di operatori:
E → E + T | T
T → T × F | F
F → number | (E)
risulta:◮ FOLLOW (E) = {$,+, )};◮ FOLLOW (T ) = FOLLOW (E) ∪ {×};◮ FOLLOW (F ) = FOLLOW (T ).
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Grammatiche LL(1) (continua)
◮ Possiamo ora rivedere in modo più compatto lanozione di grammatica LL(1).
◮ Una grammatica è LL(1) se, qualora esistano dueproduzioni A→ α e A→ β, risulta:
FIRST (α) ∩ FIRST (β) = {} ,
◮ Inoltre, se α∗
⇒ ǫ, deve valereFOLLOW (A) ∩ FIRST (β) = {}.
◮ Analogamente, se β∗
⇒ ǫ, deve valereFOLLOW (A) ∩ FIRST (α) = {}.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio◮ Si consideri la grammatica
A → B E
B → C | D
C → ǫ | cc
D → ǫ | dd
E → c | d
◮ Poiché risulta FIRST (C) ∩ FIRST (D) = {ǫ}, lagrammatica non è LL(1).
◮ Infatti, supponiamo che la stringa in input inizi con c.Dopo la riscrittura dell’assioma il parser verrebbe atrovarsi con la forma di frase BE e il carattere c ininput.
◮ A questo punto potrebbe essere corretto derivaretanto CE (se l’input fosse, ad esempio, ccd) quantoDE (se l’input fosse c).
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio◮ Si modifichi la precedente grammatica nel modo
seguente
A → B E
B → C | D
C → ǫ | cc
D → dd
E → c | d
(cancellando cioè la produzione D → ǫ).◮ Poiché risulta FIRST (D)∩ FOLLOW (B) = {d}, la
grammatica non è LL(1).◮ Il problema si verifica con input che inizia con d,
perché potrebbe essere corretto (dopo la derivazioneiniziale) derivare tanto CE (se l’input fosse d) quantoDE (se l’input fosse, ad esempio ddc).
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio
◮ Consideriamo ancora la grammatica con precedenzadi operatori:
E → E + T | T
T → T × F | F
F → number | (E)
◮ Sappiamo già che tale grammatica non è LL(1)perché contiene ricorsioni a sinistra.
◮ Possiamo anche verificare, ad esempio, cheFIRST (E)∩ FIRST (T ) k {number}.
◮ Eliminando la left-recursion si può però ottenere unagrammatica equivalente che è LL(1)
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio (continua)◮ Per la grammatica modificata
E → TE ′
E ′ → +TE ′ | ǫ
T → FT ′
T ′ → ×FT ′ | ǫ
F → number | (E)
dobbiamo solo verificare che FOLLOW (E ′) noncontenga il simbolo + e che FOLLOW (T ′) noncontenga il simbolo ×.
◮ È facile verificare che risulta:◮ FOLLOW (E) = FOLLOW (E ′) = {$, )};◮ FIRST (E ′) = {+, ǫ};◮ FOLLOW (T ) = (FIRST (E ′)\ {ǫ}) ∪ FOLLOW (E ′) ={+, $, )};
◮ FOLLOW (T ′) = FOLLOW (T ) = {+, $, )};
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Tabella di parsing LL(1)
◮ Per una grammatica LL(1) è possibile costruire(utilizzando le funzioni FIRST e FOLLOW ) unatabella, detta tabella di parsing, che, per ogni nonterminale A e ogni terminale x , prescrive ilcomportamento del parser.
◮ Indicheremo con MG la tabella di parsing relativa allagrammatica G (o semplicemente con M, se lagrammatica è evidente).
◮ La generica entry MG[A, x ] della tabella puòcontenere una produzione A→ α di G, oppureessere vuota, ad indicare che si è verificato unerrore.
◮ Disponendo di MG, la prima riga dell’algoritmo adiscesa ricorsiva (cioè la scelta della produzione)viene sostituita da un lookup alla tabella MG[A, x ].
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Costruzione della tabella di parsing
◮ L’algoritmo di costruzione della parsing table è moltosemplice ed è formato da un ciclo principale nelquale si prendono in considerazione tutte leproduzioni.
◮ Per ogni produzione A→ α:◮ per ogni simbolo x in FIRST (α) si pone
M[A, x ]=‘A→ α’;◮ se ǫ ∈ FIRST (α), per ogni simbolo y in FOLLOW (A)
si pone M[A, y ]=‘A→ α’.
◮ Tutte le altre entry della tabella vengono lasciatevuote (ad indicare l’occorrenza di un errore).
◮ Se la grammatica è LL(1), nessuna entry dellatabella viene riempita con più di una produzione.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio◮ Consideriamo ancora la grammatica
E → TE ′
E ′ → +TE ′ | ǫ
T → FT ′
T ′ → ×FT ′ | ǫ
F → number | (E)
◮ Il calcolo completo degli insiemi FIRST e FOLLOWproduce:
◮ FIRST (F ) = FIRST (T ) = FIRST (E) ={number, (};
◮ FIRST (E ′) = {+, ǫ}, FIRST (T ′) = {×, ǫ};◮ FOLLOW (E) = FOLLOW (E ′) = {$, )};◮ FOLLOW (T ) = (FIRST (E ′)\ {ǫ}) ∪ FOLLOW (E ′) ={+, $, )};
◮ FOLLOW (T ′) = FOLLOW (T ) = {+, $, )};◮ FOLLOW (F ) = (FIRST (T ′)\ {ǫ}) ∪ FOLLOW (T ′) ={×,+, $, )}.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio (continua)
◮ L’algoritmo prima delineato produce quindi laseguente tabella di parsing
N.T.Simbolo di input
number ( ) + × $E E→TE′ E→TE′
E′ E′→ǫ E′
→+TE′ E′→ǫ
T T→FT ′ T→FT ′
T ′ T ′→ǫ T ′
→ǫ T ′→×FT ′ T ′
→ǫ
F F→number F→(E)
in cui le entry vuote corrispondono ad una situazionedi errore.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio
◮ Se tentassimo di produrre la tabella di parsing per lagrammatica
E → (E)E ′ | number E ′
E ′ → +E E ′ | × E E ′ | ǫ
otterremmo
N.T.Simbolo di input
number ( ) + × $E E→numberE′ E→(E)E′
E′ E′→ǫ
E′→+EE′ E′
→×EE′
E′→ǫ
E′→ǫ E′
→ǫ
◮ Con in input il carattere + o il carattere ×, il parsernon saprebbe quindi come procedere.
◮ Il non soddisfacimento delle proprietà LL(1) è inquesto caso una conseguenza dell’ambiguità dellagrammatica.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio (continua)◮ A volte è possibile risolvere il conflitto presente in
una entry della tabella scegliendo opportunamente laproduzione da applicare (fra quelle in conflitto).
◮ Naturalmente la scelta non deve compromettere lacapacità di riconoscere il linguaggio generato dallagrammatica.
◮ Nel caso dell’esempio, si deve optare in favore delleproduzioni E ′ → +EE ′ e E ′ → ×EE ′, anziché E ′ → ǫ
(si provi, ad esempio, a riconoscere la stringanumber + number).
◮ Si noti tuttavia che, pur avendo risolto l’ambiguità,l’interpretazione delle espressioni che derivadall’albero di parsing non è quella “naturale” (nonviene soddisfatta la precedenza naturale deglioperatori).
◮ Al riguardo, si provi a costruire l’albero di parsing perla stringa number× number + number.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio◮ Consideriamo la seguente grammatica (che esprime,
usando simbologia diversa, il problema del dangling
else che abbiamo già esaminato in precedenza):
S → i E t S e S | i E t S | a
E → b
◮ Tale grammatica presenta produzioni con prefissocomune e dunque non è idonea al parsing a d.r.
◮ È possibile eliminare i prefissi comuni, ottenendo:
S → i E t S S′ | a
S′ → e S | ǫ
E → b
e risulta◮ FIRST (S) = {i, a}; FIRST (S′) = {e, ǫ};
FIRST (E) = {b};◮ FOLLOW (S) = FOLLOW (S′) = {$, e}.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio (continua)
◮ La grammatica modificata non è ancora LL(1) inquanto FIRST (eS) ∩ FOLLOW (S′) = {e}.
◮ Ciò si riflette in una definizione multipla per una entrydella tabella di parsing:
N.T.Simbolo di input
i t e a b $S S→iE tSS′ S→a
S′ S′→eS
S′→ǫ
S′→ǫ
E E→b
◮ Tuttavia se, con in input il carattere e, il parservenisse “istruito” a scegliere sempre la produzioneS′ → eS, l’ambiguità si risolverebbe (e pure conl’intepretazione “naturale”, che associa ogni else althen più vicino).
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Implementazione non ricorsiva
◮ È possibile dare un’implementazione non ricorsiva diun parser predittivo (cioè che non richiedebacktracking) utilizzando esplicitamente una pila.
◮ La pila serve per memorizzare i simboli della partedestra della produzione scelta ad ogni passo.
◮ Tali simboli verranno poi “confrontati” con l’input (seterminali) o ulteriormente riscritti (se non terminali).
◮ Il comportamento del parser è illustrato nellaseguente diapositiva.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Implementazione non ricorsiva (continua)
◮ Inizialmente, sullo stack sono contenuti (partendo dalfondo) i simboli $ ed S, mentre la variabile z punta alprimo carattere di input.
◮ Al generico passo, il parser controlla il simbolo Xsulla testa dello stack;
◮ se X è un non terminale e M[X , z] = ‘X → X1 . . .Xk ‘,esegue una pop dallo stack (rimuove cioè X ) seguitada k push dei simboli Xk , . . . ,X1, nell’ordine;
◮ se X è un non terminale e M[X , z] = ‘error ‘, segnalauna condizione di errore;
◮ se X è un terminale e X = z, esegue una pop e faavanzare z;
◮ se X è un terminale e X 6= z, segnala unacondizione di errore.
◮ Le operazioni terminano quando X = $ e la stringa èaccettata se z = $.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio
◮ Consideriamo nuovamente la grammatica delleespressioni con precedenza di operatore, della qualericordiamo la tabella di parsing:
N.T.Simbolo di input
number ( ) + × $E E→TE′ E→TE′
E′ E′→ǫ E′
→+TE′ E′→ǫ
T T→FT ′ T→FT ′
T ′ T ′→ǫ T ′
→ǫ T ′→×FT ′ T ′
→ǫ
F F→number F→(E)
◮ Supponendo di avere la stringa number+ number
in input, la seguente tabella illustra il progressivocontenuto dello stack e dell’input.
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esempio (continua)
Stack Input$E number+ number$$E ′T number+ number$$E ′T ′F number+ number$$E ′T ′number number+ number$$E ′T ′ +number$$E ′ +number$$E ′T+ +number$$E ′T number$$E ′T ′F number$$E ′T ′number number$$E ′T ′ $$E ′ $$ $
LFC
ParsingGeneralità sul parsing
Parser a discesa ricorsiva
Esercizi proposti
◮ Si calcolini gli insiemi FIRST e FOLLOW per laseguente grammatica:
S → c | AS | BS
A → aB | ǫ
B → bA | ǫ
e si costruisca la relativa tabella di parsing per unparser a discesa ricorsiva.
◮ Si calcolino gli insiemi FIRST e FOLLOW per lagrammatica G2, che descrive le espressioni regolarisu {0,1}, dopo averla modificata in modo daeliminare i prefissi comuni. Se ne costruisca quindi latabella di parsing per un parser a discesa ricorsiva.