2010-10-25 12 views
29

Ich schreibe einen Interpreter in Java für eine domänenspezifische Sprache mit einigen Skriptfunktionen. Ich habe bereits einen Parser implementiert und muss nun ein Backend machen. Zu diesem Zweck erwäge ich, entweder meinen eigenen Interpreter zu schreiben (entweder mit abstrakten Syntaxbäumen zu arbeiten oder mit einigen benutzerdefinierten Bytecodes) oder eine Ziel-JVM (Java-Bytecode zur Laufzeit auszustrahlen und auszuführen).Wie kann ich Java Bytecode zur Laufzeit ausgeben und ausführen?

Könnte jemand mit mehr Erfahrung in diesem Bereich sagen, wie machbar der Ansatz ist, JVM zu targetieren und welche Bibliotheken würden Sie für die Ausgabe von Java-Bytecode empfehlen?

+0

Wurde die DSL bereits entworfen/spezifiziert? Oder entwickeln Sie das DSL neben dem Parser und Interpreter? – Stobor

+0

@Stobor: Die Sprache wurde angegeben und der Parser geschrieben. – vitaut

Antwort

27

Hier ist ein funktionierendes "Hallo Welt" gemacht mit ObjectWeb ASM (eine Bibliothek, die ich empfehlen):

package hello; 

import java.lang.reflect.Method; 

import org.objectweb.asm.ClassWriter; 
import org.objectweb.asm.Label; 
import org.objectweb.asm.MethodVisitor; 
import org.objectweb.asm.Opcodes; 

public class HelloWorldASM implements Opcodes { 
    public static byte[] compile(String name) { 
     ClassWriter cw = new ClassWriter(0); 
     MethodVisitor mv; 

     cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "hello/HelloWorld", null, 
       "java/lang/Object", null); 

     cw.visitSource("HelloWorld.java", null); 

     { 
      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 
      mv.visitCode(); 
      Label l0 = new Label(); 
      mv.visitLabel(l0); 
      mv.visitLineNumber(4, l0); 
      mv.visitVarInsn(ALOAD, 0); 
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", 
        "()V"); 
      mv.visitInsn(RETURN); 
      Label l1 = new Label(); 
      mv.visitLabel(l1); 
      mv.visitLocalVariable("this", "Lhello/HelloWorld;", null, l0, l1, 
        0); 
      mv.visitMaxs(1, 1); 
      mv.visitEnd(); 
     } 
     { 
      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", 
        "([Ljava/lang/String;)V", null, null); 
      mv.visitCode(); 
      Label l0 = new Label(); 
      mv.visitLabel(l0); 
      mv.visitLineNumber(7, l0); 
      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", 
        "Ljava/io/PrintStream;"); 
      mv.visitLdcInsn(String.format("Hello, %s!", name)); 
      mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", 
        "(Ljava/lang/String;)V"); 
      Label l1 = new Label(); 
      mv.visitLabel(l1); 
      mv.visitLineNumber(8, l1); 
      mv.visitInsn(RETURN); 
      Label l2 = new Label(); 
      mv.visitLabel(l2); 
      mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l2, 
        0); 
      mv.visitMaxs(2, 1); 
      mv.visitEnd(); 
     } 
     cw.visitEnd(); 

     return cw.toByteArray(); 
    } 

    public static class DynamicClassLoader extends ClassLoader { 
     public Class<?> define(String className, byte[] bytecode) { 
      return super.defineClass(className, bytecode, 0, bytecode.length); 
     } 
    }; 

    public static void main(String[] args) throws Exception { 
     DynamicClassLoader loader = new DynamicClassLoader(); 
     Class<?> helloWorldClass = loader.define("hello.HelloWorld", 
       compile("Test")); 
     Method method = helloWorldClass.getMethod("main", String[].class); 
     method.invoke(null, (Object) new String[] {}); 
    } 
} 

den Code zu generieren, fand ich sehr nützlich Bytecode Outline for Eclipse Plug-in. Obwohl Sie die ASMifier wie diese verwenden könnten (mit ASM enthalten):

ClassReader cr = new ClassReader(new FileInputStream("HelloWorld.class")); 
cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0); 

Zur Laufzeit, wenn Sie das Class Objekt für die erstellte Klasse zu erhalten, dann können Sie Ihre Klasse laden, indem Sie einen Klassenlader und Publishing erstreckt (durch eine andere Methode, zum Beispiel) die defineClass Methode und die Klasse als ein Byte-Array, wie in dem Beispiel aufgeführt.

Sie können auch die erstellte Klasse mit einer Schnittstelle, wie in diesem Beispiel handhaben:

package hello; 

import org.objectweb.asm.ClassWriter; 
import org.objectweb.asm.Label; 
import org.objectweb.asm.MethodVisitor; 
import org.objectweb.asm.Opcodes; 

public class HelloWorldPlugin implements Opcodes { 
    public static interface Plugin { 
     void sayHello(String name); 
    } 

    public static byte[] compile() { 

     ClassWriter cw = new ClassWriter(0); 
     MethodVisitor mv; 

     cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "hello/MyClass", null, 
       "java/lang/Object", 
       new String[] { "hello/HelloWorldPlugin$Plugin" }); 

     cw.visitInnerClass("hello/HelloWorldPlugin$Plugin", 
       "hello/HelloWorldPlugin", "Plugin", ACC_PUBLIC + ACC_STATIC 
         + ACC_ABSTRACT + ACC_INTERFACE); 

     { 
      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 
      mv.visitCode(); 
      Label l0 = new Label(); 
      mv.visitLabel(l0); 
      mv.visitLineNumber(5, l0); 
      mv.visitVarInsn(ALOAD, 0); 
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", 
        "()V"); 
      mv.visitInsn(RETURN); 
      Label l1 = new Label(); 
      mv.visitLabel(l1); 
      mv.visitLocalVariable("this", "Lhello/MyClass;", null, l0, l1, 0); 
      mv.visitMaxs(1, 1); 
      mv.visitEnd(); 
     } 
     { 
      mv = cw.visitMethod(ACC_PUBLIC, "sayHello", 
        "(Ljava/lang/String;)V", null, null); 
      mv.visitCode(); 
      Label l0 = new Label(); 
      mv.visitLabel(l0); 
      mv.visitLineNumber(9, l0); 
      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", 
        "Ljava/io/PrintStream;"); 
      mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); 
      mv.visitInsn(DUP); 
      mv.visitLdcInsn("Hello, "); 
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", 
        "<init>", "(Ljava/lang/String;)V"); 
      mv.visitVarInsn(ALOAD, 1); 
      mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", 
        "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); 
      mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", 
        "toString", "()Ljava/lang/String;"); 
      mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", 
        "(Ljava/lang/String;)V"); 
      Label l1 = new Label(); 
      mv.visitLabel(l1); 
      mv.visitLineNumber(10, l1); 
      mv.visitInsn(RETURN); 
      Label l2 = new Label(); 
      mv.visitLabel(l2); 
      mv.visitLocalVariable("this", "Lhello/MyClass;", null, l0, l2, 0); 
      mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 
        1); 
      mv.visitMaxs(4, 2); 
      mv.visitEnd(); 
     } 
     cw.visitEnd(); 

     return cw.toByteArray(); 
    } 

    public static class DynamicClassLoader extends ClassLoader { 
     public DynamicClassLoader(ClassLoader parent) { 
      super(parent); 
     } 

     public Class<?> define(String className, byte[] bytecode) { 
      return super.defineClass(className, bytecode, 0, bytecode.length); 
     } 
    }; 

    public static void main(String[] args) throws Exception { 
     DynamicClassLoader loader = new DynamicClassLoader(Thread 
       .currentThread().getContextClassLoader()); 
     Class<?> helloWorldClass = loader.define("hello.MyClass", compile()); 
     Plugin plugin = (Plugin) helloWorldClass.newInstance(); 
     plugin.sayHello("Test"); 
    } 
} 

Haben Sie Spaß.

PS: Ich kann Kommentare hinzufügen, wenn nicht klar genug. Ich habe nicht, weil die Antwort schon zu lang ist. Nichtsdestotrotz ist mein Vorschlag für Sie, es zu versuchen, es zu debuggen.

+0

Bytecode Gliederung ist genial. Vielen Dank! – vitaut

+0

Ja, ich weiß. Und gern geschehen. Aber ich werde nicht den Kredit dafür nehmen, der Kredit geht an Andrei Loskutov, den ursprünglichen Autor. – mschonaker

15

kann ich vorschlagen, dass Sie in diesen Bibliotheken einen Blick:

+0

Danke für die Links. Hast du irgendwas davon benutzt und welches würdest du empfehlen? – vitaut

+0

Ich habe Javassist und CGLIB verwendet, aber für viel einfachere Aufgaben als Ihre Bedürfnisse. Eine Sache zu beachten ist, dass BCEL CGLIB verwendet, also vielleicht größere Fähigkeiten hat. – Bozho

7

prüfen Jetbrains MPS aus. Von Leuten gebaut, die uns IDEA gebracht haben.

0

Aus einer anderen Perspektive frage ich, ob Sie in Betracht gezogen haben, XText zu verwenden. Dies ermöglicht Ihnen, DSL, Code-Editor mit Code-Vervollständigung, Compiler, Code-Generator und so weiter zu erstellen. Ich finde es echt cool und habe eine nette documentation. Es lohnt sich, einen Blick darauf zu werfen. Sie können daraus leicht einen Compiler für Ihr DSL erstellen.

+0

Danke für den Link.Es scheint, dass ich mit XText das Frontend neu implementieren muss, das ich vermeiden möchte. – vitaut

+0

Ja, ich verstehe. Es hängt von Ihrer Teamgröße und Unternehmensgröße ab. Ich denke, dass es von Zeit zu Zeit notwendig ist, etwas völlig Eigenständiges aufzugeben, um etwas zu verwenden, was andere auch benutzen. –

Verwandte Themen