package wow.doge.mygame.state import javax.script.ScriptEngine import javax.script.ScriptEngineManager import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.typed.LogOptions import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors import ammonite.Main import ammonite.main.Defaults import ammonite.runtime.Storage.Folder import ammonite.util.Res.Success import cats.implicits._ import com.softwaremill.tagging._ import com.typesafe.scalalogging.Logger import groovy.util.GroovyScriptEngine import org.slf4j.event.Level object ScriptActor { /** * aka script representation */ sealed trait ScriptTag type ScriptObject = Any @@ ScriptTag trait Kotlin type KotlinScriptEngine = ScriptEngine @@ Kotlin final case class Error(reason: String) sealed trait Command final case class CompileAny( path: os.Path, result: ActorRef[Either[Error, ScriptObject]] ) extends Command final case class CompileAll( paths: Seq[os.Path], result: ActorRef[Map[os.Path, Either[Error, Any]]] ) extends Command val defaultScalaRunner = ammonite .Main( storageBackend = new Folder( // os.pwd / "target" Defaults.ammoniteHome, isRepl = false ) ) val defaultKotlinRunner: KotlinScriptEngine = { val manager = new ScriptEngineManager() val engine = manager.getEngineByExtension("main.kts") engine.taggedWith[Kotlin] } val defaultGroovyRunner: GroovyScriptEngine = new GroovyScriptEngine(os.pwd.toString) def apply( scalaRunner: Main = defaultScalaRunner, kotlinRunner: KotlinScriptEngine = defaultKotlinRunner, groovyRunner: GroovyScriptEngine = defaultGroovyRunner ): Behavior[ScriptActor.Command] = Behaviors.logMessages( LogOptions() .withLevel(Level.TRACE) .withLogger( Logger[ScriptActor].underlying ), 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 { case CompileAny(path, requester) => context.log.debug(s"Received $path") val res = getScript(path) context.log.debug(s"result = $res") requester ! res Behaviors.same case CompileAll(paths, requester) => context.log.debug(s"Received $paths") requester ! compileAll(paths) Behaviors.same } def getScript(path: os.Path): Either[Error, ScriptObject] = determineScriptType(path) match { case Right(ScalaType) => runScala(path, scalaRunner) .flatMap(ensureReturnedObjectNotNull) .map(_.taggedWith[ScriptTag]) case Right(KotlinType) => runKotlin(path, kotlinRunner) .flatMap(ensureReturnedObjectNotNull) .map(_.taggedWith[ScriptTag]) case Right(GroovyType) => runGroovy(path, groovyRunner) .flatMap(ensureReturnedObjectNotNull) .map(_.taggedWith[ScriptTag]) case l @ Left(err) => l.map(_.taggedWith[ScriptTag]) } type LOL = Map[os.Path, Either[wow.doge.mygame.state.ScriptActor.Error, Any]] def compileAll( paths: Seq[os.Path] ): LOL = { @annotation.tailrec def loop( paths: Seq[os.Path], scriptsMap: Map[ os.Path, Either[wow.doge.mygame.state.ScriptActor.Error, Any] ] ): LOL = paths match { case head :: next => loop(next, scriptsMap + (head -> getScript(head))) case Nil => scriptsMap } loop(paths, Map.empty) } }