Captura de erros, operadores relacionais para string e procedures

alexgarzao

Alex Sandro Garzão

Posted on May 26, 2024

Captura de erros, operadores relacionais para string e procedures

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 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 o código foi alterado para que a análise léxica, sintática e semântica retornem os erros encontrados;
  • Neste commit 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 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 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 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 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");
   }
}
Enter fullscreen mode Exit fullscreen mode

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

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

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

Este commit implementa a chamada ao método String.compareTo bem como a geração do teste (iflt) citados acima.

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

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

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

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

Este commit implementa o suporte ao tipo "procedure" na tabela de símbolos.

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

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

Para o correto suporte à chamada com argumentos foi necessário acrescentar na tabela de símbolos 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 corretamente conforme a assinatura do procedimento.

Aqui 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.

💖 💪 🙅 🚩
alexgarzao
Alex Sandro Garzão

Posted on May 26, 2024

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

Sign up to receive the latest update from our blog.

Related