scalascala-reflectscala-compilerruntime-compilation

How can I run generated code during script runtime?


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?


Solution

  • 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
    
    val tb = scala.tools.reflect.ToolBox(scala.reflect.runtime.currentMirror).mkToolBox()
    tb.eval(tb.parse("""println("Hello, world!")"""))
    // Hello, world!
    

    or

    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
    
    val settings = new scala.tools.nsc.Settings
    settings.usejavacp.value = true
    new scala.tools.nsc.interpreter.IMain(settings,
      new scala.tools.nsc.interpreter.shell.ReplReporterImpl(settings)
    ).interpret("""println("Hello, World!")""")
    // Hello, World!
    

    build.sbt

    scalaVersion := "2.13.8"
    libraryDependencies ++= scalaOrganization.value % "scala-compiler" % scalaVersion.value
    
    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
    
    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
    
    (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"
    
    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!
    
    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)
      val run = new global.Run
      run.compileSources(List(new BatchSourceFile("(inline)", code)))
      // val unit = run.units.next()
      // println("source=" + unit.source.content.mkString)
      // println("typed tree=" + unit.body)
    }
    
    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
    

    build.sbt

    scalaVersion := "2.13.8"
    // adding as a dependency, not a compiler plugin
    libraryDependencies += "org.scalameta" % "semanticdb-scalac" % "4.6.0" cross CrossVersion.full 
    

    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