Você está na página 1de 6

Problema 3 : Construo de um Analisador Sinttico

Marcus Vinicius Araujo Martins

Universidade Estadual de Feira de Santana (UEFS)


Feira de Santana BA Brasil
{viniciusfsa@gmail.com}

Resumo: Este relatrio visa descrever um projeto de analisador sinttico para


um compilador baseado na produo de uma gramtica desenvolvida em etapa
anterior, usando a notao EBNF. Aqui sero tratadas as principais tcnicas de anlise
sinttica, destacando vantagens e desvantagens de cada uma, bem como tambm
salientado o processo de tratamento de erros sintticos e breve descrio do projeto
prtico.

1 - Fundamentao terica
1.1 - O analisador sinttico
O analisador sinttico tem por funo receber uma cadeia ou conjunto de tokens
proveniente do analisador lxico e verificar se a mesma pode ser gerada pela gramtica
da linguagem fonte [1]. Neste sentido, a gramtica quem d as diretrizes para
construo de um analisador capaz de reconhecer expresses que so aceitas em suas
regras. O analisador sinttico tem o papel ainda de se recuperar dos erros que ocorram,
no intuito de continuar a processar toda entrada de tokens, e no apenas at encontrar o
primeiro erro.
Quando falado aqui em erros sintticos, no mais se est preocupado com erros
de escrita ou tipos mal formados. Isso foi um papel do analisador lxico que, no poder
de suas atribuies, pde localizar e classificar a ocorrncia de cada token dentro do
cdigo, bem como relatar possveis erros lxicos presentes no corpo de cdigo do
programador. Passada essa fase, subtende-se que a tabela de smbolos mantm um
conjunto de valores que precisam agora ser vistos como unidades e, desta forma, seu
contedo, se formado por dgitos ou letras, no mais o objetivo. Aqui, vistos de forma
atmica, os tokens precisam passar por uma etapa onde sua ordem verificada, se um
identificador vem imediatamente antes de um operador ou se um tipo primitivo antecede
um identificador, numa declarao, por exemplo, no poder de construo definido na
gramtica.
Nesse contexto, a construo do analisador sinttico a ser apresentada se baseia
nas construes definidas na gramtica, em etapa anterior, baseando-se na notao
EBNF. As possveis estratgias da anlise so esplanadas no tpico 1.2, bem como
tambm apresentado os critrios de escolha de determinada estratgia, a partir de
decises tericas e de projeto.
1.2 - Estratgias para anlise sinttica
Existem trs tipos gerais de analisadores sintticos[1]: A universal, que pode
analisar qualquer gramtica. Ex: o algoritmo de Coke-Younger-Kasami e o algoritmo de
Earley. A descendente (top-down), que constroi as rvores de derivao de cima (raiz)
para baixo (folhas), e a ascendente (bottom-up), que constroi as rvores de derivao de
baixo (folhas) para cima (raiz). Todavia, o primeiro mtodo citado se torna bastante
ineficiente, principalmente por sua complexidade em abranger todas as gramticas. Os
mtodos de anlise sinttica mais eficientes so o top-down e o bottom-up que, embora
trabalhem com apenas um grupo de gramticas, so suficientemente expressivos para
descrever a maioria das construes sintticas das linguagens de programao[1].
Como apresentado em etapas anteriores, existem diversos tipos de gramtica,
como por exemplo, gramtica fatorada esquerda, recursiva esquerda, simplificada,
etc. Alm disso, existem outras classificaes, de acordo com os nveis na hierarquia de
Chomsky, tambm j apresentada. Dessas caractersticas, foi desenvolvida uma
gramtica fatorada esquerda, sem recurso a esquerda e sem ambigidade. Assim,
para o analisador implementado, se fez necessrio utilizar a estratgia top-down, uma
vez que reconhecedores desse tipo podem processar apenas gramticas que no
apresentem recursividade a esquerda[2]. Para a estratgia bottom-up, o requisito seria o
inverso, que a gramtica no apresentasse recursividade a direita.
Assim, os mtodos de reconhecimento podem exigir transformaes na
gramtica, caso ela j no tenha sido construda nesse sentido. As transformaes
podem incluir: eliminao de produes vazias, retirada de recurso esquerda e
fatorao de produes. A gramtica sobre a qual o analisador foi construdo obedece a
esses requisitos.
Caso existisse uma recurso a esquerda, por exemplo, para um analisador
sinttico top-down, o mesmo entraria em um loop infinito, uma vez que o mesmo usa
anlise do tipo LR (Left-Right), lendo a entrada de texto da esquerda para direita. Alm
disso, produes no fatoradas a esquerda deixariam a gramtica ambgua, uma vez que
o analisador no saberia para que produo ir quando encontrasse duas derivaes com
uma mesma cadeia inicial.

1.3- A estratgia top-down


A estratgia top-down pode ser implementada de trs formas [2]:
A) recursivo com retrocesso
B) recursivo preditivo
C) tabular preditivo

A forma recursiva com retrocesso faz a expanso da arvore de derivao a partir


da raiz, expandindo sempre o no terminal mais a esquerda. Para no terminais que
possuem mais de uma produo, todas as alternativas vo ser tentadas at que se
obtenha sucesso. Essa estratgia denotada por muitos ciclos iterativos na busca pela
derivao correta. No pior caso, a varredura pode chegar a averiguar todas as
possibilidades, o que pode deixar o tempo demasiado grande, dependendo da cadeia a
ser analisada e das produes de cada no terminal. O retrocesso desse mtodo existe no
principio de que as derivaes podem voltar a determinada produo que j fora
definida, por ter encontrado alguma falha posteriormente. O retrocesso faz com que uma
produo antes definida seja trocada, por uma segunda produo, o que torna o mtodo
bastante exaustivo.
O mtodo recursivo preditivo desenvolvido sem retrocesso. Nesse mtodo, o
smbolo sob o cabeote de leitura determina exatamente qual produo deve ser
aplicada na expanso de cada no terminal. Esse foi o mtodo implementado para o
analisador em questo, aproveitando que ele exige que a gramtica no tenha
recursividade esquerda, que a gramtica seja fatorada esquerda e que, para os no-
terminais com mais de uma regra de produo, os primeiros terminais derivveis sejam
capazes de identificar, univocamente,a produo que deve ser aplicada para cada
momento da anlise sinttica [2].
Esse tipo de anlise se baseia no princpio de que cada no terminal
identificado como uma funo ou procedimento. Funciona como um grafo onde toda
regra da gramtica mapeada[3], Toda ocorrncia de um terminal x corresponde ao seu
reconhecimento na cadeia de entrada e a leitura do prximo smbolo dessa cadeia. O
exemplo de gramtica da figura 1 em correspondncia com seu procedimento
implementado na figura 2 descrevem esse princpio.

Figura 1 Exemplo de gramtica

Figura 2 Procedimento de <F>

O cdigo descrito na figura 2 corresponde a implementao do procedimento


apresentado na figura 1. Caso o no-terminal <F> seja invocado, esse procedimento
executado. Na produo especfica, F deriva a ou b ou o no terminal <E> entre
parnteses (<E>). A partir disso ele analisa primeiramente se (. Se sim, o
procedimento chama prximo token e passa a responsabilidade para o procedimento
<E>, no apresentada aqui sua estrutura de procedimento. Genericamente, caso o
contedo depois do ( seja realmente pertencente a produo <E>, ou seja, caso <E>
retorne um valor verdadeiro, <F> continuar seu processamento. Assim, ele analisa se o
prximo token ). Se sim, ele chama o prximo token, deixando o ponteiro ou cabea
do cabeote pronto para prxima anlise, e finaliza o procedimento. Se inicialmente o
primeiro token no fosse (, ento o procedimento verificaria se era a ou se era b. Se
no fosse nenhum desses, lanado um erro ou um valor falso, indicando que aquela
produo no corresponde. Sendo a ou b, o procedimento finalizado, sem
ocorrncia de erros.
A terceira estratgia top-down a tabular preditiva. Essa forma de anlise
chegou ser verificada, mas no foi implementada, devido principalmente a sua
complexidade. Esse mtodo utiliza uma tabela que faz relao entre os smbolos no
terminais e os smbolos de entrada. Para construo da tabela, se faz necessrio saber
dois conjuntos, denominados Primeiro e Seguinte. O conjunto Primeiro para um
simbolo T, por exemplo, o conjunto de smbolos terminais que iniciam as cadeias
derivadas de T, onde T qualquer cadeia de smbolos da gramtica[2]. O Seguinte de T
o conjunto de terminais a que podem aparecer imediatamente direita de T em
alguma forma sentencial[2]. Embora o mtodo tabular no tenha sido implementado, a
construo desses dois conjuntos foi necessria no tratamento de erros, descrita no
tpico 1.4.

1.4 - Tratamento de erros A Questo do Primeiro e Seguinte


Utilizando a anlise sinttica top-down preditiva recursiva, se faz necessrio
saber o que o analisador deve fazer quando encontrar um erro sinttico. Para uma
declarao dentro do cdigo, o que fazer quando o analisador espera um identificador
aps o token que define um tipo. Declaraes precisam de um tipo e depois um
identificador. Erros como esse se classificam como erros sintticos, pois compreendem
a quebra de regras de produo, no caso especfico, a regra de declarao.
Para esse problema, a soluo foi manter, dentro de cada produo, um conjunto
de possveis tokens seguintes, a serem buscados caso o procedimento lance um erro de
sintaxe. esse conjunto, o conjunto Seguinte, que ir nortear as possibilidades do
analisador em continuar seu processo sinttico. Sem esse tratamento, a anlise
terminaria ao encontrar o primeiro erro sinttico, o que contradiz o contexto de anlise
sinttica de um compilador. Assim, o conjunto Primeiro e Seguinte precisa ser
identificado para cada mtodo o procedimento. Exemplos de levantamentos desses
conjuntos para o analisador implementado so descritos no tpico 2.2.

2 - Descrio do analisador desenvolvido


2.1 - A escolha da estratgia
A escolha da estratgia top-down tem motivo exclusivo as condies de
andamento do projeto. Uma vez que se mantinha em mos uma gramtica sem recurso
esquerda e fatorada esquerda, no caberia utilizar o mtodo bottom-up, pois teria que
ser feita toda uma inverso da gramtica, o que levaria tempo.
Dentro da anlise top-down, foi escolhida a recursiva preditiva, primeiramente
mediante sua simplicidade em relao aos outros dois mtodos aqui citados. Alm
disso, como a anlise se baseia na construo de procedimentos, sua aplicao foi til
uma vez que o projeto desenvolve-se em grupo e as tarefas puderam ser mais bem
divididas entre os membros, na atribuio de mtodos para cada membro.

2.2 - Decises de projeto e estrutura do cdigo


Apenas duas classes foram criadas nessa etapa de desenvolvimento. Uma vez
que j existia o projeto de anlise lxica, coube apenas adaptar o projeto para agora
realizar tambm a anlise sinttica. As classes construdas foram: ParsingControl.java e
ProductionRules.java. A classe ProductionRules.java rene todos os procedimenos,
divididos em mtodos, para cada no terminal definido na gramtica EBNF. A classe
ParsingControl.java mantm um controle sobre o analisador sinttico e o analisador
lxico, controlando o processo de criao da tabela de tokens, pelo analisador lxico, e a
repassando para o analisador sinttico.
Os mtodos da classe ProductionRules.java so quase que exclusivamente
procedimentos dos smbolos no terminais, como descrito no tpico 1.3. Todavia, ela
possui um mtodo start() que faz com que uma tabela de smbolos passada por uma
instancia da classe controladora ParsingControl.java, seja transferida numa ordem
inversa para uma pilha de tokens, no intuito de pegar os tokens na forma correta.
Fazendo isso, os tokens so buscados sempre nessa pilha. Aps isso, o primeiro token
retirado da pilha e o primeiro procedimento chamado, o procedimento programa(). A
figura 3 mostra o incio da execuo.

Figura 3 Preparao do primeiro token e chamada do primeiro procedimento

O procedimento programa() relaciona a estrutura geral de como deve ser o


cdigo. Por deciso de projeto, ele deve comear com zero, uma ou mais declaraes de
biblioteca, seguido de zero ou mais declaraes globais e seguido obrigatoriamente por
declaraes de variveis e funes, na obrigatoriedade de existir pelo menos o mtodo
main() no cdigo do programador. Isso corresponde na prtica que o cdigo do
programador deve manter uma estrutura iniciada por <Declarao de Bibliotecas>,
depois por <TypeDef> ou <Declarao de Struct> ou <Declarao Const>, depois por
<Varveis Globais>* e <Declarao de Funes> e depois por <Principal>, que o
mtodo main().
A implementao dos procedimentos se baseou nas seguintes tcnicas,
analisando a produo de cada no terminal na gramtica.
a) quando houver uma concatenao tem de comparar na seqncia com
if's encadeados
b) quando houver uma escolha ou '|' na derivao, utiliza-se um if -
else if ... pra cada derivao
c) quando houver uma repetio do tipo { } (0 ou mais) utiliza-se o
while.
d) quando houver uma repetio do tipo { }+ (1 ou mais) utiliza-se o
do-while com flag.
A forma de tratamento de erros se baseou na construo dos conjuntos Primeiro
e Seguinte, para cada procedimento, conforme descrito na sesso 1.4. Para o
procedimento declaracaoConst() por exemplo, seus seguintes foram levantados e
definidos no escopo do prprio mtodo, conforme figura 4.

Figura 4 Exemplo de conjunto Seguinte

Ocorrendo algum erro dentro do procedimento declaracaoConst(), como por


exemplo a ausncia do smbolo = depois de um identificador, lanado um erro
sinttico com a chamada ao procedimento matchError(smbolo,seguinte). Os
parmetros dessa chamada correspondem respectivamente ao smbolo que era esperado
e o conjunto de seguintes do procedimento. O mtodo matchError() quando recebe
esses parmetros, adiciona numa lista de erros esse novo erro e faz um loop para saber
se o prximo token equivale a algum possvel seguinte a construo declaraConst().

O lanamento de erros com a chamada a matchError() no pode ser feito em


qualquer procedimento que falhe; deve ser chamada apenas quando a falha for numa
anlise de em um smbolo terminal. Em outros casos, apenas um valor falso retornado,
fazendo com que o no terminal que o invocou busque outra produo possvel para ele.
A construo do analisador no se deu obedecendo exatamente as construes
da gramtica, visto que na mesma ainda pde ser encontrada algumas ambigidades nas
produes. Problemas como esse foram resolvidos diretamente dentro do cdigo, ao
invs de fazer alteraes na gramtica primeiramente. Um exemplo disso o caso de
aceitao de ifs sem elses. Na verdade isso no o problema. O problema que
quando aconteciam de se ter dois ifs e um else, estava ambguo, na gramtica, a qual IF
o else pertencia. A soluo para isso foi a obrigatoriedade de todo IF acompanhar um
else.

3 - Referncias

[1] AHO, Alfred V.; SETHI, Ravi; ULLMAN, Jeffrey D. Compiladores, principios, tecnicas
e ferramentas. Rio de Janeiro: Guanabara Koogan, c1995.

[2] PRICE, Ana Maria de Alencar; TOSCANI, Simao Sirineo. Implementacao de linguagens
de programacao : compiladores. 3. ed. Porto Alegre: Sagra Luzatto, 2005.

[3] Compiladores - Profa. Helena Caseli, disponvel em:


http://www2.dc.ufscar.br/~helenacaseli/comp/trabalhos/Trabalho2/Trabalho2.pdf
ltimo acesso: 23/12/09