com.khubla.jvmbasic.jvmbasicc.JVMBasicCompiler.java Source code

Java tutorial

Introduction

Here is the source code for com.khubla.jvmbasic.jvmbasicc.JVMBasicCompiler.java

Source

package com.khubla.jvmbasic.jvmbasicc;

/*
 * jvmBasic Copyright 2012, khubla.com
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.khubla.jvmbasic.jvmbasicc.antlr.jvmBasicLexer;
import com.khubla.jvmbasic.jvmbasicc.antlr.jvmBasicParser;
import com.khubla.jvmbasic.jvmbasicc.antlr.jvmBasicParser.ProgContext;
import com.khubla.jvmbasic.jvmbasicc.compiler.GenerationContext;
import com.khubla.jvmbasic.jvmbasicc.compiler.LocalVariableDeclaration;
import com.khubla.jvmbasic.jvmbasicc.compiler.RTLHelper;
import com.khubla.jvmbasic.jvmbasicc.compiler.TreePrinter;
import com.khubla.jvmbasic.jvmbasicc.compiler.analysis.StaticAnalysis;
import com.khubla.jvmbasic.jvmbasicc.function.Function;
import com.khubla.jvmbasic.jvmbasicc.function.impl.rule.progRule;
import com.khubla.jvmbasic.jvmbasicc.util.FilenameUtil;

/**
 * @author tom
 */
public class JVMBasicCompiler {
    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(JVMBasicCompiler.class);

    /**
     * parse an input file
     */
    public static ProgContext parse(InputStream inputStream) throws Exception {
        try {
            if (null != inputStream) {
                final jvmBasicLexer jvmBasicLexer = new jvmBasicLexer(new ANTLRInputStream(inputStream));
                final CommonTokenStream tokens = new CommonTokenStream(jvmBasicLexer);
                final jvmBasicParser jvmBasicParser = new jvmBasicParser(tokens);
                jvmBasicParser.setBuildParseTree(true);
                return jvmBasicParser.prog();
            } else {
                throw new IllegalArgumentException();
            }
        } catch (final Exception e) {
            throw new Exception("Exception reading and parsing file", e);
        }
    }

    /**
     * write the supplied bytecode given the class name and an option output directory
     */
    public static void writeClassFile(byte[] byteCode, String classname, String outputDirectory) throws Exception {
        try {
            logger.info("Writing class '" + classname + "' to file '" + classname + ".class'");
            if (null != byteCode) {
                FileOutputStream fos = null;
                if (null != outputDirectory) {
                    new File(outputDirectory).mkdirs();
                    fos = new FileOutputStream(outputDirectory + "/" + classname + ".class");
                } else {
                    fos = new FileOutputStream(classname + ".class");
                }
                fos.write(byteCode);
                fos.flush();
                fos.close();
            }
        } catch (final Exception e) {
            throw new Exception("Exception in writeClassFile", e);
        }
    }

    /**
     * compile. This method generates the class definition
     */
    public byte[] compile(InputStream inputStream, String classname, boolean verboseOutput) throws Exception {
        try {
            /*
             * a message
             */
            logger.info("Parsing input for classname: '" + classname + "'");
            /*
             * get tree
             */
            final ProgContext progContext = parse(inputStream);
            /*
             * print tree
             */
            final TreePrinter treePrinter = new TreePrinter();
            treePrinter.printTree(progContext);
            /*
             * a message
             */
            logger.info("Generating Bytecode for class '" + classname + "'");
            /*
             * class
             */
            final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
            classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, classname, null, "java/lang/Object", null);
            /*
             * the execution context
             */
            FieldVisitor fieldVistor = classWriter.visitField(Opcodes.ACC_PUBLIC, RTLHelper.EXECUTIONCONTEXT_NAME,
                    RTLHelper.JASIC_RUNTIME_EXECUTIONCONTEXT_TYPE, null, null);
            fieldVistor.visitEnd();
            /*
             * input stream
             */
            fieldVistor = classWriter.visitField(Opcodes.ACC_PUBLIC, "inputStream", "Ljava/io/InputStream;", null,
                    null);
            fieldVistor.visitEnd();
            /*
             * output stream
             */
            fieldVistor = classWriter.visitField(Opcodes.ACC_PUBLIC, "outputStream", "Ljava/io/PrintStream;", null,
                    null);
            fieldVistor.visitEnd();
            /*
             * init method
             */
            generateInit(classname, classWriter);
            /*
             * main()
             */
            generateMain(classname, classWriter);
            /*
             * program
             */
            generateProgram(classname, classWriter, progContext);
            /*
             * generate the class
             */
            classWriter.visitEnd();
            return classWriter.toByteArray();
        } catch (final Exception e) {
            throw new Exception("Exception in compile", e);
        }
    }

    /**
     * compile. This method generates the class definition
     */
    public byte[] compile(String filename, boolean verboseOutput) throws Exception {
        final InputStream inputStream = new FileInputStream(filename);
        final String className = FilenameUtil.classNameFromFileName(filename);
        if (verboseOutput) {
            logger.info("Compiling '" + filename + "' to class: '" + className + "'");
        }
        return this.compile(inputStream, className, verboseOutput);
    }

    /**
     * compile a BAS file to a class file and return the classname
     */
    public String compileToClassfile(String filename, String outputDir, boolean verboseOutput) throws Exception {
        try {
            final FileInputStream fis = new FileInputStream(filename);
            final String className = FilenameUtil.classNameFromFileName(filename);
            final byte[] classbytes = compile(fis, className, verboseOutput);
            writeClassFile(classbytes, className, outputDir);
            return className;
        } catch (final Exception e) {
            throw new Exception("Exception in compileToClassfile", e);
        }
    }

    /**
     * generate init.
     * <p>
     * This inits the class members and assigns class members, such as the ExecutionContext.
     * </p>
     * <p>
     * <code>
     * public ExecutionContext executionContext = new ExecutionContext();
     * </code>
     * </p>
     */
    protected void generateInit(String className, ClassWriter classWriter) throws Exception {
        try {
            final MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null,
                    null);
            methodVisitor.visitCode();
            /*
             * call init()
             */
            final Label l0 = new Label();
            methodVisitor.visitLabel(l0);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
            /*
             * create a new execution context and assign it
             */
            final Label l1 = new Label();
            methodVisitor.visitLabel(l1);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitTypeInsn(Opcodes.NEW, RTLHelper.JASIC_RUNTIME_EXECUTIONCONTEXT);
            methodVisitor.visitInsn(Opcodes.DUP);
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, RTLHelper.JASIC_RUNTIME_EXECUTIONCONTEXT, "<init>",
                    "()V");
            methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, className, RTLHelper.EXECUTIONCONTEXT_NAME,
                    RTLHelper.JASIC_RUNTIME_EXECUTIONCONTEXT_TYPE);
            /*
             * return
             */
            final Label l2 = new Label();
            methodVisitor.visitLabel(l2);
            methodVisitor.visitLineNumber(8, l2);
            methodVisitor.visitInsn(Opcodes.RETURN);
            /*
             * declare variables
             */
            final Label l3 = new Label();
            methodVisitor.visitLabel(l3);
            methodVisitor.visitLocalVariable("this", "L" + className + ";", null, l0, l3, 0);
            methodVisitor.visitMaxs(3, 1);
            methodVisitor.visitEnd();
        } catch (final Exception e) {
            throw new Exception("Exception in generateInit", e);
        }
    }

    /**
     * generate void main(String[])
     * <p>
     * <code>
     * public static void main(String[] args) {
     *   ExampleProgram exampleProgram = new ExampleProgram();
     *  try {
     *       exampleProgram.inputStream = System.in;
     *       exampleProgram.outputStream = System.out;
     *       exampleProgram.program();
     *   } catch (Exception e) {
     *       e.printStackTrace();
     *    }
     *  }
     * </code>
     * </p>
     */
    protected void generateMain(String classname, ClassWriter classWriter) throws Exception {
        try {
            /*
             * make method
             */
            final MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
                    "main", "([Ljava/lang/String;)V", null, null);
            methodVisitor.visitCode();
            final Label l0 = new Label();
            final Label l1 = new Label();
            final Label l2 = new Label();
            methodVisitor.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception");
            final Label l3 = new Label();
            methodVisitor.visitLabel(l3);
            /*
             * declare a local instance of the class in the void() main, store as variable 1.
             */
            methodVisitor.visitTypeInsn(Opcodes.NEW, classname);
            methodVisitor.visitInsn(Opcodes.DUP);
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, classname, "<init>", "()V");
            methodVisitor.visitVarInsn(Opcodes.ASTORE, 1);
            /*
             * assign the input stream
             */
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "in", "Ljava/io/InputStream;");
            methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classname, "inputStream", "Ljava/io/InputStream;");
            /*
             * assign the output stream
             */
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classname, "outputStream", "Ljava/io/PrintStream;");
            /*
             * load the class instance from variable 1 and call "program"
             */
            methodVisitor.visitLabel(l0);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, classname, "program", "()V");
            methodVisitor.visitLabel(l1);
            final Label l4 = new Label();
            methodVisitor.visitJumpInsn(Opcodes.GOTO, l4);
            methodVisitor.visitLabel(l2);
            methodVisitor.visitFrame(Opcodes.F_FULL, 2, new Object[] { "[Ljava/lang/String;", classname }, 1,
                    new Object[] { "java/lang/Exception" });
            methodVisitor.visitVarInsn(Opcodes.ASTORE, 2);
            final Label l5 = new Label();
            methodVisitor.visitLabel(l5);
            methodVisitor.visitLineNumber(21, l5);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V");
            /*
             * return
             */
            methodVisitor.visitLabel(l4);
            methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            methodVisitor.visitInsn(Opcodes.RETURN);
            /*
             * declare the parameters
             */
            final Label l6 = new Label();
            methodVisitor.visitLabel(l6);
            methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, l3, l6, 0);
            methodVisitor.visitLocalVariable("exampleProgram", "L" + classname + ";", null, l0, l6, 1);
            methodVisitor.visitLocalVariable("e", "Ljava/lang/Exception;", null, l5, l4, 2);
            /*
             * done
             */
            methodVisitor.visitMaxs(2, 3);
            methodVisitor.visitEnd();
        } catch (final Exception e) {
            throw new Exception("Exception in generateMain", e);
        }
    }

    /**
     * Generate program
     * <p>
     * Java prototype is "public program()"
     * </p>
     */
    protected void generateProgram(String classname, ClassWriter classWriter, ProgContext progContext)
            throws Exception {
        try {
            final MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "program", "()V", null,
                    new String[] { "java/lang/Exception" });
            methodVisitor.visitCode();
            /*
             * label for the start of the method
             */
            final Label l0 = new Label();
            methodVisitor.visitLabel(l0);
            /*
             * do the static analysis
             */
            final StaticAnalysis programStaticAnalysis = new StaticAnalysis();
            programStaticAnalysis.performStaticAnalysis(progContext);
            /*
             * show the static analysis
             */
            logger.info("Static analyis results for '" + classname + "'");
            programStaticAnalysis.showAnalysisResults();
            /*
             * recurse into the parse tree
             */
            final Function function = new progRule();
            final GenerationContext generationContext = new GenerationContext(classname, methodVisitor, classWriter,
                    progContext, programStaticAnalysis);
            function.execute(generationContext);
            /*
             * return
             */
            final Label l1 = new Label();
            methodVisitor.visitLabel(l1);
            methodVisitor.visitInsn(Opcodes.RETURN);
            /*
             * declare the *this* local variable
             */
            final Label l2 = new Label();
            methodVisitor.visitLabel(l2);
            methodVisitor.visitLocalVariable("this", "L" + classname + ";", null, l0, l2, 0);
            /*
             * show all the other local variables
             */
            logger.info("JVM local variables");
            for (int i = 1; i < (GenerationContext.getLocalvariables().size() + 1); i++) {
                final LocalVariableDeclaration lvd = GenerationContext.getLocalvariables().get(i);
                logger.info("variable: " + lvd.getName() + " declared on line: " + lvd.getBasicLine()
                        + " frame index: " + lvd.getIndex());
            }
            /*
             * we are done
             */
            methodVisitor.visitMaxs(0, 1);
            methodVisitor.visitEnd();
        } catch (final Exception e) {
            throw new Exception("Exception in generateProgram", e);
        }
    }
}