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 tivemos algumas melhorias na captura de erros, suporte a operadores relacionais para o tipo string e a possibilidade de definir (e utilizar) as procedures do Pascal.
Nesta publicação vamos abordar o suporte às funções (functions) do Pascal. Falta pouco para podemos concluir o último objetivo do projeto: ler um número da entrada padrão e calcular o seu fatorial.
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).
Suporte às funções (functions) do Pascal
Até o momento tínhamos como definir e invocar às procedures do Pascal. A partir deste PR é possível também definir bem como invocar as functions do Pascal.
Neste commit foi implementado um programa em Java para entender como a JVM lida com a definição e a chamada de funções. A partir do programa Java abaixo:
public class FunctionCall {
public static void main(String[] args) {
System.out.println("Hello from main!");
System.out.println(myMethod());
}
static String myMethod() {
return "Hello from myMethod!";
}
}
Quando desassemblamos o class obtemos o seguinte assembly:
1: public class FunctionCall {
2: public static main([java/lang/String)V {
3: getstatic java/lang/System.out java/io/PrintStream
4: ldc "Hello from main!"
5: invokevirtual java/io/PrintStream.println(java/lang/String)V
6:
7: getstatic java/lang/System.out java/io/PrintStream
8: invokestatic FunctionCall.myMethod()java/lang/String
9: invokevirtual java/io/PrintStream.println(java/lang/String)V
10:
11: return
12: }
13:
14: static myMethod()java/lang/String {
15: ldc "Hello from myMethod!"
16:
17: areturn
18: }
19: }
Com este exemplo foi possível identificar que:
- Para invocar um método a JVM utilizou a instrução "invokestatic FunctionCall.myMethod()java/lang/String" (linha 8) onde:
- invokestatic é a instrução que recebe como argumento a assinatura completa do método a ser chamado;
- FunctionCall é o nome da classe;
- myMethod()java/lang/String é assinatura completa do método com seus parâmetros (neste exemplo nenhum) e o tipo de retorno (neste exemplo java/lang/String);
- Instrução areturn (linha 17) encerra a função e deixa na pilha a string de retorno.
Dito isso, a partir do programa Pascal abaixo:
program function_call_wo_params;
function myfunction : string;
begin
myfunction := 'Hello from myfunction!';
end;
begin
writeln('Hello from main!');
writeln(myfunction());
end.
O POJ foi ajustado para gerar o seguinte JASM:
// Code generated by POJ 0.1
public class function_call_wo_params {
;; function myfunction : string;
static myfunction()java/lang/String {
ldc "Hello from myfunction!"
astore 100 ;; Posição 100 guarda o retorno da função
aload 100 ;; Empilha o retorno da função
areturn ;; Deixa "Hello from myfunction!" na pilha
}
;; procedure principal (main)
public static main([java/lang/String)V {
;; writeln('Hello from main!');
getstatic java/lang/System.out java/io/PrintStream
ldc "Hello from main!"
invokevirtual java/io/PrintStream.print(java/lang/String)V
getstatic java/lang/System.out java/io/PrintStream
invokevirtual java/io/PrintStream.println()V
;; writeln(myfunction());
getstatic java/lang/System.out java/io/PrintStream
invokestatic function_call_wo_params.myfunction()java/lang/String
invokevirtual java/io/PrintStream.print(java/lang/String)V
getstatic java/lang/System.out java/io/PrintStream
invokevirtual java/io/PrintStream.println()V
return
}
}
Os mais atentos devem ter notado o "astore 100" acima e pensado:
- Por que guardar o retorno da função em uma variável local? Isso se deve ao fato de que em Pascal o valor de retorno de uma função pode ser definido N vezes durante a função, mas só podemos empilhar um resultado na JVM;
- Por que na posição 100? As variáveis locais de uma função ou procedimento iniciam na posição 0 então arbitrariamente foi escolhido a posição 100 para guardar o retorno;
- Mas não seria possível otimizar para que neste exemplo somente fosse gerado a instrução ldc "Hello from myfunction!" seguida da instrução areturn? Sim, seria, mas o POJ não implementa a fase de otimizações existente em compiladores de mercado, algo que pode ser implementado futuramente.
Este commit implementa o suporte ao tipo "function" na tabela de símbolos e no parser.
Nos exemplos acima as funções não tinham argumentos. Neste commit foi implementado o resultado esperado para funções com argumentos. Com isso a partir do programa Pascal abaixo:
program function_call_with_two_params;
function addvalues(value1, value2: integer) : integer;
begin
addvalues := value1 + value2;
end;
begin
writeln('2+4=', addvalues(2, 4));
end.
O POJ gerou corretamente o seguinte JASM:
// Code generated by POJ 0.1
public class function_call_with_two_params {
;; function addvalues(value1, value2: integer) : integer;
static addvalues(I, I)I {
;; addvalues := value1 + value2;
iload 0
iload 1
iadd
istore 100
iload 100
ireturn
}
;; procedure main
public static main([java/lang/String)V {
;; writeln('2+4=', ...);
getstatic java/lang/System.out java/io/PrintStream
ldc "2+4="
invokevirtual java/io/PrintStream.print(java/lang/String)V
getstatic java/lang/System.out java/io/PrintStream
;; aqui código para invocar addvalues(2, 4)
sipush 2
sipush 4
invokestatic function_call_with_two_params.addvalues(I, I)I
;; aqui código para invocar writeln com retorno addvalues
invokevirtual java/io/PrintStream.print(I)V
getstatic java/lang/System.out java/io/PrintStream
invokevirtual java/io/PrintStream.println()V
return
}
}
Próximos passos
Nas próximas publicações vamos falar sobre contextos, bugs encontrados, sentenças aninhadas, entrada de dados e concluir o último dos objetivos deste projeto: cálculo do fatorial de forma recursiva.
Código completo do projeto
O repositório com o código completo do projeto e a sua documentação está aqui.
Top comments (0)