Compiladores é um dos temas do qual tenho muito interesse. Inclusive estou publicando alguns projetos antigos meus nesta série aqui.
Porém, como comentei em uma publicação desta série, vou iniciar uma abordagem mais prática onde irei construir um compilador "from scratch", passo-a-passo, de código aberto, tanto para consolidar alguns temas abordados bem como para termos algo concreto.
Sobre o desafio
Compiladores são, por natureza, projetos que eu considero desafiadores e complexos. E para termos um objetivo claro e, dentro do possível, simples para este projeto, o que vamos fazer é implementar uma nova solução para o trabalho de faculdade que comentei nesta publicação: rodar um subset de Pascal na JVM. Para tal, iremos criar um compilador que entende este subset de Pascal e gera o arquivo class para ser executado na JVM.
Como a minha criatividade para criar nomes de projetos é baixa, vou batizar o projeto de POJ (Pascal On the JVM). Se alguém tiver um nome bacana, eu aceito :-)
Visão macro do projeto
Na solução anterior (mais detalhes aqui) o compilador gerava o arquivo class diretamente. Porém, para tentar simplificar a nossa solução, vou abstrair a geração do arquivo class utilizando um montador Java Assembly qualquer (Figura abaixo). Com isso o nosso compilador (POJ) será responsável por ler um programa contendo código Pascal e gerar o assembly Java equivalente enquanto o montador será responsável por ler este assembly e gerar o arquivo class para ser executado na JVM.
Entendo que seja uma proposta válida no momento, mas só após alguns testes com montadores assembly Java será possível validar esta proposta. Caso não seja encontrado um montador que atenda as necessidades do projeto, teremos que partir para gerar o arquivo class diretamente.
Assim como no projeto anterior, para facilitar o desenvolvimento do compilador eu optei por utilizar um gerador de parsers. Desde 2003 sou fã do ANTLR, que além de fornecer uma integração interessante entre a análise léxica e sintática, também utiliza EBNF para especificar a gramática da linguagem. Além disso, ANTLR gera código para diversas linguagens, entre elas Go :-)
A Figura abaixo contém as etapas necessárias para criarmos o nosso compilador (POJ). A partir da gramática da linguagem Pascal, o ANTLR gera o parser em Golang. A partir deste código Go gerado iremos instrumentar as regras necessárias, tanto na análise léxica bem como na sintática. Após isso utilizaremos o compilador do Go para gerar o binário do POJ.
Subset aceito
Quando falamos em subset de Pascal entendam um programa Pascal válido, mas com um conjunto reduzido de instruções e operações:
- Declaração de variáveis utilizando os tipos básicos: integer, real, boolean e string;
- Estruturas condicionais: If/Else;
- Estruturas de repetição: For, While, Repeat;
- Entrada e saída de dados via console: Write, WriteLn, Read, ReadLn;
- Operações matemáticas com precedência de operadores: soma, subtração, divisão, multiplicação e módulo;
- Declaração de blocos de código: procedures e funções.
Exemplos de programas aceitos
Abaixo é possível ver o clássico “Hello world!” em Pascal:
program Hello;
begin
writeln ('Hello world!');
end.
Abaixo temos o cálculo do fatorial, de forma recursiva:
program fatorial;
var numero : integer;
function fatorial(n : integer) : integer;
begin
if n<0 then fatorial := 0
else begin
if n<=1 then fatorial := 1
else fatorial := n * fatorial(n-1);
end;
end;
begin
write('Introduza numero inteiro: ');
readln(numero);
writeln;
writeln('O fatorial de ', numero, ' e: ', fatorial(numero));
end.
Abaixo temos um exemplo com entrada e saída de dados:
program NameAndAge;
var
MyName: String;
MyAge : Byte;
begin
Write('What is your name? '); Readln(MyName);
Write('How old are you? '); Readln(MyAge);
Writeln;
Writeln('Hello ', MyName);
Writeln('You are ', MyAge, ' years old');
end.
Para quem tiver interesse de ver a execução destes programas, sugiro utilizar o Free Pascal Compiler no momento. Nas próximas semanas poderemos utilizar o POJ para tal :-)
Por que refazer?
Uma dúvida comum talvez seja: por que refazer? Por que não melhorar o projeto anterior?
Bom, eu até encontrei o código antigo, de 1999, do projeto de faculdade. Mas esta versão utiliza o JavaCC e Java, tecnologias que praticamente só utilizei naquele período. Aprender Java e ajustar um código Java de 1999 não está nos meus objetivos. E como pretendo manter e evoluir este projeto, optei por reescrevê-lo em tecnologias que tenho mais interesse como ANTLR e Go.
Por que JVM?
Outra dúvida comum talvez seja: porque foi escolhida a JVM? Em 1999 foi utilizada a especificação 1.1 da JVM, e garanto que havia pouca documentação na época. O motivo da escolha da JVM foi porque, dentre as VMs da época, a JVM era stack based machine, o que simplifica (e muito) a geração do bytecode. Isso porque, além de necessitar de poucas operações, todas as operações da máquina virtual operam sobre a pilha. Já VMs register based necessitam de um conjunto maior de operações e precisam lidar com a alocação de registradores (mesmo que estes sejam virtuais). Para quem tiver interesse de ler mais sobre, aqui tem uma publicação bem interessante sobre este tópico. Isso sem falar que a JVM já é utilizada por várias linguagens além do Java, como Clojure, Lisp, Scala e Kotlin, isso só citando algumas das mais conhecidas.
Top comments (0)