Jagan Arikuti
Jagan Arikuti

Reputation: 385

How to call function or module using Scala ScriptEngine

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)

Upvotes: 4

Views: 1520

Answers (2)

Mike Allen
Mike Allen

Reputation: 8299

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)
}

Upvotes: 3

Jagan Arikuti
Jagan Arikuti

Reputation: 385

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

Upvotes: 1

Related Questions