Compiladores (CC3001) Aula 6: Análise sintática bottom-up

37

Transcript of Compiladores (CC3001) Aula 6: Análise sintática bottom-up

Page 1: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

Compiladores (CC3001)Aula 6: Análise sintática bottom-up

Pedro Vasconcelos

DCC/FCUP

2020

Page 2: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

Esta aula

Análise sintática LR

Análise LR(0)

Análise SLR(1)

Resolução de con�itos

Extras

Page 3: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 4: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 5: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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).

Page 6: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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)⇒ ()

Page 7: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 8: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 9: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 10: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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).

Page 11: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 12: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 13: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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)

Page 14: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 15: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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 → .

Page 16: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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)

Page 17: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 18: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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 → .

ε

ε

Page 19: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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)

Page 20: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

ε

Page 21: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 22: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 23: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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.

Page 24: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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).

Page 25: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

Exercício 1

Mostre que a gramática de parêntesis simples

A→ (A) | a

é LR(0) construindo o autómato e tabela.

Page 26: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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)

Page 27: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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)

Page 28: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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).

Page 29: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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.)

Page 30: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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).

Page 31: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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)

Page 32: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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.)

Page 33: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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)

Page 34: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 35: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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

Page 36: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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).

Page 37: Compiladores (CC3001) Aula 6: Análise sintática bottom-up

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.