Home > Enterprise >  How can I run generated code during script runtime?
How can I run generated code during script runtime?

Time:10-04

During the running of a scala script, I would like it to generate some code and execute this.

I thought I had found two examples online that might work, but they aren't successful

import scala.reflect.runtime.universe._
import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
import java.io.{File, FileWriter}

  def runstuff() = {

    val fileWriter = new FileWriter(new File("temporaryScalaFile.scala"))
    fileWriter.write("println(\"hello\")")
    fileWriter.close()
    temporaryScalaFile.scala

    val cm = scala.reflect.runtime.universe.runtimeMirror(getClass.getClassLoader)
    val tb = cm.mkToolBox()
    val str = tb.eval(tb.parse("new String(\"Yo\")"))
    println(str)

  }

These are perhaps out of date examples.

Does anyone have a working one or a fix?

CodePudding user response:

I'll adopt to Scala 2 my answer in How to compile and execute scala code at run-time in Scala3?

ammonite.Main(verboseOutput = false).runCode("""println("Hello, World!")""")
// Hello, World!

build.sbt

scalaVersion := "2.13.8"
libraryDependencies  = "com.lihaoyi" % "ammonite" % "2.5.4" cross CrossVersion.full
scala.tools.nsc.interpreter.shell.Scripted()
  .eval("""System.out.println("Hello, World!")""")
// Hello, World!

build.sbt

scalaVersion := "2.13.8"
libraryDependencies   = scalaOrganization.value % "scala-compiler" % scalaVersion.value
  • You can use Scala 2 reflective Toolbox
import scala.tools.reflect.ToolBox // implicit

val tb = scala.reflect.runtime.currentMirror.mkToolBox()
tb.eval(tb.parse("""println("Hello, world!")"""))
// Hello, world!

build.sbt

scalaVersion := "2.13.8"
libraryDependencies   = scalaOrganization.value % "scala-compiler" % scalaVersion.value
  • If you have a scala.reflect.runtime.universe.Tree q"..." rather than plain string then you don't need to parse
import scala.reflect.runtime.universe.Quasiquote // implicit for q"..." interpolator
import scala.tools.reflect.ToolBox               // implicit for .eval

scala.reflect.runtime.currentMirror.mkToolBox()
  .eval(q"""println("Hello, World!")""") // Hello, World!

build.sbt

scalaVersion := "2.13.8"
libraryDependencies   = scalaOrganization.value % "scala-compiler" % scalaVersion.value
  • If you have a scala.reflect.runtime.universe.Expr reify {...} (a statically typed wrapper over a tree) rather than plain string then you also don't need to parse
import scala.reflect.runtime.universe.reify
import scala.tools.reflect.ToolBox

scala.reflect.runtime.currentMirror.mkToolBox()
  .eval(reify{ println("Hello, World!") }.tree) 
// Hello, World!

build.sbt

scalaVersion := "2.13.8"
libraryDependencies   = scalaOrganization.value % "scala-compiler" % scalaVersion.value
  • All of the above is to run Scala 2 code in Scala 2. If we want to run Scala 3 code in Scala 2 then we can use standard Scala 3 REPL interpreter
(new dotty.tools.repl.ScriptEngine).eval("""println("Hello, World!")""")
// Hello, World!

build.sbt

scalaVersion := "2.13.8"
libraryDependencies  = scalaOrganization.value %% "scala3-compiler" % "3.1.3" cross CrossVersion.for2_13Use3
scalacOptions  = "-Ytasty-reader"
  • Also you can use JSR223 scripting. Depending on whether you have scala-compiler or scala3-compiler in your classpath you will run Scala 2 or Scala 3 (one of the two above script engines: Scala 2 scala.tools.nsc.interpreter.shell.Scripted or Scala 3 dotty.tools.repl.ScriptEngine). If you have both the dependency added first wins.
new javax.script.ScriptEngineManager(getClass.getClassLoader)
  .getEngineByName("scala")
  .eval("""println("Hello, World!")""")
// Hello, World!

If you'd like to have a better control what dependency is used (without re-importing the project) you can use Coursier and specify class loader

import coursier._ // libraryDependencies  = "io.get-coursier" %% "coursier" % "2.1.0-M6-53-gb4f448130"
val files = Fetch()
  .addDependencies(
    Dependency(Module(Organization("org.scala-lang"), ModuleName("scala-compiler")), "2.13.9"),
    // Dependency(Module(Organization("org.scala-lang"), ModuleName("scala3-compiler_3")), "3.2.0"),
  )
  .run()

val classLoader = new java.net.URLClassLoader(
  files.map(_.toURI.toURL).toArray,
  /*getClass.getClassLoader*/null // ignoring current classpath
)
new javax.script.ScriptEngineManager(classLoader)
  .getEngineByName("scala")
  .eval("""
    type T = List[Option[A]] forSome {type A} // Scala 2
    //type T = [A] =>> [B] =>> (A, B) // Scala 3
    System.out.println("Hello, World!")
  """)
// Hello, World!
  • You can implement Eval in Scala 2 yourself
import scala.reflect.internal.util.{AbstractFileClassLoader, BatchSourceFile}
import scala.reflect.io.{AbstractFile, VirtualDirectory}
import scala.reflect.runtime.universe
import scala.reflect.runtime
import scala.reflect.runtime.universe.{Mirror, TermName}
import scala.tools.nsc.{Global, Settings}

val code =
  s"""
     |package mypackage
     |
     |object Main {
     |  def main(args: Array[String]): Unit = {
     |    println("Hello, World!")
     |  }
     |}""".stripMargin

val directory = new VirtualDirectory("(memory)", None)
val runtimeMirror = createRuntimeMirror(directory, runtime.currentMirror)
compileCode(code, List(), directory)
runObjectMethod("mypackage.Main", runtimeMirror, "main", Array.empty[String])
// Hello, World!

def compileCode(
                 code: 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)
  (new global.Run).compileSources(List(new BatchSourceFile("(inline)", code)))
}

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

build.sbt

scalaVersion := "2.13.8"
libraryDependencies   = scalaOrganization.value % "scala-compiler" % scalaVersion.value

Scala reflection: compile akka actor with protobuf

Dynamic compilation of multiple Scala classes at runtime

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

Tensorflow in Scala reflection

Scala Presentation Compiler - Minimal Example

What is "Scala Presentation Compiler"?

"eval" in Scala

  • Related