Alex Sandro Garzão
Posted on July 31, 2024
Para quem não está acompanhando o POJ (Pascal on the JVM) é um compilador que transforma um subset de Pascal para JASM (Java Assembly) de forma que possamos usar a JVM como ambiente de execução.
Na última postagem resolvemos alguns bugs importantes, em especial na geração do assembly. Nesta publicação vamos falar sobre como gerar corretamente o assembly para sentenças aninhadas.
Como estamos compilando para a JVM faz-se necessário detalhar o funcionamento de vários pontos desta incrível máquina virtual. Com isso, em vários momentos eu detalho o funcionamento interno da JVM bem como algumas das suas instruções (opcodes).
Contextos
Uma das funcionalidades necessárias para lidarmos corretamente com sentenças aninhadas é a possibilidade de termos vários contextos no parser. Isso ocorre porque se o parser assume um contexto apenas, sentenças de controle aninhadas que geram labels e saltos (como if, for, while e repeat) gerariam o endereçamento de saltos incorretamente.
Existem duas formas de lidar com contextos, que são:
- Parser recursivo
- Empilhar contextos
Usualmente eu utilizaria a abordagem de parser recursivo. Porém, para podermos implementar um parser recursivo com o ANTLR, pela forma como a gramática foi estruturada no POJ, seria necessário injetar código diretamente na gramática, uma abordagem pouco recomendada. Em função disso optou-se pela abordagem de empilhar contextos.
Como já existia uma implementação de pilha que monitorava os tipos que o parser empilhou/desempilhou na JVM, para não ter que criar mais uma pilha para um tipo específico, optou-se por criar uma stack genérica neste PR. Além de poder aproveitar esta implementação posteriormente, ainda posso refatorar o código antigo e remover a stack específica existente.
Neste commit o parser foi alterado para empilhar/desempilhar corretamente os contextos das funções. Basicamente no início do parser de uma função o contexto é empilhado, e no seu final o contexto é desempilhado.
Sentenças aninhadas
Sentenças de controle como if, for, while e repeat funcionavam corretamente. Porém, caso houvesse sentenças aninhadas, o POJ não guardava contexto e acaba por gerar erroneamente os labels e saltos. Aqui e aqui foi abordado como funciona a geração do assembly para estas sentenças de controle.
Para o exemplo abaixo, que contém um if aninhado dentro de outro, o POJ gerava erroneamente os labels e saltos necessários:
program NestedIfs;
begin
if (1 > 2) then
if (2 > 3 ) then
writeln('1 > 2 and 2 > 3')
else
writeln('1 > 2 and 2 <= 3')
else
writeln('1 <= 2');
end.
Este bug era conhecido e eu optei por resolvê-lo no momento em que o parser tivesse suporte a contextos.
Neste commit foi criado a estrutura LabelsContext contendo os seguintes labels:
- Else: necessário para o caso do if com else;
- NextStatement: contém o label da próxima instrução;
- IterationStart: indica o teste da estrutura de repetição no caso de while, repeat e for.
Para validar a correta geração do assembly foram criados testes para validar if's aninhados, repeat's aninhados, while's aninhados bem como for's aninhados. Aqui foi criado os testes para validar a geração do assembly no caso de funções recursivas. Além disso foi necessário atualizar o assembly esperado de todos os testes existentes. Por fim, neste PR foi atualizado o parser para utilizar a nova estrutura de contextos.
Aqui está o PR completo destas modificações. Aqui temos o commit contendo as mudanças para o correto funcionamento da sentença if, aqui o commit referente ao repeat, aqui o commit referente ao while e aqui o commit referente ao for.
Próximos passos
Na próxima publicação vamos falar sobre entrada de dados. Agora falta pouco para concluirmos um dos objetivos deste projeto: ler um número da entrada padrão e calcular o seu fatorial.
Código completo do projeto
O repositório com o código completo do projeto e a sua documentação está aqui.
Posted on July 31, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.