Post on 27-Jun-2022
Compiladores (CC3001)Aula 6: Análise sintática bottom-up
Pedro Vasconcelos
DCC/FCUP
2020
Esta aula
Análise sintática LR
Análise LR(0)
Análise SLR(1)
Resolução de con�itos
Extras
Re-lembrar: fases dum compilador
texto do programa↓
Análise lexical↓
sequência de tokens
↓Análise sintática
↓árvore sintática abstrata
↓Análise semântica
↓AST & tabela de símbolos
Geração de código↓
código intermédio
Seleção de instruções↓
código assembly simbólico↓
Alocação de registos↓
código assembly concreto↓
Assembler & linker↓
código executável
Análise sintática bottom-up
I Na aula passada estudamos análise sintática LL(1)(Left-right parse, Leftmost derivation, 1-symbol lookahead)
I Di�culdade: pode ser trabalhoso ou mesmo impossível re-escrever uma gramáticapara a forma LL(1)
I Vamos hoje ver métodos de análise sintática LR (Left-right parse, Rightmostderivation)
I Principais vantagens:I análise LR permitem reconhecer mais linguagensI é mais fácil re-escrever uma gramática para análise LR do que LLI análise LR permite resolver ambiguidades de�nindo prioridades e associatividades de
símbolos sem alterar a gramática
Analisadores LR
Um autómato de pilha:I Uma sequência de símbolos terminais (input)I Uma pilha de símbolos (terminais e não-terminais)I Inicialmente a pilha está vazia a sequência de input está no inícioI Em cada passo escolhe uma de duas ações:
Shift Mover o próximo terminal da entrada para o topo da pilha;Reduce Escolher uma produção X → γ tal que os símbolos γ estão no topo
da pilha; remover esses símbolos e empilhar X .
Por razão técnicas: adicionamos um novo símbolo inicial S ′ e uma produção S ′ → S(em que S é o símbolo inicial da gramática original).
Exemplo 1
Uma gramática para a linguagem de parêntesis equilibrados:
S ′ → SS → (S)S | ε
Sequência de passos de um analisador LR:
pilha input açãoε ()$ shift( )$ reduce S → ε(S )$ shift(S) $ reduce S → ε(S)S $ reduce S → (S)SS $ reduce S ′ → SS ′ $ accept
Lendo de baixo para cima obtemos a derivação:
S ′ ⇒ S ⇒ (S)S ⇒ (S)⇒ ()
Exemplo 2
Uma gramática para expressões simples:
E ′ → EE → E+n | n
Sequência de passos:
pilha input açãoε n+n$ shiftn +n$ reduce E → n
E +n$ shiftE+ n$ shiftE + n $ reduce E → E + n
E $ reduce E ′ → EE ′ $ accept
Lendo de baixo para cima obtemos a derivação:
E ′ ⇒ E ⇒ E+n⇒ n+n
Autómato LR
Como escolhe o autómato a próxima ação?I pela con�guração da pilha (não apenas o símbolo do topo)I e possívelmente pelos próximos símbolos da entrada (look-ahead)
Assim:I Vamos usar inteiros para representar os estados do autómato (con�gurações da
pilha)I O autómato muda de estado quando empilhamos ou removemos símbolos da pilhaI Estas ações são descritas numa tabela de parsing LR
Tabela de parsing LR
I Cada linha corresponde a um estado (número inteiro)I Cada coluna corresponde a um símbolo (terminal ou não-terminal)I Cada entrada contém uma ação
shift q mover um terminal para a pilha e transitar para o estado qreduce k seja X → α1 . . . αn a produção número k :
1. remover da pilha αn, . . . , α1 (i.e. os símbolos do lado direito)2. retroceder para o estado associado à nova pilha e colocar X no topo;3. procurar na tabela �go q� para a entrada X nesse estado;4. transitar para o estado q
go q mudar para o estado q (depois de um reduce)accept terminar aceitando a sequência
Exemplo de uma tabela
a b c $ T R0 s3 s4 r3 r3 g1 g21 a2 r1 r13 s3 s4 r3 r3 g5 g24 s4 r3 r3 g65 s76 r4 r47 r2 r2
(0) T ′ → T(1) T → R(2) T → aTc(3) R → ε(4) R → bR
Algumas transições:I no estado 0, com próximo símbolo a, faz shift de muda para o estado 3;I no estado 3, com próximo símbolo c , faz reduce pela regra 3 (R → ε);I no estado 0, depois de um reduce T → . . . vai para o estado 1;I no estado 1, com próximo símbolo $ termina (accept).
Exemplo de parsing seguindo a tabela
a b c $ T R0 s3 s4 r3 r3 g1 g21 a2 r1 r13 s3 s4 r3 r3 g5 g24 s4 r3 r3 g65 s76 r4 r47 r2 r2
(0) T ′ → T(1) T → R(2) T → aTc(3) R → ε(4) R → bR
estado pilha input ação0 ε aabbbcc$ shift 33 a abbbcc$ shift 33 aa bbbcc$ shift 44 aab bbcc$ shift 44 aabb bcc$ shift 44 aabbb cc$ reduce R → ε; go 66 aabbbR cc$ reduce R → bR ; go 66 aabbR cc$ reduce R → bR ; go 66 aabR cc$ reduce R → bR ; go 22 aaR cc$ reduce T → R ; go 55 aaT cc$ shift 77 aaTc c$ reduce T → aTc ; go 55 aT c$ shift 77 aTc $ reduce T → aTc ; go 11 T $ accept
Algoritmo de parsing LR
stack= empty; push(0, stack); next=getToken()
loop
case table[top(stack),next] of
shift s: push(s, stack); next=getToken()
reduce p: let X = left-hand-side of production p
n = length(right-hand-side of production p)
pop n elements of stack;
lookup table[top(stack),X] and find "go s";
push(s,stack)
accept: terminate with sucess
empty: report error
Algoritmo de parsing LR (cont.)
I Em cada passo o algoritmo usa a tabela para decidir a ação a tomarI É su�ciente guardar apenas os estados na pilha
I cada estado representa uma con�guração particular de símbolos na pilhaI não é necessário guardar também os símbolos (mas ajudam a perceber a derivação)
I A parte crucial é a construção da tabela de parsing
I Vamos construir a tabela a partir da gramática (independente do input)
Construir a tabela de parsing
I Para implementar um parser LR é necessário construir um tabela de parsing
I Podemos decididir as ações usando a pilha e os próximos símbolos terminais(look-ahead):
LR(0) apenas a pilha (0 símbolos de look-ahead)LR(1) 1 símbolo de look-aheadLR(k) k símbolos de look-ahead (mais geral)
I A construção da tabela LR(0) é mais simples mas não permite reconher muitaslinguagens
I LR(k) com k ≥ 2 requer tabelas muito grandesI LR(1) é su�ciente para a maior parte das linguagens de programação
Items LR(0)
I Os items são produções com uma posição marcada no lado-direito(com um sinal de ponto �nal)
I Os estados do autómato vão ser conjuntos destes items
Exemplo: a gramática dos parêntesis (à esquerda) tem 3 produções a quecorrespondem 8 items (à direita).
S ′ → SS → (S)S | ε
S ′ → .SS ′ → S .S → .(S)SS → (.S)SS → (S .)SS → (S).SS → (S)S .S → .
Items LR(0) (cont.)
I Items representam um estado intermédio no reconhecimento de uma produçãoI Exemplo: S → (S).S
I no topo da pilha está a sequência (S)I automáto já reconheceu (S) e poderá continuar com S
I Mais geralmenteA→ β.γ
signi�ca que β está no topo da pilha e o autómato poderá continuar com γ
I A→ .γ é um item inicial: podemos começar com γ
I A→ γ. é um item completo: γ está no topo da pilha e podemos reconhecer A(reduce)
Transições do autómato
I Os estados do autómato LR vão ser itemsI Para cada item, vamos considerar as transições por símbolos terminais e
não-terminaisI Exemplos:
S → .(S)S S → (.S)S(
S → (.S)S S → (S .)SS
I Uma transição por um terminal ocorre depois de um shiftI Uma transição por não-terminal ocorre depois de um reduce
Transições-ε
I Sempre que temos um item com um próximo símbolo não-terminal B
A→ α.Bγ
acrescentamos transições-ε para todos os items iniciais de B
B → .β
I Exemplo:
S → (.S)S
S → .(S)S
S → .
ε
ε
Estado inicial
I O estado inicial do automáto LR é o item inicial
S ′ → .S
(em que S ′ é o não-terminal que acrescentamos à gramática)I O autómato não tem estados �nais: a aceitação será uma das ações a preencher
na tabela (juntamente com shift/reduce/go)
Exemplo
Autómato para a gramática dos parêntesis.
S ′ → .S S ′ → S .
S → .(S)S S → . S → (S)S .
S → (.S)S S → (S .)S S → (S).S
S
εε
(ε
ε
S )
ε S
ε
Automáto LR determinístico
I Por causa das transições-ε obtemos um autómato não determinístico (NFA)I Vamos usar a construção dos sub-conjuntos para transformar num DFAI Os estados do DFA são conjuntos de items
Exemplo
DFA correspondente ao autómato LR da linguagem de parêntesis.
0 : S ′ → .SS → .(S)SS → .
1 : S ′ → S .
2 : S → (.S)SS → .(S)SS → .
3 : S → (S .)S
4 : S → (S).SS → .(S)SS → .
5 : S → (S)S .
(
S
S
(
)
( S
Construção da tabela LR(0)
terminais não-terminais
estados
0 · · · · · ·1 · · · · · ·...n · · · · · ·
I Cada transição com um terminal: colocar uma ação shiftI Cada transição por um não-terminal: colocar uma ação goI Em cada estado com um item completo (A→ γ.): colocar uma ação reduceI No estado {S ′ → S .} e símbolo $ colocar a ação accept
A gramática será LR(0) se cada entrada tiver no máximo uma ação.
Exemplo
Tabela LR(0) para a gramática de parêntesis:
( ) $ S0 s2,r2 r2 r2 g11 a2 s2,r2 r2 r2 g33 s44 s2,r2 r2 r2 g55 r1 r1 r1
0 : S ′ → S1 : S → (S)S2 : S → ε
I Transições por terminais (shift)I Transições por não-terminais (goto)I Items completos (reduce/accept)
Existem ações shift/reduce na mesma entrada: esta a gramática não é LR(0).
Exercício 1
Mostre que a gramática de parêntesis simples
A→ (A) | a
é LR(0) construindo o autómato e tabela.
Limitações de LR(0)
I O autómato LR(0) usa apenas a informação na pilha para escolher as ações reduce
I Isto frequentemente gera con�itos na tabela � muitas gramáticas úteis não sãoLR(0)
I Podemos reconhecer muito mais linguagens se usarmos também o próximo símboloterminal (look-ahead)
I Vamos ver uma extensão simples de LR(0): análise SLR(1)(Simple Left-right parse, Rightmost derivation 1 symbol look-ahead)
Análise SLR(1)
I O algoritmo de parsing mantém-seI Construimos o autómato como no caso LR(0)I Construção da tabela de parsing:
I colocamos ações de shift e goto como anteriormente;I colocamos cada ações reduce para estados A→ γ. apenas nas colunas do símbolos
terminais em FOLLOW(A)
Exemplo
Construir a tabela para a gramática de parêntesis:
( ) $ S0 s2 r2 r2 g11 a2 s2 r2 r2 g33 s44 s2 r2 r2 g55 r1 r1
0 : S ′ → S1 : S → (S)S2 : S → ε
FOLLOW(S) = {), $}FOLLOW(S ′) = {$}
I As transições por terminais (shift) e não-terminais (goto) são como anteriormenteI Items completos (reduce) usam look-ahead
Já não existem entradas com duas ações (shift e reduce): a gramática é SLR(1).
Exercício 2
Construindo o autómato e a tabela, mostre que a gramática do exemplo inicial
T → RT → aTcR → εR → bR
é SLR(1).
(Vai obter uma tabela diferente do exemplo mas mesmo assim sem con�itos.)
Con�itos
I Quando obtemos duas ações na mesma entrada da tabela de parsing diz-se quetemos um con�ito
I O con�ito signi�ca que o autómato LR tem mais do que uma ação possível:
con�ito shift/reduce: uma ação de shift e outra(s) de reduce no mesmo estadocon�ito reduce/reduce: duas (ou mais) ações reduce no mesmo estado
I Os con�itos pode resultar de ambiguidade na gramática1
I Os geradores de parsers LR (e.g. Bison ou Happy) avisam-nos todos os con�itosna gramática (próxima aula)
I Podemos eliminar con�itos:I re-escrevendo a gramática (se for ambígua)I de�nindo associatividades e precedências para tokensI escolhendo shift em vez de reduce (por omissão)
1Mas nem sempre: a gramática dos parêntesis não é ambígua e tinha um con�ito shift/reduce na
tabela LR(0).
Exemplo: �Dangling else�
Uma gramática para comandos com �else� opcional.
S → if cond then S else SS → if cond then SS → skip
Esta gramática é ambígua: a frase
if cond then if cond then skip else skip
admite duas árvores de derivação
if cond then {if cond then skip else skip} (1)
if cond then {if cond then skip} else skip (2)
Exemplo: �Dangling else� (cont.)
Ao construir autómato SLR(1) obtemos um estado
S → if cond then S .S → if cond then S . else S
Quando o próximo token é else podemos:
I fazer shift pela 2ª produção; ou
I fazer reduce pela 1ª produção (porque FOLLOW(S) = {else, $})Logo: vamos ter um con�ito shift/reduce.
Se optarmos por shift e obtemos a árvore correspondente à interpretação usual
if cond then {if cond then skip else skip}
em linguagens de programação (Pascal, C, Java, etc.)
Análise LR(1) e LALR(1)
I Apesar da análise SLR(1) reconhecer mais do que LR(0) não é su�ciente paraalgumas construções das linguagens de programação
I A análise LR(1) é uma generalização de SLR(1) que resolve essas limitaçõesI Contudo: o método LR(1) pode produzir automátos muito mais estados que os
SLR(1)I Na prática: os geradores de analisadores LR usam uma variante mais �compacta�
designada LALR(1) � Look-ahead LR(1)I As diferenças entre SLR(1), LR(1) e LALR(1) são bastante técnicasI Mas conseguem compreender e usar os geradores de analisadores se
compreenderem análise SLR(1)
Análise LR(1)
I Os estados do autómato LR(0) representam apenas posições no lado direito dasproduções
I Generalização LR(1): usar também os símbolos de look-ahead para a de�nição dospróprios estados do autómato
I Isto vai permitir usar o símbolo de look-ahead para distinguir estados e assimevitar con�itos
Items LR(1)
I Os items são pares deI produções com uma posição no lado-direito (i.e. um item LR(0));I e um símbolo terminal (look-ahead)
(A→ α.β︸ ︷︷ ︸item LR(0)
, a︸︷︷︸look-ahead
)
I Tal como antes: os estados do autómato são conjuntos destes items
I Quando o autómato está num estado com um item
(A→ α.β, a)
a sequência α está no topo da pilha e o resto da entrada é derivável a partir de βa
Items LR(1) (cont.)
Transições por símbolos (terminais e não-terminais):
(A→ α.Xγ, a) (A→ αX .γ, a)X
Transições-ε:
(A→ α.Bγ, a) (B → .β, b)ε
Quando B é um não-terminal e para todas as produções B → β e b ∈ FIRST(γa).
Autómato LR(1)
I Acrescentamos ações shift e go como no caso LR(0) e SLR(1)I Acrescentamos ações reduce A→ α quando um estado contém um item completo
(A→ α., a)
e o próximo terminal é a.