Analisi Sintattica
Nicola Fanizzi
Corso di Linguaggi di Programmazione
Dipartimento di Informatica
Università degli Studi di Bari
5 maggio 2014
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
1 / 93
1
2
3
4
Introduzione
Definizione
Funzionalità del Parser
Modalità di Parsing
Gestione Errori
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Insiemi FIRST e FOLLOW
Forma Normale LL(k)
Trasformazioni utili
Analisi Discendente Guidata da Tabella
Algoritmo di Parsing
Grammatiche LL(1)
Gestione Errori
Analisi Top-down in Discesa Ricorsiva
Introduzione
Costruzione Parser senza gestione errori
Costruzione Parser con gestione errori
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
2 / 93
Introduzione
1
2
3
4
Introduzione
Definizione
Funzionalità del Parser
Modalità di Parsing
Gestione Errori
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Insiemi FIRST e FOLLOW
Forma Normale LL(k)
Trasformazioni utili
Analisi Discendente Guidata da Tabella
Algoritmo di Parsing
Grammatiche LL(1)
Gestione Errori
Analisi Top-down in Discesa Ricorsiva
Introduzione
Costruzione Parser senza gestione errori
Costruzione Parser con gestione errori
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
3 / 93
Introduzione
Definizione
Introduzione
Analisi Sintattica riconoscimento della struttura del programma
sorgente in termini della grammatica del LdP, con costruzione
dell’albero sintattico corrispondente in base ai simboli (token)
forniti dallo scanner
chiamata
(controllo)
sorgente
analizzatore
txt
lessicale
caratteri
token
analizzatore
sintattico
resto del
front-end
albero
di
derivazione
tabella
dei
simboli
La componente del compilatore che si occupa di questa attività è
l’analizzatore sintattico (o parser)
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
4 / 93
Introduzione
Funzionalità del Parser
Funzionalità del Parser
Analisi costruzione dell’albero di derivazione
alg. universali: alg. CYK o alg. di Earley
alg. discendenti (o top-down): dalla radice alle foglie
termina se G è in GNF
alg. ascendenti (o bottom-up): dalle foglie alla radice
termina se G non ha regole di contrazione e concatenazione
Segnalazione errori sintattici
segnalazione precisa della posizione degli errori
recupero in modo da portare a termine l’analisi se possibile
efficienza del processo
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
5 / 93
Introduzione
Modalità di Parsing
Attraversamento di un albero
Processo ricorsivo: partenza dalla radice;
per ogni nodo N possibile un’azione
post-ordine
pre-ordine
5
1
3
2
4
1
5
4
2
3
pre-ordine visita N e quindi attraversa tutti i suoi sotto-alberi da
sinistra a destra
post-ordine attraversa tutti i sotto-alberi di N (da sinistra a
destra) ed, al ritorno, visita N
in-ordine ...
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
6 / 93
Introduzione
Modalità di Parsing
Top-down Parsing
1●
2●
3●
○
○
○t 4
○t 5
○
4●
5●
○
○
●t 1
○ t2
○t 3
...
○t n
Si costruisce l’albero in pre-ordine:
Si parte con la costruzione della radice etichettata con S
ogni nodo di un non-terminale viene espanso seguendo una
regola che faccia corrispondere i terminali ai token in input
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
7 / 93
Introduzione
Modalità di Parsing
Bottom-up Parsing
3●
1●
●t 1
●t 2
2●
●t 3
●t 4
●t 5
●t6 ... ○tn
Si costruisce l’albero in post-ordine:
Si parte dai token in input e si costruiscono sotto-alberi fino a
quello con radice S
si costruiscono sotto-alberi crescenti partendo dai
sotto-alberi minimi rappresentati dai singoli token
non appena sia stato costruito un sotto-albero completo
(es. procedendo da sinistra verso destra)
si può costruire il loro padre (parte sinistra della regola)
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
8 / 93
Introduzione
Gestione Errori
Gestione Errori I
Panic-mode. Alla scoperta di un errore,
il parser scarta simboli in ingresso fino a ri-sincronizzarsi con
un token ammesso (in genere delimitatori)
vantaggi metodo universale di semplice implementazione;
va bene in caso di pochi errori attesi in una
stessa istruzione; non porta a loop infiniti
svantaggi può far scartare troppi token, sorvolando su altri
errori presenti.
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
9 / 93
Introduzione
Gestione Errori
Gestione Errori II
Recupero a livello di frase. alla scoperta di un errore il
parser può operare una correzione locale sull’input restante
rimpiazzando il prefisso con una stringa che consente di
continuare (es. ";" invece di ",")
Spesso usato con parser discendenti
vantaggi molto usato; può correggere ogni tipo di stringa
svantaggi non si sa far fronte a situazioni dove l’errore
reale sia avvenuto prima del punto determinato;
può portare a loop infiniti
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
10 / 93
Introduzione
Gestione Errori
Gestione Errori III
Produzioni d’errore. Se si ha un’idea degli errori comuni,
si può estendere la grammatica con produzioni che generino
costrutti erronei tipici
con tali produzioni, si rileva e notifica l’errore corrispondente
e lo si può gestire meglio
Correzione globale. Idealmente bisognerebbe modificare il
meno possibile sulla stringa di input da correggere:
esistono algoritmi che scelgono una sequenza minima di
cambi per ottenere una correzione dal minimo costo globale
svantaggi metodo prettamente teorico e comunque costoso
in termini di spazio e tempo
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
11 / 93
Analisi Sintattica Discendente
1
2
3
4
Introduzione
Definizione
Funzionalità del Parser
Modalità di Parsing
Gestione Errori
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Insiemi FIRST e FOLLOW
Forma Normale LL(k)
Trasformazioni utili
Analisi Discendente Guidata da Tabella
Algoritmo di Parsing
Grammatiche LL(1)
Gestione Errori
Analisi Top-down in Discesa Ricorsiva
Introduzione
Costruzione Parser senza gestione errori
Costruzione Parser con gestione errori
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
12 / 93
Analisi Sintattica Discendente
Introduzione
Analisi Sintattica Discendente I
Riconoscimento della struttura della frase in ingresso (sorgente)
costruendo l’albero sintattico dalla radice alle foglie seguendo la
derivazione canonica sinistra
ad ogni passo si espande il non-terminale più a sinistra nella
forma di frase generata dal parser fino a quel momento
Se la forma di frase è uAω con
u ∈ Σ∗
A ∈ V simbolo non-terminale corrente da espandere
ω ∈ (Σ ∪ V )∗
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
13 / 93
Analisi Sintattica Discendente
Introduzione
Analisi Sintattica Discendente II
Algoritmi di costruzione/ricerca su albero implicito
1
depth-first
2
breadth-first
Osservazioni
difficoltà principale: scelta della parte destra espandere nelle
produzioni per il non-terminale corrente
1
necessità di fare backtracking (fonte di inefficienza)
2
spazio di ricerca troppo ampio
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
14 / 93
Analisi Sintattica Discendente
Introduzione
Algoritmo Depth-First con backtracking
input G = (Σ, V , S, P): grammatica; z ∈ Σ∗ : stringa
output b: boolean
albero t.new(S); pila s.new()
token ← z.nextToken()
// token corrente sull’input
simbolo ← t.root
// simbolo corrente (in Σ ∪ V)
done ← false
// fine lavoro
do
if simbolo ∈ Σ and simbolo = token then
simbolo ← prossimo simbolo/nodo sulla frontiera
token ← z.nextToken()
else if simbolo ∈ Σ and simbolo 6= token then
if not p.empty() then backtrack(p, simbolo, token, t)
else if simbolo = A ∈ V then // considera la prima A-regola A −→ w0 ∈ P
for each x ∈ w0 do t.creaFiglio(A, x)
for each A −→ wi ∈ P, i 6= 0 do p.push(wi )
if ∃B ∈ V : w0 = uBy, u ∈ Σ∗ then simbolo ← B else simbolo ← ]
if simbolo = ] and token = EOF then done ← true
else if simbolo = ] and token 6= EOF then
if not p.empty() then backtrack(p, simbolo, token, t)
while not (done or p.empty())
return done
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
15 / 93
Analisi Sintattica Discendente
Introduzione
Algoritmo Depth-First con stack implicito
DFParse(σ, φ): Boolean
σ ∈ (Σ ∪ V )
// simbolo corrente
φ ∈ (Σ ∪ V)∗
// suffisso frontiera corrente da anlizzare
τ ← scanner.nextToken()
// token corrente
if σ = ] then
if τ = EOF then return true else return false
if σ ∈ Σ and σ 6= τ then return false
else if σ ∈ Σ and σ = τ then
Sia φ = πφ0 con π ∈ (Σ ∪ V) e φ0 ∈ (Σ ∪ V)∗
σ ← π; φ ← φ0
τ ← z.nextToken()
else if σ = A ∈ V then
sottoalberoCompletato ← false
while ∃A −→ ω ∈ P and not sottoalberoCompletato do
Sia ω = σ 0 ω 0
sottoalberoCompletato ← DFParse(σ 0 , ω 0 φ)
return sottoalberoCompletato
Da invocare con: if DFParse(S], ) then accetta else errore
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
16 / 93
Analisi Sintattica Discendente
Introduzione
Algoritmo Depth-First – Esempio
(1)
Data una grammatica G
con le seguenti produzioni:
1o passo S =⇒ aAZ
(2)
2o passo aAZ =⇒ abWZ
(4)
(1)
(2)
(3)
(4)
(5)
(6)
S
A
A
W
B
Z
−→
−→
−→
−→
−→
−→
aAZ
bW
bB
c
d
e
consideriamo la frase
in ingresso abde
N. Fanizzi
Linguaggi di prog.+Lab
3o passo abWZ =⇒ abcZ
la stringa abc non è un prefisso
della stringa d’ingresso quindi
bisogna fare backtracking
(3)
2o passo aAZ =⇒ abBZ
(5)
3o passo abBZ =⇒ abdZ
(6)
4o passo abdZ =⇒ abde
Analisi Sintattica
5 maggio 2014
17 / 93
Analisi Sintattica Discendente
Introduzione
Algoritmo Breadth-First
input: G = (Σ, V , S, P): grammatica; z ∈ Σ∗ : stringa
output: Boolean
albero t.new(S); coda q.new(S)
do
nodo ← q.get() con nodo = uAσ e A ∈ V, u ∈ Σ∗ , σ ∈ V ∗
i ← 0 // indice dell’ultima produzione usata
espansioneCompleta ← false
do
k
if 6 ∃(A −→ w) ∈ P con k > i then espansioneCompleta ← true
if not espansioneCompleta then
p
p0
let A −→ ω ∈ P tale che ∀A −→ ω 0 ∈ P : p0 > p > i
if uwσ 6∈ Σ∗ and ∃v ∈ Σ∗ : v ∈ prefissi(z) ∧ v ∈ prefissi(uωσ) then
q.insert(uωσ)
t.creaNodoFiglio(uAσ, uωσ)
i←p
while not (espansioneCompleta or z = uωσ)
while not (q.empty() or z = uωσ)
return z = uωσ
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
18 / 93
Analisi Sintattica Discendente
Introduzione
Algoritmo Breadth-First - Esempio I
Sia G+ con produzioni
S −→ A
A −→ T|A+T
T −→ b|(A)
Per riconoscere (b+b) si può costruire l’albero
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
19 / 93
Analisi Sintattica Discendente
Introduzione
Algoritmo Breadth-First - Esempio II
b
(b)
T
(T)
((A))
(A)
(A+T+T)
(A+T)
(T+T)
S
(A+T+T+T)
(T+T+T)
((A)+T)
(b+T)
(b+b)
A
(A+T)+T
(A)+T
(T)+T
T+T
b+T
A+T
(A)+T+T
(A+T+T)+T
(T+T)+T
((A))+T
(b)+T
(A+T)+T+T
(T)+T+T
T+T+T
b+T+T
A+T+T
(A)+T+T+T
T+T+T+T
b+T+T+T
A+T+T+T
A+T+T+T+T+T
A+T+T+T+T
T+T+T+T+T
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
20 / 93
Analisi Sintattica Discendente
Introduzione
Analisi Discendente Deterministica
Problemi dell’Analisi Discendente
inefficienza causata dal non-determinismo
1
2
spazio: ampiezza in alg. BF
tempo: backtracking in alg. DF
ritrattazione azioni già intraprese
(per l’analisi semantica)
Rimedi:
1
trasformare la grammatica ai fini dell’analisi discendente
2
utilizzare l’informazione fornita dai simboli successivi
(lookahead)
Forme Normali per grammatiche libere
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
21 / 93
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Parsing deterministico predittivo
Sia z la stringa di terminali (token) da riconoscere
e si supponga già lavorato sul suo prefisso u ∈ Σ∗
∗
S =⇒ uAω
con A ∈ V, ω ∈ (Σ ∪ V)∗
Per scegliere la regola per A,
il lookahead sulla stringa di input aiuta a ridurre il numero di
opzioni: una sola regola nel caso deterministico
Se z = uaω allora a ∈ Σ è il simbolo di lookahead per scegliere la
regola per A, scartando le regole che non portano a tale prefisso
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
22 / 93
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Lookahead I
Esempio
Sia G con produzioni:
S −→ aS|cA
A −→ bA|cB|
B −→ cB|a|
per derivare acbb:
prefisso lookahead produzione derivazione
a
S −→ aS
S =⇒ aS
a
c
S −→ cA
=⇒ acA
ac
b
A −→ bA
=⇒ acbA
acb
b
A −→ bA
=⇒ acbbA
acbb
A −→ =⇒ acbb
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
23 / 93
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Lookahead II
Generalizzando al caso di stringhe di lookahead:
Definizione (insiemi di lookahead)
Data una grammatica libera G = (Σ, V, S, P) e A ∈ V:
l’insieme di lookahead di A è definito:
n
o
∗
∗
LA(A) = x ∈ Σ∗ | S =⇒ uAω =⇒ ux ∈ Σ∗
l’insieme di lookahead di A −→ ω è definito:
n
o
∗
∗
LA(A −→ ω) = x ∈ Σ∗ | S =⇒ uAβ ∧ ωβ =⇒ x
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
24 / 93
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Lookahead III
Per un dato simbolo non-terminale A ∈ V, siano
A −→ ω1 | · · · | ωn
le relative regole di produzione
Per una scelta univoca della regola da applicare guidata dalle
stringhe di lookahead devono valere:
Proprietà degli insiemi di lookahead
1
LA(A) =
n
[
LA(A −→ ωi )
i=1
2
N. Fanizzi
LA(A −→ ωi ) ∩ LA(A −→ ωj ) = ∅
Linguaggi di prog.+Lab
Analisi Sintattica
∀i, j : 1 ≤ i 6= j ≤ n
5 maggio 2014
25 / 93
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Lookahead IV
Esempio
Data la grammatica con produzioni:
S −→ Aabd|cAbcd
A −→ a|b|
Insiemi di lookahead per S e le sue produzioni:
LA(S) = {aabd, babd, abd, cabcd, cbbcd, cbcd}
LA(S −→ Aabd) = {aabd, babd, abd}
LA(S −→ cAbcd) = {cabcd, cbbcd, cbcd}
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
26 / 93
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Lookahead V
Insiemi di lookahead per le produzioni di A:
LA(A −→ a) = {aabd, abcd}
LA(A −→ b) = {babd, bbcd}
LA(A −→ ) = {abd, bcd}
Quanti simboli servono a scegliere tra le produzioni ?
Notare che: il prefisso ab può essere ottenuto
sia applicando A −→ a alla FDF Abcd,
sia applicando A −→ alla FDF Abd
Quindi un lookahead di due simboli non è sufficiente per
decidere tra le due regole
Conviene basarsi su stringhe che risultino le più brevi possibili
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
27 / 93
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Lookahead limitato I
Definizione (insiemi di lookahead limitati)
Data una grammatica libera G = (Σ, V, S, P), A ∈ V e k ∈ N:
l’insieme di lookahead di A di lunghezza k è definito:
LAk (A) = trunck (LA(A))
l’insieme di lookahead di A −→ ω di lunghezza k è definito:
LAk (A −→ ω) = trunck (LA(A −→ ω))
con trunck (T) = {u ∈ T | |u| ≤ k ∨ (uw ∈ T ∧ |u| = k)}
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
28 / 93
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Lookahead limitato II
Per k = 3, per la grammatica precedente risulta:
LAk (S −→ Aabd)
LAk (S −→ cAbcd)
LAk (A −→ a)
LAk (A −→ b)
LAk (A −→ )
=
=
=
=
=
{aab, bab, abd}
{cab, cbb, cbc}
{aab, abc}
{bab, bbc}
{abd, bcd}
Si osservi che k = 3 è sufficiente per decidere la regola giusta per
la grammatica in oggetto
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
29 / 93
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Lookahead limitato III
Esempio
Dato il linguaggio L = {ai abci | i > 0} generato dalle
grammatiche seguenti, studiamo i diversi insiemi di lookahead
G1 : S −→ aSc
S −→ aabc
G2 : S −→ aA
A −→ Sc
A −→ abc
G3 : S −→ aaAc
A −→ aAc
A −→ b
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
{aaa}
{aab}
{aa}
{ab}
{a}
{b}
5 maggio 2014
30 / 93
Analisi Sintattica Discendente
Insiemi FIRST e FOLLOW
Insiemi FIRST e FOLLOW I
Per incorporare l’informazione del lookahead nel parser
occorre generare gli insiemi (limitati) a partire dalla grammatica
Per ogni A ∈ V, LAk (A) contiene i prefissi (di lung. max k) di
stringhe generabili a partire da A
se A deriva stringhe di lunghezza inferiore a k, il resto del
lookahead viene da derivazioni che seguono A in una FDF
insiemi FIRSTk (A) e FOLLOWk (A) per costruire LAk (A):
FIRSTk (A) prefissi delle stringhe di token derivabili da A
FOLLOWk (A) prefissi delle stringhe di token che seguono le
stringhe derivabili da A
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
31 / 93
Analisi Sintattica Discendente
Insiemi FIRST e FOLLOW
Insiemi FIRST e FOLLOW II
Definizione (Insiemi FIRST limitati)
Data una grammatica libera G = (Σ, V, S, P) e k ∈ N,
per ogni FDF ω ∈ (V ∪ Σ)∗ :
∗
FIRSTk (ω) = trunck {x ∈ Σ∗ | ω =⇒ x}
Esempio Data la grammatica G con produzioni
P = {S −→ ABCabcd, A −→ a|, B −→ b|, C −→ c|}:
FIRST1 (ABC) = {a, b, c, }
FIRST2 (ABC) = {ab, ac, bc, a, b, c, }
FIRST3 (S) = {abc, aca, aba, aab, bca, bab, cab}
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
32 / 93
Analisi Sintattica Discendente
Insiemi FIRST e FOLLOW
Insiemi FIRST e FOLLOW III
Lemma (proprietà dei FIRST)
Data una grammatica libera G = (Σ, V, S, P), per ogni k ∈ N:
1
FIRSTk () = {}
2
FIRSTk (a) = {a},
3
FIRSTk (aβ) = {az | z ∈ FIRSTk−1 (β)},
4
FIRSTk (αβ) = trunck (FIRSTk (α) · FIRSTk (β))
5
se (A −→ ω) ∈ P: FIRSTk (ω) ⊆ FIRSTk (A)
N. Fanizzi
Linguaggi di prog.+Lab
∀a ∈ Σ
Analisi Sintattica
∀a ∈ Σ
5 maggio 2014
33 / 93
Analisi Sintattica Discendente
Insiemi FIRST e FOLLOW
Insiemi FIRST e FOLLOW IV
Definizione (Insiemi FOLLOW limitati)
Data una grammatica libera G = (Σ, V, S, P) e k ∈ N,
per ogni A ∈ V :
∗
FOLLOWk (A) = trunck {x ∈ Σ∗ | S =⇒ uAσ ∧ x ∈ FIRSTk (σ)}
Esempio Data la grammatica G con
P = {S −→ ABCabcd, A −→ a|, B −→ b|,
FOLLOW1 (S) = {}
FOLLOW1 (A) = {a, b, c}
FOLLOW1 (B) = {a, c}
FOLLOW1 (C) = {a}
N. Fanizzi
Linguaggi di prog.+Lab
C −→ c|} :
FOLLOW2 (S) = {}
FOLLOW2 (A) = {ab, bc, ba, ca}
FOLLOW2 (B) = {ca, ab}
FOLLOW2 (C) = {ab}
Analisi Sintattica
5 maggio 2014
34 / 93
Analisi Sintattica Discendente
Insiemi FIRST e FOLLOW
Insiemi FIRST e FOLLOW V
Osservazioni
FOLLOWk (B) si ottiene dalle regole che hanno B nella parte
destra: A −→ αBβ
Se A −→ αB ∈ P allora ogni stringa che segua A può seguire
anche B e le stringhe di token che seguono B appartengono a
quelle generabili da α concatenate a quelle derivate da A
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
35 / 93
Analisi Sintattica Discendente
Insiemi FIRST e FOLLOW
Insiemi FIRST e FOLLOW VI
Lemma (proprietà dei FOLLOW)
Data una grammatica libera G = (Σ, V, S, P), per ogni k ∈ N:
1
∈ FOLLOWk (S)
2
Se A −→ αB ∈ P allora
FOLLOWk (A) ⊆ FOLLOWk (B)
3
Se A −→ αBβ ∈ P allora
trunck (FIRSTk (β) · FOLLOWk (A)) ⊆ FOLLOWk (B)
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
36 / 93
Analisi Sintattica Discendente
Insiemi FIRST e FOLLOW
Da FIRST e FOLLOW a LA - I
Dalle definizioni dell’insieme di lookahead limitato a k token
(e della funzione di troncamento):
Teorema
Data una grammatica libera G = (Σ, V, S, P) e k ∈ N,
per ogni A ∈ V e A −→ ω ∈ P con ω = s1 s2 · · · sn :
LAk (A) = trunck (FIRSTk (A) · FOLLOWk (A))
LAk (A −→ ω) = trunck (FIRST(ω) · FOLLOWk (A))
= trunck (FIRST(s1 ) · · · FIRST(sn ) · FOLLOWk (A))
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
37 / 93
Analisi Sintattica Discendente
Insiemi FIRST e FOLLOW
Da FIRST e FOLLOW a LA - II
Esempio
Data G con P = {S −→ Aabd|cAbcd, A −→ a|b|}
FIRST3 (S)
FIRST3 (A)
FIRST3 (a)
FIRST3 (b)
FIRST3 (c)
FIRST3 (d)
FOLLOW3 (S)
FOLLOW3 (A)
=
=
=
=
=
=
=
=
{aab, bab, abd, cab, cbb, cbc}
{a, b, }
{a}
{b}
{c}
{d}
{}
{abd, bcd}
LA3 (S −→ Aabd) =
= trunck (FIRSTk (A)FIRSTk (a)FIRSTk (b)FIRSTk (d)FOLLOW3 (S)))
= trunck ({a, b, } · {a} · {b} · {d} · {})
= trunck ({aabd, babd, abd}) = {aab, bab, abd}
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
38 / 93
Analisi Sintattica Discendente
Forma Normale LL(k)
Forma Normale LL(k) - I
Classe di grammatiche (libere) in cui
L la stringa in ingresso va esaminata da sinistra a destra
(LeftToRight)
L va costruita la derivazione canonica sinistra
(Leftmost derivation)
k numero di simboli di lookahead per la scelta della produzione
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
39 / 93
Analisi Sintattica Discendente
Forma Normale LL(k)
Forma Normale LL(k) - II
Può essere utile considerare token aggiuntivi finali come
marcatori di fine stringa ]k , in modo che i LAk contengano
stringhe di k token
Se S non è ricorsivo basta aggiungere ]k a destra di ogni sua
produzione, altrimenti si considera un nuovo S0 −→ S]k
Definizione (grammatiche strong LL(k))
Una grammatica libera G = (Σ, V , S, P) con marcatore ]k è in
forma strong LL(k) se, per ogni coppia di derivazioni canoniche
sinistre
∗
∗
S =⇒ u1 Aσ1 =⇒ u1 ω1 σ1 =⇒ u1 zw1
∗
∗
S =⇒ u2 Aσ2 =⇒ u2 ω2 σ2 =⇒ u2 zw2
(con ui , wi , z ∈ Σ∗ , σi ∈ (V ∪ Σ)∗ , i = 1, 2 e |z| = k)
risulta necessariamente: ω1 = ω2
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
40 / 93
Analisi Sintattica Discendente
Forma Normale LL(k)
Forma Normale LL(k) - III
Teorema (caratterizzazione)
Una grammatica libera G = (Σ, V , S, P) è strong LL(k) sse,
LAk (A) risulta partizionabile dai LAk (A −→ ω) per ogni A ∈ V
Caratterizzazione negative:
Teorema (ricorsione sinistra)
Una grammatica libera G che abbia una variabile ricorsiva a
sinistra non può essere strong LL(k), per ogni k ∈ N,
Teorema (ambiguità)
Una grammatica libera G che sia strong LL(k), per un certo k ∈ N,
non può essere ambigua
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
41 / 93
Analisi Sintattica Discendente
Trasformazioni utili
Trasformazioni utili I
S −→ A]
A −→ T
S −→ A]
A −→ T
A −→ TZ
G1 : A −→ A+T elim. ricorsione sin. G2 : Z −→ +T
Z −→ +TZ
T −→ b
T −→ b
T −→ (A)
T −→ (A)
S −→ A]
A −→ TB
B −→ Z
B −→ fattorizzazione G3 : Z −→ +TY strong LL(1) !
Y −→ Z
Y −→ T −→ b
T −→ (A)
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
42 / 93
Analisi Sintattica Discendente
Trasformazioni utili
Trasformazioni utili II
Problemi:
1
ricorsione sinistra nelle produzioni della grammatica
2
presenza di prefissi comuni in parti destre per lo stesso NT
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
43 / 93
Analisi Sintattica Discendente
Trasformazioni utili
Trasformazioni utili III
Rimedi:
1
eliminazione della ricorsione sinistra diretta1
A −→ Aρ1 | · · · |Aρr |α1 | · · · |αn
diventa
A −→ α1 B| · · · |αn B
B −→ ρ1 B| · · · |ρr B|
2
N. Fanizzi
fattorizzazione sinistra:
A −→ αβ|αγ
con α, β ∈ (Σ ∪ V)+ , γ ∈ (Σ ∪ V)∗
uno dei suffissi può essere e possono non avere prefissi
comuni
A −→ αA0
A −→ αβ|αγ diventa
A0 −→ β|γ
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
44 / 93
Analisi Sintattica Discendente
Trasformazioni utili
Trasformazioni utili IV
Esempio
Data la grammatica:
E ::= T | -T | E + T | E - T
T ::= F | T * F
F ::= i | (E )
eliminando le ricorsioni
sinistre:
E ::= TE 0 | -TE 0
E 0 ::= +TE 0 | -TE 0 | T ::= FT 0
T 0 ::= * FT 0 | F ::= i | (E )
1
Per le forme indirette si impone un ordine dei NT e si utilizza la sostituzione
e l’eliminazione della ricorsione diretta per trasformare le prod. che non
rispettano l’ordine
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
45 / 93
Analisi Sintattica Discendente
Trasformazioni utili
Grammatiche LL(k) I
Le grammatiche strong LL(k) danno un metodo globale di
scelta della regola giusta
Si può far dipendere la scelta della regola dalla particolare
FDF dove occorre A da espandere (info contestuale)
Definizione (grammatiche LL(k))
Una grammatica libera G = (Σ, V , S, P) con marcatore ]k è in
forma LL(k) se, per ogni coppia di derivazioni canoniche sinistre:
∗
∗
S =⇒ uAv =⇒ uω1 v =⇒ uzw1
∗
∗
S =⇒ uAv =⇒ uω2 v =⇒ uzw2
(con u, wi , z ∈ Σ∗ e |z| = k)
risulta necessariamente: ω1 = ω2
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
46 / 93
Analisi Sintattica Discendente
Trasformazioni utili
Grammatiche LL(k) II
Osservazioni
La famiglia delle grammatiche strong LL(k) costituisce un
sottoinsieme delle LL(k)
Tuttavia non tutti i ling. deterministici sono generabili da
grammatiche LL(k)
Per le LL(k), si definiscono insiemi di lookahead
per FDF:
LAk (uAσ) = FIRSTk (Aσ)
per FDF, data una regola:
LAk (uAσ | A −→ ω) = FIRSTk (ωσ)
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
47 / 93
Analisi Sintattica Discendente
Trasformazioni utili
Grammatiche LL(k) III
Algoritmo deterministico
parse(G ∈ LL(k), z ∈ Σ∗ ): boolean
begin
φ←S
do
Si considera φ = uAv per una certa u ∈ Σ∗ e A ∈ V
Si considera z ← uyp con |y| = k
for each (A −→ ωi ) ∈ P
si costruisce LAk (uAv | A −→ ωi )
if ∃(A −→ ωi ) ∈ P : y ∈ LAk (uAv | A −→ ωi ) then
φ ← uωi v
while (φ 6= z and ∃(A −→ ωi ) ∈ P : y ∈ LAk (uAv | A −→ ωi ))
if φ = z then return true
else return false
end
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
48 / 93
Analisi Discendente Guidata da Tabella
1
2
3
4
Introduzione
Definizione
Funzionalità del Parser
Modalità di Parsing
Gestione Errori
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Insiemi FIRST e FOLLOW
Forma Normale LL(k)
Trasformazioni utili
Analisi Discendente Guidata da Tabella
Algoritmo di Parsing
Grammatiche LL(1)
Gestione Errori
Analisi Top-down in Discesa Ricorsiva
Introduzione
Costruzione Parser senza gestione errori
Costruzione Parser con gestione errori
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
49 / 93
Analisi Discendente Guidata da Tabella
Analisi discendente guidata da tabella - I
Si consideri una grammatica LL(1) descritta da una tabella in cui
righe non-terminali
colonne terminali
caselle parti destre delle produzioni / info errori
Tabella guida
V /Σ
E
E0
T
T0
F
N. Fanizzi
i
TE 0
+
+TE 0
-TE 0
-TE 0
*
FT 0
Linguaggi di prog.+Lab
)
#
FT 0
i
(
TE 0
0
*FT
Analisi Sintattica
(E)
5 maggio 2014
50 / 93
Analisi Discendente Guidata da Tabella
Analisi discendente guidata da tabella - II
Osservazioni
Derivazione canonica sinistra =⇒ occorre mantenere solo la
parte destra dell’albero sintattico (ancora da espandere) in
uno stack
Il parser chiede il prossimo simbolo allo scanner o usa un
indice per la stringa in ingresso che viene preventivamente
bufferizzata
l’algoritmo risultante simula un PDA
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
51 / 93
Analisi Discendente Guidata da Tabella
Algoritmo di Parsing
Algoritmo di Parsing I
Inizialmente sullo stack c’è S e
l’indice punta al primo terminale sulla stringa in ingresso
Il parser scorre la tabella in base al top dello stack e all’indice
1
2
se al top c’e’ un simbolo terminale
esso deve coincidere con quello puntato dall’indice,
in tal caso il top viene cancellato e l’indice viene incrementato;
se al top c’e’ un non simbolo terminale
si sceglie una sua produzione in base al terminale puntato
dall’indice, considerando la casella corrispondente in tabella
si fa avanzare l’indice;
altrimenti si ricade in condizione di errore
(codificabile nella tabella stessa)
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
52 / 93
Analisi Discendente Guidata da Tabella
Algoritmo di Parsing
Algoritmo di Parsing II
Esempio
N. Fanizzi
Riconoscimento di - i + i *
stack
frase
#E
- i + i * i
#E 0 T#E 0 T
- i + i * i
#E 0 T 0 F
#E 0 T 0 i
#E 0 T 0
- i + i * i
#E 0
#E 0 T+
#E 0 T
- i + i * i
#E 0 T 0 F
#E 0 T 0 i
#E 0 T 0
- i + i * i
#E 0 T 0 F *
#E 0 T 0 F
- i + i * i
#E 0 T 0 i
#E 0 T 0
- i + i * i
#E 0
#
Linguaggi di prog.+Lab
i #
#
casella
tab[E][-]
#
tab[T][i]
tab[F][i]
#
tab[T 0 ][+]
tab[E 0 ][+]
#
tab[T][i]
tab[F][i]
#
tab[T 0 ][*]
#
tab[F][i]
#
tab[T 0 ][#]
tab[E 0 ][#]
Analisi Sintattica
5 maggio 2014
53 / 93
Analisi Discendente Guidata da Tabella
Algoritmo di Parsing
Implementazione I
nel seguito: n = |V|, m=|Σ|
1
void parse(StringaToken w) {
2
3
4
5
6
PilaSimboli stack; // pila
int ip; // indice prox simbolo
ParteDestra tab[n][m];
simbolo x;
7
8
9
10
init(stack);
push(stack,#);
push(stack,S);
11
12
13
ip = 1;
w = w+#;
14
15
16
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
54 / 93
Analisi Discendente Guidata da Tabella
Algoritmo di Parsing
Implementazione II
do
ss = pop(stack);
if (terminale(ss))
if (ss == w[ip]) // token atteso trovato
ip++; // passa al prossimo
else error(1);
else { // NT
fdf = tab[ss][w[ip]]; // p.destra in tab.
if (fdf == { }) // casella vuota
error(2); // nessuna azione prevista
else // casella con parte destra
for (i=|fdf|; i>0 : i--)
push(stack,fdf[i]);
} // else NT
while (!empty(stack));
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
}
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
55 / 93
Analisi Discendente Guidata da Tabella
Grammatiche LL(1)
Grammatiche LL(1): insiemi FIRST
Data una grammatica LL(1) e ω ∈ (Σ ∪ V )+
FIRST(ω) ins. dei prefissi di stringhe terminali derivabili da ω
FIRST(t) = {t} ∀t ∈ Σ
∀A ∈ V tale che A −→ ω1 |ω2 | . . . |ωn ∈ P:
n
[
FIRST(A) =
FIRST(ωi ) =
i=1
n
o
+
= t ∈ Σ | A =⇒ tσ, σ ∈ (Σ ∪ V)∗
∀ω = σ1 σ2 · · · σn ,
σi ∈ (V ∪ Σ)
+
se σ1 =⇒
6
allora FIRST(ω) = FIRST(σ1 )
+
+
se σi =⇒ , ∀i = 1, . . . , k e σk+1 =⇒
6
k+1
[
allora FIRST(ω) =
FIRST(σi )
i=1
+
se σi =⇒ , ∀i = 1, . . . , n allora FIRST(ω) =
n
[
FIRST(σi )
i=1
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
56 / 93
Analisi Discendente Guidata da Tabella
Grammatiche LL(1)
Grammatiche LL(1): insiemi FOLLOW
FOLLOW insieme dei terminali che in una derivazione possono
seguire immediatamente A ∈ (Σ ∪ V)
Formalmente:
n
o
+
FOLLOW(A) = t ∈ Σ | S =⇒ uAtσ con u ∈ Σ∗ , σ ∈ (Σ ∪ V )∗
Se Y −→ uAv ∈ P con A ∈ V , u ∈ Σ∗ , σ ∈ (Σ ∪ V)∗
i due insiemi sono legati dalla relazione
+
FOLLOW(A) ⊇ FIRST(σ) se σ =⇒
6
ovvero
FOLLOW(A) ⊇ FIRST(σ) ∪ FOLLOW(Y) altrimenti
Tali insiemi possono essere calcolati da appositi algoritmi (vedi
testo)
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
57 / 93
Analisi Discendente Guidata da Tabella
Grammatiche LL(1)
Condizioni LL(1)
Una grammatica è LL(1) sse, per ogni NT A con produzioni
A −→ ω1 | ω2 | . . . | ωn , sono soddisfatte le seguenti condizioni:
1
FIRST(ωi ) ∩ FIRST(ωj ) = ∅
n
[
FIRST(A) =
FIRST(ωi )
∀i 6= j
i=1
2
N. Fanizzi
+
esiste al più un solo ωj tale che ωj =⇒ e, nel caso: FIRST(A) ∩ FOLLOW(A) = ∅
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
58 / 93
Analisi Discendente Guidata da Tabella
Grammatiche LL(1)
Costruzione Tabella per il Parser
Per ogni A ∈ V con le produzioni
A −→ ω1 | ω2 | . . . | ωn
si pone
tab[A][t] = ωi
∀t ∈ FIRST(ωi )
∗
Per ogni ωj tale che ωj =⇒ si pone:
tab[A][b] = ∀b ∈ FOLLOW(A)
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
59 / 93
Analisi Discendente Guidata da Tabella
Gestione Errori
Gestione Errori (analisi guidata da tabella)
Tipi di errore
1
mancata corrispondenza tra terminale corrente e quello al
top dello stack
2
accesso ad un elemento della tabella che risulta vuoto
Trattamento
1
nel primo caso si hanno due alternative:
1
2
2
scartare un certo numero dei prossimi simbolo in ingresso
finchè si trovino simboli per far riprendere l’analisi
inserire (virtualmente) il simbolo mancante in modo da
riprendere l’analisi (senza causare altri errori)
nel secondo caso non ci si può basare solo sullo stato
corrente:
coppia (top stack, token terminale) corrente
ma occorre tener conto dell’analisi già effettuata
(se ne trova traccia sullo stack) usando criteri euristici
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
60 / 93
Analisi Top-down in Discesa Ricorsiva
1
2
3
4
Introduzione
Definizione
Funzionalità del Parser
Modalità di Parsing
Gestione Errori
Analisi Sintattica Discendente
Parsing deterministico e Lookahead
Insiemi FIRST e FOLLOW
Forma Normale LL(k)
Trasformazioni utili
Analisi Discendente Guidata da Tabella
Algoritmo di Parsing
Grammatiche LL(1)
Gestione Errori
Analisi Top-down in Discesa Ricorsiva
Introduzione
Costruzione Parser senza gestione errori
Costruzione Parser con gestione errori
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
61 / 93
Analisi Top-down in Discesa Ricorsiva
Introduzione
Parsing di grammatiche LL(1)
Il parser top-down di una grammatica LL(1) è in grado di
scegliere univocamente la parte destra in base al prossimo
simbolo a della stringa in ingresso:
1
se a ∈ FIRST(ωi )
allora si espande A con la parte destra ωi
2
se a 6∈ FIRST(ωi )
∀i ∈ {1, . . . , n}
∗
ma ∃|j ∈ {1, . . . , n} : ωj =⇒ (e per nessun’altra k 6= j)
e a ∈ FOLLOW(A) allora si espande A con 3
altrimenti si segnala la situazione di ERRORE
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
62 / 93
Analisi Top-down in Discesa Ricorsiva
Introduzione
Analisi Top-down in Discesa Ricorsiva
Tecnica rapida di scrittura di procedure ricorsive di
riconoscimento in base alle produzioni della grammatica LL(1)
Lo stack viene realizzato implicitamente dal meccanismo di
gestione delle chiamate delle procedure associate ad ogni non
terminale
agenda nel seguito:
passaggio da EBNF alle procedure senza gestione errori
passaggio da EBNF alle procedure con gestione errori
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
63 / 93
Analisi Top-down in Discesa Ricorsiva
Introduzione
Richiamo sulla (E)BNF
Nelle produzioni (libere) si separa la parte sinistra da quella
destra usando ::= invece di −→
uso dei metasimboli (, ), [, ], {, }:
gruppo A ::= (x | y)z equivale a A −→ xz | yz
opzionalità A ::= [x]yz equivale a A −→ xyz | yz
chiusura A ::= {x}y equivale a A −→ y | xy | xxy | . . .
chiusura lim. {x}nm come sopra ma per un numero di
ripetizioni tra m e n
Quando si vogliono usare questi simboli letteralmente essi
vanno racchiusi tra apici
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
64 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser senza gestione errori
Costruzione Parser da BNF I
Implementazione senza gestione errori.
Per ogni non-terminale, si costruisce una procedura
corrispondente
Siano A −→ ω1 |ω2 | · · · |ωn ;
+
se A =⇒
6
allora la procedura A() da scrivere è:
void A() {
if (FIRST[A,1].contains(token.code))
3
/* codice relativo alla fdf ω1 */
4
else if (FIRST[A,2].contains(token.code))
5
/* codice relativo alla fdf ω2 */
6
...
7
else if (FIRST[A,n].contains(token.code))
8
/* codice relativo alla fdf ωn */
9
else error();
10 }
1
2
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
65 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser senza gestione errori
Costruzione Parser da BNF II
+
Se A =⇒ allora la procedura A() da scrivere sarà:
void A() {
if (FIRST[A,1].contains(token.code))
3
/* codice relativo alla fdf ω1 */
4
else if (FIRST[A,2].contains(token.code))
5
/* codice relativo alla fdf ω2 */
6
...
7
else if (FIRST[A,n].contains(token.code))
8
/* codice relativo alla fdf ωn */
9
else if (!FOLLOW[A].contains(token.code))
10
error();
11 }
1
2
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
66 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser senza gestione errori
Costruzione Parser da BNF III
Osservazioni
token: variabile (globale) utilizzata dallo scanner per passare
il prossimo simbolo
per quanto riguarda il codice relativo ad ogni parte destra xi
un’istruzione per simbolo in xi nell’ordine dato:
per ogni NT: chiamata della relativa procedura
per ogni token T: istruzione:
1
2
if (token.code == T) scan(token);
else error(n);
si chiama scan(token) leggere il prossimo token
nel main del parser:
1
2
initScan(token);
S();
inizializza lo scanner e fa partire il riconoscimento del
simbolo iniziale della grammatica
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
67 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser senza gestione errori
Esempio
Data la grammatica:
E ::= T | E + T
T ::= F | T * F
F ::= i | (E)
trasformando le ricorsioni
sinistre:
E ::= T EE
EE ::= + T EE | T ::= F TT
TT ::= * F TT | F ::= i | (E)
N. Fanizzi
Linguaggi di prog.+Lab
Si calcolano gli insiemi
FIRST e FOLLOW:
FIRST(T EE) = [ID, PARSIN]
FIRST(+T EE) = [PIU]
FIRST(F TT) = [ID, PARSIN]
FIRST(*F TT) = [PER]
FIRST((E)) = [PARSIN]
FIRST(i) = [ID]
FOLLOW(EE) = [PARDES, EOF]
FOLLOW(TT) = [PIU, PARDES, EOF]
Analisi Sintattica
5 maggio 2014
68 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser senza gestione errori
parser() {
initScan(token);
3
E();
4
if (token.code != EOF)
5
error(1);
6}
1
2
7
E() {
if (FIRST[T EE].contains(token.code)) {
10
T(); EE();
11
} else error(1);
12 }
8
9
13
EE() {
if (FIRST[+T EE].contains(token.code)) {
16
scan(token); T(); EE();
17
} else if (!FOLLOW[EE].contains(token.code))
18
error(3);
19 }
14
15
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
69 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser senza gestione errori
20
21
22
T() {
if (FIRST[F TT].contains(token.code)) {
25
F(); TT();
26
} else error(1);
27 }
23
24
28
29
TT() {
if (FIRST[*F TT].contains(token.code)) {
32
scan(token); F(); TT();
33
} else if (!FOLLOW[TT].contains(token.code))
34
error(3);
35 }
30
31
36
37
38
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
70 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser senza gestione errori
F() {
if (FIRST[(E)].contains(token.code)) {
41
scan(token); E();
42
if (token.code == PARDES)
43
scan(token);
44
else error(4);
39
40
45
else
if (FIRST[i].contains(token.code))
scan(token);
else error(1);
46
47
48
49
50
}
51
52
// PARSER
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
71 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser con gestione errori
Costruzione Parser con gestione errori I
La gestione degli errori all’analisi ricorsiva discendente si può
effettuare con l’aggiunta, a ciascuna procedura, di
un’istruzione di prologo
un’istruzione di epilogo
Insiemi di token per la Sincronizzazione
Si saltano i token successivi se non sono contenuti nell’insieme di
token validi fornito:
s ins. token di sincronizzazione in ingresso
contiene elementi di FIRST(A) ma risulta più preciso
tiene conto della particolare derivazione in locale
z ins. token di sincronizzazione in uscita
si garantisce l’appartenenza del prossimo token ad un
insieme di token susseguenti
s⊆z
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
72 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser con gestione errori
Implementazione I
NT che non derivano la stringa vuota
A(TokenSet s,z) {
skipTo(s+FIRST(A))); // prologo
3
// salta ad un simbolo di FIRST(A) o s
4
if (FIRST(A).contains(token.code)) {
5
// codice relativo al corpo di A()
6
} else error(...);
7
skipTo(z); // epilogo
8}
1
2
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
73 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser con gestione errori
Implementazione II
NT che può derivare la stringa vuota
Y(TokenSet s,z) {
skipTo(s+FIRST(Y)+z));
3
// salta ad un simbolo di FIRST(Y) o s o z */
4
if (FIRST(Y).contains(token.code)) {
5
// codice relativo al corpo di Y
6
} else if (!FOLLOW(Y).contains(token.code))
7
error(...)
8
}
9
skipTo(z); // epilogo
10 }
1
2
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
74 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser con gestione errori
Implementazione III
Funzione di salto token:
skipTo(TokenSet s) {
if (!s.contains(token.code)) {
3
beginSkipMsg(); // messaggio inizio skip
4
do
5
scan(token);
6
while (!s.contains(token.code));
7
endSkipMsg(); // messaggio uscita skip
8
}
9}
1
2
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
75 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser con gestione errori
Implementazione IV
Esempio Data la grammatica EBNF:
E ::= T { + T }
T ::= F { * F }
F ::= i | ( E )
parser3() {
FIRST[E] = FIRST[T] = FIRST[F] = [ID, PARSIN];
3
initScan(token);
4
E([EOF],[EOF]);
5}
1
2
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
76 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser con gestione errori
Implementazione V
E(TokenSet s,z) {
skipTo(s+FIRST[E]);
3
if (FIRST[E].contains(token.code)) {
4
T(s,[PIU]+z);
5
while (token.code == PIU) {
6
scan(token);
7
T(s,[PIU]+z);
8
}
9
} else error(1);
10
skipTo(z);
11 }
1
2
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
77 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser con gestione errori
Implementazione VI
T(TokenSet s,z) {
skipTo(s+FIRST[T]);
3
if (FIRST[T].contains(token.code)) {
4
F(s,[PER]+FIRST[F]+z);
5
while (([PER]+FIRST[F]).contains(token.code)) {
6
if (token.code == PER) scan(token);
7
else error(2);
8
F(s,[PER]+FIRST[F]+z);
9
}
10
} else error(1);
11
skipTo(z);
12 }
1
2
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
78 / 93
Analisi Top-down in Discesa Ricorsiva
Costruzione Parser con gestione errori
Implementazione VII
F(TokenSet s,z) {
skipTo(s+FIRST[T]);
3
if (FIRST[F].contains(token.code))
4
if (token.code == PARSIN) {
5
scan(token);
6
E(s+[PARDES],[PARDES]+z);
7
if (token.code == PARDES) scan(token);
8
else error(4);
9
} else scan(token);
10
else error(1);
11
skipTo(z);
12 }
1
2
ottimizzabile eliminando le istruzioni di sincronizzazione
ridondanti
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
79 / 93
Appendice - Analisi Sintattica Ascendente
Appendice
5
Appendice - Analisi Sintattica Ascendente
Grammatiche LR(k)
Riduzione
Algoritmo LR
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
80 / 93
Appendice - Analisi Sintattica Ascendente
Introduzione
Analisi Sintattica Ascendente I
L’analisi sintattica ascendente (o bottom-up) consente di trattare
una classe di grammatiche libere più ampia di quella gestita
tramite tecniche top-down e consente una più sofisticata gestione
degli errori
In questo caso, si intende costruire l’albero di derivazione a
partire dalle foglie (token) risalendo fino al simbolo distintivo
L’albero viene costruito mediante riduzioni successive:
Definizione (riduzione)
la riduzione è la relazione inversa rispetto alla derivazione
Esempio
Data la grammatica G con prod.
S −→ aABe
A −→ Abc | b
B −→ d
N. Fanizzi
Linguaggi di prog.+Lab
riduzioni per abbcde
Analisi Sintattica
abbcde
aAbcde
aAde
aABe
S
b←A
Abc ← A
d←B
aABe ← S
5 maggio 2014
81 / 93
Appendice - Analisi Sintattica Ascendente
Grammatiche LR(k)
Grammatiche LR(k)
L’Analisi Sintattica Ascendente si avvale in genere della classe di
grammatiche LR(k)
(spesso per k = 1)
L: la stringa di ingresso viene esaminata da sinistra (Left) verso
destra
R: viene effettuata la riduzione destra (Right)
processo inverso della derivazione canonica destra
k: numero di simboli (di lookahead) successivi alla parte già
riconosciuta della stringa in ingresso utili alla decisione da
prendere
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
82 / 93
Appendice - Analisi Sintattica Ascendente
Riduzione
Riduzione I
Il parser bottom-up effettua l’analisi riducendo la frase in
ingresso al simbolo iniziale della grammatica
forma di frase corrente fi
si individua una sottostringa che coincide con la parte destra
di una produzione della grammatica
si sostituisce questa sottostringa in fi con la parte sinistra
ottenendo fi+1 (forma di frase ridotta di fi )
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
83 / 93
Appendice - Analisi Sintattica Ascendente
Riduzione
Riduzione II
La sequenza di forme di frase costituisce una riduzione destra
della stringa in ingresso
Una forma di frase può contenere varie parti destre di
produzioni:
parte destra riducibile di fi (detta handle): la sottostringa
che ridotta produce fi+1
prefisso LR riducibile di fi : un prefisso che contiene la parte
destra riducibile come suffisso
(ossia non ha altri simboli più a destra)
prefisso LR: un qualunque prefisso di un prefisso riducibile;
prefisso LR candidato alla riduzione: un prefisso LR che ha
come suffisso la parte destra di una produzione
Osservazione
N. Fanizzi
prefissi riducibili ⊆ prefissi LR candidati
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
84 / 93
Appendice - Analisi Sintattica Ascendente
Riduzione
Proprietà I
Una volta effettuata la riduzione di fi = axw
dove ax è il prefisso riducibile (a, x ∈ (Σ ∪ V)∗ e w ∈ Σ∗ )
nella forma di frase fi+1 = aAw
mediante la regola A −→ x
la stringa aA è ancora un prefisso LR di fi+1
Quindi:
si può utilizzare una pila per memorizzare il prefisso riducibile
corrente
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
85 / 93
Appendice - Analisi Sintattica Ascendente
Riduzione
Proprietà II
Data la grammatica:
S −→ E
E −→ E + T | T
T −→ T ∗ F | F
F −→ i | (E)
N. Fanizzi
Linguaggi di prog.+Lab
Sequenza di riduzioni:
i+i*i
F+i*i
T+i*i
E+i*i
E+F*i
E+T*i
E+T*F
E+T
E
S
Analisi Sintattica
5 maggio 2014
86 / 93
Appendice - Analisi Sintattica Ascendente
Riduzione
Proprietà III
Derivazione canonica destra:
S =⇒ E
=⇒ E + T
=⇒ E + T ∗ F =⇒
=⇒ E + T ∗ i =⇒ E + F ∗ i =⇒ E + i ∗ i
=⇒
=⇒ T + i ∗ i =⇒ F + i ∗ i =⇒ i + i ∗ i
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
87 / 93
Appendice - Analisi Sintattica Ascendente
Algoritmo LR
Algoritmo
Algoritmi shift-reduce
(es. alg. LR canonico [Knuth, 65])
il parser funziona come un PDA
analisi guidata da tabella (di difficile costruzione)
osservazione: a dispetto della lunghezza dell’input, della
forma di frase corrente e della profondità dello stack corrente
il numero di situazioni possibili è ridotto:
una per ogni simbolo della grammatica
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
88 / 93
Appendice - Analisi Sintattica Ascendente
Algoritmo LR
Tabelle-Guida LR(k)
Si suppone di avere sullo stack il prefisso LR corrente,
le azioni possibili in tabella sono
sposta quando il prefisso LR presente sullo stack non è
riducibile, si legge il prossimo simbolo in ingresso
ponendolo sullo stack
riduci quando lo stack compare un prefisso riducibile, si
sostituisce la parte destra riducibile con il rispettivo
non-terminale della parte destra
accetta lo stack contiene il simbolo iniziale;
la stringa in input viene accettata
errore viene richiamata un’apposita procedura di gestione
degli errori
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
89 / 93
Appendice - Analisi Sintattica Ascendente
Algoritmo LR
Osservazioni
Se la parte destra è riducibile allora si trova certamente nella
parte alta dello stack (suffisso del prefisso)
Per decidere se il prefisso candidato sullo stack sia proprio
quello riducibile, il parser LR(1) usa il prossimo simbolo nella
stringa di ingresso (lookahead)
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
90 / 93
Appendice - Analisi Sintattica Ascendente
Algoritmo LR
−−−−→
Esempio (cont.) i + i ∗ i
grammatica:
S −→ E
E −→ E+T | T
T −→ T *F | F
F −→ i | (E)
i
+ +
i F T E E E
1 2 3 4 5 6
N. Fanizzi
F
+
E
7
Linguaggi di prog.+Lab
T
+
E
8
*
T
+
E
9
i
*
T
+
E
10
F
*
T
+
E
11
T
+
E E S
12 13 14
Analisi Sintattica
1
sposta i
2
riduci F −→ i
3
riduci T −→ F
4
riduci E −→ T
5
sposta +
6
sposta i
7
riduci F −→ i
8
riduci T −→ F
9
sposta *
10
sposta i
11
riduci F −→ i
12
riduci T −→ T *
F
13
riduci E −→ E + T
14
riduci S −→ E
5 maggio 2014
91 / 93
Appendice - Analisi Sintattica Ascendente
Algoritmo LR
Una grammatica è adatta all’analisi bottom-up LR(k) se il parser,
rilevando un prefisso candidato in cima allo stack,
decide univocamente l’azione da intraprendere
in base ai prossimi k simboli in ingresso
Tipologie di parsing bottom-up
LR(k) metodo potente ma oneroso nella costruzione della
tabella
SLR(k) metodo più debole ma di facile implementazione
tabella compatta
LALR(k) metodo quasi al pari di LR(k) ma con tabella
compatta come nel caso precedente
Es. Yacc genera simili tabelle per l’analizzatore sintattico
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
92 / 93
Appendice - Analisi Sintattica Ascendente
Algoritmo LR
Fonti
Aho, Lam, Sethi, Ullman: Compilatori - Principi, tecniche e
strumenti. 2a ed., Pearson
Dos Reis: Compiler Construction Using Java, JavaCC, and Yacc.
Wiley-IEEE
Sudkamp: Languages and Machines, 3rd ed., Addison Wesley
Grune, Bal, Jacobs, Langendoen: Modern Compiler Design,
Wiley
Galles: Modern Compiler Design, Scott/Jones Publishing
N. Fanizzi
Linguaggi di prog.+Lab
Analisi Sintattica
5 maggio 2014
93 / 93