Passing script results to main program in Scala 2.11 ScriptEngine

951 Views Asked by At

Using Scala Scripting Engine in 2.11 Milestone 7, how do I get a typed value back from the script engine? I'm getting error messages like "mypackage.Holler cannot be cast to mypackage.Holler".

Here is the use case (reduced to essentials). I want to use scripts to prepare and configure objects of a standard type that I will process in my main program. I have a trait:

package mypackage

trait Holler {
  def shout: Unit
}

I have a user script in Scala, saved in the file /home/me/Foo.scala

object Foo extends mypackage.Holler {
  def shout: Unit = println("Hello World!")
}

When I run this script using IMain.eval(Reader), I expect that the object Foo will be returned since it is the result of the last statement. Here is a program, including a couple useful printouts to run the script:

package mypackage

import javax.script.ScriptEngineManager
import scala.tools.nsc.interpreter.IMain

object Runner {
  def main(args: Array[String]): Unit = {
    //  Create the script engine
    val javaxEngine = new ScriptEngineManager().getEngineByName("scala")
    val scalaEngine = javaEngine.asInstanceOf[IMain]

    // Configure script engine to use the Java classpath
    val useJavaClassPath = scalaEngine.settings.usejavacp.tryToSet(List("true"))
    println("Use Java CP? " + useJavaClassPath)

    val script = new java.io.FileReader("/home/me/Foo.scala")
    val result = scalaEngine.eval(script)
    println("Script Result Type: " + result.getClass.getName)
    println("Defined Symbols: " + scalaEngine.definedSymbolList)
    val myHoller = result.asInstanceOf[mypackage.Holler]
  }
}

The script runs just fine under the script engine. But the result cannot be cast to Holler. The output of this program is as follows:

Use Java CP? Some(List(true))
Script Result Type: $line3.$read$$iw$$iw$Foo$
Defined Symbols: List(value engine, object iw$Foo)
Exception in thread "main" java.lang.ClassCastException: $line3.$read$$iw$$iw$Foo$ cannot be cast to mypackage.Holler

This tells me that the classpath is successfully recognized by the script engine, and that the Foo object is being constructed. But the trait mypackage.Holler (from the common classpath) inside the script is different from the trait mypackage.Holler in the main program.

If I add the following lines to the script:

Foo.shout
val result: Holler = Foo

I see:

  • The shout method being exercised ("Hello World!" prints out),
  • "result" is added to the list of defined symbols
  • result is clearly compatible with type Holler.

I can bind a "catcher" object to the script engine. Its code looks like this:

package mypackage

class Catcher {
  var item: Holler = null
}

And I bind with

val mycatcher = new Catcher
scalaEngine.bind("catcher", mycatcher)
scalaEngine.eval("catcher = Foo")

Now "catcher" shows up in the list of defined symbols to the script engine and I can use the catcher to go into the script engine with a command like

scalaScriptEngine.eval("catcher.item = result")

But then I get strange "compile time" ClassCastExceptions saying:

mypackage.Holler cannot be cast to mypackage.Holler

If I make the "item" in the Catcher an Any, then I don't get the exception until I do

mycatcher.item.asInstanceOf[Holler]

in the main program. But I still get pretty much the same exception. It is as if two incompatible class loaders are being used with the same classpath. So how, from the main program, do I access the Foo object as an instance of Holler (which it clearly implements in the script engine)?

0

There are 0 best solutions below