From b63a60fb5441b014d08db112a296a6b0f401c3ea Mon Sep 17 00:00:00 2001 From: Jon Pretty Date: Wed, 19 Feb 2025 12:01:26 +0100 Subject: [PATCH] Some progress on Austronesian --- build.mill | 2 +- .../src/core/austronesian-core.scala | 9 ++++ .../src/core/austronesian.Austronesian2.scala | 50 ++++++++++++++++++- .../src/core/austronesian.Proxy.scala | 12 +++++ .../src/core/austronesian.Restorable.scala | 39 +++++++++++++++ .../src/test/austronesian.Example.scala | 6 +++ .../src/test/austronesian.Tests.scala | 12 +++++ 7 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 lib/austronesian/src/core/austronesian.Proxy.scala create mode 100644 lib/austronesian/src/core/austronesian.Restorable.scala create mode 100644 lib/austronesian/src/test/austronesian.Example.scala diff --git a/build.mill b/build.mill index 5e060d05e..9b35292dd 100644 --- a/build.mill +++ b/build.mill @@ -302,7 +302,7 @@ object anticipation extends SoundnessModule { object austronesian extends SoundnessModule { object core extends SoundnessSubModule { - def moduleDeps = Seq(wisteria.core, distillate.core) + def moduleDeps = Seq(wisteria.core, distillate.core, hellenism.core) } object test extends ProbablyTestModule { diff --git a/lib/austronesian/src/core/austronesian-core.scala b/lib/austronesian/src/core/austronesian-core.scala index 8ebd689ea..c9b48783d 100644 --- a/lib/austronesian/src/core/austronesian-core.scala +++ b/lib/austronesian/src/core/austronesian-core.scala @@ -34,9 +34,18 @@ package austronesian import anticipation.* +import hellenism.* import prepositional.* export Austronesian.Stdlib extension [ValueType: Encodable in Stdlib](value: ValueType) def stdlib: Stdlib = ValueType.encoded(value) + +extension (classloader: Classloader) + inline def isolate[ResultType](inline invoke: ResultType): ResultType = + ${Austronesian2.isolated('classloader, 'invoke)} + +extension (context: StringContext) + def o(): Proxy = Proxy(context.parts.head.tt, true) + def c(): Proxy = Proxy(context.parts.head.tt, false) diff --git a/lib/austronesian/src/core/austronesian.Austronesian2.scala b/lib/austronesian/src/core/austronesian.Austronesian2.scala index 01ee7a384..2181b8dc3 100644 --- a/lib/austronesian/src/core/austronesian.Austronesian2.scala +++ b/lib/austronesian/src/core/austronesian.Austronesian2.scala @@ -34,14 +34,20 @@ package austronesian import scala.compiletime.* +import scala.quoted.* import anticipation.* import contingency.* import distillate.* +import fulminate.* +import hellenism.* import prepositional.* +import proscenium.* import rudiments.* import wisteria.* +given Realm = realm"austronesian" + object Austronesian2: object EncodableDerivation extends Derivation[[Type] =>> Type is Encodable in Stdlib]: @@ -73,9 +79,51 @@ object Austronesian2: summonInline[Tactic[StdlibError]].give(abort(StdlibError())) inline def split[DerivationType: SumReflection]: DerivationType is Decodable in Stdlib = - case Array(label: String, stdlib: Stdlib) => + case Array(label: String @unchecked, stdlib: Stdlib @unchecked) => delegate(label): [VariantType <: DerivationType] => _.decoded(stdlib) case other => summonInline[Tactic[StdlibError]].give(abort(StdlibError())) + + def isolated[ResultType: Type](classloader: Expr[Classloader], invoke: Expr[ResultType]) + (using Quotes) + : Expr[ResultType] = + + import quotes.reflect.* + + invoke.asTerm match + case term => println(term) + + '{???} + + + def proxy + (className: Expr[Text], + methodName: Expr[String], + arguments: Expr[Seq[Any]], + classloader: Expr[Classloader], + singleton: Expr[Boolean]) + (using Quotes) + : Expr[Any] = + + import quotes.reflect.* + + val args: IArray[Expr[Stdlib]] = arguments.absolve match + case Varargs(arguments) => IArray.from(arguments).map: + case '{ $argument: argumentType } => + + val encodable = Expr.summon[argumentType is Encodable in Stdlib].getOrElse: + halt(m"${Type.of[argumentType]} is not encodable as a standard library parameter") + + '{$encodable.encoded($argument)} + + if singleton.valueOrAbort then + '{ val javaClass = Class.forName($className.s+"$", true, $classloader.java).nn + val instance = javaClass.getField("MODULE$").nn.get(null).nn + val method = javaClass.getMethod($methodName, classOf[Object]).nn + method.invoke(instance, null) } + else + '{ val javaClass = Class.forName($className.s, true, $classloader.java).nn + val method = javaClass.getMethod($methodName).nn + method.invoke(null, null) } diff --git a/lib/austronesian/src/core/austronesian.Proxy.scala b/lib/austronesian/src/core/austronesian.Proxy.scala new file mode 100644 index 000000000..55bd55500 --- /dev/null +++ b/lib/austronesian/src/core/austronesian.Proxy.scala @@ -0,0 +1,12 @@ +package austronesian + +import language.dynamics + +import anticipation.* +import hellenism.* + +class Proxy(name: Text, singleton: Boolean) extends Dynamic: + transparent inline def applyDynamic(method: String)(inline arguments: Any*) + (using classloader: Classloader) + : Any = + ${Austronesian2.proxy('name, 'method, 'arguments, 'classloader, 'singleton)} diff --git a/lib/austronesian/src/core/austronesian.Restorable.scala b/lib/austronesian/src/core/austronesian.Restorable.scala new file mode 100644 index 000000000..c7081559d --- /dev/null +++ b/lib/austronesian/src/core/austronesian.Restorable.scala @@ -0,0 +1,39 @@ +package austronesian + +import scala.quoted.* + +import anticipation.* +import hellenism.* +import wisteria.* + +object Restorable extends ProductDerivation[[Type] =>> Type is Restorable]: + given Text is Restorable: + def restore(value: Expr[Stdlib])(using Quotes, Classloader): Expr[Text] = + '{ if $value.isInstanceOf[String] then $value.asInstanceOf[Text] + else throw new RuntimeException() } + + given Int is Restorable: + def restore(value: Expr[Stdlib])(using Quotes, Classloader): Expr[Int] = + '{ if $value.isInstanceOf[Int] then $value.asInstanceOf[Int] + else throw new RuntimeException() } + + inline def join[DerivationType <: Product: ProductReflection]: DerivationType is Restorable = + new Restorable: + type Self = DerivationType + + def restore(value: Expr[Stdlib])(using quotes: Quotes, classloader: Classloader) + : Expr[Self] = + + import quotes.reflect.* + given Type[DerivationType] = compiletime.summonInline[Type[DerivationType]] + + val x: Symbol = TypeRepr.of[DerivationType].typeSymbol.primaryConstructor + + println(x) + ??? + + +trait Restorable: + type Self + + def restore(value: Expr[Stdlib])(using Quotes, Classloader): Expr[Self] diff --git a/lib/austronesian/src/test/austronesian.Example.scala b/lib/austronesian/src/test/austronesian.Example.scala new file mode 100644 index 000000000..131cd5025 --- /dev/null +++ b/lib/austronesian/src/test/austronesian.Example.scala @@ -0,0 +1,6 @@ +package austronesian + + +object Example: + def run(name: Any): Unit = + println(s"Hello, $name!") diff --git a/lib/austronesian/src/test/austronesian.Tests.scala b/lib/austronesian/src/test/austronesian.Tests.scala index 66de8156d..bbbaeb3d6 100644 --- a/lib/austronesian/src/test/austronesian.Tests.scala +++ b/lib/austronesian/src/test/austronesian.Tests.scala @@ -87,3 +87,15 @@ object Tests extends Suite(t"Austronesian tests"): test(t"Roundtrip a complex datatype"): unsafely(data.stdlib.decode[List[Something]]) . assert(_ == data) + + + + + suite(t"Proxy testing"): + test(t"Invoke a Proxy method"): + import classloaders.system + o"austronesian.Example".run(t"hello") + .assert() + + test(t"Invoke the macro"): + summon[Person is Restorable]