DEV Community

Alex Sandro Garzão
Alex Sandro Garzão

Posted on

1

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.

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay