• Nenhum resultado encontrado

Erros na construção de tabela

Localização de handle, revisão

3.4.3 Erros na construção de tabela

Como segundo exemplo da construção de tabela LR(1), considere a gramática ambígua para a construção if-then-else clássica. A abstração dos detalhes da expressão de controle e todos os outros comandos (tratando-os como símbolos terminais) produzem a seguinte gramática de quatro produções:

1 AlvoCmd

2 Cmdif expr then Cmd

3 | if expr then Cmd else Cmd

4 | assign

Ela tem dois símbolos não terminais, Alvo e Cmd, e seis símbolos terminais, if, expr, then, else, assign e eof implícito.

A construção começa inicializando cc0 para o item [Alvo → • Cmd, eof] e executando

closure para produzir o primeiro conjunto.

A partir desse conjunto, a construção começa derivando os membros restantes da coleção canônica dos conjuntos de itens LR(1).

A Figura 3.26 mostra o progresso da construção. A primeira iteração examina as transições a partir de cc0 para cada símbolo da gramática. Ela produz três novos

conjuntos para a coleção canônica de cc0: cc1 para Cmd, cc2 para if, e cc3 para assign.

Estes conjuntos são:

A segunda iteração examina transições a partir desses três novos conjuntos. Apenas uma combinação produz um novo conjunto, a partir de cc2 com o símbolo expr.

A próxima iteração calcula transições de cc4; ela cria cc5 como goto(cc4, then).

A quarta iteração examina transições a partir de cc5. Ela cria novos conjuntos para

Cmd, para if e para assign.

A quinta iteração examina cc6, cc7 e cc8. Embora a maior parte das combinações

produza o conjunto vazio, duas delas levam a novos conjuntos. A transição em else a partir de cc6 leva a cc9, e a sobre expr a partir de cc7 cria cc10.

Quando a sexta iteração examina os conjuntos produzidos na iteração anterior, cria dois novos conjuntos, cc11 a partir de cc9 em Cmd e cc12 a partir de cc10 em then. E,

também, conjuntos duplicados para cc2 e cc3 a partir de cc9.

A iteração final examina cc15. Como o marcador • está no final de cada item em cc15,

ele só pode gerar conjuntos vazios. Neste ponto, nenhum conjunto adicional de itens pode ser acrescentado à coleção canônica, de modo que o algoritmo alcançou um ponto fixo, e para.

A ambiguidade na gramática torna-se aparente durante o algoritmo de preenchimento de tabela. Os itens nos estados de cc0 a cc12 não geram conflitos. O estado cc13 contém

quatro itens:

1. [Cmd → if expr then Cmd •, else]

2. [Cmd → if expr then Cmd •, eof ]

3. [Cmd → if expr then Cmd • else Cmd, else]

4. [Cmd → if expr then Cmd • else Cmd, eof ]

O item 1 gera uma entrada reduce para cc13 e a antecipação else; o item 3 gera uma

entrada shift para o mesmo local na tabela. Claramente, a entrada da tabela não pode manter as duas ações. Esse conflito shift-reduce indica que a gramática é ambígua. Os itens 2 e 4 geram um conflito shift-reduce semelhante com uma antecipação de eof. Quando o algoritmo de preenchimento de tabela encontra esse conflito, a construção terá falhado. O gerador de tabela deve relatar o problema — uma ambiguidade fundamental entre as produções nos itens LR(1) específicos — ao construtor de compiladores. Neste caso, o conflito surge porque a produção 2 na gramática é um prefixo da produção 3. O gerador de tabela poderia ser projetado para resolver esse conflito em favor do deslocamento (shift); isto força o parser a reconhecer a produção mais longa e vincula o else ao if mais próximo (mais interno).

Uma gramática ambígua também pode produzir um conflito reduce-reduce, que pode ocorrer se ela contiver duas produções A → g d e B → g d com o mesmo lado direito g d. Se um estado contém os itens [A → g d •, a] e [B → g d •, a], então ele gerará duas ações reduce em conflito para o símbolo de antecipação a — uma para cada produção. Novamente, esse conflito reflete uma ambiguidade fundamental na gramática básica; o construtor de compiladores deve remodelar a gramática para eliminá-la (ver Seção 3.5.3).

Uma mensagem de erro típica de um gerador de parser inclui os itens LR(1) que geram o conflito; outro motivo para estudar a construção de tabela.

Como existem muitos geradores de parser que automatizam este processo, o método de escolha para determinar se uma gramática tem a propriedade LR(1) é invocar um gerador de parser LR(1) sobre ela. Se o processo tiver sucesso, a gramática tem a propriedade LR(1).

REVISÃO DA SEÇÃO

Parsers LR(1) são muito usados nos compiladores construídos tanto na indústria como nas universidades. Eles aceitam uma grande classe de linguagens e usam um tempo proporcional ao tamanho da derivação que constroem. Existem muitas ferramentas que geram um parser LR(1) em uma grande variedade de linguagens de implementação. O algoritmo de construção de tabela LR(1) é uma aplicação elegante da teoria na prática. Ele constrói sistematicamente um modelo do DFA de reconhecimento de handles e depois traduz esse modelo em um par de tabelas que controlam o es- queleto de parser. A construção de tabela é um empreendimento complexo, que exige atenção cuidadosa aos detalhes. Este é exatamente o tipo de tarefa que deve ser automatizada — geradores de parser são melhores para seguir essas longas cadeias de computações do que os humanos. Apesar disso, um construtor de compiladores habilidoso deve entender os algoritmos de construção de tabela, pois oferecem per- cepções de como os parsers funcionam, quais tipos de erros o gerador de parser pode encontrar, como esses erros surgem e como eles podem ser remediados.

O Exercício 12 mostra uma gramática LR(1) que não tem uma gramática LL(1) equivalente.

Como um exemplo final, as tabelas LR para a gramática de expressão clássica aparecem nas Figuras 3.31 e 3.32.

QUESTÕES DE REVISÃO

1. Mostre as etapas que o esqueleto de parser LR(1), com as tabelas para a gramática