DEV Community

Alex Sandro Garzão
Alex Sandro Garzão

Posted on

1

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.

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)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

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

Okay