Implementação do Analisador Léxico Ricardo Mendes do Nascimento Universidade Regional Integrada do Alto Uruguai e das Missões (URI) Santo Ângelo RS Brasil rnascom@urisan.tche.br Resumo. Este artigo tem como objetivo apresentar a implementação do analisador léxico solicitado na disciplina de Linguagens Formais e Compiladores I. 1. O Problema Durante a disciplina de Linguagens Formais e Compiladores I, foi proposta a implementação de um software que analisasse lexicamente um código fonte supostamente criado para uma linguagem especificada no trabalho. Este programa deveria então, ler um determinado arquivo que contivesse o código fonte a ser analisado, e executar a análise léxica sobre ele. Ao final este deveria gerar uma lista de tokens que poderá ser utilizada futuramente pelo analisador sintático da linguagem em questão. 2. Solucionado o Problema O processo para a criação de um analisador léxico é muito simples, embora possa ser trabalhosa a criação das gramáticas e autômatos finitos conforme a especificação da linguagem. Primeiramente é necessária a especificação da linguagem a ser reconhecida pelo analisador léxico. Após são criadas as gramáticas e autômatos finitos para cada sentença, estes são agrupados em um único autômato finito que é determinizado. Feito isso é possível implementar um algoritmo que utilize este autômato para a validação de um código fonte escrito para a linguagem. Esta validação consiste em ler todo código e, cadeia a cadeia, verificar a existência de uma sentença. Para cada sentença reconhecida um token é adicionado a lista de tokens. Os erros encontrados durante a análize léxica, também serão adicionados a lista de tokens, simbolicamente através do token <ERROR>, além de serem estes contabilizados e informados ao usuário, de forma que o código só é valido se não existirem erros. Trabalho de Linguagens Formais e Compiladores I 28/06/2007 1/7
3. Criando o Autômato Para a criação do autômato finito determinizado da linguagem, é necessário primeiramente criar uma gramática para cada uma das sentenças desta. A partir da gramática regular de cada umas das sentenças, é gerado um autômato para cada uma destas, que será capaz de validar uma cadeia de símbolos como sentença da linguagem. No nosso caso as sentenças que serão validadas pelo autômato fazem parte de uma linguagem assembly formada pelas instruções HLT, LDA, STR, ADD, SUB, JZ, JP, JN, JMP, GET, PRT, e pelas pseudo-instruções: ORG, EQU e END. Todas estas reconhecidas apenas se escritas com todos caracteres maiúsculas. Abaixo serão apresentados as gramáticas e autômatos gerados para cada uma das instruções. Figura 1. Gramáticas e Autômatos Finitos das Instruções Além das instruções e pseudo-instruções a linguagem é formada por sentenças que são reconhecidas como um número inteiro, um operando ou um rótulo. Um número inteiro é formado por qualquer cadeia de caracteres formada somente com os caracteres 0, 1, 2, 3, 4, 5, 6, 7, 8 e 9, independente da seqüência ou quantidade de cada um dos símbolos. Da mesma forma é reconhecido um operando quando uma cadeia formada pelos símbolos x, y, z, w e i, e um rótulo quando encontrada uma cadeia formada pelos símbolos a, b, c, d, e e. Na figura abaixo é possível visualizar cada uma das gramáticas e autômatos gerados para estes casos. Trabalho de Linguagens Formais e Compiladores I 28/06/2007 2/7
Figura 2. Números Inteiros, Operandos e Rótulos. Após a criação de todos autômatos para cada sentença, estes são agrupados em somente um que já será capaz de validar qualquer sentença da linguagem. Para isso deve ser cuidado para que nenhum autômato inicial tenha algum estado com o mesmo nome de algum estado de outro autômato, somente assim é possível agrupá-los com a garantia do reconhecimento de todas sentenças. Figura 3. Autômato Finito Não - Determinístico. Embora este autômato seja capaz de reconhecer qualquer sentença da linguagem, a validação através deste ainda não é eficiente e simples se comparado com um autômato finito determinístico. Logo, este deve ser determinizado para obtermos um autômato mais eficiente. Trabalho de Linguagens Formais e Compiladores I 28/06/2007 3/7
Figura 4. Autômato Finito Determinístico. Após a determinização foram substituídas as nomenclaturas dos estados novos que surgiram durante o processo.assim o autômato finito determinístico final ficou como apresentado abaixo. Figura 5. Autômato Finito Determinístico Final. Trabalho de Linguagens Formais e Compiladores I 28/06/2007 4/7
4. Implementação do analisador O software foi implementado utilizando orientação a objeto e foi estruturado com as seguintes classes apresentadas no diagrama abaixo. Figura 6. Diagrama de classes. 4.1. Classe Analyzer A principal classe deste sistema é a Analyzer. Esta possui como atributo um objeto da classe Automaton que representa o autômato finito determinístico criado. O vetor tokenlist armazena a lista de tokens que serão gerados pelo método buildtokenlist. Este método é quem faz a mágica da análise léxica, lendo através da classe utlitária Arquivo todo o código fonte especificado pelo usuário, e validando-o através do autômato determinístico. Em uma única passagem pelo código fonte é possível gerar a lista de tokens e concluir se a código é valido para a linguagem. Figura 7. Classe Analyzer. 4.2. Classe Automaton O autômato é representado por objeto Automaton utilizado como atributo pela classe Analyser. Neste existe um vetor bidimensional nextstate que guarda todas as o próximo estado de todas as combinações possíveis entre cada símbolo da linguagem e os estados existentes. Estes estados estão representados no vetor vstate. Para encontrar o próximo estado que o analisador irá obter, o autômato localiza o índice da coluna, dentro do vetor nextstate, referente ao símbolo que ele está analisando, através do método columnofsymbol. Da mesma forma localiza o e o índice referente ao estado atual através daométodo rowofstate. Com o resultado deste dois métodos possível identifica o próximo estado que será retornado para o objeto analyzer. Trabalho de Linguagens Formais e Compiladores I 28/06/2007 5/7
Figura 8. Classe Automaton. 4.3. Classe Token Para a manipulação de tokens, foi criada uma classe denominada Token que nos possibilita tratar cada token como um objeto. Estes objetos possuem dois atributos: o name, que nos permitiu nomear cada token, facilitando a identificação de cada objeto gerado, e o atributo id, que é o armazena o estado final que gera o token em questão. Figura 9. Classe Token. 4.4. Classe Editor Para a interação com o usuário foi criada uma classe chamada Editor. A partir da construção de um objeto editor no método main desta mesma classe, o usuário pode interagir com o objeto analyser, solicitando a criação do tokenlist de um arquivo editado na interface gráfica gerada pelo objeto, ou carregado através do método open. Figura 10. Classe Editor. Trabalho de Linguagens Formais e Compiladores I 28/06/2007 6/7
4.5. Classe Arquivo Para a manipulação de arquivos foi utilizada a classe Arquivo pertencente a uma biblioteca de uso genérico chamada ricna, criada justamente para reaproveitamento de código em diversas aplicações. Esta classe é utilizada somente para abrir, fechar, escrever e ler arquivos. 5. Considerações Finais Através da implementação foi possível verificar e validar as soluções estudadas teoricamente em sala de aula. Utilizando a técnica para gerar as gramáticas de cada sentença da linguagem, para gerar autômatos finitos a partir de cada gramática e a técnica de determinização sobre o agrupamento de todos autômatos, possibilitou a implementação de um algoritmo muito simples frente a um problema que aparentemente necessitaria de um código complexo quando não aplicadas as técnicas vistas em aula. Isso também é pode ser afirmado por quem já implementou, sem aplicar nenhuma destas técnicas, o famoso montador de linguagem assembly para linguagem de máquina. Neste é necessária a identificação das sentenças da linguagem em uma das passagens, porém, sem um mínimo conhecimento de linguagens formais e autômatos, a implementação torna-se um tanto complexa devido a imensa quantidade de verificações necessárias simplesmente para validar as cadeias encontradas no código fonte. Logo, o esforço despendido no estudo e construção da gramática e autômato finito determinizado de uma linguagem, além de essencialmente necessário para solucionar problemas desta ordem, é compensado com uma implementação tranqüila e eficientemente garantida. 5. Referências Menezes, Paulo Blauth. Linguagens formais e autômatos. Porto Alegre: Instituo de Informática da UFRGS: Editora Sagra Luzzatto, 2005. Notas de aulas de Linguagens Formais e Compiladores I Prof. Dr. Bráulio Adriano Mello Trabalho de Linguagens Formais e Compiladores I 28/06/2007 7/7