Etapa 7: Geração de Código para JVM
Objetivos
- Aprender sobre a máquina de pilha da JVM (Java Virtual Machine).
- Utilizar a biblioteca ASM para gerar bytecode.
- Finalizar o compilador gerando um artefato executável (
.class).
Fundamentação Teórica
A JVM não possui registradores de uso geral (como EAX, EBX). Ela opera sobre uma Pilha de Operandos. Exemplo para a = b + c: 1. iload_1 (Carrega b na pilha) 2. iload_2 (Carrega c na pilha) 3. iadd (Desempilha dois, soma, empilha o resultado) 4. istore_0 (Desempilha o resultado e salva em a)
Atividades Práticas
1. Configurando a Biblioteca ASM
Adicione ao pom.xml:
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.6</version>
</dependency>2. O CodeGen Visitor
Crie um pacote br.com.comcet.tp7. Crie nele um Visitor que percorre a AST e emite instruções ASM. Ele precisará de: - ClassWriter: Para criar a estrutura da classe. - MethodVisitor: Para escrever instruções dentro do método main.
Mapeamento de Variáveis: Diferente da Tabela de Símbolos (que usa nomes), a JVM usa índices numéricos (0, 1, 2…). Você precisará de um mapa String -> Integer para saber qual índice representa a variável x.
3. Implementando Instruções
- Programa: Cria a classe
Program, cria o métodopublic static void main(String[] args). - Atribuição (
x := expr):- Visita
expr(gera código que deixa o resultado na pilha). - Emite
istore varIndex(salva da pilha na variável).
- Visita
- Soma (
a + b):- Visita
a(pilha: a). - Visita
b(pilha: a, b). - Emite
iadd(pilha: a+b).
- Visita
- Print (
writeln(x)):getstatic System.out- Visita
x(carrega valor) invokevirtual println
4. Compilador Final
O seu programa deve: 1. Ler arquivo .pas. 2. Analisar Lexico/Sintatico. 3. Analisar Semântico. 4. Gerar arquivo .class.
5. Testes Automatizados (JUnit) — públicos
Crie ao menos um teste JUnit público que valide o resultado final de ponta a ponta: compilar um programinha, gerar .class e executar.
Uma forma simples é: - gerar o .class em um diretório temporário; - executar com ProcessBuilder e comparar a saída.
Crie, por exemplo, src/test/java/br/com/comcet/tp7/CodeGenSmokeTest.java:
package br.com.comcet.tp7;
import org.junit.jupiter.api.Test;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*;
public class CodeGenSmokeTest {
@Test
void geraEClassExecuta() throws Exception {
// Exemplo mínimo: gerar uma classe que imprime 120.
// Adapte o código para o seu Mini-Pascal (pode ser writeln(120) ou equivalente).
String codigo = "program p; begin writeln(120); end.";
Path outDir = Files.createTempDirectory("codegen-test");
Compiler compiler = new Compiler(); // adapte para o nome do seu compilador/driver
String className = "P"; // adapte para sua estratégia de nome
compiler.compileToClass(codigo, outDir, className); // adapte assinatura
Process p = new ProcessBuilder(
"java",
"-cp",
outDir.toAbsolutePath().toString(),
className
).redirectErrorStream(true).start();
String output;
try (InputStream is = p.getInputStream()) {
output = new String(is.readAllBytes(), StandardCharsets.UTF_8).trim();
}
int exit = p.waitFor();
assertEquals(0, exit);
assertEquals("120", output);
}
}Observação: este teste é um “smoke test”. Ele não verifica todas as instruções, mas garante que o pipeline completo gera um
.classexecutável e com saída esperada.
O que entregar
- O compilador completo funcionando.
- Exemplo
fatorial.pascompilado e executado. - Pelo menos um teste JUnit público (smoke test) para a geração e execução do
.class.
Exemplo de execução:
java -jar target/comcet-1.0-SNAPSHOT.jar fatorial.pas
# Gera fatorial.class
java fatorial
# Saída: 120