Suporte às funções do Pascal
Tags: go, antlr, learning, compiling
Published: 2024-07-07 17:20:08

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](https://dev.to/alexgarzao/captura-de-erros-operadores-relacionais-para-string-e-procedures-3e46) 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](https://github.com/alexgarzao/poj/pull/31) é possível também definir bem como invocar as _functions_ do Pascal.

[Neste commit](https://github.com/alexgarzao/poj/pull/31/commits/d89e401c7ed70da6b79ed9921f526d64de545395) 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](https://www.geeksforgeeks.org/phases-of-a-compiler/?ref=lbp) existente em compiladores de mercado, algo que pode ser implementado futuramente.

Este [commit](https://github.com/alexgarzao/poj/pull/31/commits/4ab69de56d815424051466228ed9fc3ef7df6d2c) 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](https://github.com/alexgarzao/poj/pull/31/commits/8a65062ae36139886f1066847294007b6aeb2de1) 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](https://github.com/alexgarzao/poj).