- Introdução
- Dependências
- Estrutura do Projeto
- Makefile
- Tipos e Sintaxe
- Estrutura de Arquivos
- Objetivo do Projeto
- Próximos Passos
Este projeto é um compilador desenvolvido em C por Eduardo Peretto durante a disciplina de Compiladores na UFRGS. O projeto é baseado nos ensinamentos do livro Engineering a Compiler de Keith D. Cooper. A implementação do compilador é feita de forma gradativa, abrangendo diferentes módulos como análise léxica, análise sintática, inferência de tipos, tabelas de identificadores, geração de linguagem intermediária, entre outros.
Para o desenvolvimento deste projeto, é necessário a instalação das seguintes ferramentas:
- FLEX
- (Documentação)
- Instalação (Ubuntu):
sudo apt-get install flex
- BISON
- (Documentação)
- Instalação (Ubuntu):
sudo apt-get install bison
O compilador é dividido em diferentes etapas de implementação, cada uma correspondendo a um módulo específico do processo de compilação.
- Etapa 1: Analisador Léxico
- Utiliza o FLEX para a análise léxica do código.
- Etapa 2: Analisador Sintático
- Utiliza o BISON para a análise sintática e geração da árvore de sintaxe abstrata (AST).
- Etapa 3: Geração da Árvore de Nodos (AST)
- Construção da árvore de nodos a partir do código analisado.
- Etapa 4: Tabela de Hash e Verificação de Tipos
- Criação de uma tabela hash para salvar identificadores e verificação de tipos.
- Etapa 5: Geração de Código Intermediário
- Geração de código intermediário na linguagem ILOC, conforme proposto no livro Engineering a Compiler.
- Etapa 6: Geração de Código Assembly
- Conversão do código intermediário para código assembly.
O projeto inclui um Makefile com os seguintes targets para facilitar a compilação e execução do código:
make: Compila todo o projeto e gera o executável.make run: Executa o compilador usando o arquivoinput.txtcomo entrada.make clean: Limpa todos os arquivos gerados pela compilação do projeto.
Atualmente, a linguagem suportada pelo compilador possui três tipos básicos:
- bool
- float
- int
A sintaxe da linguagem possui algumas particularidades, como o uso de vírgula para finalizar uma expressão.
A declaração de uma função começa com a declaração de parâmetros cobertos por parênteses e separados por ponto e vírgula, seguido por um |, o tipo da função, uma barra /, seguida, finalmente, do nome da função. Após isso, abre-se o escopo utilizando chaves { }.
Exemplo Simples:
(bool b; bool c) | int /a{
if (b | c) {
return 0,
}
return 1,
}A declaração de variáveis é dada pelo tipo, seguido de um ou mais identificadores separados por ponto e vírgula. Não é suportada a inicialização das variáveis junto à declaração.
Exemplo Simples:
float a; b; c,- main.c: Arquivo de entrada do código, responsável por receber o input e acionar a análise do compilador.
- hash_table.c: Funções relacionadas à tabela hash de identificadores, variáveis e funções, incluindo a verificação de tipos.
- parser.y: Arquivo BISON com a análise sintática, geração de nodos e AST, contendo as principais regras de compilação.
- scanner.l: Analisador léxico, responsável pela leitura do código e conversão em tokens.
- tree.c: Funções relacionadas à geração de nodos e AST.
O objetivo deste projeto é proporcionar um aprendizado prático sobre o funcionamento de um compilador, permitindo a compreensão detalhada de cada etapa do processo de compilação. Por ser um projeto didático, alguns itens ou otimizações comuns em compiladores reais foram intencionalmente deixados de lado, a fim de simplificar a implementação e focar nos conceitos fundamentais de análise léxica, análise sintática, geração de código intermediário e outras etapas essenciais.
Que esse projeto possa servir de inspiração ou aprendizado a demais interessados. Sintam-se convidados a abrir pull requests ou entrar em contato para discutir implementações.
Como desenvolvimento futuro, pretendo utilizar ferramentas visuais como gráficos, e permitir uma customização do compilador de maneira rápida, para observar efeitos causados.