• Nenhum resultado encontrado

A gramática para valor absoluto unário introduziu um novo símbolo terminal como o operador unário Considere a inclusão do menos unário à gramática de expres-

Profundidade de pilha

2. A gramática para valor absoluto unário introduziu um novo símbolo terminal como o operador unário Considere a inclusão do menos unário à gramática de expres-

são clássica. O fato do mesmo símbolo terminal ocorrer como menos unário ou como menos binário gera complicações? Justifique sua resposta.

3.6 TÓPICOS AVANÇADOS

Para construir um parser satisfatório, o construtor de compiladores precisa entender os fundamentos da criação de uma gramática e um parser. Com um parser funcionando, normalmente existem maneiras de melhorar seu desempenho. Esta seção examina duas questões específicas na construção do parser. Primeiro, examinamos as transformações na gramática que reduzem o tamanho de uma derivação para produzir uma análise mais rápida. Essas ideias aplicam-se a parsers top-down e bottom-up. Segundo, dis- cutimos as transformações na gramática e nas tabelas Action e Goto que reduzem o tamanho da tabela. Essas técnicas aplicam-se apenas a parsers LR.

3.6.1 Otimização de uma gramática

Embora a análise sintática não consuma mais uma grande fatia do tempo de compilação, o compilador não deve desperdiçar um tempo indevido nesta análise. A forma real de uma gramática tem efeito direto sobre a quantidade de trabalho exigida para analisá-la. Os parsers top-down e bottom-up constroem derivações. O primeiro realiza uma ex- pansão para cada produção na derivação. O segundo, uma redução para cada produção na derivação. Uma gramática que produza derivações mais curtas exige menos tempo para a análise.

O construtor de compiladores normalmente pode reescrever a gramática para reduzir a altura da árvore sintática, reduzindo assim o número de expansões em um parser top-down e o número de reduções em um parser bottom-up. A otimização da gramática não pode mudar o comportamento assintótico do parser; afinal, a árvore sintática precisa ter um nó de folha para cada símbolo no fluxo de entrada. Ainda assim, a redução das constantes em partes altamente utilizadas da gramática, como a gramática de expressão, pode gerar uma diferença suficiente para justificar o esforço.

Considere, novamente, a gramática de expressão clássica da Seção 3.2.4. (As tabelas LR(1) para a gramática aparecem nas Figuras 3.31 e 3.32.) Para impor a precedência desejada entre os operadores, acrescentamos dois não terminais, Termo e Fator, e remo- delamos a gramática para a forma mostrada na Figura 3.29a, que produz árvores sintáticas um tanto grandes, mesmo para expressões simples. Por exemplo, na expressão a + 2 × b, esta árvore tem 14 nós, como mostra a Figura 3.29b. Cinco desses nós são folhas que não podemos eliminar. (Mudar a gramática não pode encurtar o programa de entrada.)

Qualquer nó interior que tenha apenas um filho é um candidato para otimização. A sequência de nós de Expr para Term para Fator para 〈nome,a〉 usa quatro nós para uma única palavra no fluxo de entrada. Podemos eliminar pelo menos uma camada, a de nós Fator, desdobrando as expansões alternativas para Fator em Termo, como mostra a Figura 3.30a. Ela multipica por três o número de alternativas para Termo, mas encurta a árvore sintática em uma camada, mostrada na Figura 3.30b.

Em um parser LR(1), essa mudança elimina três das nove ações reduce, e deixa os cinco shifts intactos. Em um parser de descida recursiva para uma gramática preditiva equivalente, este processo eliminaria 3 das 14 chamadas de procedimento.

Em geral, qualquer produção que tenha um único símbolo no seu lado direito pode ser desdobrada dessa forma. Essas produções, por vezes, são chamadas produções inúteis. Às vezes, produções inúteis têm uma finalidade — tornar a gramática mais compacta e, talvez, mais legível, ou forçar a derivação a assumir uma forma em particular. (Lembre-se de que a mais simples de nossas gramáticas de expressão aceita a + 2 × b, mas não codifica qualquer noção de precedência na árvore sintática.) Conforme veremos no Capítulo 4, o construtor de compiladores pode incluir uma produção inútil simplesmente para criar um ponto na derivação onde uma ação em particular possa ser realizada.

Desdobrar produções inúteis tem seus custos. Em um parser LR(1), isto pode tornar as tabelas maiores. Em nosso exemplo, eliminar Fator remove uma coluna da tabela Goto, mas as produções extras para Termo aumentam o tamanho de CC de 32 para 46 conjuntos. Assim, as tabelas têm uma coluna a menos, mas 14 linhas extras. O parser resultante realiza menos reduções (e executa mais rapidamente), mas tem tabelas maiores.

Em um parser de descida recursiva codificado à mão, a gramática maior pode aumentar o número de alternativas que devem ser comparadas antes de expandir

algum lado esquerdo. O construtor de compiladores às vezes pode compensar o custo aumentado combinando casos. Por exemplo, o código para as expansões não triviais de Expr’ na Figura 3.10 é idêntico. O construtor de compiladores poderia combiná-los com um teste que corresponda word a + ou a –. Como alternativa, poderia atribuir tanto + quanto - à mesma categoria sintática, fazer que o parser ins- pecione a categoria sintática e usar o lexema para diferenciar entre os dois quando for preciso.

3.6.2 Redução do tamanho das tabelas LR(1)

Infelizmente, as tabelas LR(1) geradas para gramáticas relativamente pequenas podem ser grandes. As Figuras 3.31 e 3.32 mostram as tabelas LR(1) canônicas para a gramática de expressão clássica. Existem muitas técnicas para encolher essas tabelas, incluindo as três abordagens para reduzir o tamanho da tabela des- critas nesta seção.