Analisi sintattica

Transcript

Analisi sintattica
Analisi sintattica
Analisi sintattica
programma
scanner
tokens
parser
albero
sintattico
Errori sintattici
Un parser deve riconoscere la struttura di una stringa di ingresso,
la cui struttura è fornita in termini di regole di produzione di una
CFG, BNF, o diagrammi sintattici. Un parser è una macchina
“astratta” che raggruppa input in accordo con regole
grammaticali.
1
Sintassi
„ La sintassi è costituita da un insieme di regole che
„ definiscono le frasi formalmente corrette
„ permettono di assegnare ad esse una struttura (albero
sintattico) che ne indica la decomposizione nei costituenti
immediati.
„ Ad es. la struttura di una frase (ovvero di un programma) di
un linguaggio di programmazione ha come costituenti le
parti dichiarative e quelle esecutive. Le parti dichiarative
definiscono i dati usati dal programma. Le parti esecutive si
articolano nelle istruzioni, che possono essere di vari tipi:
assegnamenti, istruzioni condizionali, frasi di lettura, ecc. I
costituenti del livello più basso sono gli elementi lessicali
già considerati, che dalla sintassi sono visti come atomi
indecomponibili. Infatti la loro definizione spetta al livello
lessicale.
Grammatiche non contestuali:
vantaggi
„ La teoria formale dei linguaggi offre diversi modelli, ma
nella quasi totalità dei casi il tipo di sintassi adottata è
quello noto come sintassi libera o non-contestuale (contextfree) che corrisponde al tipo 2 della gerarchia di Chomsky.
„ I metodi sintattici per il trattamento del linguaggio sono
„
„
„
„
semplici
efficienti
la definizione del linguaggio attraverso le regole delle sintassi
libere dal contesto è diretta ed intuitiva
gli algoritmi deterministici di riconoscimento delle frasi sono
veloci (hanno complessità lineare) e facili da realizzare
partendo dalla sintassi.
„ Tutti questi vantaggi hanno imposto le grammatiche libere
dal contesto come l’unico metodo pratico per definire
formalmente la struttura di un linguaggio.
2
Grammatiche non contestuali:
svantaggi
„ Non tutte le regole di un linguaggio di programmazione possono essere
espresse in termini nel modello noncontestuale. Ad esempio non puo
espremere le seguenti regole:
„ in un programma ogni identificatore di variabile deve comparire in
una dichiarazione
„ il tipo del parametro attuale di un sottoprogramma deve essere
compatibile con quello del parametro formale corrispondente
„ Un altro limite del modello sintattico libero tocca le esigenze di calcolo
della traduzione. Se si guardano le traduzioni definibili con i soli metodi
sintattici, esse sono
„
„
decisamente povere
inadeguate ai problemi reali (compilazione di un linguaggio
programmativo nel linguaggio macchina)
„ Da queste critiche sarebbe sbagliato concludere che i metodi sintattici
sono inutili: al contrario essi sono indispensabili come supporto
(concettuale e progettuale) su cui poggiare i più completi strumenti della
semantica.
Parser
• Il parser lavora con stringhe di tokens
Programa
sorgente
Scanne
r
token
get next token
Parse
r
Albero sintattico
3
Grammatiche di tipo Context-Free
„ Le strutture ricorsive presenti nei linguaggi di
programmazione sono definite da una grammatica
context-free.
„ Una grammatica context-free (CFG) è definita come
„ Un insieme finito di terminali V (sono i token prodotti
dallo scanner)
„ Un insieme di non terminali V.
„ Un assioma o simbolo iniziale (uno dei non terminali.
„ Un insieme di produzioni (dette anche “rewriting rules”
con la seguente forma
A → X1 ... Xm
dove X1 to Xm possono essere terminali o non
terminali.
„ Se m =0 si ha A → ε che è una produzione valida
Grammatiche di tipo Context-Free
„ Le strutture ricorsive presenti nei linguaggi di
programmazione sono definite da una grammatica
context-free.
„ Una grammatica context-free (CFG) è definita come
„ Un insieme finito Esempio
di terminali V (sono i token prodotti
dallo scanner)
T = { (, ), id , + , * , / , -}
V = {E} V.
„ Un insieme di non terminali
E→ E+E
„ Un assioma o simbolo iniziale (uno dei non terminali.
E→ E–E
E → E(dette
* E anche “rewriting rules”
„ Un insieme di produzioni
E
→
E
/E
con la seguente forma
E→-E
A → X1 ... XmE → ( E )
E →essere
id
dove X1 to Xm possono
terminali o non
terminali.
„ Se m =0 si ha A → ε che è una produzione valida
4
Derivazioni
E ⇒ E+E
„ E+E deriva da E
„
„
Possiamo sostituire E con con E+E
Perr poter fare questo deve esistere una produzione E→E+E nella
grammatica.
E ⇒ E+E ⇒ id+E ⇒ id+id
„ Una sequenza di sostituzioni di non-terminali e chiamata una derivazione di
id+id da E.
„ In generale un passo di derivazione è
αAβ ⇒ αγβ
α1 ⇒ α2 ⇒ ... ⇒ αn
se ∃ A→γ in G
α ∈ (V ∪ T)* e β ∈ (V ∪ T)*
(αn deriva da α1 o α1 è derivato da αn )
⇒
one step
⇒* zero o più steps
⇒+ 1 o più steps
V = { Prog, Seq, Istr, Expr}
T = {; , id , + }
Assioma = Prog
Prog → { Seq }
V = { Prog, Seq, Istr, Expr}
Seq →Seq ; Istr
T = {; , id , + }
Seq → ε
Assioma = Prog
Istr →Seq
Prog → { Seq }
Istr →id = Expr
Expr →id
Seq →Seq ; Istr | ε
Expr →Expr + id
Istr →Seq | id = Expr
Expr →id | Expr + id
5
„ Per esempio partendo da Prog possiamo generate,
aplicando ripetutamente le produzione le seguenti
stringhe:
Prog
{ Seq }
{ Seq ;Istr}
{Istr;Istr}
{ id = Expr ;Istr}
{ id = id ;Istr}
{ id = id ; id = Expr }
{ id = id ; id = Expr + id}
{ id = id ; id = id + id}
„ Le produzioni devono avere le seguenti
proprietà:
no produzioni inutili o ridondanti (i.e., A → A),
„ no non-terminali senza produzioni
„
„
(e.g., A → Ba dove B non è definito),
no cicli infiniti (e.g., A → Aa senza altre
produzioni per A),
„ no ambiguita’: una grammatica con più alberi
sintattici per la stessa espressione è ambigua
„ descrivere correttamente il linguaggio.
„
6
CFG - Terminologia
„ L(G) è il linguaggio generato da G.
„ Una frase di L(G) è una stringa di simboli terminali di G.
„ Se S è l’assioma o start symbol di G allora
+
„ ω è una frase di L(G) iff S ⇒ ω dove ω ∈ T*.
• Se G è una grammatica context-free, L(G) è un
linguaggio context-free.
• Due grammatiche sono equivalenti se producono lo
stesso linguaggio.
•* S ⇒ α
Se α contiene non-terminali, è chiamata forma di frase di G.
Se α non contiene no-terminali è chiamata frase di G.
Derivazioni Left-Most e Right-Most
Left-Most Derivation
E ⇒ -E ⇒ -(E) ⇒ -(E+E) ⇒ -(id+E) ⇒ -(id+id)
Right-Most Derivation
E ⇒ -E ⇒ -(E) ⇒ -(E+E) ⇒ -(E+id) ⇒ -(id+id)
„ top-down parser cerca una left-most derivation del
programma sorgente
„ bottom-up parser cerca una right-most derivation del
programma sorgente
7
Parse Tree (Abstract Sintax Tree)
I nodi non foglie dell’albero sintattico sono simboli non-terminal, le foglie simboli
terminali.
E
E ⇒ -E
⇒ -(E)
-
E
E
-
E
(
E
)
E
E
-
⇒ -(id+E)
-
E
(
E
)
E
+
E
id
E
⇒ -(E+E)
⇒ -(id+id)
E
(
E
)
E
+
E
E
(
E
)
E
+
E
id
id
Un albero sintattico è una rappresentazione grafica di una derivazione.
„ Per esempio partendo da Prog possiamo generate,
aplicando ripetutamente le produzione le seguenti
stringhe:
Prog
{ Seq }
Seq
{ Seq ;Istr}
{Istr;Istr}
Seq
Istr
{ id = Expr ;Istr}
Istr
{ id = id ;Istr}
{ id = id ; id = Expr }
{ id = id ; id = Expr + id}
{ id = id ; id = id + id}
8
Esempio: Consideriamo l’analisi della frase
1+2*3
secondo la seguente grammatica
<expr> ::= <expr> + <expr>
| <expr> * <expr>
| numberper
„ L’analisi sintattica può essere vista come un processo
Parser
costruire gli alberi sintattici (parse tree).
„ La sintassi di un programma è descritto da un agrammatica
libera dal contesto (CFG). La notazione BNF (Backus-Naur
Form) è una notazione per la descrzione di una CFG.
„ Il parser verifica se un programma sorgente soddisfa le regole
implicate da una grammatica context-free o no.
„
„
Se le soddisfa, il parser crea l’albero sintattico (del programma
Altrimenti genera un messaggio di errore.
„ Una grammatica non contestuale
„ Da una specifica rigorosa della sintassi dei linguaggi di
programmazione
„ Il progetto della grammatica e la fase iniziale del progetto del
compilatore
„ Esistono strumenti automatici per costruire automaticamente il
compilatore dalla grammatica
Ambiguita
• Una grammatica è ambigua se esistono più alberi sintattici per la
stessa frase.
E
E
E ⇒ E+E ⇒ id+E ⇒ id+E*E
⇒ id+id*E ⇒ id+id*id
Esempio
T = { (, ), id , + , * , / , -}
= {E}
EV⇒
E*E ⇒ E+E*E ⇒ id+E*E
E⇒
→ id+id*E
E + E ⇒ id+id*id
E→ E–E
E
E→ E*E
E→ E/E
id
E→-E
E→ (E)
E → id
id
+
E
id
E
*
E
id
E
E
+
*
E
E
id
id
9
Esempio
Ambiguita
T = { (, ), id , + , * , / , -}
V = {E}
E→ E+E
E→ E–E
•E Una
è ambigua se esistono più alberi sintattici per la
→ Egrammatica
*E
stessa
frase.
E→ E/E
E
E→-E
E +
E
EE
→⇒
( EE+E
) ⇒ id+E ⇒ id+E*E
E →⇒
id id+id*E ⇒ id+id*id
id
E
E *
id
E ⇒ E*E ⇒ E+E*E ⇒ id+E*E
⇒ id+id*E ⇒ id+id*id
E
id
id
E
E
+
*
E
E
id
id
„ Per poter costruire un parser la grammatica
non deve essere ambigua
„ Grammatica non ambigua Î ∃ un unico
albero sintattico per ogni frase del linguaggio
„ Le ambiguità nella grammatica devono
essere eliminate durante il progetto del
compilatore
10
Ambiguità
istr → if expr then istr |
if expr then istr else istr
| otheristrs
if E1 then if E2 then S1 else S2
istr
istr
if expr then
E1
istr
else
istr
if expr then istr
S2
E2
S1
1
if expr then istr
E1
if expr then istr else istr
E2
S1
S
2
Ambiguità
• Noi preferiamo il secondo albero sintattico (else corrisponde al
if più vicino).
• Dobbiamo eliminare l’ambiguità con tale obiettivo
• La grammatica non-ambigua sarà:
istr → matchedistr | unmatchedistr
matchedistr → if expr then matchedistr else matchedistr
| otheristrs
unmatchedistr → if expr then istr |
if expr then matchedistr else unmatchedistr
11
Ambiguita – Precedenza degli
operatori
„ Grammatiche ambigue possono essere rese non-ambigue
in accordo alle precedenze degli operatori e alle regole di
associativià degli operatori.
E → E+E | E*E | E^E | id | (E)
precedenze:
^ (right to left)
* (left to right)
+ (left to right)
E → E+T | T
T → T*F | F
F → G^F | G
G → id | (E)
Ricorsione sinistra (Left Recursion)
„ Una grammatica è “left recursive” se ha un non
+
terminale A tale che
A ⇒ Aα
per qualche stringa α
„ Le tecniche di parser Top-down non possono gestire
grammatiche left-recursive.
„ Una grammatica left-recursive deve esssere
convertita in una non left-recursive.
„ La ricorsione sinistra può comparire in un singolo
passo della derivazione (immediate left-recursion), o
può comparire in più che un passo.
12
Immediate Left-Recursion
A→Aα| β
dove β ≠ Aγ e γ ∈ (V ∪ T)*
⇓
A → β A’
A’ → α A’ | ε
In generale
grammatica equivalente
A → A α1 | ... | A αm | β1 | ... | βn
dove βi ≠ Aγ e γ ∈ (V ∪ T)*
⇓
A → β1 A’ | ... | βn A’
A’ → α1 A’ | ... | αm A’ | ε
grammatica equivalente
Esempio
E → E+T
E→ T
T → T*F
T→ F
eliminate immediate left recursion
F → id
F →(E)
E → T E’
E’ → +T E’
E’ → ε
T → F T’
T’ → *F T’
T’ → ε
F → id
F → (E)
13
Left-Recursion – Problema!!
• Una grammatica che non e direttamente left-recursive, ma lo è in
modo indirettp.
• Anche in questo caso va eliminata la ricorsione sinistra
Esempio
S → Aa | b
A → Sc | d
S ⇒ Aa ⇒ Sca
A ⇒ Sc ⇒ Aac
Algoritmo per l’eliminazione della
ricorsione sinistra
- Ordinare I non-terminali A1 ... An
- for i from 1 to n do {
- for j from 1 to i-1 do {
sostituire ogni produzione
Ai → Aj γ
con
Ai → α1 γ | ... | αk γ
dove Aj → α1 | ... | αk
}
- eliminare la ricorsione sinistra nelle produzioni di Ai
}
14
Esempio
S → Aa | b
A → Ac | Sd | f
- Ordinare I non-terminali: S, A
per S:
- non c’e una ricorsione sinistra diretta.
for A:
- sostituiamo A → Sd con A → Aad | bd
Cosi avremoA → Ac | Aad | bd | f
- Eliminiamo la ricorsione sinistra in A
A → bdA’ | fA’
A’ → cA’ | adA’ | ε
Avremo la grammatica non ricorsiva equivalente:
S → Aa | b
A → bdA’ | fA’
A’ → cA’ | adA’ | ε
Esempio
S → Aa | b
A → Ac | Sd | f
- Ordinare I non-terminali : A, S
per A:
Eliminamo la ricorsione sinistra in A
A → SdA’ | fA’
A’ → cA’ | ε
per S:
- Sostituiamo S → Aa with S → SdA’a | fA’a
Così avremo S → SdA’a | fA’a | b
- Eliminamo la ricorsione sinistra in S
S → fA’aS’ | bS’
S’ → dA’aS’ | ε
Avremo la grammatica non ricorsiva equivalente:
S → fA’aS’ | bS’
S’ → dA’aS’ | ε
A → SdA’ | fA’
A’ → cA’ | ε
15
Left-Factoring (fattorizzazione
sinistra)
„ Un parser top down deterministico richiede
una grammatica left-factored.
grammatica Î grammatica equivalente
istr → if (expr ) istr else istr
if (expr) istr
|
Left-Factoring (cont.)
„ In generale,
A → αβ1 | αβ2
dove α ∈ (V∪T)* - {ε}, β1∈ (V∪T)* - {ε}, β2∈ (V∪T)* {ε}
e β1≠.
„ Noi possiamo riscrivere la grammatica come segue
A → αA’
A’ → β1 | β2
16
Algoritmo
„ Per ogni non-terminale A con due o più
alternative (produzioni) con una parte non
vuota comune
A → αβ1 | ... | αβn | γ1 | ... | γm
diventa
A → αA’ | γ1 | ... | γm
A’ → β1 | ... | βn
Esempio
A → abB | aB | cdg | cdeB | cdfB
⇓
A → aA’ | cdg | cdeB | cdfB
A’ → bB | B
⇓
A → aA’ | cdA’’
A’ → bB | B
A’’ → g | eB | fB
17
Esempio
A → ad | a | ab | abc | b
⇓
A → aA’ | b
A’ → d | ε | b | bc
⇓
A → aA’ | b
A’ → d | ε | bA’’
A’’ → ε | c
istr → if (expr ) istr else istr
if (expr) istr
|
istr → if (expr ) istr X
X → else istr
X →ε
18
„ Le frasi possono essere analizzate da
sinistra a destra (L parser), possono
essere costruite con derivazioni left-most
(LL(k) parser) o right-most (LR(k) parser)
utilizzando k symboli di look-ahead!
LL è più conosciuta come top-down parser.
„ LR è più conosciuta come bottom-up
parser.
„ Per ragioni pratiche k deve essere piccolo.
„
„ Per un compilatore è auspicabile l’uso di
grammatiche che possono essere analizzate in
modo deterministico con al più k symboli di lookahead. L’assenza di ambiguità è condizione
necessaria per un analisi determinstica
„ Consideriamo un bottom up parse per abbcde
generato dalla seguente grammatica con assioma
S, eseguento un approccio “left-most matches
First”.
S → aAcBe
A → Ab|b
B →d
19
abbcde
applicando B → d
aAbcBe
applicando A → b
aAcBe
applicando A → Ab
S
applicando S → aAcBe
S4
A2
B3
A1
a
b
b
c
d
e
Parser
„ Due differenti metodi di analisi sintattica
„
„
top-down: l’albero sintattico è creato dalla radice alle foglie
bottom-up: l’albero sintattico è creato dalle foglie alla radice
„ Il parser può lavorare in una varietà di modi ma esso
tipicamente processa l’ input da sinistra a destra.
„ Parser sia top-down che bottom-up possono essere
implmentati in modo efficente solo per alcune sottoclassi di
gramatiche context-free:
„
„
LL per top-down
LR per bottom-up
„ Una “left-most (right-most) derivation” è una derivazione nella
quale durante ogni passo viene sostituito solo il non-terminale
più a sinistra (destra).
20
Definzione di First(α)
Data una grammatica non contestuale G(V,T,P,S) si definisce
First(α) = {a in T | α ⇒* aβ}
Quindi First(α) è il seguente sottoinsieme di T:
• se α = aβ con a ∈ T allora a ∈ First(α)
• se α = Aβ con A ∈ T allora
• se ∃ una produzione A → γ allora First(γ) ⊆ First(α)
• se ∃ una produzione A → ε allora First(β) ⊆ First(α)
First(α) è l’insieme di tutti i terminali con cui può iniziare una
stringa derivata da α.
Se α → *ε allora ε ∈ First(α).
First(α) = {a ∈ T | α → * a β} ∪ if a → *ε then {ε} else ∅
Esempio
T = {a, b, c, d, e},
V = {S, B, C}
P = { S → aSe | B,
B → bBe | C,
C → cCe | d }
S=S
First(aSe) = {a}
First(B) = First(bBe) ∪ First(C) = {b} ∪ First(cCe) ∪ First(d) =
{b} ∪ {c} ∪ {d} = {b, c, d}
First(bBe) = {b}
First(C) = First(cCe) ∪ First(d) = {c} ∪ {d} = {c, d}
First(cCe) = {c}
21
Esempio
T = {a, b, c, d, e}
V = {A, B, C, D}
P = { A → B | Ce | a
B → bC,
C → Dc
D → d | ε}
S=A
First(B) = First(b) ={b}
First(C e) = First (Dc) = First(d) ∪ First(ε) = {d} ∪{ε} = {d,
ε}
First(a) = {a}
First(bC )= {b}
Esempio
T = {(, +, ), v, f}
V = {E, Prefix, Tail}
P = { E → Prefix(E) | v Tail
Prefix → f | ε
Tail → + E | ε }
S=E
First(Prefix(E) ) = {f} ∪{ε}
First(v Tail) = {v}
First(f) = {f}
First(ε )= {ε}
22
Esempio
T = {a, (, ), +, ","}
V = {E, T, L, P}
P={ E→ E+T|T
T → a | (E) |
a(L)
L→ P|ε,
P → E | P ","
E}
S=E
First(E) = {a, (}
First(T) = {a, (}
First(L) = {ε , a, (}
T = {a, b, c, d, e}
V = {A, B, C, D}
P = { A → B | C | Ce | a
B → bC
C → Dc | D
D→d| ε}
S=A
First(A) = {a, b, d, c, e, ε }
First(B) = {b}
First(C) = {d, c, ε }
Definizione di Follow(A)
Data una grammatica non contestuale G(V,T,P,S) si definisce
Follow(A) l’insieme dei terminali che seguono in una qualsiasi frase
Esso è definito come segue:
Follow(A) = {a ∈ T | S → +… Aa ….} ∪
(if S ⇒* … A then {ε} else ∅}
23
T = {a, b, c, d, e},
T = {a, (, ), +, ","}
V = {S, B, C}
V = {E, T, L, P}
P = { S → aSe | B,
P={ E→ E+T|T
B → bBe | C,
T → a | (E) | a(L)
C → cCe | d }
L→ P|ε,
S=S
P → E | P "," E}
Follow(S) = {e, $}
S=E
Follow(B) = {e, $}
Follow(E) = {+, ) , “,“ , $}
Follow(C) = {e, $}
Follow(T) = {+, ) , “,“ , $}
Follow(L) = { ) }
Follow(P) = {) , “,”}
T = {(, +, ), v, f}
V = {E, Prefix, Tail}
P = { E → Prefix(E) | v
Tail
Prefix → f | ε
Tail → + E | ε }
S=E
Follow(Prefix ) = { ( }
Follow(E) = {$}
Follow(Tail) = { $ }
T = {a, b, c, d, e}
V = {A, B, C, D}
P = { A → B | Ce | a
B → bC,
C → Dc
D → d | ε}
S=A
Follow(A) = {$}
Follow(B) = {$}
Follow(C) = {$, e}
Follow(D) = {c}
24
T = {a, b, c, d, e}
V = {A, B, C, D}
P = { A → B | C | Ce | a
B → bC
C → Dc | D
D→d| ε}
S=A
Follow(A) = {$}
Follow(B) = Follow(A) = {$}
Follow(C) = Follow(A) ∪ Follow(B) ∪ {e} = {e , $}
Follow(D) = {c } ∪ Follow(C) = {c, e , $}
25