Home > scala > Remedial Scala: Interpreting Scala from Scala

Remedial Scala: Interpreting Scala from Scala

January 25th, 2009

For my “learning Scala” project, rooscaloo, I needed to dynamically generate and compile Scala code as rules are loaded into the engine. I had initially intended to do this as an internal DSL, but I honestly couldn’t think of a way to handle the fact that variables are bound at match time in the rete network. This is a learning project anyway, so I decided to just generate code and compile it.  Now, I could have sworn I had seen a Scala scripting example using JSR-223 in a blog somewhere, but couldn’t find it again.  I only found one very basic example of how to do this, in the Scala bug tracker. So I figured I’d write down what I came up with.

Note that this code makes use of the scala.tools.nsc package which is not part of the standard Scala library. Thus, it may change in future versions of Scala. Also note that after the fact, I was able to track down two partial JSR-223 implementations here and here. It’s comforting that I ended up in basically the same place.

The example given in the bug tracker works, but it’s very simple and there are two things it doesn’t do. First, it prints all its output to stdout, including error information. Second, it doesn’t show how to retrieve results from executing scripts. That is, code goes in, but nothing comes out. These are both solved pretty easily.  In the first case, we simply supply our own PrintWriter when instantiating the internal Scala interpreter:

  private val writer = new java.io.StringWriter()
  private val interpreter = new ScalaInterpreter(new Settings(), 
                                                 new PrintWriter(writer));

  ...

  def exec(code : String) {
    // Clear the previous output buffer
    writer.getBuffer.setLength(0)

    // Execute the code and catch the result
    val ir = interpreter.interpret(code);

    // Return value or throw an exception based on result
    ir match {
      case Success => ()
      case Error => throw new ScriptException("error in: '" +
                                              code + "'\n" + writer toString)
      case Incomplete => throw new ScriptException("incomplete in :'" + 
                                                   code + "'\n" + writer toString)
    }
  }

For the second issue, returning results, I had to think a little longer, probably longer than necessary. scala.tools.nsc.Interpreter doesn’t return anything besides Success, Error, or Incomplete as seen above. But, you can pass objects in, so I ended up creating a container object to hold the result:

class ResultHolder(var value : Any)

  ...

  def eval(code : String) : Any = {
    // Clear the previous output buffer
    writer.getBuffer.setLength(0)

    // Create an object to hold the result and bind in the interpreter
    val holder = new ResultHolder(null)
    bind("$result__", holder);

    // Execute the code and catch the result
    val ir = interpreter.interpret("$result__.value = " + code);

    // Return value or throw an exception based on result
    ir match {
      case Success => holder.value
      case Error => throw new ScriptException("error in: '" + 
                                              code + "'\n" + writer toString)
      case Incomplete => throw new ScriptException("incomplete in :'" + 
                                                   code + "'\n" + writer toString)
    }
  }

As you can see, I ended up with two separate routines. exec executes a Scala statement, ignoring its return value. This is useful for imports, class declarations, anything that does not evaluate to a value.  eval executes a Scala statement, catches the result and returns it.

Anyway, that’s about it. It seems that full JSR-223 support is in the plan for Scala. Until then, what I have here works for me and it was a good learning experience. Here’s the full source for my solution.

scala ,

  1. philip andrew
    October 12th, 2009 at 10:02 | #1

    Hi, any updates on interpreter? I want to do this for some scala code and keep the state of the interpreter hanging around.

  1. No trackbacks yet.