2010-10-25 22 views
29

Piszę interpreter w Javie dla języka specyficznego dla domeny z pewnymi funkcjami skryptowania. Wcześniej zaimplementowałem analizator składni, a teraz muszę wykonać backend. W tym celu rozważam albo napisanie własnego interpretera (albo z użyciem abstrakcyjnych drzewek składniowych, albo z niestandardowymi bajtodami) albo docelową maszynę JVM (emituj i wykonuj kod bajtowy Java w czasie wykonywania).Jak emitować i wykonywać kod bajtowy Java w czasie wykonywania?

Czy osoba z większym doświadczeniem w tej dziedzinie może określić, w jaki sposób można zastosować JVM do kierowania i jakie biblioteki zaleciłbyś do emitowania kodu bajtowego Java?

+0

Czy DSL zostało już zaprojektowane/wyspecyfikowane? Czy rozwijasz DSL równolegle z parserem i tłumaczem? – Stobor

+0

@Stobor: określono język i zapisano parser. – vitaut

Odpowiedz

27

Oto robocza "hello world" wykonane z ObjectWeb ASM (biblioteki, które polecam):

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[] {}); 
    } 
} 

Aby wygenerować kod, znalazłem bardzo przydatny Bytecode Outline for Eclipse plug-in. Chociaż można użyć ASMifier (w zestawie z ASM) tak:

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

w czasie wykonywania, jeśli chcesz uzyskać Class obiekt dla tworzonej klasy, można załadować swoją klasę przez rozszerzenie programu ładującego klasy i publikowania (poprzez inną metodę, na przykład), metodę defineClass i zapewnienie klasy jako tablicy bajtów, jak podano w przykładzie.

Można również obsługiwać utworzoną klasę z interfejsem, jak w poniższym przykładzie:

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"); 
    } 
} 

bawić.

PS: Mogę dodać komentarze do kodu, jeśli nie są wystarczająco jasne. Nie zrobiłem tego, ponieważ odpowiedź jest już zbyt długa. Niemniej jednak moją propozycją jest próba debugowania.

+0

Bytecode Outline jest niesamowity. Dzięki! – vitaut

+0

Tak, wiem. I nie ma za co. Ale nie zasługuję na uznanie, zasługa Andrieja Loskutova, oryginalnego autora. – mschonaker

15

mogę sugerujemy zapoznać się z tych bibliotek:

+0

Dzięki za linki. Czy użyłeś któregoś z nich i który poleciłbyś? – vitaut

+0

Użyłem Javassist i CGLIB, ale dla prostszych zadań niż twoje potrzeby. Warto zauważyć, że BCEL używa CGLIB, więc może ma większe możliwości. – Bozho

7

Check out Jetbrains MPS. Zbudowany przez facetów, którzy przynieśli nam IDEA.

0

Z innej perspektywy pytam, czy rozważyć użycie XText. Ma to na celu umożliwienie tworzenia DSL, edytora kodu z uzupełnieniem kodu, kompilatorem, generatorem kodu i tak dalej. Myślę, że to jest naprawdę fajne i fajnie documentation. Warto się temu przyjrzeć. Możesz łatwo stworzyć kompilator na podstawie swojego DSL.

+0

Dzięki za link.Wygląda na to, że w XText będę musiał ponownie wdrożyć nakładkę, której chciałbym uniknąć. – vitaut

+0

Tak, rozumiem. To zależy od rozmiaru twojej drużyny i wielkości firmy. Myślę, że czas po jakimś czasie trzeba całkowicie zrezygnować z czegoś, czym można się posłużyć za coś, z czego inni również korzystają. –

Powiązane problemy