Parsing - Dipartimento di Matematica e Informatica

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