O ano era 1999 (ou 2000, não tenho mais certeza rsrsrs). Foi um dos trabalhos da disciplina de Compilação Avançada, e posso afirmar que foi um dos mais divertidos da minha graduação em Ciência da Computação 🙂
Basicamente o objetivo do projeto era implementar um compilador capaz de ler um subset de Pascal e gerar um arquivo class válido de forma a poder executá-lo na JVM. Por subset entendam um programa Pascal válido, mas com um conjunto reduzido de instruções e operações:
- Declaração de variáveis: tipos básicos apenas;
- Estruturas de repetição: If, For, While, Repeat;
- Entrada e saída de dados: 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.
Abaixo é possível ver o clássico “Hello world!” em Pascal:
program Hello;
begin
writeln ('Hello world!');
end.
Abaixo temos um exemplo em Pascal um pouco mais elaborado onde temos definição de variáveis, entrada e saída de dados:
program Example;
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 facilitar o desenvolvimento do compilador, optei por utilizar um gerador de parsers. Apesar de já ter utilizado os clássicos Lex/YACC e Flex/Bison em outros momentos, eu sentia falta de uma integração maior entre a análise léxica e sintática nestes geradores. Além disso, estes geradores utilizavam BNF, e eu gostaria de utilizar EBNF para especificar a gramática da minha linguagem (subset de Pascal).
Com isso optei por utilizar o JavaCC, um gerador de parsers que, a partir da gramática EBNF que criei, gerava um parser em Java capaz de ler este subset de Pascal e, a cada etapa das análises léxicas e sintáticas, executar ações determinadas por mim. Nestas ações eu inseria código Java que realizava o que eu precisava: gerar a tabela de símbolos (que continham as strings e números existentes no programa em Pascal) e o bytecode equivalente ao programa lido. No final do parser, caso nenhum erro ocorresse, o arquivo class era gerado.
Para exemplificar o processo de tradução que o compilador realizava vamos usar o clássico “Hello world!” em Pascal abaixo:
program Hello;
begin
writeln ('Hello world!');
end.
A partir deste programa o compilador então traduzia para o seu equivalente em Assembly Java, similar ao exemplo abaixo:
class Hello
{
method public static void main (java.lang.String[])
max_stack 2
{
getstatic java.io.PrintStream java.lang.System.out
ldc "Hello World!"
invokevirtual void java.io.PrintStream.println(java.lang.String)
return
}
}
Posteriormente este assembly era traduzido pelo compilador para o seu formato binário (bytecode) e armazenado no arquivo class.
No fim, o trabalho foi concluído com sucesso. Eu tinha uma gramática em JavaCC que gerava um parser capaz de ler qualquer programa em Pascal (desde que respeitando o subset definido) e gerar o arquivo class, arquivo este que era executado diretamente pela JVM. Aliás, fiquei fã da JVM nesta época.
Top comments (0)