Geração de Código para Smallpascala 1. Já estudamos como SableCC faz uso do “design pattern visitor” para construir compiladores modulares. Vimos também os principais componentes da máquina virtual Java e a sintaxe da linguagem Jasmin, um assembler para um subconjunto do máquina virtual Java. Vamos agora estudar como código Jasmin pode ser gerado a partir de uma linguagem de expressões simples chamada Smallpascal. public class CodeGenerator extends DepthFirstAdapter private Hashtable table = new Hashtable(); private String source; private String class name; PrintWriter out; 2. O gerador de código estende DepthFirstAdapter implementando a interpretação que gerará código Jasmin a partir de Smallpascal. a Baseado na implementaç˜ ao de Samllpascal, por Fidel Viegas. brainycreatures.co.uk/download/smallpascal.asp) 1 (http://www. 3. A variável table armazenará os identificadores declarados no programa sendo compilado. Programas em Smallpascal são “case insensitive”, ou seja, não é feita distinção entre letras maiúsculas e minúsculas. 4. A variável source armazena o nome do arquivo original e class name armazena o nome do arquivo original sem a extensão pas. Esta string define o nome da classe Java gerada. Finalmente out guarda o “stream” com o texto Jasmin gerado pelo compilador. O nome do arquivo gerado é o valor de class name concatenado a .j. public CodeGenerator(String source) ... 5. O construtor “default” recebe uma string contendo o nome do arquivo Smallpascal sendo compilado. A partir desta string os campos (atributos) source, class name e out são instanciados apropriadamente. 2 public void inAProgram(AProgram node) out.println(".source " + source); out.println(".class public " + class_name); out.println(".super java/lang/Object"); out.println(".method public <init>()V"); out.println(" aload_0"); out.println(" invokenonvirtual java/lang/Object/<init>()V"); out.println(" return"); out.println(".end method"); 6. A máquina virtual Java espera que toda classe tenha pelo menos um construtor. Todo programa Smallpascal é mapeado para uma classe na JVM. Desta maneira é necessário gerar o construtor default para a classe gerada a partir do programa Smallpascal sendo compilado. O construtor default chama o construtor de java.lang.Object. Isso é feito na ação associada ao header do programa junto com as diretivas .source, .class e .super de Jasmin. 3 public void outASingleIdentifierList (ASingleIdentifierList node) String key = node.getIdentifier().getText().toUpperCase(); String var_name = node.getIdentifier().getText(); String var_image = class_name + "/"+ var_name; out.println(".field public static "+ var_name + " I = 0"); table.put(key, var_image); 7. Declarações de variáveis são mapeadas para declarações de campos (atributos) em Jasmin. Todas as variáveis em Samllpascal são int. Lembre-se que variáveis em Jasmin são representadas pelo nome da classe mais o nome da variável. Guardamos então este nome completo da variável na tabela de sı́mbolos para que este nome completo possa ser descoberto quando a variável for utilizada numa expressão. 4 public void inABody(ABody node) out.println(); out.println( ".method public static main([Ljava/lang/String;)V"); out.println(" .limit stack 5"); out.println(" .limit locals 1"); 8. Um programa em Smallpascal tem a seguinte organização: program = program_heading declarations body dot ; O corpo (“body”) de um programa em Smallpascal é então mapeado na função main da classe sendo gerada para o programa. Como em Smallpascal não existem variáveis locais, a pilha é limitada a valores, o que é suficiente para cumprir os requisitos da JVM. Da mesma maneira o número de variáveis locais é limitado a correspondendo aos parâmetros args[] passados ao método main. 5 public void outABody(ABody node) out.println(" return"); out.println(".end method"); out.flush(); 9. Na ação out da produção Body deve ser gerado código para que a função main retone, dado que todos os métodos, que são procedimentos, na máquina virtual devem terminar com “return”. A diretiva adequada deve também ser gerada e finalmente o código do programa é escrito no disco. 6 public void outAAssignmentStatement (AAssignmentStatement node) String key = node.getIdentifier().getText().toUpperCase(); String var_image = (String) table.get(key); out.println(" putstatic " + var_image + " I"); 10. Este é o código gerado para uma atribuição em Smallpascal. Primeiro o nome completo do identificador é obtido da tabela de sı́mbolos. Assume-se que o resultado da avaliação da expressão sendo atribuı́da a variável esteja no topo da pilha de operandos. A instrução putstatic copia o valor do topo da pilha para a variável que é do tipo int, como todas as variáveis em Smallpascal. 7 public void inAWritelnStatement(AWritelnStatement node) out.println( "getstatic java/lang/System/out Ljava/io/PrintStream;"); public void outAWritelnStatement(AWritelnStatement node) out.println( "invokevirtual java/io/PrintStream/println(I)V"); 11. O comando writeln de Smallpascal é implementado através das ações in e out da produção AWritelnStatement. Na ação in o objeto java.lang.System.out é empilhado e na ação out o método println da classe PrintStream é invocado. 8 public void outAPlusExpression(APlusExpression node) out.println(" iadd"); public void outAMinusExpression(AMinusExpression node) out.println(" isub"); // ... public void outANumberFactor(ANumberFactor node) String value = node.getNumber().getText(); push(value); 12. As operações aritméticas são traduzidas simplesmente para suas contrapartes na JVM. O processo de empilhamento dos operandos é feito pelo método push, da classe CodeGenerator, que deve primeiro verificar qual inteiro está sendo empilhado de forma a utilizar a instrução adequada. O método push, que pode sinalizar uma exceção se o inteiro for mal-formado, é invocado quando um fator está sendo processado. Isso porque neste exemplo não está sendo utilizada a árvore de sintaxe abstrata. 9 private void push(String value) // ... // ... switch(int_value) case 5: // gera iconst_X se X estiver entre 0 e 5 out.println(" iconst_" + value); break; default: // instrução para empilhar um byte if ((int_value >= -128) && (int_value <= 127)) out.println(" bipush " + value); // instrução para empilhar short integer else if ((int_value >= -32768) && (int_value <= 32767)) out.println(" sipush " + value); // instrução para empilhar integer else out.println(" ldc " + value); break; // ... 10 public void outAIdentifierFactor(AIdentifierFactor node) String key = node.getIdentifier().getText(). toUpperCase(); String var_image = (String) table.get(key); out.println(" getstatic " + var_image + " I"); 13. Uma instrução getstatic deve ser utilizada para se recuperar o valor de uma variável no lado direito de uma expressão. 11