Captura de erros, operadores relacionais para string e procedures
Tags: go, compiling, antlr, jasm
Published: 2024-05-26 19:32: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/estruturas-de-repeticao-repeat-while-e-for-4ph6) foi adicionado suporte às estruturas de repetição repeat, while e for.

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 (_Java Assembly_).


## Melhorias na saída de erros

Sempre que existia um erro léxico, sintático ou semântico no código Pascal, o POJ apenas listava os erros gerados sem nenhum tipo de abstração. Além disso, a compilação seguia normalmente.

Para implementar melhorias, as seguintes modificações foram realizadas:
- [Neste _commit_](https://github.com/alexgarzao/poj/pull/26/commits/233c325e5ef09fa9eab6fbd47d8704ce263d0fbb) o código foi alterado para que a análise léxica, sintática e semântica retornem os erros encontrados;
- [Neste _commit_](https://github.com/alexgarzao/poj/pull/26/commits/26816073da6fffead78b371911da0fb5bda173b6) foi criada uma classe customizada de erros para ser utilizada pelo _runtime_ do ANTLR. Com isso podemos obter os erros encontrados pelo _parser_ bem como realizar o tratamento adequado;
- [Neste _commit_](https://github.com/alexgarzao/poj/pull/26/commits/0a7c9be57bbf4fc5cb9837591b5026b19a05171e) o código principal do POJ obtém os possíveis erros gerados, lista eles e aborta o processo de compilação quando necessário;
- [Neste _commit_](https://github.com/alexgarzao/poj/pull/26/commits/c47aa50abcec1cb9ab2f0b5929e549321e6117fc) foram introduzidos programas em Pascal inválidos bem como a saída de erros esperada. Com isso os testes automatizados, além de validarem a saída esperada de programas válidos (_Java Assembly_), também verificam a saída de erros esperada a partir de programas inválidos (lista de erros).

[Aqui](https://github.com/alexgarzao/poj/pull/26/files) está o PR completo.


## Operadores relacionais para o tipo _String_

Até o momento tínhamos o suporte aos operadores relacionais apenas para o tipo inteiro (_integer_). 

[Neste _commit_](https://github.com/alexgarzao/poj/pull/28/commits/42c6b32be74368381b5e011b926fd7477732aba9) foi implementado um programa em Java para entendermos como a JVM lida com os operadores relacionais para o tipo _String_. A partir do programa Java abaixo:

```
public class IfWithStrings {
   public static void main(String[] args) {
      String v1 = "aaa";
      String v2 = "bbb";
      if (v1.compareTo(v2) > 0)
         System.out.println("v1>v2");
      else
         System.out.println("v1<=v2");
   }
}
```

Quando desassemblamos o arquivo _class_ obtemos o _assembly_ abaixo. Trechos irrelevantes foram omitidos, bem como o trecho original (em Java) que deu origem ao _assembly_ foi inserido com ";;":

```
public class IfWithStrings {

    ;; public static void main(String[] args)
    public static main([java/lang/String)V {

        ;; String v1 = "aaa";
        ldc "aaa"
        astore 1

        ;; String v2 = "bbb";
        ldc "bbb"
        astore 2

        ;; v1.compareTo(v2)
        aload 1
        aload 2
        invokevirtual java/lang/String.compareTo(java/lang/String)I

        ;; if (v1.compareTo(v2) > 0)
        ifle label3

        ;; System.out.println("v1>v2");
        getstatic java/lang/System.out java/io/PrintStream
        ldc "v1>v2"
        invokevirtual java/io/PrintStream.println(java/lang/String)V
        goto label5
        
        ;; System.out.println("v1<=v2");
        label3:
        getstatic java/lang/System.out java/io/PrintStream
        ldc "v1<=v2"
        invokevirtual java/io/PrintStream.println(java/lang/String)V

        label5:
        return
    }
}
```

Com este exemplo foi possível identificar que para comparar duas _strings_ a JVM obtém da pilha as _strings_ e executa o método "compareTo" da classe _String_. Este método compara as _strings_ e empilha o seguinte resultado:

- \-1, caso o 1o valor seja menor que o segundo;
- 0, caso os dois valores sejam iguais;
- +1, caso o 2o valor seja maior que o primeiro.

Dito isso, a partir do programa Pascal abaixo:

```
program IfWithStrings;
begin
  if ( 'aaa' > 'bbb' ) then
    write('true')
  else
    write('false');
end.
```

O POJ foi ajustado para gerar o seguinte JASM:

```
// Code generated by POJ 0.1
public class if_with_strings {
    public static main([java/lang/String)V {

        ;; if ( 'aaa' > 'bbb' ) then
        ldc "aaa"
        ldc "bbb"
        invokevirtual java/lang/String.compareTo(java/lang/String)I
        iflt L3
        iconst 1 
        goto L4

    L3: iconst 0 

    L4: ifeq L1

        ;; write('true')
        getstatic java/lang/System.out java/io/PrintStream
        ldc "true"
        invokevirtual java/io/PrintStream.print(java/lang/String)V
        goto L2

    L1: ;; write('true')
        getstatic java/lang/System.out java/io/PrintStream
        ldc "false"
        invokevirtual java/io/PrintStream.print(java/lang/String)V

    L2: return
    }
}
```

Este [commit](https://github.com/alexgarzao/poj/pull/28/commits/e689ba9f797847a68934dda216c8bbe220430148) implementa a chamada ao método _String.compareTo_ bem como a geração do teste (_iflt_) citados acima.

[Aqui](https://github.com/alexgarzao/poj/pull/28/files) está o PR completo.



## Chamada de _procedures_

Até o momento tínhamos que implementar todo o código no bloco principal (_main_) do programa em Pascal. [Neste PR](https://github.com/alexgarzao/poj/pull/30/files) foi implementado o suporte à chamada de _procedures_. Reforçando que, em Pascal, uma _procedure_ é o equivalente a uma _function_ que não retorna um resultado.

[Neste commit](https://github.com/alexgarzao/poj/pull/30/commits/58082456f186692389695ee7e52aef4e2d041ecb) foi implementado um programa em Java para entender como a JVM lida com a chamada de procedures (funções sem retorno). A partir do programa Java abaixo:

```
public class ProcedureCall {
   public static void main(String[] args) {
      System.out.println("Hello from main!");
      myMethod();
   }

   static void myMethod() {
      System.out.println("Hello from myMethod!");
   }
}
```

Quando desassemblamos o _class_ obtemos o seguinte _assembly_:

```
public class ProcedureCall {

    ;; public static void main(String[] args)
    public static main([java/lang/String)V {

        ;; System.out.println("Hello from main!");
        getstatic java/lang/System.out java/io/PrintStream
        ldc "Hello from main!"
        invokevirtual java/io/PrintStream.println(java/lang/String)V

        ;; myMethod();
        invokestatic ProcedureCall.myMethod()V

        return
    }

    ;; static void myMethod()
    static myMethod()V {

        ;; System.out.println("Hello from myMethod!");
        getstatic java/lang/System.out java/io/PrintStream
        ldc "Hello from myMethod!"
        invokevirtual java/io/PrintStream.println(java/lang/String)V

        return
    }
}
```

Com este exemplo foi possível identificar que para invocar uma _procedure_ a JVM utiliza a instrução "invokestatic ProcedureCall.myMethod()V" onde:
- invokestatic é a instrução que recebe como argumento a assinatura completa do método a ser chamado;
- ProcedureCall é o nome da classe;
- myMethod()V é assinatura completa do método com seus parâmetros (neste exemplo nenhum) e o tipo de retorno (neste exemplo V - _void_ - que indica nenhum).

Dito isso, a partir do programa Pascal abaixo:

```
program procedure_call_wo_params;

procedure myprocedure;
begin
    write('Hello from myprocedure!');
end;

begin
    write('Hello from main!');
    myprocedure();
end.
```

O POJ foi ajustado para gerar o seguinte JASM:

```
// Code generated by POJ 0.1
public class procedure_call_wo_params {

    ;; procedure myprocedure;
    static myprocedure()V {

        ;; write('Hello from myprocedure!');
        getstatic java/lang/System.out java/io/PrintStream
        ldc "Hello from myprocedure!"
        invokevirtual java/io/PrintStream.print(java/lang/String)V

        return
    }

    ;; bloco principal (main)
    public static main([java/lang/String)V {

        ;; write('Hello from main!');
        getstatic java/lang/System.out java/io/PrintStream
        ldc "Hello from main!"
        invokevirtual java/io/PrintStream.print(java/lang/String)V

        ;; myprocedure();
        invokestatic procedure_call_wo_params.myprocedure()V 

        return
    }
}
```

Este [commit](https://github.com/alexgarzao/poj/pull/30/commits/09ecc90a3a56f40a3c4e7283fae87bca7efe7dbe) implementa o suporte ao tipo "_procedure_" na tabela de símbolos.

Este [commit](https://github.com/alexgarzao/poj/pull/30/commits/708232d2a4561d1c50f941cb247bde39e10d4892) implementa o suporte a geração do _assembly_ correto. Para tal, o POJ precisa lidar com contextos (_procedure_ sendo interpretada) para saber quando está interpretando o código de um procedimento ou do bloco principal.



## Passagem de argumentos para o procedimento

Até então tínhamos a chamada de procedimentos funcional, mas sem argumentos. Neste [commit](https://github.com/alexgarzao/poj/pull/30/commits/b1e00fc706a196c0531720b28e710d3a6f3f8d53) foi implementado um programa em Java para identificar como a JVM lida com a passagem de argumentos. No exemplo é possível ver que, assim como com outros _opcodes_, no início de sua execução o procedimento retira seus argumentos da pilha. Com isso basta empilhar os argumentos antes de invocar o procedimento.

Dito isso, a partir do programa Pascal abaixo:

```
program procedure_call_add_numbers;

procedure add(value1, value2: integer);
begin
    write(value1 + value2);
end;

begin
    add(4, 6);
end.
```

O POJ gera o seguinte JASM:

```
// Code generated by POJ 0.1
public class procedure_call_add_numbers {

    ;; procedure add(value1, value2: integer);
    static add(I, I)V {

        ;; write(value1 + value2);
        getstatic java/lang/System.out java/io/PrintStream
        iload 0 ;; carrega o parâmetro 0 (value1)
        iload 1 ;; carrega o parâmetro 1 (value2)
        iadd 
        invokevirtual java/io/PrintStream.print(I)V

        return
    }

    ;; Bloco principal (main)
    public static main([java/lang/String)V {
        ;; add(4, 6);
        sipush 4
        sipush 6
        invokestatic procedure_call_add_numbers.add(I, I)V 

        return
    }
}
```

Para o correto suporte à chamada com argumentos foi necessário [acrescentar na tabela de símbolos](https://github.com/alexgarzao/poj/pull/30/commits/8b4ba9f4e69d08560486b96c7f3d902d7b607b39) os tipos dos argumentos dos procedimentos. Por sua vez, para a correta invocação dos procedimentos, o [_parser_ teve que validar bem como gerar o _assembly_](https://github.com/alexgarzao/poj/pull/30/commits/a89bedf78dabfa7fbd1de513e35cad932dcfcf65) corretamente conforme a assinatura do procedimento.

[Aqui](https://github.com/alexgarzao/poj/pull/30/files) está o PR completo.



## Próximos passos

Na próxima publicação vamos falar sobre funções, entrada de dados e, se possível, concluir um 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).