Skip to content

Commit

Permalink
path dsl
Browse files Browse the repository at this point in the history
  • Loading branch information
arainko committed Jul 9, 2023
1 parent 548af9c commit a8a4f48
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ object Interpreter {

case Plan.BetweenUnwrappedWrapped(sourceTpe, destTpe) =>
Constructor(destTpe.repr).appliedTo(value.asTerm).asExpr

case Plan.UserDefined(source, dest, transformer) =>
transformer match {
case '{ $t: UserDefinedTransformer[src, dest] } =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.github.arainko.ducktape

import scala.quoted.*
import io.github.arainko.ducktape.internal.macros.DebugMacros
import scala.collection.mutable.ListBuffer
import scala.annotation.tailrec

object PathMatcher {
// inline def run[A](inline expr: Selector ?=> A => Any) = ${ readPath('expr) }

def readPath[A: Type](expr: Expr[Selector ?=> A => Any])(using Quotes): List[String | Type[?]] = {
import quotes.reflect.{ Selector as _, * }

@tailrec
def recurse(using Quotes)(acc: List[String | Type[?]], term: quotes.reflect.Term): List[String | Type[?]] = {
import quotes.reflect.*

given Printer[TypeRepr] = Printer.TypeReprShortCode

term match {
case Inlined(_, _, tree) =>
recurse(acc, tree)
case Lambda(_, tree) =>
recurse(acc, tree)
case Block(_, tree) =>
recurse(acc, tree)
case Select(tree, name) =>
recurse(name :: acc, tree)
case TypeApply(Apply(TypeApply(Select(Ident(_), "at"), _), tree :: Nil), tpe :: Nil) =>
recurse(tpe.tpe.asType :: acc, tree)
case Ident(_) => acc
case other => report.errorAndAbort(other.show(using Printer.TreeShortCode))
}
}
recurse(Nil, expr.asTerm)
}
}
12 changes: 6 additions & 6 deletions ducktape/src/main/scala/io/github/arainko/ducktape/Plan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ enum Plan[+E <: PlanError] {
s"UserDefined(${sourceTpe.show}, ${destTpe.show}, Transformer[...])"
case PlanError(sourceTpe, destTpe, context, message) =>
s"Error(${sourceTpe.show}, ${destTpe.show}, ${context.render}, ${message})"
case BetweenUnwrappedWrapped(sourceTpe, destTpe) =>
case BetweenUnwrappedWrapped(sourceTpe, destTpe) =>
s"BetweenUnwrappedWrapped(${sourceTpe.show}, ${destTpe.show})"
case BetweenWrappedUnwrapped(sourceTpe, destTpe, fieldName) =>
case BetweenWrappedUnwrapped(sourceTpe, destTpe, fieldName) =>
s"BetweenWrappedUnwrapped(${sourceTpe.show}, ${destTpe.show}, $fieldName)"
}
}
Expand Down Expand Up @@ -78,15 +78,15 @@ enum Plan[+E <: PlanError] {
}

case Upcast(sourceTpe: Type[?], destTpe: Type[?]) extends Plan[Nothing]
case UserDefined(sourceTpe: Type[?], destTpe: Type[?], transformer: Expr[UserDefinedTransformer[?, ?]]) extends Plan[Nothing]
case BetweenUnwrappedWrapped(sourceTpe: Type[?], destTpe: Type[?]) extends Plan[Nothing]
case BetweenWrappedUnwrapped(sourceTpe: Type[?], destTpe: Type[?], fieldName: String) extends Plan[Nothing]
case BetweenSingletons(sourceTpe: Type[?], destTpe: Type[?], expr: Expr[Any]) extends Plan[Nothing]
case BetweenProducts(sourceTpe: Type[?], destTpe: Type[?], fieldPlans: Map[String, Plan[E]]) extends Plan[E]
case BetweenCoproducts(sourceTpe: Type[?], destTpe: Type[?], casePlans: Map[String, Plan[E]]) extends Plan[E]
case BetweenOptions(sourceTpe: Type[?], destTpe: Type[?], plan: Plan[E]) extends Plan[E]
case BetweenNonOptionOption(sourceTpe: Type[?], destTpe: Type[?], plan: Plan[E]) extends Plan[E]
case BetweenCollections(destCollectionTpe: Type[?], sourceTpe: Type[?], destTpe: Type[?], plan: Plan[E]) extends Plan[E]
case BetweenSingletons(sourceTpe: Type[?], destTpe: Type[?], expr: Expr[Any]) extends Plan[Nothing]
case UserDefined(sourceTpe: Type[?], destTpe: Type[?], transformer: Expr[UserDefinedTransformer[?, ?]]) extends Plan[Nothing]
case BetweenUnwrappedWrapped(sourceTpe: Type[?], destTpe: Type[?]) extends Plan[Nothing]
case BetweenWrappedUnwrapped(sourceTpe: Type[?], destTpe: Type[?], fieldName: String) extends Plan[Nothing]
// TODO: Use a well typed error definition here and not a String
case Error(sourceTpe: Type[?], destTpe: Type[?], context: Plan.Context, message: String) extends Plan[Plan.Error]
}
Expand Down
47 changes: 34 additions & 13 deletions ducktape/src/main/scala/io/github/arainko/ducktape/Planner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.arainko.ducktape

import scala.quoted.*
import io.github.arainko.ducktape.internal.modules.*
import io.github.arainko.ducktape.Plan.Context

object Planner {
import Structure.*
Expand All @@ -10,17 +11,19 @@ object Planner {

def printMacro[A: Type, B: Type](using Quotes): Expr[Unit] = {
val plan = createPlan[A, B]
quotes.reflect.report.info(plan.toString())
quotes.reflect.report.info(plan.show)
'{}
}

def createPlan[Source: Type, Dest: Type](using Quotes): Plan[Plan.Error] = recurseAndCreatePlan[Source, Dest](Plan.Context(Type[Source], Type[Dest], Vector.empty))
def createPlan[Source: Type, Dest: Type](using Quotes): Plan[Plan.Error] =
recurseAndCreatePlan[Source, Dest](Plan.Context(Type[Source], Type[Dest], Vector.empty))

private def recurseAndCreatePlan[Source: Type, Dest: Type](context: Plan.Context)(using Quotes): Plan[Plan.Error] = {
val src = Structure.of[Source]
val dest = Structure.of[Dest]
val plan = recurse(src, dest, context)
val updated = plan.updateAt(Type.of[Sum1.Leaf2] :: "duspko" :: Nil)(plan => Plan.BetweenSingletons(Type.of[Int], Type.of[Int], '{ 123 }))
val updated =
plan.updateAt(Type.of[Sum1.Leaf2] :: "duspko" :: Nil)(plan => Plan.BetweenSingletons(Type.of[Int], Type.of[Int], '{ 123 }))
// println(updated.get.show)
println()
println(plan.show)
Expand All @@ -47,38 +50,56 @@ object Planner {

case (source: Product, dest: Product) =>
val fieldPlans = dest.fields.map { (destField, destFieldStruct) =>
val plan =
val plan =
source.fields
.get(destField)
.map(recurse(_, destFieldStruct, context.add(destField)))
.getOrElse(Plan.Error(source.tpe, dest.tpe, context.add(destField), s"No field named '$destField' found in ${source.tpe.repr.show}"))
.getOrElse(
Plan.Error(
source.tpe,
dest.tpe,
context.add(destField),
s"No field named '$destField' found in ${source.tpe.repr.show}"
)
)
destField -> plan
}
Plan.BetweenProducts(source.tpe, dest.tpe, fieldPlans)

case (source: Coproduct, dest: Coproduct) =>
val casePlans = source.children.map { (sourceName, sourceCaseStruct) =>
val plan =
dest
.children
val plan =
dest.children
.get(sourceName)
.map(recurse(sourceCaseStruct, _, context.add(sourceCaseStruct.tpe)))
.getOrElse(Plan.Error(source.tpe, dest.tpe, context.add(sourceCaseStruct.tpe), s"No child named '$sourceName' found in ${dest.tpe.repr.show}"))
.getOrElse(
Plan.Error(
source.tpe,
dest.tpe,
context.add(sourceCaseStruct.tpe),
s"No child named '$sourceName' found in ${dest.tpe.repr.show}"
)
)
sourceName -> plan
}
Plan.BetweenCoproducts(source.tpe, dest.tpe, casePlans)

case (source: Structure.Singleton, dest: Structure.Singleton) if source.name == dest.name =>
Plan.BetweenSingletons(source.tpe, dest.tpe, dest.value)

case (source: ValueClass, dest) if source.paramTpe.repr <:< dest.tpe.repr =>
case (source: ValueClass, dest) if source.paramTpe.repr <:< dest.tpe.repr =>
Plan.BetweenWrappedUnwrapped(source.tpe, dest.tpe, source.paramFieldName)

case (source, dest: ValueClass) if source.tpe.repr <:< dest.paramTpe.repr =>
case (source, dest: ValueClass) if source.tpe.repr <:< dest.paramTpe.repr =>
Plan.BetweenUnwrappedWrapped(source.tpe, dest.tpe)

case (source, dest) =>
Plan.Error(source.tpe, dest.tpe, context, s"Couldn't build a transformation plan between ${source.tpe.repr.show} and ${dest.tpe.repr.show}")
case (source, dest) =>
Plan.Error(
source.tpe,
dest.tpe,
context,
s"Couldn't build a transformation plan between ${source.tpe.repr.show} and ${dest.tpe.repr.show}"
)
}

object UserDefinedTransformation {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.github.arainko.ducktape

import io.github.arainko.ducktape.internal.macros.DebugMacros
import scala.annotation.nowarn

final case class Value(int: Int) extends AnyVal
final case class ValueGen[A](int: A) extends AnyVal

Expand All @@ -18,8 +21,40 @@ enum Sum2 {
case Single
}

trait Selector {
extension [A](self: A) def at[B <: A]: B
}

@main def main = {

def costam[A](selectors: (Selector ?=> A => Any)*) = ???

val sel: Selector = ???

// Inlined(
// None,
// Nil,
// TypeApply(
// Apply(TypeApply(Select(Ident("sel"), "at"), List(TypeIdent("Sum1"))), List(Select(Ident("Sum1"), "Single"))),
// List(TypeSelect(Ident("Sum1"), "Leaf1"))
// )
// )

// val evidence$1 =
// DebugMacros.structure(
// sel.at[Sum1](Sum1.Single)[Sum1.Leaf1]
// )


PathMatcher.run[Sum1](_.at[Sum1].at[Sum1.Single.type])

// DebugMacros.code {
// costam[Sum1](
// _.at[Sum1.Leaf1].int.at[Int].toByte.toByte.toByte.at[Byte]
// // _.at[Sum1.Leaf2].str,
// )
// }

final case class HKT[F[_]](value: F[Int])

final case class Person1(int: Int, str: String, opt: List[Nested1])
Expand All @@ -32,12 +67,11 @@ enum Sum2 {
val p = Person1(1, "asd", Nested1(1) :: Nil)

// Planner.print[Person1, Person2]


// Planner.print[Person1, Person2]

// Transformer.Debug.showCode {
Interpreter.transformPlanned[Person1, Person2](p)
// Interpreter.transformPlanned[Person1, Person2](p)

// }
}

0 comments on commit a8a4f48

Please sign in to comment.