Last active
November 13, 2024 18:27
-
-
Save Jacoby6000/a60b024dd22ac7d6a2c8ac46ee06cc3d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.reflect.ClassTag | |
import scala.reflect.runtime.universe._ | |
import scala.language.dynamics | |
import scala.language.implicitConversions | |
import scala.reflect.api.{Mirror => SRMirror, TypeCreator, Universe} | |
import scala.Predef.{implicitly, locally, any2stringadd => _} | |
class ScalaScripter(val a: Any) extends Dynamic { | |
import ScalaScript.{enable => toScalaScript, disable => unScalaScript} | |
private val mirror = runtimeMirror(getClass.getClassLoader) | |
private val runtimeClass = a.getClass | |
private val classMirror = mirror.reflect(a) | |
private val symbol = classMirror.symbol | |
private val typ = symbol.toType | |
implicit val typeTag: TypeTag[_] = TypeTag(mirror, new TypeCreator { self => | |
def apply[U <: Universe with Singleton](m: SRMirror[U]): U#Type = | |
typ.asInstanceOf[U#Type] | |
}) | |
implicit val classTag: ClassTag[_] = ClassTag(runtimeClass) | |
private val typeOfA = typeOf(typeTag) | |
private def rewriteKey(key: String): String = | |
key.replaceAll("""\$""", """\$dollar""").replaceAll("""\+""", """\$plus""").replaceAll(""":""", """\$colon""") | |
val map: scala.collection.mutable.HashMap[String, ScalaScripter] = | |
scala.collection.mutable.HashMap() | |
// lol | |
private def doSafe[B](key: String, args: List[Any], a : => B): B = | |
try { a } catch { | |
case ex: ScalaReflectionException => | |
val message = s"Failed to invoke function '$key' with args ${args.mkString("'", "' , '", "'")} on instance of type ${typ}" | |
println(message + ": " + ex.getMessage) // for scastie visibility | |
throw new RuntimeException(message, ex) | |
case ex: IllegalArgumentException => | |
val message = s"Failed to invoke function '$key' with args ${args.mkString("'", "' , '", "'")} on instance of type ${typ}" | |
println(message + ": " + ex.getMessage) // for scastie visibility | |
throw new RuntimeException(message, ex) | |
} | |
private def varargsToValueAny(args: Any*): Any = | |
if(args.isEmpty) null | |
else if (args.length == 1) args.head | |
else args | |
private def findMethod(methodName: String, args: List[Any]): Option[MethodMirror] = { | |
val methodSymbols = classMirror.symbol.typeSignature.member(TermName(rewriteKey(methodName))) match { | |
case NoSymbol => Nil | |
case sym if sym.isMethod && sym.asMethod.isOverloaded => sym.asTerm.alternatives.map(_.asMethod) | |
case sym if sym.isMethod => List(sym.asMethod) | |
case _ => Nil | |
} | |
val methodsWithCorrectArgCount = methodSymbols.filter { method => | |
val params = method.paramLists.headOption.getOrElse(Nil) | |
params.length == args.length | |
} | |
methodsWithCorrectArgCount.find { method => | |
val params = method.paramLists.headOption.getOrElse(Nil) | |
params.zip(args).forall { case (param, arg) => | |
val paramType = param.typeSignature | |
val argType = mirror.reflectClass(mirror.classSymbol(arg.getClass)).symbol.toType | |
argType <:< paramType | |
} | |
}.orElse(methodsWithCorrectArgCount.headOption).map(classMirror.reflectMethod) | |
} | |
def invokeMethod(key: String, value: Any*): Any = { | |
findMethod(key, value.toList) | |
.map(_.apply(value: _*)) | |
.getOrElse { | |
val message = s"No such method ${key} matches args ${args.mkString("'", "' , '", "'")} on instance of type ${typ}" | |
println(message) | |
throw new NoSuchMethodException(message) | |
} | |
} | |
def selectDynamic(key: String): ScalaScripter = doSafe(key, Nil, | |
map.get(rewriteKey(key)).map(unScalaScript(_)).getOrElse { | |
try { | |
applyDynamic(key)() | |
} catch { | |
case _: Throwable => classMirror.reflectField(typeOfA.decl(TermName(rewriteKey(key))).asTerm.accessed.asTerm) | |
} | |
}) | |
def updateDynamic(key: String, value: Any*): ScalaScripter = doSafe(key, value.toList, { | |
try { | |
invokeMethod(key + "_=", value: _*) | |
} catch { | |
case _: Throwable => map.update(key, varargsToValueAny(value: _*)) | |
} | |
}) | |
def applyDynamic(key: String)(value: Any*): ScalaScripter = doSafe (key, value.toList, { | |
invokeMethod(key, value: _*) | |
}) | |
override def toString = if(a != null) a.toString else "null" | |
} | |
object ScalaScript { | |
type In = ScalaScripter | |
type Out = Any | |
implicit def enable[A](a: A): ScalaScripter = new ScalaScripter(a) | |
implicit def disable(s: ScalaScripter): Any = s.a | |
} | |
import ScalaScript._ | |
val x: In = List(1,2,3) | |
val y: In = List("4", "5", "6") | |
println(y.reverse) | |
println(y.reverse ::: x) // fails at runtime, but compiles |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment