Inherit class loader in ScriptEngine

This works:

@main def Test() =
   dotty.tools.repl.ScriptEngine().eval("""println("Hello, World!")""")

but this doesn’t:

def up(str: String) = str.toUpperCase

@main def Test() =
   dotty.tools.repl.ScriptEngine().eval("""up(println("Hello, World!"))""")

because the REPL cannot load function up.

Is there a way to customize the class loader used by the REPL so it has access to code from the current project (or even from an arbitrary URL)?

Checking the source, it seems that ScriptEngine starts a ReplDriver with hardcoded arguments:

    Array(
      "-classpath", "", // Avoid the default "."
      "-usejavacp",
      "-color:never",
      "-Xrepl-disable-display"
    )

Is there a way to specify a custom class loader somewhere in the process?

1 Like

I don’t think you mean REPL but the script runner?

There is a big difference between making REPL artifacts available and making script artifacts available.

This fails on both versions:

object X {
  def up(s: String) = s.toUpperCase
}

object Test extends App {
  println {
    //dotty.tools.repl.ScriptEngine().eval("""X.up("hello, world")""")
    scala.tools.nsc.interpreter.shell.Scripted().eval("""X.up("hello, world")""")
  }
}

I mentioned the REPL because the first thing ScriptEngine does is create an instance of ReplDriver. I haven’t seen scala.tools.nsc.interpreter.shell.Scripted before. Documentation is somewhat succinct (not a single line of it for the entire class). Anyway, that’s Scala 2, right? I’m in Scala 3.

I guess you mean:

def up(str: String) = str.toUpperCase

@main def Test() =
   dotty.tools.repl.ScriptEngine().eval("""println(up("Hello, World!"))""")

but I guess that doesn’t work either?

If you press TAB in REPL you’ll see this:

And there is some stuff named setContext and setBindings that might be useful for what you are trying to achieve? (I have no idea what they do but their name sounds promissing…)

Yes, I meant to apply up to the string, not to println.

My impression is that contexts and bindings let me add names that refer to existing values, but what I need is to load code from outside the REPL, which means using a suitable class loader.

I’m also interested in this. I managed to once for Scala 2.11 integrate with the REPL but I have no idea on how to do it for Scala 3…

This was how I did it after trial and error in Scala 2.11 (be ware of hacky old code), and I had to fiddle with settings so that the Java class path was used etc: Init and logic

I started to try to find out ho to use the new ReplDriver in Scala 3 but never found a way to use the same class path as the invoking app and do the bindings you want. Here is my abandoned trial: main and other stuff but I have forgotten now what made me give up. I guess I was not able to tell from the code without doc-comments how to use it.

If you find a way I’m also interested…

This StackOverflow answer has a few interesting pointers (java - How to compile and execute scala code at run-time in Scala3? - Stack Overflow). I was hoping to avoid a dependency on an eval written by someone else. But I should try those and see how they handle class loaders.

I assume we want this for the same purpose, i.e., for teaching. I face scenarios where I don’t want to give students a function signature, but I still want to be able to compile a test suite if they messed it up.

1 Like

Yes I wanted this for teaching, but with another goal: to provide a REPL with a DSL already invoked and a simple swing GUI that can integrate with the custom repl state.

I think it would be really useful with a simple api for those who just want to use the REPL for:

  • binding stuff in the surronding environment
  • run code from a String
  • get the result back with ability to cast it to known types in the surronding environment that created the repl instance
  • get a customized jline-based loop similar to the actual scala repl

Currently, given the code here, I finally managed to setup my own ReplDriver but I found the api difficult to grasp with a lot of imports etc that i did not understand.

I actually just got this hack to build and run like this (with a big fat assembly will all the compiler dependencies - I have published the fat jar here under assets called reqt4.jar ):

$ java -jar reqt4.jar 
*** Hello reqt4 ***
Welcome to Scala 3.1.1-RC1 (17.0.4, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.

reqt4> val s1 = reqt.replDriver.run("val x = 42")(using reqt.replDriver.exposeMyState.get)
val x: Int = 42

reqt4> s1 == reqt.replDriver.exposeMyState.get
val res0: Boolean = true

reqt4> val s2 = reqt.replDriver.run("x")(using reqt.replDriver.exposeMyState.get)
val res0: Int = 42


Unfortunately this did not work:

reqt4> x                                                                                                                       
-- [E006] Not Found Error: -----------------------------------------------------
1 |x
  |^
  |Not found: x

reqt4> val y = 42
val y: Int = 42

reqt4> val s3 = reqt.replDriver.run("y")(using reqt.replDriver.exposeMyState.get)
-- [E006] Not Found Error: -----------------------------------------------------
1 |y
  |^
  |Not found: y

So I guess we need a new kind of ReplDriver with mutable state… But I dont know yet how to make the meta-circular session above work…

The state shouldn’t be an issue: You feed a state to run, which returns an updated state, to feed to the next call to run. It’s incorporating code defined elsewhere that is the challenge.

Well, as you see above, the code in my Main is accessible inside the repl that my Main runs… For instance the reqt.replDriver itself is accessible inside the running repl instance… If that is what you mean?

package reqt

import dotty.tools.repl.{*, given}

val replDriver =  OpenReplDriver(Array("-usejavacp"), prompt = "reqt4> ")

var finalState:  Option[State] = None

object Main:
  def main(args: Array[String]): Unit = 
    println("*** Hello reqt4 ***")
    finalState = replDriver.tryRunning()

But if you evaluate the string "val y = 42" it should be added to the same state and it should work. Definitions outside of eval go to a different state.

1 Like

You can see my hacked ReplDriver here with the exposed state hack, but I didn’t get it to work with new definitions of the same running session outside the run method, so my exposed state is somehow not really updated and I don’t get why (but I’m starting to get tired and need to eat very soon :slight_smile: )