4

How to call function or module using ScriptEngine.

here is my sample code , which is compiling fine , but at runtime its throwing exception scalaVersion := "2.12.4" and sbt.version = 0.13.16, java is jdk1.8.0_131

import java.io.FileReader
import javax.script._

object DemoApp extends App {
    val engine: ScriptEngine with Compilable with javax.script.Invocable  = new ScriptEngineManager()
    .getEngineByName("scala")
    .asInstanceOf[ScriptEngine with javax.script.Invocable with Compilable]
    val reader = new FileReader("src/main/scala/Demo.sc")
    engine.compile(reader).eval()
    val result = engine.invokeFunction("fun")
}

below is the Demo.sc

def fun: String = {
"Rerutn from Fun"
}

Below is the exception at runtime

Exception in thread "main" java.lang.ClassCastException: scala.tools.nsc.interpreter.Scripted cannot be cast to javax.script.Invocable
at DemoApp$.delayedEndpoint$DemoApp$1(DemoApp.scala:13)
at DemoApp$delayedInit$body.apply(DemoApp.scala:5)
at scala.Function0.apply$mcV$sp(Function0.scala:34)
at scala.Function0.apply$mcV$sp$(Function0.scala:34)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App.$anonfun$main$1$adapted(App.scala:76)
at scala.collection.immutable.List.foreach(List.scala:389)
at scala.App.main(App.scala:76)
at scala.App.main$(App.scala:74)
at DemoApp$.main(DemoApp.scala:5)
at DemoApp.main(DemoApp.scala)
Mike Allen
  • 8,139
  • 2
  • 24
  • 46
Jagan Arikuti
  • 365
  • 1
  • 2
  • 11

2 Answers2

3

I think the problem is that the Scala script engine implements Compilable, but not Invocable, which is why you're getting a cast exception.

In any case, when you call eval on the result of the compilation, your code is executed, so you don't need to invoke anything via Invocable.

Using asInstanceOf is a little frowned-upon, so the following is more idiomatic.

Try this:

import java.io.FileReader
import javax.script._

object DemoApp extends App {
  // Get the Scala engine.
  val engine = new ScriptEngineManager().getEngineByName("scala")

  // See if the engine supports compilation.
  val compilerEngine = engine match {
    case c: Compilable => Some(c)
    case _ => None
  }

  // If the engine supports compilation, compile and run the program.
  val result = compilerEngine.map {ce =>
    val reader = new FileReader("src/main/scala/Demo.sc")
    ce.compile(reader).eval()
  }

  println(result.fold("Script not compilable")(_.toString))
}

Alternatively, if you just want to get your original code working, you should so this:

import java.io.FileReader
import javax.script._

object DemoApp extends App {
  val engine = new ScriptEngineManager()
    .getEngineByName("scala")
    .asInstanceOf[ScriptEngine with Compilable]
  val reader = new FileReader("src/main/scala/Demo.sc")
  val result = engine.compile(reader).eval()
  // Output the result
  println(result.toString)
}
Mike Allen
  • 8,139
  • 2
  • 24
  • 46
  • how to call /eval a function from the compile script by passing argument – Jagan Arikuti Mar 17 '18 at 02:02
  • 1
    @JaganArikuti I don't think it's possible if you use the _Scala_ script engine, since it doesn't implement `Invocable`. If you use the _nashorn_ script engine - which processes _JavaScript_ scripts instead of _Scala_ - then you can both compile _JavaScript_ scripts as well as invoke methods defined by them. With the _Scala_ script engine, all you can do is interpret _Scala_ scripts, or compile them and evaluate the result. – Mike Allen Mar 17 '18 at 03:51
1

workaround using actor in scripts -

Main application Demo

class SampleActor extends Actor {
    implicit val log = Logging(context.system, this)
    def fun() = {
        val settings: Settings = new Settings
        settings.sourcepath.value = "src/main/scripts"
        settings.usejavacp.value = true
        settings.dependencyfile.value = "*.scala"
        val engine: Scripted = Scripted(new Scripted.Factory, settings)
        engine.getContext.setAttribute("context0",context,ScriptContext.ENGINE_SCOPE)

        val reader = new FileReader("src/main/scripts/ActorScript.scala")
        engine.eval("import akka.actor.ActorContext \n" +"val context1 = context0.asInstanceOf[ActorContext]")

        val compiledScript : CompiledScript = engine.compile(reader)
        val x = compiledScript.eval()
        x.asInstanceOf[ActorRef] ! "Arikuti"
        x.asInstanceOf[ActorRef] !  1
    }
    override def receive: Receive = {
        case x : String =>
          log.info("Receveid  from ScriptEngine: " +  x)
        case i : Int =>
          log.info("Receveid from ScriptEngine : " +  i)
    }

    override def preStart(): Unit = {
        super.preStart()
        fun()
      }
    }

object ActorDemo {
  def main(args: Array[String]): Unit = {
  val system = ActorSystem("clientAdapter")
  val x = system.actorOf(Props(classOf[SampleActor]),"Main")
}
}

And below 3 scrips are i placed in src/main/scripts

ActorScript.scala

import akka.actor.{Actor, ActorRef, Props}
import akka.event.Logging


class ActorScript extends Actor {
implicit val log = Logging(context.system, this)

override def receive = {
case y : Int   =>
  log.info("Recevied from Main Int : " +  y.toString )
  log.info(Convert.fun())
  sender.tell(2,self)
case x : String =>
  log.info("Recevied from Main String " + x)
  log.info(Second.fun())
  sender.tell("Arikuti",self)
}
}

object ActorScript {
  def apply: ActorRef = {
    context1.actorOf(Props(new ActorScript),"ScriptActor")
  }
}

ActorScript.apply

Convert.scala

object Convert {
  def fun(): String = {
    "I am from Converter:: fun"
  }
}

Second.scala

object Second {
  def fun(): String = {
    "I am from Second::fun"
  }
}

In build.sbt

excludeFilter in unmanagedSourceDirectories :=  "src/main/scripts/*.scala"

now from Application i can send message to compiled script actor and recevied processed values form the Scripipts

Jagan Arikuti
  • 365
  • 1
  • 2
  • 11