Introduction: Language Processors, the structure of a compiler, the science of building a compiler, programming language basics.
Lexical Analysis: The Role of the Lexical Analyzer, Input Buffering, Recognition of Tokens, The Lexical-Analyzer Generator Lex, Finite Automata, From Regular Expressions to Automata, Design of a Lexical-Analyzer Generator, Optimization of DFA-Based Pattern Matchers
Introduction: Language Processors, the structure of a compiler, the science of building a compiler, programming language basics.
Lexical Analysis: The Role of the Lexical Analyzer, Input Buffering, Recognition of Tokens, The Lexical-Analyzer Generator Lex, Finite Automata, From Regular Expressions to Automata, Design of a Lexical-Analyzer Generator, Optimization of DFA-Based Pattern Matchers
1. LFC
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
Linguaggi formali e compilazione
Corso di Laurea in Informatica
A.A. 2008/2009
2. LFC
Linguaggi formali e compilazione
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
Parsing di linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
3. LFC
Linguaggi formali e compilazione
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
Parsing di linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
4. LFC
Parsing di linguaggi liberi
Ogni linguaggio libero può essere riconosciuto in
◮ Parsing di
linguaggi liberi
tempo proporzionale al cubo della lunghezza della Introduzione
stringa in input. Parsing top-down
Parsing bottom-up
L’algoritmo che stabilisce questo limite di
◮
complessità, basato su una classica applicazione
della programmazione dinamica, è tuttavia
inutilizzabile in applicazioni reali.
Gli algoritmi di parsing utilizzati nella pratica sono
◮
generalmente classificati base alla strategia utilizzata
per la costruzione (anche solo “ideale”) del
parse-tree, top-down o bottom-up.
All’interno di queste due categorie generali, gli
◮
algoritmi si differenziano in base a scelte strategiche
più dettagliate.
Tali algoritmi non possono analizzare tutti i linguaggi
◮
context-free.
5. LFC
Tipi di parser
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
In concreto, i tipi di parser più diffusi includono:
◮ Parsing bottom-up
parser a discesa ricorsiva (top-down),
◮
parser di tipo LR (bottom-up),
◮
parser a precedenza di operatori (bottom-up).
◮
Nel parsing top-down si cerca una derivazione
◮
canonica per la stringa/programma in input partendo
dall’assioma iniziale.
La costruzione top-down dell’albero di derivazione
◮
corrisponde in modo naturale al riconoscimento di un
blocco/procedura/espressione in termini delle parti
costituenti.
6. LFC
Tipi di parser (continua)
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Nel parsing bottom-up si cerca una derivazione Parsing bottom-up
◮
canonica per la stringa/programma in input partendo
dalla stringa stessa e applicando riduzioni
successive.
Una riduzione non è nient’altro che l’applicazione “in
◮
senso contrario” di una produzione della grammatica.
La costruzione bottom-up dell’albero di derivazione
◮
corrisponde in modo naturale al riconoscimento di
singole porzioni di un programma e alla loro
composizione in parti più complesse.
7. LFC
Non determinismo
Parsing di
linguaggi liberi
Introduzione
In entrambi i tipi di parser (top-down o bottom-up) è
◮ Parsing top-down
Parsing bottom-up
possibile (anche se non desiderabile) che la scelta
della produzione da utilizzare, in una delle due
direzioni, non sia determinabile con certezza.
Se la scelta di una produzione è “errata” il
◮
riconoscimento fallisce.
In tal caso, prima di dichiarare che la stringa è
◮
sintatticamente errata, è necessario operare un
backtracking sull’input, ritornando al punto di scelta.
Per i parser a discesa ricorsiva (e implementati
◮
mediante ricorsione) ciò può essere sufficientemente
agevole, anche se computazionalmente pesante.
8. LFC
Linguaggi formali e compilazione
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
Parsing di linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
9. LFC
Parsing a discesa ricorsiva (recursive
descent)
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
Un parser a discesa ricorsiva (d.r.) può essere
◮
realizzato come collezione di procedure
(mutuamente) ricorsive, una per ogni simbolo non
terminale della grammatica.
Siano A → α1 . . . αk le produzioni relative al
◮
i i
nonterminale A, e sia αi = X1 . . . Xni , i = 1, . . . , k.
La procedura relativa al simbolo A è illustrata nel
◮
seguente algoritmo, in cui (per semplicità di
notazione) evitiamo i doppi indici e supponiamo che
il simbolo corrente di input sia contenuto nella
variabile globale x.
10. LFC
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Algorithm 1 Procedura per il nonterminale A Parsing bottom-up
→
1: Scegli “opportunamente” una produzione A
X1 X2 . . . Xk (Xj ∈ V)
2: for j = 1, . . . , k do
if Xj ∈ N then
3:
Xj ()
4:
else if Xj = x then
5:
x ← read()
6:
else
7:
error()
8:
11. LFC
Backtracking
La scelta della produzione da applicare (riga 1 Parsing di
◮
linguaggi liberi
dell’algoritmo 1) può essere meno critica se, nel caso Introduzione
Parsing top-down
di fallimento, viene effettuato backtracking sull’input. Parsing bottom-up
L’algoritmo deve essere modificato in modo tale da
◮
dichiarare fallimento solo quando tutte le produzioni
per il nonterminale A sono state provate senza
successo.
Deve però essere disposto un meccanismo per
◮
riposizionare il puntatore di input su quello che era il
simbolo corrente al momento della chiamata della
procedura.
Ne consegue che: o il parser bufferizza l’input
◮
ricevuto dallo scanner, oppure è quest’ultimo che
deve essere opportunamente programmato per la
ripetizione di certe sequenze di token.
12. LFC
Scelta della produzione
Fortunatamente, in molti casi concreti il backtracking
◮ Parsing di
linguaggi liberi
non è necessario perché la scelta della produzione Introduzione
Parsing top-down
può essere fatta con lookahead limitato (cioè Parsing bottom-up
guardando pochi token a partire dalla posizione
corrente di input).
Consideriamo la semplice grammatica:
◮
A → aA | bB
B → ǫ | bB
Per entrambi i non terminali, la scelta della
◮
produzione da usare può essere fatta guardando il
prossimo simbolo x in input.
Per A: se x = a, usa la produzione A → aA; se
◮
x = b, usa la produzione A → bB.
Per B. se x = $ (end of input), usa la produzione
◮
B → ǫ; se x = b, usa la produzione B → bB;
13. LFC
Grammatiche inadatte al parsing a discesa
ricorsiva
Parsing di
Alcune produzioni sono inadatte al r.d. parsing. linguaggi liberi
◮
Introduzione
Produzioni con ricorsioni a sinistra, che possono
◮ Parsing top-down
Parsing bottom-up
causare cicli infiniti, come nella grammatica
→ E +T | T
E
→ T ∗F | F
T
→ id | (E )
F
Produzioni con prefissi comuni (come il noto caso del
◮
“dangling else”), che rendono non limitabile a priori la
quantità di lookahead. Un altro esempio (artificiale) è
A → aB | aC
B → aB | b
C → aC | c
Come sappiamo, esistono opportune trasformazioni
◮
per risolvere questi problemi.
14. LFC
FIRST e FOLLOW
Data una grammatica G, l’implementazione di parser
◮
Parsing di
a d.r. per G utilizza (secondo le “teorie” più accettate) linguaggi liberi
Introduzione
due funzioni, dette FIRST e FOLLOW , che Parsing top-down
Parsing bottom-up
determinano la scelta della produzione da utilizzare.
Data G = (N , T , P, S) e data una stringa
◮
α ∈ (N ∪ T )∗ , si definisce FIRST (α) l’insieme dei
simboli terminali con cui può iniziare una frase
derivabile da α, più eventualmente ǫ se α ⇒∗ ǫ:
FIRST (α) = {x ∈ T |α ⇒∗ xβ, β ∈ T ∗ }
∪ {ǫ} , se α ⇒∗ ǫ.
Per ogni nonterminale A ∈ N si definisce
◮
FOLLOW (A) l’insieme dei terminali che si possono
trovare immediatamente alla destra di A in una
qualche forma di frase. In altri termini,
x ∈ FOLLOW (A) se S ⇒∗ αAxβ, con α e β
opportuni.
15. LFC
Calcolo di FIRST (α)
Parsing di
linguaggi liberi
Introduzione
Definiamo innanzitutto come si calcola FIRST (α) nel
◮ Parsing top-down
Parsing bottom-up
caso in cui α sia un singolo simbolo della
grammatica, cioè α = X con X ∈ N ∪ T .
Se X è un terminale, si pone naturalmente
◮
FIRST (X ) = {X };
se X è un nonterminale poniamo innanzitutto
◮
FIRST (X ) = {}.
Se esiste la produzione X → X1 . . . Xn , e risulta
◮
ǫ ∈ FIRST (Xj ), j = 1, . . . , k − 1, poniamo
FIRST (X ) = FIRST (X ) ∪ {x} per ogni simbolo
x ∈ FIRST (Xk ).
Infine, se esiste la produzione X → ǫ oppure
◮
ǫ ∈ FIRST (Xj ), j = 1, . . . , k , poniamo
FIRST (X ) = FIRST (X ) ∪ {ǫ}.
16. LFC
Esempi
Si consideri nuovamente la grammatica
◮
Parsing di
linguaggi liberi
′ ′
→ (E ) E | id E
E Introduzione
Parsing top-down
Parsing bottom-up
E′ → + E E′ | × E E′ | ǫ
Per questa grammatica risulta
◮
FIRST (E) = {( , id}
◮
FIRST (E ′ ) = {+, ×, ǫ}
◮
Si consideri ora la grammatica per an bm ck
◮
A → aA | BC | ǫ
B → bB | ǫ
C → cC | ǫ
Per questa grammatica risulta
◮
FIRST (C) = {c, ǫ}
◮
FIRST (B) = {b, ǫ}
◮
FIRST (A) = {a, b, c, ǫ}
◮
17. LFC
Esempio
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Si riconsideri la grammatica per le espressioni che
◮ Parsing bottom-up
“forza” la precedenza di operatori:
→ E +T | T
E
→ T ×F | F
T
→ id | (E )
F
Per questa grammatica risulta
◮
FIRST (F ) = {( , id};
◮
FIRST (T ) = FIRST (F );
◮
FIRST (E) = FIRST (T ).
◮
18. LFC
Calcolo di FIRST (α) (continua)
Parsing di
linguaggi liberi
Introduzione
Il calcolo di FIRST (α), dove α = X1 . . . Xn è una Parsing top-down
◮
Parsing bottom-up
generica stringa di terminali e nonterminali, può ora
essere svolto nel modo seguente
a ∈ FIRST (α) se e solo se, per qualche indice
◮
k ∈ 1, . . . n − 1, risulta a ∈ Xk e ǫ ∈ Xj ,
j = 1, . . . , k − 1 (si suppone sempre ǫ ∈ FIRST (X0)).
Se inoltre ǫ ∈ FIRST (Xj ), j = 1, . . . , n, allora
◮
ǫ ∈ FIRST (α).
Ad esempio, nel caso della seconda grammatica del
◮
lucido precedente risulta: FIRST (aA) = {a} e
FIRST (BC) = {b, c, ǫ}.
19. LFC
Calcolo di FOLLOW (A)
Parsing di
linguaggi liberi
Introduzione
Il calcolo di FOLLOW (A), per un generico non Parsing top-down
◮
Parsing bottom-up
terminale A, può essere svolto in questo modo.
Se A = S, si inserisce il simbolo speciale $
◮
(marcatore di fine input) in FOLLOW (A).
Se esiste la produzione B → αAβ, tutti i terminali in
◮
FIRST (β) si inseriscono in FOLLOW (A); inoltre, se
ǫ ∈ FIRST (β), tutti i terminali che stanno in
FOLLOW (B) si inseriscono in FOLLOW (A) .
Analogamente, se esiste la produzione B → αA, tutti
◮
i terminali che stanno in FOLLOW (B) si inseriscono
in FOLLOW (A) .
20. LFC
Esempio
Parsing di
Consideriamo la solita grammatica
◮
linguaggi liberi
Introduzione
Parsing top-down
′ ′
→ (E ) E | id E
E Parsing bottom-up
′ ′ ′
→ +E E | × E E | ǫ
E
Possiamo subito stabilire che FOLLOW (E ) include il
◮
simbolo $ e il simbolo ); inoltre contiene i simboli in
FIRST (E ′) (eccetto ǫ) e cioè + e ×.
FOLLOW (E ′) include FOLLOW (E ), a causa (ad
◮
esempio) della produzione E → idE ′ .
La produzione E ′ → +EE ′ , unitamente a E ′ → ǫ,
◮
stabilisce che vale anche il contrario, e cioè che
FOLLOW (E ) include FOLLOW (E ′ ).
Mettendo tutto insieme si ottiene
◮
FOLLOW (E ) = FOLLOW (E ′) = {$, ) , +, ×}.
21. LFC
Esempio
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
Per la grammatica
◮
A → aA | BC | ǫ
B → bB | ǫ
C → cC | ǫ
risulta FOLLOW (A) = FOLLOW (C) = {$} e
FOLLOW (B) = {c, $}.
22. LFC
Esempio
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
Per la grammatica con precedenza di operatori:
◮
→ E +T | T
E
→ T ×F | F
T
→ id | (E )
F
risulta:
FOLLOW (E) = {$, +, )};
◮
FOLLOW (T ) = FOLLOW (E) ∪ {×};
◮
FOLLOW (F ) = FOLLOW (T ).
◮
23. LFC
Grammatiche LL(1)
Per opportune classi di grammatiche libere è
◮
Parsing di
possibile realizzare parser a d.r. che non richiedono linguaggi liberi
Introduzione
backtracking (tali parser sono anche detti predittivi). Parsing top-down
Parsing bottom-up
Una tale classe di grammatiche è la LL(1), dove le
◮
due L stanno per “left-to-right” e “leftmost derivation”.
In altri termini, il parsing avviene leggendo l’input
◮
unidirezionalmente, da sinistra a destra, e
costruendo una derivazione canonica sinistra della
stringa. Il parametro 1 indica che è richiesto un solo
simbolo di lookahead.
La classe LL(1) è in grado di esprimere molte fra le
◮
caratteristiche della sintassi dei linguaggi di
programmazione.
Nessuna grammatica ricorsiva a sinistra o con
◮
produzioni aventi previssi comuni può però essere
LL(1) (come pure nessuna grammatica ambigua).
24. LFC
Grammatiche LL(1) (continua)
Parsing di
linguaggi liberi
Come possiamo stabilire se una grammatica è
◮ Introduzione
Parsing top-down
LL(1)? Parsing bottom-up
Consideriamo la procedura per un generico
◮
nonterminale A.
Se esistono due produzioni A → α e A → β, allora
◮
certamente non deve accadere che
FIRST (α) ∩ FIRST (β) = {} ,
perché in tal caso un solo carattere di lookahead non
basterebbe per decidere correttamente la
produzione da usare.
Tuttavia ciò potrebbe non essere sufficiente.
◮
25. LFC
Grammatiche LL(1) (continua)
Parsing di
linguaggi liberi
⇒∗
Supponiamo (ad esempio) che α ǫ. Potremmo
◮ Introduzione
Parsing top-down
trovarci nella seguente situazione: Parsing bottom-up
S ⇒∗ γAxδ, γ ∈ T ∗, x ∈ T , δ ∈ V ∗
dove, se il prossimo carattere di input fosse proprio
x, potrebbe essere corretto usare la produzione
A → α.
Tuttavia, se x fosse anche in FIRST (β) potrebbe
◮
essere corretto pure usare la produzione A → β.
Deve quindi risultare α ⇒∗ ǫ implica
◮
FOLLOW (A) ∩ FIRST (β) = {} e, analogamente,
β ⇒∗ ǫ implica FOLLOW (A) ∩ FIRST (α) = {}
26. LFC
Esempio
Si consideri la grammatica
◮
Parsing di
linguaggi liberi
A → BE Introduzione
Parsing top-down
B → C|D Parsing bottom-up
C → ǫ | cc
D → ǫ | dd
→ c|d
E
Poiché risulta FIRST (C) ∩ FIRST (D) = {ǫ}, la
◮
grammatica non è LL(1).
Infatti, supponiamo che la stringa in input inizi con c.
◮
Dopo la riscrittura dell’assioma il parser verrebbe a
trovarsi con la forma di frase BE e il carattere c in
input.
A questo punto potrebbe essere corretto derivare
◮
tanto CE (se l’input fosse, ad esempio, ccd) quanto
DE (se l’input fosse c).
27. LFC
Esempio
Si modifichi la precedente grammatica nel modo
◮
Parsing di
seguente linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
A → BE
B → C|D
C → ǫ | cc
D → dd
→ c|d
E
(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 derivazione
iniziale) derivare tanto CE (se l’input fosse d) quanto
DE (se l’input fosse, ad esempio ddc).
28. LFC
Costruzione della tabella di parsing
Parsing di
Per una grammatica LL(1) è possibile costruire linguaggi liberi
◮
Introduzione
(utilizzando le funzioni FIRST e FOLLOW ) una Parsing top-down
Parsing bottom-up
tabella, detta tabella di parsing, che, per ogni
nonterminale A e ogni terminale x, prescrive il
comportamento del parser.
Indicheremo con MG la tabella di parsing relativa alla
◮
grammatica G (o semplicemente con M, se la
grammatica è evidente).
La generica entry MG [A, x] della tabella può
◮
contenere una produzione A → α di G, oppure
essere vuota, ad indicare che si è verificato un
errore.
Disponendo di MG , la prima riga dell’algoritmo 1
◮
viene sostituita da un lookup a MG [A, x].
29. LFC
Costruzione della tabella di parsing
(continua)
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
L’algoritmo di costruzione della parsing table è molto
◮ Parsing bottom-up
semplice ed è formato da un ciclo principale nel
quale si prendono in considerazione tutte le
produzioni.
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 lasciate
◮
vuote (ad indicare l’occorrenza di un errore).
Se la grammatica è LL(1), nessuna entry della
◮
tabella viene riempita con più di una produzione.
30. LFC
Esempio
Consideriamo ancora la grammatica con precedenza
◮
Parsing di
di operatori: linguaggi liberi
Introduzione
→ E +T | T
E Parsing top-down
Parsing bottom-up
→ T ×F | F
T
→ id | (E )
F
Tale grammatica (pur non ambigua) non è LL(1)
◮
perchè (p.e.), entrambe le produzioni E → E + T e
E → T verrebbero poste nella entry M[E , id].
Eliminando la left-recursion si può però ottenere una
◮
grammatica equivalente che è LL(1):
→ TE ′
E
E ′ → +TE ′ | ǫ
→ FT ′
T
T ′ → ×FT ′ | ǫ
→ id | (E )
F
31. LFC
Esempio (continua)
Per la grammatica modificata
◮
Parsing di
linguaggi liberi
′
→ TE
E Introduzione
Parsing top-down
Parsing bottom-up
E ′ → +TE ′ | ǫ
→ FT ′
T
T ′ → ×FT ′ | ǫ
→ id | (E )
F
abbiamo:
FIRST (F ) = FIRST (T ) = FIRST (E) = {id, ( };
◮
FIRST (E ′ ) = {+, ǫ};
◮
FIRST (T ′ ) = {×, ǫ};
◮
FOLLOW (E) = FOLLOW (E ′ ) = {$, )};
◮
FOLLOW (T ) = (FIRST (E ′ ) {ǫ}) ∪ FOLLOW (E ′ ) =
◮
{+, $, )};
FOLLOW (T ′ ) = FOLLOW (T ) = {+, $, )};
◮
FOLLOW (F ) = (FIRST (T ′ ) {ǫ}) ∪ FOLLOW (T ′ ) =
◮
{×, +, $, )}.
32. LFC
Esempio (continua)
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
L’algoritmo produce la seguente tabella di parsing
◮
Simbolo di input
N.T.
( ) + ×
id $
E → TE ′ E → TE ′
E
E′ ′ ′ ′ ′
E →ǫ E → +TE E →ǫ
T → FT ′ T → FT ′
T
T′ T′ → ǫ T′ → ǫ T ′ → ×FT ′ T′ → ǫ
F → id F → (E)
F
in cui le entry vuote corrispondono ad una situazione
di errore.
33. LFC
Esempio
Se tentassimo di produrre la tabella di parsing per la
◮ Parsing di
linguaggi liberi
grammatica Introduzione
Parsing top-down
Parsing bottom-up
′ ′
→ (E ) E | id E
E
E′ → + E E′ | × E E′ | ǫ
otterremmo
Simbolo di input
N.T.
( ) + ×
id $
E → idE ′ E → (E)E ′
E
E ′ → +EE ′ E ′ → ×EE ′
E′ E′ → ǫ E′ → ǫ
E′ → ǫ E′ → ǫ
Con in input il carattere + o il carattere ×, il parser
◮
non saprebbe quindi come procedere.
Il non soddisfacimento delle proprietà LL(1) è una
◮
conseguenza dell’ambiguità della grammatica.
34. LFC
Esempio (continua)
Parsing di
linguaggi liberi
Introduzione
In questo caso è comunque possibile forzare nel
◮ Parsing top-down
Parsing bottom-up
parser una regola di scelta senza compromettere la
capacità di riconoscere il linguaggio generato dalla
grammatica.
Possiamo (anzi, dobbiamo!) semplicemente optare
◮
in favore delle produzioni E ′ → +EE ′ e E ′ → ×EE ′ ,
anziché E ′ → ǫ.
Si noti comunque che, pur avendo risolto l’ambiguità,
◮
l’interpretazione delle espressioni che deriva
dall’albero di parsing non è quella “naturale” (non
viene soddisfatta la precedenza naturale degli
operatori).
35. LFC
Esempio
Consideriamo la seguente grammatica:
◮
Parsing di
linguaggi liberi
S → iE tS eS | iE tS | a Introduzione
Parsing top-down
→b
E Parsing bottom-up
che mette a fuoco il problema del dangling else di
molti linguaggi e che, avendo produzioni con un
prefisso comune, non è idonea al parsing a d.r.
Operando nel modo già illustrato, otteniamo:
◮
S → i E t S S′ | a
S′ → e S | ǫ
→b
E
e risulta
FIRST (S) = {i, a};
◮
FIRST (S ′ ) = {e, ǫ};
◮
FIRST (E) = {b};
◮
FOLLOW (S) = FOLLOW (S ′ ) = {$, e}.
◮
36. LFC
Esempio (continua)
Parsing di
La grammatica modificata è non è ancora LL(1) in linguaggi liberi
◮
Introduzione
quanto FIRST (eS) ∩ FOLLOW (S ′ ) = {e}. Parsing top-down
Parsing bottom-up
Ciò si riflette in una definizione multipla per una entry
◮
della tabella di parsing:
Simbolo di input
N.T.
i t e a b $
S → iEtSS ′ S→a
S
′
S → eS
S′ S′ → ǫ
S′ → ǫ
E→b
E
Tuttavia se, con in input il carattere e, il parser
◮
venisse “istruito” a scegliere sempre la produzione
S ′ → eS, l’ambiguità si risolverebbe (e pure con
l’intepretazione “naturale”, che associa ogni else al
then più vicino).
37. LFC
Implementazione non ricorsiva
Parsing di
linguaggi liberi
È possibile dare un’implementazione non ricorsiva di
◮ Introduzione
Parsing top-down
un parser predittivo (cioè che non richiede Parsing bottom-up
backtracking) mantenendo esplicitamente una pila.
La pila serve per memorizzare i simboli della parte
◮
destra della produzione scelta.
Tali simboli verranno poi “confrontati” con l’input (se
◮
terminali) o ulteriormente riscritti (se non terminali).
Il comportamento del parser può essere descritto nel
◮
modo seguente.
Inizialmente, sullo stack sono contenuti (partendo dal
◮
fondo) i simboli $ ed S, mentre un opportuno
puntatore (diciamo z) punta al primo carattere di
input.
38. LFC
Implementazione non ricorsiva (continua)
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Al generico passo, il parser controlla il simbolo X
◮ Parsing bottom-up
sulla testa dello stack;
se X è un nonterminale e M[X , z] = ‘X → X1 . . . Xk ‘,
◮
esegue una pop dallo stack (rimuove cioè X ) seguita
da k push dei simboli X1 , . . . , Xk , nell’ordine;
se X è un nonterminale e M[X , z] = ‘error ‘, segnala
◮
una condizione di errore;
se X è un terminale e X = z, esegue una pop e fa
◮
avanzare z;
se X è un terminale e X = z, segnala una
◮
condizione di errore.
Le operazioni terminano quando X = $.
◮
39. LFC
Esempio
Parsing di
linguaggi liberi
Introduzione
Consideriamo nuovamente la grammatica delle
◮ Parsing top-down
Parsing bottom-up
espressioni con precedenza di operatore, della quale
ricordiamo la tabella di parsing:
Simbolo di input
N.T.
( ) + ×
id $
E → TE ′ E → TE ′
E
E′ E′ → ǫ E ′ → +TE ′ E′ → ǫ
T → FT ′ T → FT ′
T
T′ T′ → ǫ T′ → ǫ T ′ → ×FT ′ T′ → ǫ
F → id F → (E)
F
Supponendo di avere la stringa id + id in input, la
◮
seguente tabella illustra il progressivo contenuto
dello stack e dell’input.
40. LFC
Esempio (continua)
Parsing di
linguaggi liberi
Stack Input Introduzione
$E id + id$ Parsing top-down
Parsing bottom-up
$E ′ T id + id$
$E ′ T ′ F id + id$
$E ′ T ′ id id + id$
$E ′ T ′ +id$
$E ′ +id$
$E ′ T + +id$
$E ′ T id$
$E ′ T ′ F id$
$E ′ T ′ id id$
$E ′ T ′ $
$E ′ $
$ $
41. LFC
Esercizi proposti
Parsing di
linguaggi liberi
Si calcolini gli insiemi FIRST e FOLLOW per la
◮
Introduzione
seguente grammatica: Parsing top-down
Parsing bottom-up
S → c | AS | BS
A → aB | ǫ
B → bA | ǫ
e si costruisca la relativa tabella di parsing per un
parser a discesa ricorsiva.
Si calcolino gli insiemi FIRST e FOLLOW per la
◮
grammatica G2 , che descrive le espressioni regolari
su {0, 1}, dopo averla modificata in modo da
eliminare i prefissi comuni. Se ne costruisca quindi la
tabella di parsing per un parser a discesa ricorsiva.
42. LFC
Linguaggi formali e compilazione
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
Parsing di linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
43. LFC
Elementi generali
Un parser generico di tipo bottom-up procede Parsing di
◮
linguaggi liberi
cercando di operare riduzioni, operazioni che Introduzione
Parsing top-down
possiamo vedere come l’inverso delle derivazioni, a Parsing bottom-up
partire dalla stringa di input α0 = α.
Al generico passo di riduzione il parser individua,
◮
nella stringa corrente αi , un’opportuna sottostringa β
che corrisponde alla parte destra di una produzione
A → β e sostituisce β con A, così riducendo αi ad
αi+1 :
αi = γβδ, αi+1 = γAδ
Il processo termina con successo se, per un
◮
opportuno valore di i, risulta αi = S.
Nell’ambito del processo di riduzione il parser può
◮
costruire (dal basso verso l’alto) un albero di
derivazione e/o produrre codice.
44. LFC
Parsing “Shift-reduce”
Parsing di
Un parser shift-reduce è un parser di tipo bottom-up
◮ linguaggi liberi
che fa uso di uno stack nel quale vengono Introduzione
Parsing top-down
memorizzati simboli (terminali o non terminali) della Parsing bottom-up
grammatica.
Il nome deriva dal fatto che le due operazioni
◮
fondamentali eseguite del parser sono dette,
appunto, shift e riduzione.
L’operazione di shift legge un simbolo dallo stream di
◮
input e lo inserisce sullo stack.
L’operazione di riduzione sostituisce sullo stack gli
◮
ultimi k simboli inseriti (poniamo X1 , . . . , Xk ) con il
simbolo A, naturalmente se esiste la produzione
A → X1 . . . Xk .
Le altre operazioni che il parser esegue sono
◮
l’accettazione dell’input e la segnalazione di una
condizione di errore.
45. LFC
Maniglie (handle)
Parsing di
Un parser di tipo shift-reduce deve individuare, come linguaggi liberi
◮
Introduzione
sottostringhe da ridurre a non terminale, non già Parsing top-down
Parsing bottom-up
qualunque sequenza che formi la parte destra di una
produzione, bensì esattamente quelle sequenze (e
quelle produzioni) usate nella derivazione canonica
destra.
Tali sequenze devono inoltre essere individuate
◮
“nell’ordine giusto”, e cioè l’ordine rovesciato rispetto
alla corrispondente derivazione canonica destra.
Queste sequenze (ma meglio sarebbe dire
◮
“produzioni”) vengono chiamate handle (maniglie), di
modo che il problema centrale della realizzazione di
un tale parser può essere espresso sinteticamente
come il problema di individuare le maniglie.
46. LFC
Esempio
Si consideri la grammatica:
◮
Parsing di
linguaggi liberi
→ E +T | T
E Introduzione
Parsing top-down
→ T ×F | F
T Parsing bottom-up
→ id | (E )
F
La corretta riduzione di id + id × id ad E individua le
◮
maniglie indicate dalla sottolineatura:
id + id × id ⇐ F + id × id
⇐ T + id × id
⇐ E + id × id
⇐ E + F × id
⇐ E + T × id
⇐ E +T ×F
⇐ E +T
⇐E
47. LFC
Maniglie (continua)
Parsing di
In un parser shift-reduce le successive stringhe αi linguaggi liberi
◮
Introduzione
(che rappresentano la trasformazione dall’input Parsing top-down
Parsing bottom-up
all’assioma) si trovano “parte sullo stack e parte
ancora sullo stream di input“.
In particolare, non sarebbe difficile dimostrare che,
◮
se il parser ricostruisce (in ordine inverso) derivazioni
canoniche destre, la maniglie appaiono sempre sulla
cima dello stack.
Ad esempio, se αi = γβδ e β è una maniglia, allora
◮
γβ sta sullo stack (nell’ordine, dal fondo verso la
cima) mentre δ sta ancora nello stream di input.
La maniglia, in questo caso β, sta sulla cima dello
◮
stack e viene “sostituita” con A dall’operazione di
riduzione.
48. LFC
Esempio
Azioni eseguite (su input id + id × id) da un parser
◮
Parsing di
shift-reduce che riconosce correttamente le maniglie. linguaggi liberi
Introduzione
Stack Input Azione Stringa αi Parsing top-down
Parsing bottom-up
id + id × id$ id + id × id$
$ shift
+id × id$ id + id × id$
$id reduce
+id × id$ F + id × id$
$F reduce
+id × id$ T + id × id$
$T reduce
+id × id$ E + id × id$
$E shift
id × id$ E + id × id$
$E + shift
×id$ E + id × id$
$E + id reduce
×id$ E + F × id$
$E + F reduce
×id$ E + T × id$
$E + T shift
$E + T × E + T × id$
id$ shift
$E + T × id E + T × id$
$ reduce
$E + T × F E + T × F$
$ reduce
$E + T $ E + T$
reduce
$E $ E$
accept
49. LFC
Esempio (continua)
Parsing di
Si noti che, nella colonna azione, bisognerebbe
◮
linguaggi liberi
correttamente indicare, nel caso di riduzione, quale Introduzione
Parsing top-down
produzione va utilizzata. Parsing bottom-up
In generale, la stessa parte destra potrebbe infatti
◮
corrispondere a più di una produzione (non è questo
il caso), ed è per questo motivo che si definiscono le
handle come produzioni e non come semplici
sequenze.
Si noti inoltre come le maniglie “corrette”
◮
(corrispondenti alla derivazione canonica destra)
appaiano effettivamente sempre sulla cima dello
stack.
Questa proprietà non varrebbe nel caso volessimo
◮
riprodurre, poniamo, una derivazione canonica
sinistra.
50. LFC
Il cuore computazionale del problema
Parsing di
linguaggi liberi
La difficoltà di progettazione del parser sta tutta nella
◮ Introduzione
Parsing top-down
capacità di riconoscere quando è corretto operare Parsing bottom-up
uno shift e quando invece è corretto operare una
riduzione.
Il problema coincide con quello di determinare
◮
esattamente le maniglie. Infatti, se fossimo in grado
di risolvere quest’ultimo sapremmo sempre quando
“shiftare” e quando “ridurre”.
Dovremmo infatti ridurre quando e solo quando una
◮
maniglia appare sulla cima dello stack.
Sfortunatamente ci sono grammatiche per le quali il
◮
paradigma shift-reduce non è applicabile, ad
esempio grammatiche ambigue.
51. LFC
Esercizi proposti
Parsing di
linguaggi liberi
Per la grammatica Introduzione
◮ Parsing top-down
Parsing bottom-up
S → (S)S | ǫ
si tracci (sulla falsariga di quanto visto nell’esempio
di slide 48) il comportamento di un parser
shift-reduce su input (()()) e ()(()), nell’ipotesi che il
parser sia in grado di individuare correttamente le
maniglie.
Si verifichi l’esistenza di un conflitto shift-reduce per
◮
la grammatica che astrae il problema del
dangling-else nei linguaggi di programmazione (slide
35)
52. LFC
Parser LR
Parsing di
linguaggi liberi
Si tratta di una classe di parser di tipo shift-reduce
◮ Introduzione
Parsing top-down
(con analisi dell’input da sinistra a destra, “Left to Parsing bottom-up
Right”), caratterizzati da una struttura di programma
comune ma con capacità di analisi diverse.
La diversa capacità di effettuare il parsing dipende
◮
dall’informazione contenuta in apposite tabelle di
parsing che guidano il comportamento del
programma.
In questi appunti analizzeremo un solo tipo di parser
◮
LR, il più semplice, che prende (non a caso) il nome
di (parser) SLR(1).
Per prima cosa vedremo però la “program structure”
◮
comune.
53. LFC
Struttura di un parser LR
Un parser LR è caratterizzato da un programma di Parsing di
◮
linguaggi liberi
controllo (essenzialmente un automa a stati finiti) che Introduzione
Parsing top-down
ha accesso ad uno stack e ad una tabella di parsing, Parsing bottom-up
oltre che a opportuni supporti di input e output.
Le tabelle prescrivono il comportamento del
◮
programma di controllo esclusivamente in funzione
del contenuto dello stack e dei primi k caratteri
presenti in input.
Il valore di k è uno dei parametri che rende più o
◮
meno potente il parser (nel seguito vedremo solo il
caso k = 1)
Lo stack, a differenza dei parser shift-reduce visti
◮
precedentemente, contiene stati anziché simboli.
Tuttavia, come vedremo, ad ogni stato è associato
univocamente un simbolo della grammatica.
54. LFC
Tabelle di parsing
Parsing di
linguaggi liberi
Le tabelle di parsing di un parser LR hanno un
◮ Introduzione
numero di righe pari al numero di stati dell’automa Parsing top-down
Parsing bottom-up
che costituisce il controllo.
Le colonne sono indicizzate dai simboli terminali e
◮
non terminali. Le colonne relative ai terminali
formano quella che viene detta “parte ACTION” della
tabella, mentre le altre formano la “parte GOTO”.
Nella parte action sono previste 4 tipi di azioni:
◮
avanzamento di un carattere sullo stream di input e
◮
inserimento di uno stato in cima allo stack;
utilizzo di una riduzione;
◮
accettazione dell’input;
◮
rilevamento di un errore.
◮
La parte GOTO prescrive stati da inserire nello stack.
◮
55. LFC
Funzionamento del parser
Il funzionamento del parser è definito come segue.
◮
Parsing di
linguaggi liberi
Inizialmente, lo stack contiene un solo stato (lo stato
◮ Introduzione
Parsing top-down
iniziale, naturalmente). Parsing bottom-up
Al generico passo, sia q lo stato in cima allo stack e
◮
x il prossimo carattere in input.
Se ACTION [q, x] = shift r , il parser avanza il
◮
puntatore di input e inserisce lo stato r sullo stack.
Se ACTION [q, x] = reduce i, il parser utilizza la
◮
i-esima produzione (secondo una numerazione
arbitraria ma prefissata). Più precsamente, se A → α
è tale produzione, il parser rimuove ki = |α| stati
dallo stack e vi inserisce lo stato GOTO [q ′ , A] dove
q ′ è lo stato sulla cima dello stack dopo le ki
rimozioni.
Il parser si arresta (naturalmente) in seguito ad
◮
accettazione o errore.
56. LFC
Esempio
Consideriamo la grammatica che genera sequenze Parsing di
◮
linguaggi liberi
di parentesi bilanciate: Introduzione
Parsing top-down
S → (S)S Produzione 1 Parsing bottom-up
S→ǫ Produzione 2
e consideriamo la sguente tabella di parsing (di cui
vedremo più avanti la costruzione):
ACTION GOTO
Stato
( ) $ S
0 shift 2 reduce 2 reduce 2 1
1 accept
2 shift 2 reduce 2 reduce 2 3
3 shift 4
4 shift 2 reduce 2 reduce 2 5
5 reduce 1 reduce 1
Consideriamo il comportamento del parser su input
◮
()().
57. LFC
Esempio (continua)
Stack Input Azione
Parsing di
$0 ()()$ shift 2 linguaggi liberi
Introduzione
→ǫ
$02 )()$ reduce S Parsing top-down
Parsing bottom-up
$023 )()$ shift 4
$0234 ()$ shift 2
→ǫ
$02342 )$ reduce S
$023423 )$ shift 4
→ǫ
$0234234 $ reduce S
→ (S)S
$02342345 $ reduce S
→ (S)S
$02345 $ reduce S
$01 $ accept
Si ricordi che la riduzione con S → (S)S prima
◮
rimuove 4 stati dallo stack, quindi inserisce lo stato
GOTO[q ′ , S], dove q ′ è lo stato che rimane in cima
allo stack dopo le rimozioni.
Analogamente, la riduzione con S → ǫ rimuove 0
◮
stati.
58. LFC
Parsing SLR(1)
Parsing di
linguaggi liberi
Introduzione
Il primo tipo di parser LR che analizziamo è detto Parsing top-down
◮
Parsing bottom-up
Simple LR parser (o semplicemente SLR).
È caratterizzato da tabelle di parsing di relativamente
◮
semplice costruzione (da cui il nome) ma che danno
minori garanzie sulla possibilità di analisi di
grammatiche libere.
In altri termini, ci sono diverse grammatiche libere di
◮
interesse che non possono essere analizzate con
parser SLR (e, segnatamente, SLR(1)).
Si tratta comunque di un primo caso di interesse per
◮
capire la “logica” di un parser LR.
59. LFC
Automa LR(0)
Il passo fondamentale consiste nella definizione di
◮
Parsing di
un automa (che di fatto sarà poi “trasferito” nella linguaggi liberi
Introduzione
tabella di parsing), detto automa LR(0). Parsing top-down
Parsing bottom-up
Data G (la grammatica) si procede innanzitutto
◮
inserendo una produzione aggiuntiva, S ′ → S (il cui
significato sarà chiaro più avanti).
A partire dalle produzioni, si definiscono poi speciali
◮
“oggetti”, che chiameremo item (usando la
terminologia standard inglese).
Un item è una produzione con inserito un marcatore
◮
nella parte destra, tipicamente un punto.
Ad esempio, gli item associati alla produzione
◮
S → (S)S sono: S → ·(S)S, S → (·S)S, S → (S·)S,
S → (S) · S e S → (S)S·.
Ad una produzione tipo S → ǫ è associato il solo
◮
item S → ·.
60. LFC
Automa LR(0) (continua)
Parsing di
linguaggi liberi
Il significato intuitivo di un item associato ad una
◮ Introduzione
Parsing top-down
produzione è di indicare in quale punto siamo arrivati Parsing bottom-up
nel processo di riconoscimento della parte destra
della produzione stessa.
Ad esempio, l’item S → (S) · S indica che abbiamo
◮
riconosciuto una stringa descritta da (S) e che
“speriamo” di riconoscere successivamente una
stringa descrivibile da S.
Gli item vengono poi raggruppati in insiemi (gruppi o
◮
collezioni) che descrivono essenzialmente le stesse
situazioni relative all’avanzamento del processo di
parsing.
Gli insiemi di item costituiranno gli stati dell’automa.
◮
61. LFC
Automa LR(0) (continua)
Con riferimento all’item S → (S) · S, se siamo al
◮ Parsing di
linguaggi liberi
punto da esso descritto (cioè, in particolare, al punto Introduzione
Parsing top-down
in cui speriamo di vedere una stringa descrivibile da Parsing bottom-up
S) possiamo dire di essere anche al punto descritto
dai seguenti due item: → ·(S)S e S → ·.
Questo naturalmente perché il non terminale S (che
◮
speriamo di vedere) può assumere le due forme
(S)S e ǫ.
Si noti che l’intersezione di due collezioni può non
◮
essere vuota.
Ad esempio, l’item S → (·S)S forma gruppo ancora
◮
con S → ·(S)S e S → · (per lo stesso motivo).
Sono le collezioni nel loro insieme che devono
◮
essere distinte.
Si noti che l’item S → (S·)S forma un gruppo da solo.
◮
62. LFC
Esempio
Per la grammatica “aumentata”
◮ Parsing di
linguaggi liberi
Introduzione
S′ → S Parsing top-down
Parsing bottom-up
S → (S)S | ǫ
sono definiti i seguenti insiemi di item:
I0 : S ′ → ·S I3 : S → (S·)S
S → ·(S)S
S→· I4 : S → (S) · S
S → ·(S)S
I1 : S ′ → S· S→·
I2 : S → (·S)S I5 : S → (S)S·
S → ·(S)S
S→·
63. LFC
Come costruire gli insiemi LR(0)
Diamo ora una descrizione dettagliata del
◮
Parsing di
procedimento di costruzione degli insiemi di item. linguaggi liberi
Introduzione
Parsing top-down
L’insieme iniziale (che indicheremo sempre con I0 )
◮ Parsing bottom-up
contiene l’item S ′ → ·S e tutti gli item ottenuti dalle
produzioni di S inserendo il punto all’inizio.
Nell’esempio appena considerato, si aggiungono a
◮
S ′ → ·S due soli item (perché ci sono due produzioni
relative ad S).
Si procede poi iterativamente, lavorando ad ogni
◮
passo su un insieme Ij già formato.
Si considerano tutti i simboli della grammatica
◮
immediatamente alla destra del punto in item di Ij .
Per ogni simbolo così individuato, si forma un gruppo
◮
Ik che contiene, inizialmente, gli item ottenuti
spostando il punto alla destra del simbolo
considerato.
64. LFC
Come costruire gli insiemi LR(0) (continua)
Parsing di
linguaggi liberi
Ad esempio, fra gli item di I0 (per la grammatica
◮ Introduzione
Parsing top-down
appena considerata) ci sono due soli simboli alla Parsing bottom-up
destra del punto, S e ( . Per ognuno di essi si creano
due nuovi insiemi, I1 e I2 , che contengono
inizialmente un solo item ciascuno, S ′ → S· in I1 e
S → (·S)S in I2 .
Tornando alla descrizione generale, se il nuovo
◮
insieme Ik appena inizializzato contiene item in cui il
punto precede un simbolo non terminale A , si
aggiungono ad Ik tutti gli item ottenuti dalle
produzioni di A inserendo il punto all’inizio.
Quest’ultima operazione, che abbiamo già visto più
◮
volte, è detta chiusura dell’insieme Ik .
65. LFC
Come costruire gli insiemi LR(0) (continua)
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
Ad esempio, ad I2 l’operazione di chiusura aggiunge
◮
gli item S → ·(S)S e S → ·.
Ad I1 invece non viene aggiunto nulla, perché l’unico
◮
item iniziale (S ′ → S·) non ha simboli non terminali
immediatamente a destra del punto.
Se Ik non coincide con nessuno degli insiemi già
◮
generati viene tenuto, altrimenti viene scartato.
Il procedimento si arresta quando non ci sono più
◮
insiemi di item da considerare.
66. LFC
Esempio
Consideriamo la seguente grammatica (aumentata)
◮ Parsing di
linguaggi liberi
che genera il linguaggio {an bn |n ≥ 1}: Introduzione
Parsing top-down
Parsing bottom-up
′
→S
S
S → aSb | ab
L’insieme iniziale di item è:
◮
I0 = {S ′ → ·S, S → ·aSb, S → ·ab}.
◮
I simboli immediatamente a destra del punto in I0
◮
sono S e a, per cui inizializziamo due insiemi:
I1 = {S ′ → S·};
◮
I2 = {S → a · Sb, S → a · b}.
◮
La chiusura non aggiunge nulla a I1 (poiché l’item
◮
S ′ → ·S ha un non terminale immediatamente a
destra del punto), mentre aggiunge item ad I2 , che
diventa
I2 = {S → a · Sb, S → a · b, S → ·aSb, S → ·ab}.
◮
67. LFC
Esempio (continua)
Parsing di
linguaggi liberi
Introduzione
L’insieme non dà origine ad altri insiemi di item
◮ Parsing top-down
Parsing bottom-up
(perché non ci sono simboli a destra del punto).
Nel’insieme I2 ci sono tre simboli distinti a destra del
◮
punto, per cui formiamo tre insiemi:
I3 = {S → aS · b};
◮
I4 = {S → ab·};
◮
I5 = {S → a · Sb, S → a · b}.
◮
La chiusura modifica solo I5 , che diviene
◮
I5 = {S → a · Sb, S → a · b, S → ·aSb, S → ·ab},
◮
ma che viene scartato in quanto coincidente con I2 .
Infine lavorando su I3 si ottiene:
◮
I5 = {S → aSb·},
◮
68. LFC
Esempio (continua)
Parsing di
linguaggi liberi
Ricapitolando, gli insiemi LR(0) di item associati alla
◮ Introduzione
Parsing top-down
grammatica sono: Parsing bottom-up
I0 : S ′ → ·S I3 : S → aS · b
S → ·aSb
S → ·ab I4 : S → ab·
I1 : S ′ → S· I5 : S → aSb·
→ a · Sb
I2 : S
→a·b
S
→ ·aSb
S
→ ·ab
S
69. LFC
Funzioni CLOSURE e GOTO
Parsing di
linguaggi liberi
Il prodedimento appena descritto (in maniera
◮ Introduzione
Parsing top-down
alquanto discorsiva) può essere sinteticamente Parsing bottom-up
ricapitolato facendo uso delle due funzioni
CLOSURE e GOTO, che lavorano su insiemi di item.
Dato un insieme di item I, CLOSURE (I) coincide con
◮
l’insieme I cui siano stati aggiunti tutti gli item del tipo
B → ·β (dove B → β è una produzione della
grammatica) qualora almeno un item di X sia del tipo
A → α · Bγ.
Se I è un insieme di item e X un simbolo della
◮
grammatica GOTO(I, X ) è definito come la chiusura
dell’insieme J di item del tipo A → αX · β, dove
A → α · X β è in I.
70. LFC
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Parsing bottom-up
Algorithm 2 Insiemi di item LR(0)
1: C ← {CLOSURE ({S ′ → S})}
2: repeat
for each I ∈ C do
3:
for each X ∈ T ∪ N do
4:
if GOTO(I, X ) = {}and GOTO(I, X ) ∈ C then
5:
C ← C ∪ {GOTO(I, X )}
6:
7: until No new state is added to C
71. LFC
Esempio
Parsing di
linguaggi liberi
Introduzione
Da ultimo, consideriamo la costruzione degli insiemi
◮ Parsing top-down
Parsing bottom-up
di item LR(0) per la grammatica aumentata
E′ → E
→ E +T | T
E
→ T ×F | F
T
→ (E ) | id
F
che, ricordiamo, non è adatta al parsing top-down.
Nella slide seguente presentiamo direttamente la
◮
collezione degli insiemi di item ottenuta applicando
l’algoritmo 2.
72. LFC
Esempio (continua)
Parsing di
linguaggi liberi
I0 : E ′ → ·E → (·E ) I7 : T → T × ·F
I4 : F Introduzione
Parsing top-down
E → ·E + T → ·E + T F → · (E )
E Parsing bottom-up
E → ·T → ·T F → ·id
E
T → ·T × F → ·T × F
T
T → ·F → ·F I8 : E → E · +T
T
F → · (E ) → · (E ) F → (E ·)
F
F → ·id → ·id
F
I9 : E → E + T ·
I1 : E ′ → E · I5 : F → id· T → T · ×F
E → E · +T
→ E + ·T I10 : T → T × F ·
I6 : E
I2 : E → T · → ·T × F
T
T → T · ×F → ·F I11 : F → (E ) ·
T
→ · (E )
F
I3 : T → F · → ·id
F
73. LFC
Automa LR(0)
Parsing di
linguaggi liberi
Come già anticipato, le collezioni di item LR(0)
◮ Introduzione
Parsing top-down
determinate con la procedura appena descritta Parsing bottom-up
costituiscono gli stati dell’automa LR(0) (che, a sua
volta, è alla base del parsing SLR(1) che stiamo
costruendo).
Per completare la descrizione dell’automa è
◮
necessario definire la funzione δ di transizione.
In realtà abbiamo già descritto tale funzione, che
◮
coincide “essenzialmente” con la funzione GOTO.
Si noti che, tuttavia, che GOTO(I, X ) “costruisce”
◮
nuovi stati e dunque J = GOTO(I, X ) non viene
aggiunto se risulta già definito,
In tale caso vale comunqe δ(I, X ) = J.
◮
74. LFC
Esempio
Parsing di
L’automa LR(0) per la grammatica linguaggi liberi
◮
Introduzione
Parsing top-down
Parsing bottom-up
′
→S
S
S → (S)S | ǫ
è:
accept
I1
$
S
S
I0 I4 I5
( (
I2 )
S
( I3
75. LFC
Esempio
Parsing di
L’automa LR(0) per la grammatica linguaggi liberi
◮
Introduzione
Parsing top-down
Parsing bottom-up
′
→S
S
S → aSb | ab
è:
accept
I1
$
S
b
I0 I3 I5
S
a
b
I2 I4
a
76. LFC
Esempio
L’ultimo esempio è per la grammatica
◮
′ Parsing di
→
E E
linguaggi liberi
→ E +T | T
E
Introduzione
→ T ×F | F
T Parsing top-down
Parsing bottom-up
→ (E) | id
F
accept
$
+ T
I1 I6
F
+
E
I3 I9
id
F
(
)
id
I0 I8 I11
I5
F id
×
( id
T F
I4
T (
(
F
I10
I2 I7
×
77. LFC
Tabelle di parsing SLR(1)
Parsing di
linguaggi liberi
Introduzione
Completiamo ora la descrizione del parser con Parsing top-down
◮
Parsing bottom-up
l’algoritmo di definizione della tabella di parsing.
Le tabelle incorporano le informazioni contenute
◮
nell’automa, che da sono non è sufficiente per
eseguire l’analisi (si ricordi che un automa a stati
finiti non è in grado di riconoscere linguaggi liberi
(che non siano anche regolari).
L’algoritmo esamina gli stati dell’automa e le
◮
transizioni uscendi da ciascuno stato.
Esso necessita anche di conoscere, per ogni simbolo
◮
non terminale A, l’insieme di simboli FOLLOW (A).
78. LFC
Tabelle di parsing SLR(1) (continua)
Per ogni stato Ij , consideriamo le transizioni uscenti.
◮
Parsing di
linguaggi liberi
Se esiste una transizione da Ij a Ik etichettata X ∈ T
◮ Introduzione
poniamo ACTION [j, X ] = shift k. Parsing top-down
Parsing bottom-up
Se esiste una transizione etichettata X ∈ N poniamo
◮
GOTO [j, X ] = shift X .
Se nell’insieme di item corrispondenti a Ij esiste un
◮
item A → α·, allora poniamo
ACTION [j, X ] = reduce A → α per tutti i simboli X in
FOLLOW (A).
Se Ij contiene l’item S ′ → S· si pone
◮
ACTION[i, $] = accept.
Se, ad un qualunque passo dell’algoritmo, si
◮
manifesta un cosiddetto conflitto shft-reduce (cioè si
tenta di inserire in una entry della parte ACTION sia
un’azione di shift che una di riduzione) allora la
grammatica non è SLR(1).
79. LFC
Esempio
Parsing di
linguaggi liberi
Introduzione
Per la grammatica
◮ Parsing top-down
Parsing bottom-up
S → aSb Produzione 1
S → ab Produzione 2
l’algoritmo appena delineato produce la seguente
tabella:
ACTION GOTO
Stato
$ S
a b
0 shift 2 1
1 accept
2 shift 2 shift 4 3
3 shift 5
4 reduce 2 reduce 2
5 reduce 1 reduce 1
80. LFC
Esempio (continua)
Parsing di
linguaggi liberi
Introduzione
Parsing top-down
Consideriamo il comportamento del parser su input
◮ Parsing bottom-up
aabb
Stack Input Azione
$0 shift 2
aabb$
$02 shift 2
abb$
$022 shift 4
bb$
reduce S → ab
$0224 b$
$023 shift 5
b$
reduce S → aSb
$0235 $
$01 $ accept
81. LFC
Esempio
Diamo infine la tabella di parsing per la grammatica
◮
Parsing di
E → E + T Prod. 1 T→F Prod. 4 linguaggi liberi
Introduzione
E→T F → (E ) Prod. 5
Prod. 2 Parsing top-down
Parsing bottom-up
T → T × F Prod. 3 F → id Prod. 6
ACTION GOTO
Stato
×
+ ( )
id $ E T F
0 s5 s4 1 2 3
1 s6 accept
2 r2 s7 r2 r2
3 r4 r4 r4 r4
4 s5 s4 8 2 3
5 r6 r6 r6 r6
6 s5 s4 9 3
7 s5 s4 10
8 s6 s 11
9 r1 s7 r1 r1
10 r3 r3 r3 r3
11 r5 r5 r5 r5
82. LFC
Esempio (continua)
Consideriamo il comportamento del parser su input
◮
Parsing di
id × (id + id) linguaggi liberi
Introduzione
Parsing top-down
Stack Input Azione Parsing bottom-up
$0 id × (id + id) $ shift 5
$0 5 × (id + id) $ reduce F → id
$0 3 × (id + id) $ reduce T →F
$0 2 × (id + id) $ shift 7
$0 2 7 (id + id) $ shift 4
$0 2 7 4 id + id) $ shift 5
$0 2 7 4 5 +id) $ reduce F → id
$0 2 7 4 3 +id) $ reduce T →F
$0 2 7 4 2 +id) $ reduce E →T
$0 2 7 4 8 +id) $ shift 6
$0 2 7 4 8 6 id) $ shift 5
$0 2 7 4 8 6 5 )$ reduce F → id
$0 2 7 4 8 6 3 )$ reduce T →F
$0 2 7 4 8 6 9 )$ →E +T
reduce E
$0 2 7 4 8 )$ shift 11
$0 2 7 4 8 11 $ → (E)
reduce F
$0 2 7 10 $ reduce T →T ×F
$0 2 $ reduce E →T
$0 1 $ accept