How can I run generated code during script runtime?
Asked Answered
H

1

1

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?

Hallux answered 30/9, 2022 at 16:50 Comment(1)
Why do you think they aren't successful? scastie.scala-lang.org/DmytroMitin/xT7scU9BTbWtsFw8Y8wcbQ What is current behavior and what is desirable?Unparliamentary
U
1

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
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
  • You can use scala.tools.nsc.interpreter
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
  • 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 using actual compiler
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
  • Also Scalameta Semanticdb has method scala.meta.interactive.InteractiveSemanticdb.newCompiler() returning a scala.tools.nsc.interactive.Global extending above scala.tools.nsc.Global.

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

Unparliamentary answered 1/10, 2022 at 15:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.