segunda-feira, 23 de janeiro de 2012

Compilador MiniJava


Desde quando comecei meus estudos de computação e aprendi a primeira linguagem de programação, C, eu tenho a curiosidade de entender o funcionamento de um compilador.

Ficava fascinado pelo fato de um programa transformar um arquivo texto contendo construções da linguagem, em outro arquivo contendo código que uma máquina consegue compreender preservando a semântica, ou seja, seu significado. Esse é o processo de compilação.

Para iniciantes, isso fica um pouco abstrato porque o foco inicial é aprender a programar e não como funciona um compilador.
Finalmente, no meu 7° período de curso (bem que poderia ser antes!) tive a oportunidade de, não só entender todo esse processo de compilação, mas também construir um compilador seguindo as etapas de projeto. Cursei a disciplina Compiladores ministrada pelo professor Marcus Ramos o qual agradeço a paciência e disponibilidade para tirar dúvidas e compartilhar seu conhecimento, o que possibilitou a realização desse trabalho!

A linguagem usada para a construção foi MiniJava. Ela é uma versão reduzida do Java. Possui apenas construções simples e comandos básicos como if, while, System.out.println(), permite criação de métodos e atributos de classe e possui apenas tipos int, boolean, objeto e vetor de int.

Como nosso tempo foi curto (pouco menos de 2 meses, porque antes tivemos que ver a teoria que envolve a construção de compiladores), algumas decisões de projeto tiveram de ser tomadas para que pelo menos algo de cada etapa (análise sintática, análise de contexto e geração de código) pudesse ser feito.

A análise sintática consiste em reconhecer os símbolos (tokens) a partir da leitura de um arquivo texto de entrada e verificar se eles respeitam as regras gramaticais da linguagem fonte (no caso foi usada uma BNF estendida que pode ser encontrada aqui).

Na semântica, já com os tokens corretamente reconhecidos, o programa é verificado se está correto agora observando o significado das construções. Basicamente acontece a identificação (reconhecimento de variáveis e métodos) e vinculação (vincula cada uso de variável com sua declaração). Possíveis erros são facilmente identificados e mensagens exatas podem ser emitidas nessas etapas.

E, por fim, geração de código. Com o programa bem escrito e com o significado correto perante as regras da linguagem, as instruções podem ser traduzidas passo a passo para linguagem de máquina. Gerando o PUSH para reservar espaço para variáveis, STORE para guardar valores, LOAD para carregar valor de um registrador, JUMP para desvios, dentre outras. Essas são algumas básicas, cada máquina possui seu conjunto de instruções.

No nosso caso, o código de máquina foi gerado com base em uma máquina abstrata usada a partir do livro base da disciplina (link dele aqui), o TAM (Triangle Abstract Machine).

Aqui nesse link você encontra o arquivo compactado contendo o executável do compilador em bytecode juntamente com uma classe de exemplo. Também há um manual explicando como que instala e usa o compilador! Fique à vontade para testar e comentar.

Um abraço,
Bruno Pinho.

4 comentários:

Sérgio Aurélio disse...

Show de bola Bruno!!!

Diego disse...

Valeu Bruno, cada postagem melhor que a outra. Cada vez mais, vejo que vai virar um astronauta nesse curso!!! haha abração!

Bruno Sampaio Pinho disse...

Valeu!! Considero esse trabalho como um dos melhores que já fiz!
Abração

Rogério disse...

Cara muito bom o artigo postado.