Geração de Código para Smallpascal a 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ção de Samllpascal, por Fidel Viegas. brainycreatures.co.uk/download/smallpascal.asp) (http://www. 1
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