Как испускать и выполнять байт-код Java во время выполнения?

Я пишу интерпретатор в Java для языка, специфичного для домена, с некоторыми возможностями сценариев. Я уже реализовал синтаксический анализатор и теперь должен сделать задний конец. С этой целью я рассматриваю возможность либо написать собственный интерпретатор (либо работать с абстрактными синтаксическими деревьями, либо с помощью некоторых пользовательских байт-кодов), либо целевой JVM (испускать и выполнять байт-код Java во время выполнения).

Может ли кто-нибудь с большим опытом в этой области сказать, насколько целесообразным является подход к таргетингу JVM и какие библиотеки вы бы рекомендовали использовать для испускания байт-кода Java?

Вот рабочий «мир привет», созданный с помощью ObjectWeb ASM (библиотека, которую я рекомендую):

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

Чтобы сгенерировать код, я нашел очень полезный Bytecode Outline для плагина Eclipse . Хотя вы можете использовать ASMifier (входит в ASM) следующим образом:

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

Во время выполнения, если вам нужно получить объект Class для созданного classа, вы можете загрузить свой class, расширив загрузчик classов и опубликовать (например, другим методом) метод defineClass и предоставив class в виде массива байтов, как указано в в примере.

Вы также можете обработать созданный class с помощью интерфейса, как в этом примере:

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

Повеселись.

PS: Я могу добавить комментарии к коду, если не достаточно ясно. Я не сделал этого, потому что ответ уже слишком длинный. Тем не менее, мое предложение для вас – попробовать отладить его.

Я могу предложить вам взглянуть на эти библиотеки:

  • CGLIB
  • BCEL
  • как м
  • Javassist

Проверьте Jetbrains MPS . Построены парни, которые принесли нам IDEA.

С другой точки зрения, я прошу, если вы решили использовать XText . Это позволяет создавать DSL, редактор кода с завершением кода, компилятор, генератор кода и так далее. Я думаю, что это действительно здорово и иметь хорошую документацию . Стоит взглянуть на него. Вы можете легко создать компилятор, основанный на нем для вашего DSL.

  • Что означает == $ 0 (double equals dollar zero) в инструментах разработчика Chrome?
  • Файлы заголовков C ++, разделение кода
  • Как определить неиспользуемые определения css
  • Покрытие кода, не показывающее результаты с помощью Xcode + gcov
  • Проверка указателя NULL в C / C ++
  • Может ли class Java добавлять метод вовремя?
  • Когда можно использовать обработку исключений для бизнес-логики?
  • Форматирование ReSharper: выравнивание равных операндов
  • Код C ++ в файлах заголовков
  • Codesign API Dropbox не работает в Xcode 4.6.3: «Объект кода вообще не подписан»
  • Отключение автоматического форматирования в Visual Studio
  • Давайте будем гением компьютера.