scalamacrosscala-macrosscala-3

How to compile and execute scala code at run-time in Scala3?


I would like to compile and execute Scala code given as a String at run-time using Scala3. Like for example in Scala 2 I would have used Reflection

import scala.reflect.runtime.universe as ru
import scala.tools.reflect.ToolBox
val scalaCode = q"""println("Hello world!")"""
val evalMirror = ru.runtimeMirror(this.getClass.getClassLoader)
val toolBox = evalMirror.mkToolBox()
toolBox.eval(scalaCode) //Hello world!

If I try to run this code in Scala3 I get

Scala 2 macro cannot be used in Dotty. See https://dotty.epfl.ch/docs/reference/dropped-features/macros.html
To turn this error into a warning, pass -Xignore-scala2-macros to the compiler

How can I translate this code in Scala3 ?


Solution

  • This program compiles Scala 3 code dynamically:

    import dotty.tools.dotc.Driver
    import java.net.{URL, URLClassLoader}
    import java.nio.file.{Files, Path}
    import java.io.File
    
    object DynamicCompiler:
    
      private val driver = Driver()
    
      def compile(code: String, className: String, outputDir: Path): Boolean =
        val sourceFile = Files.createTempFile(className, ".scala").toFile
        sourceFile.deleteOnExit()
        Files.writeString(sourceFile.toPath, code)
    
        val args = Array(
           sourceFile.getAbsolutePath,
           "-d",
           outputDir.toAbsolutePath.toString,
           "-classpath",
           System.getProperty("java.class.path")
        )
    
        val result = driver.process(args)
        !result.hasErrors
    
      def loadClass(className: String, classDir: Path): Option[Class[?]] =
        val loader = URLClassLoader(Array(classDir.toUri.toURL), getClass.getClassLoader)
        try Some(loader.loadClass(className))
        catch case _: ClassNotFoundException => None
    
      def main(args: Array[String]): Unit =
        val outDir = Files.createTempDirectory("scala3-compiled")
        val objectName = "DynamicRoot"
        val className = "Root"
    
        val code =
          s"""
             |case class Address(city: Option[String], country: Option[String], street: Option[String])
             |case class Person(address: Option[Address], age: Option[Long], name: Option[String])
             |case class $className(people: Option[Seq[Person]])
             |object $objectName
             |""".stripMargin
    
        if compile(code, objectName, outDir) then
          println(s"Compilation succeeded. Output in: $outDir")
          loadClass(objectName, outDir) match
            case Some(objCls) => println(s"Loaded object class: $objCls")
            case None         => println(s"Failed to load object class: $objectName")
    
          loadClass(className, outDir) match
            case Some(rootCls) => println(s"Loaded case class: $rootCls")
            case None          => println(s"Failed to load case class: $className")
        else
          println("Compilation failed.")