Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic compilation of multiple Scala classes at runtime

I know I can compile individual "snippets" in Scala using the Toolbox like this:

import scala.reflect.runtime.universe
import scala.tools.reflect.ToolBox

object Compiler {
  val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()

  def main(args: Array[String]): Unit = {
    tb.eval(tb.parse("""println("hello!")"""))
  }
}

Is there any way I can compile more than just "snippets", i.e., classes that refer to each other? Like this:

import scala.reflect.runtime.universe
import scala.tools.reflect.ToolBox

object Compiler {
  private val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()

  val a: String =
    """
      |package pkg {
      |
      |class A {
      |def compute(): Int = 42
      |}}
    """.stripMargin

  val b: String =
    """
      |import pkg._
      |
      |class B {
      |def fun(): Unit = {
      |    new A().compute()
      |}
      |}
    """.stripMargin

  def main(args: Array[String]): Unit = {
    val compiledA = tb.parse(a)
    val compiledB = tb.parse(b)
    tb.eval(compiledB)
  }
}

Obviously, my snippet doesn't work as I have to tell the toolbox how to resolve "A" somehow:

Exception in thread "main" scala.tools.reflect.ToolBoxError: reflective compilation has failed:

not found: type A

like image 204
John Reese Avatar asked Oct 15 '22 13:10

John Reese


1 Answers

Try

import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe
import scala.tools.reflect.ToolBox

val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()

val a = q"""
          class A {
            def compute(): Int = 42
          }"""

val symbA = tb.define(a)

val b = q"""
          class B {
            def fun(): Unit = {
              new $symbA().compute()
            }
          }"""

tb.eval(b)

https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/reflect/ToolBox.scala#L131-L138


In cases more complex than those the toolbox can handle, you can always run the compiler manually

import scala.reflect.internal.util.{AbstractFileClassLoader, BatchSourceFile}
import scala.reflect.io.{AbstractFile, VirtualDirectory}
import scala.tools.nsc.{Global, Settings}
import scala.reflect.runtime
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._

val a: String =
  """
    |package pkg {
    |
    |class A {
    |  def compute(): Int = 42
    |}}
""".stripMargin

val b: String =
  """
    |import pkg._
    |
    |class B {
    |  def fun(): Unit = {
    |    println(new A().compute())
    |  }
    |}
""".stripMargin

val directory = new VirtualDirectory("(memory)", None)
compileCode(List(a, b), List(), directory)
val runtimeMirror = createRuntimeMirror(directory, runtime.currentMirror)
val bInstance = instantiateClass("B", runtimeMirror)
runClassMethod("B", runtimeMirror, "fun", bInstance) // 42

def compileCode(sources: List[String], classpathDirectories: List[AbstractFile], outputDirectory: AbstractFile): Unit = {
  val settings = new Settings
  classpathDirectories.foreach(dir => settings.classpath.prepend(dir.toString))
  settings.outputDirs.setSingleOutput(outputDirectory)
  settings.usejavacp.value = true
  val global = new Global(settings)
  val files = sources.zipWithIndex.map { case (code, i) => new BatchSourceFile(s"(inline-$i)", code) }
  (new global.Run).compileSources(files)
}

def instantiateClass(className: String, runtimeMirror: Mirror, arguments: Any*): Any = {
  val classSymbol = runtimeMirror.staticClass(className)
  val classType = classSymbol.typeSignature
  val constructorSymbol = classType.decl(termNames.CONSTRUCTOR).asMethod
  val classMirror = runtimeMirror.reflectClass(classSymbol)
  val constructorMirror = classMirror.reflectConstructor(constructorSymbol)
  constructorMirror(arguments: _*)
}

def runClassMethod(className: String, runtimeMirror: Mirror, methodName: String, classInstance: Any, arguments: Any*): Any = {
  val classSymbol = runtimeMirror.staticClass(className)
  val classType = classSymbol.typeSignature
  val methodSymbol = classType.decl(TermName(methodName)).asMethod
  val instanceMirror = runtimeMirror.reflect(classInstance)
  val methodMirror = instanceMirror.reflectMethod(methodSymbol)
  methodMirror(arguments: _*)
}

//def runObjectMethod(objectName: String, runtimeMirror: Mirror, methodName: String, arguments: Any*): Any = {
//  val objectSymbol = runtimeMirror.staticModule(objectName)
//  val objectModuleMirror = runtimeMirror.reflectModule(objectSymbol)
//  val objectInstance = objectModuleMirror.instance
//  val objectType = objectSymbol.typeSignature
//  val methodSymbol = objectType.decl(TermName(methodName)).asMethod
//  val objectInstanceMirror = runtimeMirror.reflect(objectInstance)
//  val methodMirror = objectInstanceMirror.reflectMethod(methodSymbol)
//  methodMirror(arguments: _*)
//}

def createRuntimeMirror(directory: AbstractFile, parentMirror: Mirror): Mirror = {
  val classLoader = new AbstractFileClassLoader(directory, parentMirror.classLoader)
  universe.runtimeMirror(classLoader)
}

dynamically parse json in flink map

Tensorflow in Scala reflection

How to eval code that uses InterfaceStability annotation (that fails with "illegal cyclic reference involving class InterfaceStability")?

like image 157
Dmytro Mitin Avatar answered Oct 30 '22 21:10

Dmytro Mitin