package wow.doge.mygame.state import akka.actor.typed.scaladsl.Behaviors import ammonite.Main import akka.actor.typed.ActorRef import ammonite.runtime.Storage.Folder import ammonite.main.Defaults import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.ActorContext import ammonite.util.Res.Success import javax.script.ScriptEngine import javax.script.ScriptEngineManager import groovy.util.GroovyScriptEngine import cats.implicits._ object ScriptActor { type Kotlin type MyScriptEngine[T] = ScriptEngine type KotlinScriptEngine = MyScriptEngine[Kotlin] final case class Error(reason: String) sealed trait Command final case class CompileAny( path: os.Path, result: ActorRef[Either[Error, Any]] ) extends Command def defaultScalaRunner() = ammonite .Main( storageBackend = new Folder( // os.pwd / "target" Defaults.ammoniteHome, isRepl = false ) ) def defaultKotlinRunner(): KotlinScriptEngine = { val manager = new ScriptEngineManager() val engine = manager.getEngineByExtension("main.kts") engine } def defaultGroovyRunner(): GroovyScriptEngine = new GroovyScriptEngine(os.pwd.toString()) def apply( scalaRunner: Main = defaultScalaRunner(), kotlinRunner: KotlinScriptEngine = defaultKotlinRunner(), groovyRunner: GroovyScriptEngine = defaultGroovyRunner() // parent: ActorRef[ScriptStoringActor.Command] ): Behavior[ScriptActor.Command] = Behaviors.setup(ctx => new ScriptActor( scalaRunner, kotlinRunner, groovyRunner, ctx ).receiveMessage ) sealed trait ScriptType case object ScalaType extends ScriptType case object KotlinType extends ScriptType case object GroovyType extends ScriptType def determineScriptType(path: os.Path): Either[Error, ScriptType] = path.toString match { case s if s.endsWith(".sc") => Right(ScalaType) case s if s.endsWith(".main.kts") => Right(KotlinType) case s if s.endsWith(".groovy") => Right(GroovyType) case _ => Left(Error("Unknown script type")) } def runScala(path: os.Path, scalaRunner: ammonite.Main): Either[Error, Any] = scalaRunner .runScript( path, Seq.empty ) ._1 match { case ammonite.util.Res.Exception(t, msg) => Left(Error(msg)) case Success(obj) => Right(obj) case _ => Left(Error("Failed to run script")) } def runKotlin( path: os.Path, kotlinRunner: KotlinScriptEngine ): Either[Error, Any] = Either .catchNonFatal(kotlinRunner.eval(os.read(path))) .leftMap(t => Error(t.getMessage())) def runGroovy( path: os.Path, groovyRunner: GroovyScriptEngine ): Either[Error, Any] = Either .catchNonFatal(groovyRunner.run(path.relativeTo(os.pwd).toString(), "")) .leftMap(t => Error(t.getMessage())) def ensureReturnedObjectNotNull(scriptObject: Any): Either[Error, Any] = Either.fromOption(Option(scriptObject), Error("unknown object")) } class ScriptActor( val scalaRunner: ammonite.Main, val kotlinRunner: ScriptActor.KotlinScriptEngine, val groovyRunner: GroovyScriptEngine, context: ActorContext[ScriptActor.Command] ) { import ScriptActor._ def receiveMessage: Behavior[Command] = Behaviors.receiveMessage { msg => msg match { case CompileAny(path, requester) => context.log.debug(s"Received $path") val res = getScript(path) context.log.debug(s"result = $res") requester ! res Behaviors.same } } def getScript(path: os.Path): Either[Error, Any] = determineScriptType(path) match { case Right(ScalaType) => runScala(path, scalaRunner).flatMap(ensureReturnedObjectNotNull) case Right(KotlinType) => runKotlin(path, kotlinRunner).flatMap(ensureReturnedObjectNotNull) case Right(GroovyType) => runGroovy(path, groovyRunner).flatMap(ensureReturnedObjectNotNull) case l @ Left(err) => l } }