如何在运行时编译/评估Scala表达式



Scala新手,如果有惯用解决方案的话,可以查找指向该解决方案的指针。

我希望将任意用户提供的Scala函数(允许它们引用我在代码中定义的函数/类)应用于一些数据。

例如:我在myprog.scala中定义了foo(s: String): Stringbar(s: String): String函数。用户运行我的程序如下:

$ scala myprog data.txt --func='(s: Str) => foo(bar(s)).reverse'

这将在数据文件中逐行运行,并发出将用户指定的函数应用于该行的结果。

对于额外的点,我可以确保在用户定义的函数中没有副作用吗?如果不是,我是否可以限制函数仅使用函数的受限子集(我可以保证这是安全的)?

@kenjiyoshida有一个很好的要点,展示了如何评估Scala代码。请注意,当从该要点使用Eval时,当Scala默认推断Nothing时,不指定返回值将导致运行时失败。

scala> Eval("println("Hello")")
Hello
java.lang.ClassCastException: scala.runtime.BoxedUnit cannot be cast to scala.runtime.Nothing$
... 42 elided

scala> Eval[Unit]("println("Hello")")
Hello

它也很好地处理了范围内的任何内容。

object Thing {
val thing: Int = 5
}
object Eval {
def apply[A](string: String): A = {
val toolbox = currentMirror.mkToolBox()
val tree = toolbox.parse(string)
toolbox.eval(tree).asInstanceOf[A]
}
def fromFile[A](file: File): A =
apply(scala.io.Source.fromFile(file).mkString(""))
def fromFileName[A](file: String): A =
fromFile(new File(file))
}
object Thing2 {
val thing2 = Eval[Int]("Thing.thing") // 5
}

Twitter的util包曾经有util-eval,但现在似乎已经被弃用了(编译时还会触发编译器错误)。

至于问题的第二部分,答案似乎是否定的。即使您禁用默认的Predef并自行导入,用户也可以使用完全限定的包名称访问这些函数。您也许可以使用Scala的scala.tools.reflect.ToolBox首先解析字符串,然后在传递到eval之前与白名单进行比较,但在这一点上,事情可能会变得非常棘手,因为您将手动编写代码来净化Scala AST(或者至少拒绝危险的输入)。这似乎绝对不是一个"惯用的解决方案">

这应该可以通过使用标准的Java JSR 223脚本引擎来实现

参见https://issues.scala-lang.org/browse/SI-874

(还提到使用scala.tools.nsc.Interpriser,但不确定这是否仍然可用)

import javax.script.*;
ScriptEngine e = new ScriptEngineManager().getEngineByName("scala");
e.getContext().setAttribute("label", new Integer(4), ScriptContext.ENGINE_SCOPE);
try {
engine.eval("println(2+label)");
} catch (ScriptException ex) {
ex.printStackTrace();
}

最新更新