DESENVOLVIMENTO DO COMPILADOR PARA A LINGUAGEM SIMPLE Jeferson MENEGAZZO 1, Fernando SCHULZ 2, Munyque MITTELMANN 3, Fábio ALEXANDRINI 4. 1 Aluno 5ª fase do Curso de Ciência da Computação do Instituto Federal Catarinense - Campus Rio do Sul, e-mail: jef.menegazzo@gmail.com; 2 Aluno 5ª fase do Curso de Ciência da Computação do Instituto Federal Catarinense - Campus Rio do Sul, e-mail: fernando_rsl3@hotmail.com; 3 Aluna 5ª fase do Curso de Ciência da Computação do Instituto Federal Catarinense - Campus Rio do Sul, e-mail: munyquee@gmail.com; 4 Professor de Ensino Superior, Técnico e Tecnológico do Instituto Federal Catarinense - Campus Rio do Sul, e-mail: fabalexandrini@yahoo.com.br. RESUMO Os compiladores são tradutores que transpõem programas escritos em linguagem de alto nível para programas equivalentes em linguagem simbólica ou linguagem de máquina. A entrada para o compilador é um arquivo de texto, que será lido caractere a caractere para compor a estrutura do programa, assim a primeira etapa de um compilador é a análise léxica, que identifica quais são as palavras e as associa ao seu tipo. A seguir, há a análise sintática, que determina a estrutura do programa. A terceira análise é chamada de semântica que computa o significado do programa traduzido. A tarefa final do compilador é gerar código executável para uma máquina-alvo. Utilizando de conceitos explanados neste artigo, foi construído um compilador em linguagem Java, sem o auxílio de geradores. Os analisadores desenvolvidos para este trabalho verificam sobre uma linguagem própria, denominada SIMPLE, a qual implementa algumas das estruturas mais básicas de uma linguagem de programação. A implementação do analisador léxico consiste em um autômato finito, que lê e analisa um código de entrada, dividindo-o em lexemas a partir da análise de delimitadores definidos, associando-os a seus respectivos tokens, ou erros léxicos. Enquanto que o analisador sintático recebe os tokens da análise anterior e usa validações por meio de chamadas de métodos, os quais utilizam de análise ascendente, expressões regulares e verificações da definição da linguagem, para validar ou não um (ou um conjunto) de lexema (s). Referente à análise semântica, foram implementadas estruturas para verificações típicas, como declaração de variáveis, associação e operações entre tipos definidos, as quais complementam as demais analises, de forma a se obter uma análise completa e bem estruturada do código fornecido. Palavras-Chave: Linguagem Simple, Compiladores, Ciência da Computação. INTRODUÇÃO Os programas de computadores, na década de 1940, foram escritos em linguagem de máquina, através de códigos numéricos representando operações de máquina a serem efetivamente executadas. Entretanto, esta forma de programação consome muito tempo, gera códigos de difícil legibilidade e escrita, dispendendo, portanto, grande custo para implementação e manutenção. A linguagem de montagem rapidamente substituiu esta forma de codificação, adotando formas simbólicas para instruções e endereçamento de memória. Os códigos simbólicos e os endereços de memória da linguagem de montagem são traduzidos para os códigos correspondentes da linguagem de máquina pelo que é denominado montador. Além da ainda presente dificuldade na escrita e leitura, outro problema, segundo Louden (2004, p. 3), é que a linguagem de montagem é extremamente dependente de uma máquina em particular para qual ela seja escrita; portanto, o código escrito para um computador precisa ser completamente reescrito para outro. A evolução seguinte na tecnologia de programação foi o desenvolvimento de compiladores, o que gerou a possibilidade de escrever as operações de um programa em 1
uma forma concisa, mais semelhante a uma linguagem natural, independente de uma máquina em particular e traduzível por um programa em código executável. Os estudos de Noam Chomsky sobre a estrutura da linguagem natural levaram à classificação de linguagens segundo a complexidade de suas gramáticas (as regras que especificam sua estrutura) e o poder dos algoritmos necessários para reconhecê-las. Desta forma, estes estudos tornaram a construção de compiladores mais simples e parcialmente automatizável (LOUDEN, 2004). Tendo como entrada uma sequência de caracteres (o programa-fonte), conforme Ricarte (2008, p.53), a primeira etapa que um compilador deve realizar, denominada análise léxica, é identificar quais são as palavras e associá-las individualmente ao seu tipo. A seguir, há a análise sintática, que, a partir de das marcas (tokens) fornecidas pela análise léxica, determina a estrutura do programa. A terceira análise de um programa é chamada de semântica e determina seu comportamento durante a execução de acordo com seus significados, em contraste com sua sintaxe ou estrutura. Os programas Yacc, escrito por Steve Johnson, e Lex, por Mike Lesk, são exemplos de ferramentas criadas, respectivamente, para análise sintática e léxica do sistema Unix. Estes programas surgiram da necessidade de automatizar o desenvolvimento de compiladores. O presente trabalho destina-se a implementação de um compilador para a linguagem de programação própria denominada SIMPLE. MATERIAL E MÉTODOS O presente trabalho foi desenvolvido a partir de pesquisas literárias. Um compilador para a linguagem própria de programação SIMPLE foi implementado através da linguagem Java, utilizando o ambiente integrado de desenvolvimento (IDE) Eclipse. Os analisadores foram desenvolvidos sem o auxílio de geradores de analisadores léxicos e sintáticos. A análise léxica, cujo objetivo é reconhecer quais cadeias de símbolos do programa fonte representam uma única entidade ou token, foi desenvolvida usando métodos de especificação e de reconhecimento de padrões tais quais expressões regulares e autômatos finitos. Expressões regulares representam padrões de cadeias de caracteres. Autômatos finitos são uma forma matemática de descrever tipos particulares de algoritmos que podem ser construídos a partir de expressões regulares. A segunda fase do compilador, o analisador sintático verifica se as construções usadas no programa estão gramaticalmente corretas. As gramáticas livres de contexto (GLC) formam a base para a análise sintática das linguagens de programação, pois permitem descrever a maioria de linguagens de programação utilizadas atualmente. Louden (2004) destaca que a análise sintática envolve uma escolha entre diversos métodos algorítmicos distintos, cada qual apresentando propriedades e recursos próprios. Há duas categorias gerais de algoritmos para a análise sintática: descendente e ascendente. Para desenvolvimento do compilador, optou-se pela estratégia ascendente. Os métodos de análise baseados na estratégia ascendente (bottom-up ou redutiva) realizam a análise a partir dos tokens do texto fonte (folhas da árvore de derivação) e constroem a árvore até o símbolo inicial da gramática (PRICE, TOSCANI, 2008). A análise semântica requer a computação de informações que estão além das capacidades das gramáticas livres de contexto e dos algoritmos padrão de análise sintática. 2
RESULTADOS E DISCUSSÃO Na linguagem SIMPLE, têm-se os comandos básicos de escrita (WRITE) e leitura (READ), estrutura de seleção (IF ELSE IF ELSE END), estrutura de repetição (WHILE END) e declaração de variáveis com tipos pré-definidos (INT LONG FLOAT DOUBLE CHAR STRING), além de suportar estruturas aninhadas. A Figura 1 exemplifica um programa nesta linguagem, cuja é baseada em Pascal e Matlab. Figura 1: Aba Principal - Inserção do código a ser analisado. A divisão do código fonte em lexemas é feita a partir da análise de delimitadores definidos, como operadores lógicos, relacionais, matemáticos, de atribuição, parênteses, aspas, espaços, etc., lidos por um autômato finito. Já a identificação ocorre por meio de validações e chamadas de métodos, os quais utilizam de expressões regulares ou verificações na definição da linguagem, para validar ou não um lexema. Para os lexemas aceitos, são associados tokens correspondentes, definidos em enumerações Java. Já para os não reconhecidos, são associados erros. O analisador ainda especifica a linha e posição do lexema, para fácil localização e correção em caso de erro léxico (Figura 2). 3
Figura 2: Resultado da análise léxica. Tendo o código fonte preparado pelo analisador léxico, a análise sintática recorre aos tokens para reconhecimento das sequências, seguindo as seguintes regras para realizar as verificações: 1. Para o reconhecimento de variáveis, é necessário ter um tipo definido, e identificador diferente das variáveis já definidas. 2. A variável só pode ser utilizada depois de declarada. 3. Só podem ser atribuídos à variável, dados de tipos compatíveis. 4. Expressões lógicas e aritméticas são reduzidas a partir da análise de operadores, operandos e parênteses, respeitando os tipos definidos e precedências de operadores. 5. Para cada comando IF é necessário um comando END, que define o fim do bloco de código. Entre estes, é possível haver nenhum, um ou muitos ELSE IF e /ou ELSE, além de estruturas aninhadas. 6. Para cada comando WHILE é necessário um comando END que defina o fim do bloco, também é possível ter estruturas aninhadas. 4
Caso alguma sequência não obedeça às regras definidas da linguagem, um erro correspondente é associado. Caso seja aceita, é associado à estrutura correspondente (Figura 3). Figura 3: Resultado da análise sintática. Na análise semântica, a informação computada está fortemente relacionada com seu significado, ou seja, a semântica do programa traduzido. A análise semântica requer a construção de uma tabela de símbolos (Figura 4) para acompanhar o significado dos nomes estabelecidos nas declarações e efetuar inferência e verificação de tipos em expressões e declarações, de forma a determinar sua correção pelas regras de tipos da linguagem (LOUDEN, 2004). 5
Figura 4: Tabela de Símbolos. O analisador semântico desenvolvido para a linguagem SIMPLE verifica se a variável está declarada e foi inicializada antes do uso e se é a única variável declarada com o tal identificador. O analisador também verifica se os operandos são aplicáveis ao operado, podendo os operandos serem variáveis ou literais. Há a verificação se uma expressão, variável ou literal é aplicável na atribuição ou na estrutura (como if ou while). 6
Figura 5: Resultado da análise semântica. A tarefa final de um compilador é gerar código executável para uma máquinaalvo, cujo deve ser uma representação fiel da semântica do código-fonte. Caso não seja encontrado nenhum erro nas análises léxica, sintática e semântica (Figura 6), o compilador gera o código-alvo e o executa (Figura 7). Caso contrário, o compilador apenas notifica quais análises encontraram erros e quais são. 7
Figura 6: Conclusão da análise e geração de código. Figura 7: Programa executado. 8
A geração de código é a fase mais complexa do compilador, pois depende não apenas das características da linguagem-fonte, mas também de informações detalhadas da arquitetura-alvo, da estrutura do ambiente de execução e do sistema operacional da máquina-alvo. 9
CONSIDERAÇÕES FINAIS Conhecer a organização e as operações básicas de um compilador, apesar de não ser imprescindível para todos os profissionais atuantes na computação, é deveras importante pela sua utilização em quase todas as formas de computação. Entender e construir um compilador é um processo complexo, que pode parecer distante da realidade atual dos desenvolvedores de software. Entretanto, o desenvolvimento de interpretadores de comandos e programas de interface é uma tarefa recorrente em aplicações computacionais e, embora menores que compiladores, utilizam as mesmas técnicas. Este trabalho destinou-se ao estudo e implementação um compilador. Para isso, foi necessário o estudo de técnicas teóricas, com enfoque às teorias das linguagens, gramáticas e autômatos. Mais informações sobre a linguagem SIMPLE e seu compilador estão disponíveis em simpleprog.wordpress.com. REFERÊNCIAS BROOKSHEAR, J. G. Ciência da Computação: uma visão abrangente. 11. ed. Porto Alegre: Bookman, 2013. BROWN, D. F; WATT, D. A. Programming Language Processors in Java: Compilers and Interpreters. Harlow: Prentice Hall, 2000. DELAMARO, M. E. Como Construir um Compilador - Utilizando Ferramentas Java. São Paulo: Novatec, 2004. LIPSCHUTZ, S.; LIPSON, M. Matemática Discreta. Porto Alegre: Bookman, 2013. LOUDEN, K. C. Compiladores: Princípios e Práticas. São Paulo: Cengage Learning, 2004. MENEZES, P. B. Linguagens Formais e Autômatos. 6. ed. Porto Alegre: Bookmann: 2011. PRICE, A. M. A; TOSCANI, S. S. Implementação de linguagens de programação: compiladores. 3. ed. Porto Alegre: Bookman, 2008. RICARTE, I. Introdução à compilação. Rio de Janeiro: Elsevier, 2008. ROSEN, K. H. Matemática discreta e suas aplicações. 6. ed. São Paulo: McGraw- Hill, 2009. SEBESTA, R. W. Conceitos de Linguagens de Programação. 9. ed. Porto Alegre: Bookman, 2010. 10