这篇回答的Scala 2版本在这里:如何在脚本运行时运行生成的代码?
在Scala 3中:
ammonite.Main(verboseOutput = false).runCode("""println("Hello, World!")""")
build.sbt
scalaVersion := "3.1.3"
libraryDependencies += "com.lihaoyi" % "ammonite" % "2.5.4-22-4a9e6989" cross CrossVersion.full
excludeDependencies ++= Seq(
ExclusionRule("com.lihaoyi", "sourcecode_2.13"),
ExclusionRule("com.lihaoyi", "fansi_2.13"),
)
com.eed3si9n.eval.Eval()
.evalInfer("""println("Hello, World!")""")
.getValue(this.getClass.getClassLoader)
build.sbt
scalaVersion := "3.2.0"
libraryDependencies += "com.eed3si9n.eval" % "eval" % "0.1.0" cross CrossVersion.full
com.github.dmytromitin.eval.Eval[Unit]("""println("Hello, World!")""")
scalaVersion := "3.2.1"
libraryDependencies += "com.github.dmytromitin" %% "eval" % "0.1"
dotty.tools.repl.ScriptEngine().eval("""println("Hello, World!")""")
build.sbt
scalaVersion := "3.1.3"
libraryDependencies += scalaOrganization.value %% "scala3-compiler" % scalaVersion.value
- 如果您有一个
scala.quoted.Expr
'{...}
(一个静态类型的包装器,覆盖了一个抽象语法树scala.quoted.Quotes#Tree
),而不是普通的字符串,那么您可以使用运行时多层次
import scala.quoted.*
given staging.Compiler = staging.Compiler.make(getClass.getClassLoader)
staging.run('{ println("Hello, World!") })
build.sbt
scalaVersion := "3.1.3"
libraryDependencies += scalaOrganization.value %% "scala3-staging" % scalaVersion.value
- 以上所有内容都是为了在Scala 3中运行Scala 3代码。如果我们想在Scala 3中运行Scala 2代码,那么我们仍然可以使用Scala 2反射工具箱。Scala 2宏不起作用,因此我们不能使用
runtime.currentMirror
或q"..."
,但可以使用universe.runtimeMirror
或tb.parse
。
import scala.tools.reflect.ToolBox
val tb = scala.reflect.runtime.universe
.runtimeMirror(getClass.getClassLoader)
.mkToolBox()
tb.eval(tb.parse("""println("Hello, World!")"""))
build.sbt
scalaVersion := "3.1.3"
libraryDependencies ++= scalaOrganization.value % "scala-compiler" % "2.13.8"
- 除此之外,您还可以使用标准的Scala 2 REPL解释器在Scala 3中运行Scala 2代码。
scala.tools.nsc.interpreter.shell.Scripted()
.eval("""System.out.println("Hello, World!")""")
build.sbt
scalaVersion := "3.1.3"
libraryDependencies ++= scalaOrganization.value % "scala-compiler" % "2.13.8"
- 同时,您还可以使用JSR223脚本。根据您的类路径中是否有
scala3-compiler
或scala-compiler
,您将运行Scala 3或Scala 2(其中一个脚本引擎:Scala 3 dotty.tools.repl.ScriptEngine
或Scala 2 scala.tools.nsc.interpreter.shell.Scripted
)。如果您添加了两个依赖项,则先添加的依赖项将生效。
new javax.script.ScriptEngineManager(getClass.getClassLoader)
.getEngineByName("scala")
.eval("""println("Hello, World!")""")
如果您想更好地控制使用哪个依赖项(而无需重新导入项目),您可以使用Coursier并指定类加载器。
import coursier.*
val files = Fetch()
.addDependencies(
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,
null
)
new javax.script.ScriptEngineManager(classLoader)
.getEngineByName("scala")
.eval("""
type T = [A] =>> [B] =>> (A, B) // Scala 3
//type T = List[Option[A]] forSome {type A} // Scala 2
System.out.println("Hello, World!")
""")
- 你可以使用实际编译器在Scala 3中自己实现Eval
import dotty.tools.io.AbstractFile
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.Driver
import dotty.tools.dotc.util.SourceFile
import dotty.tools.io.{VirtualDirectory, VirtualFile}
import java.net.URLClassLoader
import java.nio.charset.StandardCharsets
import dotty.tools.repl.AbstractFileClassLoader
import scala.io.Codec
import coursier.{Dependency, Module, Organization, ModuleName, Fetch}
val code =
s"""
|package mypackage
|
|object Main {
| def main(args: Array[String]): Unit = {
| println("Hello, World!")
| }
|}""".stripMargin
val outputDirectory = VirtualDirectory("(memory)")
compileCode(code, List(), outputDirectory)
val classLoader = AbstractFileClassLoader(outputDirectory, this.getClass.getClassLoader)
runObjectMethod("mypackage.Main", classLoader, "main", Seq(classOf[Array[String]]), Array.empty[String])
def compileCode(
code: String,
classpathDirectories: List[AbstractFile],
outputDirectory: AbstractFile
): Unit = {
class DriverImpl extends Driver {
private val compileCtx0 = initCtx.fresh
given Context = compileCtx0.fresh
.setSetting(
compileCtx0.settings.classpath,
classpathDirectories.map(_.path).mkString(":")
).setSetting(
compileCtx0.settings.usejavacp,
true
).setSetting(
compileCtx0.settings.outputDir,
outputDirectory
)
val compiler = newCompiler
}
val driver = new DriverImpl
import driver.given Context
val sourceFile = SourceFile(VirtualFile("(inline)", code.getBytes(StandardCharsets.UTF_8)), Codec.UTF8)
val run = driver.compiler.newRun
run.compileSources(List(sourceFile))
}
def runObjectMethod(
objectName: String,
classLoader: ClassLoader,
methodName: String,
paramClasses: Seq[Class[?]],
arguments: Any*
): Any = {
val clazz = Class.forName(s"$objectName$$", true, classLoader)
val module = clazz.getField("MODULE$").get(null)
val method = module.getClass.getMethod(methodName, paramClasses*)
method.invoke(module, arguments*)
}
(previous version)
build.sbt
scalaVersion := "3.1.3"
libraryDependencies += scalaOrganization.value %% "scala3-compiler" % scalaVersion.value
另请参见:从Scala 3宏中获取类的注释(在Scala 3中进行多级编程的黑客和实现我们自己的eval
代替Scala 2中禁止在Scala 3宏中使用的context.eval
或staging.run
)。
Scala Presentation Compiler介绍
将字符串中的Scala 3代码解析为Scala 3 AST并在运行时执行
Scala 3反射
帮助使用dotty编译器和运行时类加载
q"..."
(Scala 2 宏)替换为toolbox.parse("...")
即可。请参见更新。 - Dmytro Mitin