Scala - How to compile code from an external file at runtime?

external-dependencies, scala, scala-compiler

Solution

Here's how to do it using only standard Scala. The non-obvious stuff is all in `GreenhouseFactory`:

package customizable

abstract class Plant

case class Rose() extends Plant

abstract class Greenhouse {
  def getPlant(): Plant
}

case class GreenhouseFactory(implFilename: String) {
  import reflect.runtime.currentMirror
  import tools.reflect.ToolBox
  val toolbox = currentMirror.mkToolBox()
  import toolbox.u._
  import io.Source

  val fileContents = Source.fromFile(implFilename).getLines.mkString("\n")
  val tree = toolbox.parse("import customizable._; " + fileContents)
  val compiledCode = toolbox.compile(tree)

  def make(): Greenhouse = compiledCode().asInstanceOf[Greenhouse]
}

object Main {
  def main(args: Array[String]) {
    val greenhouseFactory = GreenhouseFactory("external.scala")
    val greenhouse = greenhouseFactory.make()
    val p = greenhouse.getPlant()

    println(p)
  }
}

Put your override expression in `external.scala`:

new Greenhouse {
  override def getPlant() = new Rose()
}

The output is:

Rose()

The only tricky thing is that `GreenhouseFactory` needs to prepend that `import` statement to provide access to all the types and symbols needed by the external files. To make that easy, make a single package with all those things.

The compiler `ToolBox` is sort of documented here. The only thing you really need to know, other than the weird imports, is that `toolbox.parse` converts a string (Scala source code) into an abstract syntax tree, and `toolbox.compile` converts the abstract syntax tree into a function with signature `() => Any`. Since this is dynamically compiled code, you have to live with casting the `Any` to the type that you expect.

Problem

I want to design a Scala program that accepts Scala files as parameters which can customize the execution of the program. In particular, I want to supply at runtime files that contain implementations of methods that will be invoked by the program. How can I properly depend on external files and invoke their methods dynamically at runtime? Ideally, I would also like those files to be able to depend on methods and classes in my program. Example Scenario: I have a function that contains the line `val p: Plant = Greenhouse.getPlant()`, and the `Greenhouse` class with the `getPlant` method is defined in one of the files that will be supplied at runtime. In that file, the method `getPlant` returns a `Rose`, where `Rose <: Plant` and `Plant` is defined in the original program. How do I achieve (or approximate) this interdependency, assuming the files are only joined at runtime and not at compile-time?

Original source

Related problems