Java tutorial
/** * KuemmelGtr - A graphing calculator * Copyright (C) 2013 Jonas Kmmerlin <rgcjonas@gmail.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/>. */ package com.github.rgcjonas.kuemmelgtr.core; import java.lang.reflect.Method; import org.objectweb.asm.*; /** * The Compiler class will compile a stream of RPNTokens into a java method, and create a class. */ public class Compiler { public static byte[] compileFunc(TokenSource<RPNToken> source, String variable) throws ParsingError { // initialize class ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); writer.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, "com/github/rgcjonas/kuemmelgtr/jit/BogusName", null, CompiledClassBase.class.getName().replace('.', '/'), new String[] { Evaluator.Function.class.getName().replace('.', '/') }); // create constructor MethodVisitor construct = writer.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); construct.visitCode(); construct.visitVarInsn(Opcodes.ALOAD, 0); construct.visitMethodInsn(Opcodes.INVOKESPECIAL, CompiledClassBase.class.getName().replace('.', '/'), "<init>", "()V"); construct.visitInsn(Opcodes.RETURN); construct.visitMaxs(0, 0); construct.visitEnd(); MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, "evaluate", "(D)D", null, new String[] { "com/github/rgcjonas/kuemmelgtr/core/RuntimeError" }); compileCommon(source, method, variable); writer.visitEnd(); return writer.toByteArray(); } public static byte[] compileBasic(TokenSource<RPNToken> source) throws ParsingError { // initialize class ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); writer.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, "com/github/rgcjonas/kuemmelgtr/jit/BogusName", null, CompiledClassBase.class.getName().replace('.', '/'), new String[] { Evaluator.Basic.class.getName().replace('.', '/') }); // create constructor MethodVisitor construct = writer.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); construct.visitCode(); construct.visitVarInsn(Opcodes.ALOAD, 0); construct.visitMethodInsn(Opcodes.INVOKESPECIAL, CompiledClassBase.class.getName().replace('.', '/'), "<init>", "()V"); construct.visitInsn(Opcodes.RETURN); construct.visitMaxs(0, 0); construct.visitEnd(); MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, "evaluate", "()D", null, new String[] { "com/github/rgcjonas/kuemmelgtr/core/ParsingError", "com/github/rgcjonas/kuemmelgtr/core/RuntimeError" }); compileCommon(source, method, null); writer.visitEnd(); return writer.toByteArray(); } private static void compileCommon(TokenSource<RPNToken> source, MethodVisitor method, String variable) throws ParsingError { method.visitCode(); // we now can compile our token stream int currentStackSize = 0; // we keep track of the current stack size to throw errors if we encounter an invalid instruction RPNToken t; while ((t = source.nextToken()) != null) { if (t instanceof RPNToken.Operand) { // we push it onto the stack method.visitLdcInsn(((RPNToken.Operand) t).getValue()); currentStackSize++; } else if (t instanceof RPNToken.Operator) { RPNToken.Operator op = (RPNToken.Operator) t; if (currentStackSize < op.getNumOperands()) throw new ParsingError(t.getSrcLine(), t.getSrcColumn(), "Missing operand(s)"); switch (op.getName()) { case "_add": method.visitInsn(Opcodes.DADD); currentStackSize--; break; case "_mult": method.visitInsn(Opcodes.DMUL); currentStackSize--; break; case "_sub": method.visitInsn(Opcodes.DSUB); currentStackSize--; break; case "_div": method.visitInsn(Opcodes.DDIV); currentStackSize--; break; case "_pow": method.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Math", "pow", "(DD)D"); currentStackSize--; break; default: //HACK: support every function in java/lang/Math. Will be way more performant than any lookup-and-evaluate //TODO: implement more functions inline if (currentStackSize < 1) throw new ParsingError(t.getSrcLine(), t.getSrcColumn(), "Missing operand"); try { Method meth = Math.class.getDeclaredMethod(op.getName(), double.class); // just check if it's available and hope it returns a double if (meth.getReturnType() != double.class) throw new NoSuchMethodException(); // we don't want to blow up at runtime, do we? method.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Math", op.getName(), "(D)D"); } catch (NoSuchMethodException e) { // we do not give up. The method may be available at runtime. method.visitVarInsn(Opcodes.ALOAD, 0); method.visitInsn(Opcodes.DUP_X2); // swap the this pointer and double method.visitInsn(Opcodes.POP); method.visitLdcInsn(op.getName()); method.visitLdcInsn((int) op.getSrcLine()); method.visitLdcInsn((int) op.getSrcColumn()); method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CompiledClassBase.class.getName().replace('.', '/'), "resolveAndEvaluateFunction", "(DLjava/lang/String;II)D"); } } } else if (t instanceof RPNToken.VariableRecall) { // maybe, maybe the variable is an argument we can load if (variable != null && ((RPNToken.VariableRecall) t).getName().equalsIgnoreCase(variable)) { method.visitVarInsn(Opcodes.DLOAD, 1); } else { // we let our parent class do the hard work method.visitVarInsn(Opcodes.ALOAD, 0); method.visitLdcInsn(((RPNToken.VariableRecall) t).getName()); method.visitLdcInsn(t.getSrcLine()); method.visitLdcInsn(t.getSrcColumn()); method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CompiledClassBase.class.getName().replace('.', '/'), "getVariable", "(Ljava/lang/String;II)D"); } currentStackSize++; } else if (t instanceof RPNToken.VariableAssignment) { // also defer to our parent class // we do not give up. The method may be available at runtime. method.visitVarInsn(Opcodes.ALOAD, 0); method.visitInsn(Opcodes.DUP_X2); // swap the this pointer and double method.visitInsn(Opcodes.POP); method.visitLdcInsn(((RPNToken.VariableAssignment) t).getVariableName()); method.visitLdcInsn(t.getSrcLine()); method.visitLdcInsn(t.getSrcColumn()); method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CompiledClassBase.class.getName().replace('.', '/'), "setVariable", "(DLjava/lang/String;II)D"); } else { throw new ParsingError(t.getSrcLine(), t.getSrcColumn(), "Unknown instruction: " + t); } } if (currentStackSize != 1) throw new ParsingError(0, 0, "Expected stack to be one value, found " + currentStackSize); method.visitInsn(Opcodes.DRETURN); method.visitMaxs(0, 0); method.visitEnd(); } /** * Base class for compiled expressions */ public static abstract class CompiledClassBase { private FunctionResolver resolver = null; private VariableStore vars = null; public void initialize(FunctionResolver resolver, VariableStore vars) { this.resolver = resolver; this.vars = vars; } protected double resolveAndEvaluateFunction(double argument, String name, int srcLine, int srcCol) throws RuntimeError { Evaluator.Function func = resolver.resolveFunc(name); if (func != null) { return func.evaluate(argument); } else { throw new RuntimeError(srcLine, srcCol, "Unknown function: " + name); } } protected double getVariable(String name, int srcLine, int srcCol) throws RuntimeError { return vars.get(name, srcLine, srcCol); } protected double setVariable(double value, String name, int srcLine, int srcCol) throws RuntimeError { return vars.set(name, value, srcLine, srcCol); } } /** * instantiate a compiled and loaded class * @param vars * @throws ParsingError */ public static Object instantiate(Class<?> klass, FunctionResolver resolver, VariableStore vars) throws ParsingError { try { CompiledClassBase k = (CompiledClassBase) klass.newInstance(); k.initialize(resolver, vars); return k; } catch (Exception e) { e.printStackTrace(); throw new ParsingError(-1, -1, "Internal error: couldn't instantiate class"); } } }