Suporte às funções do Pascal

alexgarzao

Alex Sandro Garzão

Posted on July 7, 2024

Suporte às funções do Pascal

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!";
    }
}
Enter fullscreen mode Exit fullscreen mode

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: }
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
alexgarzao
Alex Sandro Garzão

Posted on July 7, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related