/** * Copyright (c) 2000, Fidel Viegas (viegasfh@hotmail.com). * All rights reserved. * * Please read the LICENSE file for license information. * * This is the class that generates the jasmin syntax. Jasming is an assembler * that generates Java bytecode from Java assembly. For further information * visit http://www.cat.nyu.edu/meyer/jasmin */ package org.sablecc.pascal.code; import org.sablecc.pascal.node.*; import org.sablecc.pascal.analysis.*; import java.util.*; import java.io.*; public class CodeGenerator extends DepthFirstAdapter { // this is where the identifiers are stored with their original // image. For example if we declare myVar, this will be the // image generated by the code generator. But this doesn't mean that // we cannot call the variable as for example myvar. This is correct, but // when the class genarates the code, it generates the original image. // For instance, if we use the above variable in the statement: // myvar := 23;, this doesn't affect the code generation because we have // stored the image of myvar as myVar. And when we retrieve it we use the // key MYVAR, in uppercase just as we used in the semantic analyser. private Hashtable table = new Hashtable(); // this variable stores the name of the pascal source file private String source; // this variable stores the name of the class file to be generated // which is equal to the filename without the .pas extension private String class_name; // this is the field that generates the jasmin source file // it stores all the generated code in a buffer and then // writes all to a file whose name is the pascal filename // with .j extension instead of .pas PrintWriter out; // default constructor that takes the name of the source file // as argument, assigns the name of the source file to source, // and creates a new instance of class PrintWriter, which writes // to the original pascal source file with the .j extension. public CodeGenerator(String source) { this.source = source; // name of the source file to be processed // class name of the file to be generated class_name = source.substring(0, source.length() - 4); // create an instance of the PrintWriter class whose destination // filename is .j try{ out = new PrintWriter( new BufferedWriter( new FileWriter(class_name + ".j"))); } catch(IOException exc) { System.out.println(exc); } } // this method is used to generate the jasmin file heading, the // code for the default constructor and the heading for the main method. // it generates the class name, the super class the source from which // it was compiled. // Although we don't provide a default class file // when we compile a simple Java program, the compiler generates the // default constructor for us. The JVM expects a default constructor // for each class. public void inAProgram(AProgram node) { // write some comments at the top of the file out.println("; This file was generated by SmallPascal compiler."); out.println("; author: Fidel Viegas (viegasfh@hotmail.com)"); out.println(); // leave a space // generate the object file headings // this indicates the source from which the file was compiled out.println(".source " + source); out.println(".class public " + class_name); // class to be generated // all the generated classes are subclasses of class Object out.println(".super java/lang/Object"); // generate the code for the default constructor out.println(".method public ()V"); out.println(" aload_0"); out.println(" invokevirtual java/lang/Object/()V"); out.println(" return"); out.println(".end method"); out.println(); // leave a space } // now the methods that generate the code // this method translates a Pascal variable declaration // into a jasmin field declaration. All SmallPascal variables // are of type integer public void outASingleIdentifierList(ASingleIdentifierList node) { // this is the key of the variable in uppercase for // latter reference in a expression or statement String key = node.getIdentifier().getText().toUpperCase(); // this is the original name of variable String var_name = node.getIdentifier().getText(); // this is how the variable is represented in a jasmin program // when referenced in an expression or statement String var_image = class_name + "/" + var_name; // translate to jasmin source code and initialise it to zero out.println(".field public static " + var_name + " I = 0"); // store the variable in the symbol table for later reference table.put(key, var_image); } // this method does the same as the outASingleIdentifierList public void outAMultipleIdentifierList(AMultipleIdentifierList node) { // this is the key of the variable in uppercase for // latter reference in a expression or statement String key = node.getIdentifier().getText().toUpperCase(); // this is the original name of variable String var_name = node.getIdentifier().getText(); // this is how the variable is represented in a jasmin program // when referenced in an expression or statement String var_image = class_name + "/" + var_name; // translate to jasmin source code and initialise it to zero out.println(".field public static " + var_name + " I = 0"); // store the variable in the symbol table for later reference table.put(key, var_image); } // this method generates the heading for the main method public void inABody(ABody node) { // leave a space after the variables declaration out.println(); // generate the heading for the main method out.println(".method public static main([Ljava/lang/String;)V"); // we also set the size of the stack to 5 // assuming that we will never have more than // 5 values on the stack // this would be ok to pass the JVM requirements out.println(" .limit stack 5"); // we don't declare local variable, so we set the // limit of the local variables to 1, which corresponds // to the args[] passed to the main method out.println(" .limit locals 1"); out.println(); // leave some space } // this method generates the the return statement for the // main method and the directive for the end of the method public void outABody(ABody node) { // each JVM method must terminate with a return // instruction. In this case just return, which // is used for void methods out.println(" return"); // this is the end of the method out.println(".end method"); // this is the last method to be traversed, so we write // to the file here out.flush(); // dump all the code to the file } // this method generates the code for the assingment statement // Because the Java Virtual Machine is stack based, we pop the // resulting value last. So, we are going to implement the outXX // method of the assignment statement public void outAAssignmentStatement(AAssignmentStatement node) { // we need the key to get the image of the variable String key = node.getIdentifier().getText().toUpperCase(); // get the image of the variable String var_image = (String) table.get(key); // in JVM assembly we use putstatic to store the value // on top of the stack in a field // the I at the end indicates that the field is of type // integer out.println(" putstatic " + var_image + " I"); } // in order to implement the writeln statement, we need // to push the object java.lang.System.out onto the stack // so we need to implement the inXX method for the // writeln statement public void inAWritelnStatement(AWritelnStatement node) { out.println(" getstatic java/lang/System/out Ljava/io/PrintStream;"); } // after we have pushed all the values to be displayed on the // screen we invoke the println method that will display the // values. This is done in method outXX of writeln statement public void outAWritelnStatement(AWritelnStatement node) { // in JVM assembly we must use the invokevirtual instruction // to call a method of a static object // in this case we call the method void println(int value); out.println(" invokevirtual java/io/PrintStream/println(I)V"); } // now its time to generate the instruction for the arithmetic operations // this method generates the code for the addition operation public void outAPlusExpression(APlusExpression node) { //this instruction is used to add two integers out.println(" iadd"); } // this method generates the instruction for the subtraction operation public void outAMinusExpression(AMinusExpression node) { // this instruction is used to subtract two integers out.println(" isub"); } // this method generates the instruction for the multiplication operation public void outAMultTerm(AMultTerm node) { // this instruction is used to multiply two integers out.println(" imul"); } // this method generates the instruction for the // integer division, that is for the div operator public void outADivTerm(ADivTerm node) { // generate the instruction for integer division out.println(" idiv"); } // we now implement the method that push the integer numbers // and the variables onto the stack // this method calls the push method, which generates an instruction // to push either a byte, a short or an integer according to the value // being pushed. public void outANumberFactor(ANumberFactor node) { // value of the number String value = node.getNumber().getText(); push(value); // call push method to generate the appropriate // instruction } // this method is used to generate the instruction // that retrieves the value of any variable on the // right hand side of an assignent statement or // in a writeln statement public void outAIdentifierFactor(AIdentifierFactor node) { // get the key whose image is to be retrieved String key = node.getIdentifier().getText().toUpperCase(); // store the image of the variable String var_image = (String) table.get(key); // generate the code out.println(" getstatic " + var_image + " I"); } // this is the method that generates the code // to push a number onto the stack private void push(String value) { try { // this may generate an exception if the number // is malformed int int_value = Integer.parseInt(value); switch(int_value) { case 0: case 1: case 2: case 3: case 4: case 5: // generate iconst_X if X is between 0 and 5 out.println(" iconst_" + value); break; default: // this generates the instruction to push a byte if ((int_value >= -128) && (int_value <= 127)) { out.println(" bipush " + value); } // this generates the instruction to push a short integer else if ((int_value >= -32768) && (int_value <= 32767)) { out.println(" sipush " + value); } // this generates the code to push an integer else { out.println(" ldc " + value); } break; } } catch(Exception exc) { System.out.println(exc); } } }