Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compile/eval a Scala expression at runtime?

Tags:

scala

New to Scala and looking for pointers to an idiomatic solution, if there is one.

I'd like to have arbitrary user-supplied Scala functions (which are allowed to reference functions/classes I have defined in my code) applied to some data.

For example: I have foo(s: String): String and bar(s: String): String functions defined in my myprog.scala. The user runs my program like this:

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

This would run line by line through the data file and emit the result of applying the user-specified function to that line.

For extra points, can I ensure that there are no side-effects in the user-defined function? If not, can I restrict the function to use only a restricted subset of functions (which I can assure to be safe)?

like image 416
Arkady Avatar asked Jun 10 '16 22:06

Arkady


2 Answers

@kenjiyoshida has a nice gist that shows how to eval Scala code. Note that when using Eval from that gist, not specifying a return value will result in a runtime failure when Scala defaults to inferring Nothing.

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

vs

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

It nicely handles whatever's in scope as well.

 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's util package used to have util-eval, but that seems to have been deprecated now (and also triggers a compiler bug when compiled).

As for the second part of your question, the answer seems to be no. Even if you disable default Predef and imports yourself, a user can always get to those functions with the fully qualified package name. You could perhaps use Scala's scala.tools.reflect.ToolBox to first parse your string and then compare against a whitelist, before passing to eval, but at that point things could get pretty hairy since you'll be manually writing code to sanitize the Scala AST (or at the very least reject dangerous input). It definitely doesn't seem to be an "idiomatic solution."

like image 183
badcook Avatar answered Nov 20 '22 13:11

badcook


This should be possible by using the standard Java JSR 223 Scripting Engine

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

(also mentions using scala.tools.nsc.Interpreter but not sure this is still available)

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();
}
like image 1
Somatik Avatar answered Nov 20 '22 13:11

Somatik