Skip to content

Commit

Permalink
use update methods on Plans instead of raw copy
Browse files Browse the repository at this point in the history
  • Loading branch information
arainko committed Dec 15, 2024
1 parent 61ad87b commit 6ec73ec
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 62 deletions.
107 changes: 61 additions & 46 deletions ducktape/src/main/scala/io/github/arainko/ducktape/internal/Plan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ private[ducktape] object Plan {
private val alternative: () => Plan[Erroneous, Nothing]
) extends Plan[Nothing, Nothing] {
lazy val alt: Plan[Erroneous, Nothing] = alternative()

inline def update(inline f: Plan[Erroneous, Nothing] => Plan[Erroneous, Nothing]) =
copy(alternative = () => f(alternative()))
}

case class UserDefined[+F <: Fallible](
Expand Down Expand Up @@ -80,7 +83,7 @@ private[ducktape] object Plan {
inline def updateEach[EE >: E <: Erroneous, FF >: F <: Fallible](
inline f: Plan[E, F] => Plan[EE, FF]
): BetweenProductFunction[EE, FF] =
copy(argPlans = argPlans.transform((_, argPlan) => f(argPlan)))
copy(argPlans = argPlans.updateEach(f))

inline def updateOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argName: String)(
inline f: Plan[E, F] => Plan[EE, FF],
Expand All @@ -104,25 +107,21 @@ private[ducktape] object Plan {
inline def updateEach[EE >: E <: Erroneous, FF >: F <: Fallible](
inline f: Plan[E, F] => Plan[EE, FF]
): BetweenTupleFunction[EE, FF] =
copy(argPlans = argPlans.transform((_, argPlan) => f(argPlan)))
copy(argPlans = argPlans.updateEach(f))

inline def updateByNameOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argName: String)(
inline f: Plan[E, F] => Plan[EE, FF],
inline alt: Plan.Error
): Plan[Erroneous, FF] = {
argPlans
.get(argName)
.map(argPlan => copy(argPlans = argPlans.updated(argName, f(argPlan))))
.getOrElse(alt)
}
) = argPlans
.updateByName(argName)(f, plans => copy(argPlans = plans))
.getOrElse(alt)

inline def updateByIndexOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argIdx: Int)(
inline f: Plan[E, F] => Plan[EE, FF],
alt: Plan.Error
): Plan[Erroneous, FF] =
argPlans.toVector
.lift(argIdx)
.map((name, fieldPlan) => copy(argPlans = argPlans.updated(name, f(fieldPlan))))
argPlans
.updateByIndex(argIdx)(f, plans => copy(argPlans = plans))
.getOrElse(alt)
}

Expand Down Expand Up @@ -192,8 +191,7 @@ private[ducktape] object Plan {
) extends Plan[E, F] {
inline def updateEach[EE >: E <: Erroneous, FF >: F <: Fallible](
inline f: Plan[E, F] => Plan[EE, FF]
) =
copy(plans = plans.map(argPlan => f(argPlan)))
): BetweenProductTuple[EE, FF] = copy(plans = plans.map(f))

inline def updateByNameOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](name: String)(
inline f: Plan[E, F] => Plan[EE, FF],
Expand All @@ -213,43 +211,29 @@ private[ducktape] object Plan {
inline alt: Plan.Error
): Plan[Erroneous, FF] =
plans
.lift(argIdx)
.map(fieldPlan => copy(plans = plans.updated(argIdx, f(fieldPlan))))
.updateByIndex(argIdx)(f, plans => copy(plans = plans))
.getOrElse(alt)
}

case class BetweenTupleProduct[+E <: Erroneous, +F <: Fallible](
source: Structure.Tuple,
dest: Structure.Product,
plans: VectorMap[String, Plan[E, F]]
) extends Plan[E, F],
Ops.UpdateByName[E, F, BetweenTupleProduct] {
) extends Plan[E, F] {
inline def updateEach[EE >: E <: Erroneous, FF >: F <: Fallible](
inline f: Plan[E, F] => Plan[EE, FF]
) =
copy(plans = plans.transform((_, argPlan) => f(argPlan)))
): BetweenTupleProduct[EE, FF] = copy(plans = plans.updateEach(f))

def rebuild[EE <: Erroneous, FF <: Fallible](plans: VectorMap[String, Plan[EE, FF]]): BetweenTupleProduct[EE, FF] =
copy(plans = plans)

// inline def updateByNameOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argName: String)(
// inline f: Plan[E, F] => Plan[EE, FF],
// inline alt: Plan.Error
// ): Plan[Erroneous, FF] = {
// plans
// .get(argName)
// .map(argPlan => copy(plans = plans.updated(argName, f(argPlan))))
// .getOrElse(alt)
// }
inline def updateByNameOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argName: String)(
inline f: Plan[E, F] => Plan[EE, FF],
inline alt: Plan.Error
): Plan[Erroneous, FF] = plans.updateByName(argName)(f, plans => copy(plans = plans)).getOrElse(alt)

inline def updateByIndexOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argIdx: Int)(
inline f: Plan[E, F] => Plan[EE, FF],
inline alt: Plan.Error
): Plan[Erroneous, FF] =
plans.toVector
.lift(argIdx)
.map((name, fieldPlan) => copy(plans = plans.updated(name, f(fieldPlan))))
.getOrElse(alt)
plans.updateByIndex(argIdx)(f, plans => copy(plans = plans)).getOrElse(alt)
}

case class BetweenTuples[+E <: Erroneous, +F <: Fallible](
Expand All @@ -260,42 +244,52 @@ private[ducktape] object Plan {
inline def updateEach[EE >: E <: Erroneous, FF >: F <: Fallible](
inline f: Plan[E, F] => Plan[EE, FF]
) =
copy(plans = plans.map(argPlan => f(argPlan)))
copy(plans = plans.map(f))

inline def updateByIndexOrElse[EE >: E <: Erroneous, FF >: F <: Fallible](argIdx: Int)(
inline f: Plan[E, F] => Plan[EE, FF],
inline alt: Plan.Error
): Plan[Erroneous, FF] =
plans
.lift(argIdx)
.map(fieldPlan => copy(plans = plans.updated(argIdx, f(fieldPlan))))
.getOrElse(alt)
plans.updateByIndex(argIdx)(f, plans => copy(plans = plans)).getOrElse(alt)
}

case class BetweenCoproducts[+E <: Erroneous, +F <: Fallible](
source: Structure.Coproduct,
dest: Structure.Coproduct,
casePlans: Vector[Plan[E, F]]
) extends Plan[E, F]
) extends Plan[E, F] {
inline def updateEach[EE >: E <: Erroneous, FF >: F <: Fallible](
inline f: Plan[E, F] => Plan[EE, FF]
) = copy(casePlans = casePlans.map(f))
}

case class BetweenOptions[+E <: Erroneous, +F <: Fallible](
source: Structure.Optional,
dest: Structure.Optional,
plan: Plan[E, F]
) extends Plan[E, F]
) extends Plan[E, F] {
inline def update[EE >: E <: Erroneous, FF >: F <: Fallible](inline f: Plan[E, F] => Plan[EE, FF]) =
copy(plan = f(plan))
}

case class BetweenNonOptionOption[+E <: Erroneous, +F <: Fallible](
source: Structure,
dest: Structure.Optional,
plan: Plan[E, F]
) extends Plan[E, F]
) extends Plan[E, F] {
inline def update[EE >: E <: Erroneous, FF >: F <: Fallible](inline f: Plan[E, F] => Plan[EE, FF]) =
copy(plan = f(plan))
}

case class BetweenCollections[+E <: Erroneous, +F <: Fallible](
source: Structure.Collection,
dest: Structure.Collection,
factory: Expr[Factory[?, ?]],
plan: Plan[E, F]
) extends Plan[E, F]
) extends Plan[E, F] {
inline def update[EE >: E <: Erroneous, FF >: F <: Fallible](inline f: Plan[E, F] => Plan[EE, FF]) =
copy(plan = f(plan))
}

case class Error(
source: Structure,
Expand Down Expand Up @@ -351,15 +345,36 @@ private[ducktape] object Plan {
inline f: Plan[E, F] => Plan[EE, FF],
inline alt: Plan.Error
): Plan[Erroneous, FF] = {
plans.update(argName)(f, rebuild).getOrElse(alt)
plans.updateByName(argName)(f, rebuild).getOrElse(alt)
}
}
}

extension [E <: Erroneous, F <: Fallible](self: VectorMap[String, Plan[E, F]]) {
inline def update[EE >: E <: Erroneous, FF >: F <: Fallible, A](
inline def updateEach[EE <: Erroneous, FF <: Fallible](
inline f: Plan[E, F] => Plan[EE, FF]
): VectorMap[String, Plan[EE, FF]] =
self.transform((_, plan) => f(plan))

inline def updateByName[EE >: E <: Erroneous, FF >: F <: Fallible, A](
name: String
)(inline f: Plan[E, F] => Plan[EE, FF], rebuild: VectorMap[String, Plan[EE, FF]] => A) =
self.get(name).map(plan => rebuild(self.updated(name, f(plan))))

inline def updateByIndex[EE >: E <: Erroneous, FF >: F <: Fallible, A](
idx: Int
)(inline f: Plan[E, F] => Plan[EE, FF], rebuild: VectorMap[String, Plan[EE, FF]] => A) =
self.toVector
.lift(idx)
.map((name, fieldPlan) => rebuild(self.updated(name, f(fieldPlan))))
}

extension [E <: Erroneous, F <: Fallible](self: Vector[Plan[E, F]]) {
inline def updateByIndex[EE >: E <: Erroneous, FF >: F <: Fallible, A](
idx: Int
)(inline f: Plan[E, F] => Plan[EE, FF], rebuild: Vector[Plan[EE, FF]] => A) =
self
.lift(idx)
.map(fieldPlan => rebuild(self.updated(idx, f(fieldPlan))))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private[ducktape] object PlanConfigurer {
case None =>
Plan.Error.from(parent, ErrorMessage.FallibleConfigNotPermitted(config.span, config.side), None)
case nonFallible: Configuration.Instruction[Nothing] =>
parent.copy(plan = recurse(plan, tail, parent, nonFallible))
parent.update(recurse(_, tail, parent, nonFallible))

def handleElement(
segment: Path.Segment.Element,
Expand All @@ -41,21 +41,22 @@ private[ducktape] object PlanConfigurer {
): Plan[Erroneous, F] =
current match {
case parent @ BetweenCollections(_, _, _, plan) =>
parent.copy(plan = recurse(plan, tail, parent, config))
parent.update(recurse(_, tail, parent, config))

case parent @ BetweenOptions(_, _, plan) =>
parent.copy(plan = recurse(plan, tail, parent, config))
parent.update(recurse(_, tail, parent, config))

case parent @ BetweenNonOptionOption(_, _, plan) =>
parent.copy(plan = recurse(plan, tail, parent, config))
parent.update(recurse(_, tail, parent, config))

case parent @ BetweenFallibleNonFallible(source, dest, plan) if config.side.isSource =>
traverseBetweenNotFallible(parent, plan, tail)

case parent @ BetweenFallibles(source, dest, mode, plan) if config.side.isSource =>
parent.copy(plan = recurse(plan, tail, parent, config))
parent.update(recurse(_, tail, parent, config))

case paren: Upcast =>
// TODO: use paren.update
recurse(paren.alt, segments, parent, config)

case other => invalidPathSegment(config, other, segment)
Expand All @@ -69,7 +70,7 @@ private[ducktape] object PlanConfigurer {

// passthrough BetweenFallibles, the dest is just a normal field in this case
case parent @ BetweenFallibles(source, dest, mode, plan) if config.side.isDest =>
parent.copy(plan = recurse(plan, segments, parent, config))
parent.update(recurse(_, tail, parent, config))

case parent @ BetweenProducts(sourceTpe, destTpe, fieldPlans) =>
parent.updateOrElse(segment.name)(
Expand Down Expand Up @@ -102,6 +103,7 @@ private[ducktape] object PlanConfigurer {
)

case paren: Upcast =>
// TODO: use paren.update
recurse(paren.alt, segments, parent, config)

case other => invalidPathSegment(config, other, segment)
Expand All @@ -122,7 +124,7 @@ private[ducktape] object PlanConfigurer {

// passthrough BetweenFallibles, the dest is just a tuple elem in this case
case parent @ BetweenFallibles(source, dest, mode, plan) if config.side.isDest =>
parent.copy(plan = recurse(plan, segments, parent, config))
parent.update(recurse(_, segments, parent, config))

case parent @ BetweenTuples(source, dest, plans) =>
Logger.debug(ds"Matched $parent")
Expand All @@ -137,7 +139,6 @@ private[ducktape] object PlanConfigurer {
recurse(_, tail, parent, config),
Plan.Error.from(parent, ErrorMessage.InvalidTupleAccesor(index, config.span), None)
)


case parent @ BetweenTupleProduct(source, dest, plans) if config.side.isSource =>
Logger.debug(ds"Matched $parent")
Expand All @@ -154,6 +155,7 @@ private[ducktape] object PlanConfigurer {
)

case paren: Upcast =>
// TODO: use paren.update
recurse(paren.alt, segments, parent, config)

case other =>
Expand All @@ -167,7 +169,7 @@ private[ducktape] object PlanConfigurer {
current match {
// BetweenNonOptionOption keeps the same type as its source so we passthrough it when traversing source nodes
case parent: BetweenNonOptionOption[Erroneous, F] if config.side.isSource =>
parent.copy(plan = recurse(parent.plan, segments, parent, config))
parent.update(recurse(_, segments, parent, config))

case parent @ BetweenCoproducts(sourceTpe, destTpe, casePlans) =>
def sideTpe(plan: Plan[Erroneous, Fallible]) =
Expand All @@ -179,6 +181,7 @@ private[ducktape] object PlanConfigurer {
.getOrElse(Plan.Error.from(parent, ErrorMessage.InvalidCaseAccessor(tpe, config.span), None))

case paren: Upcast =>
// TODO: use paren.update
recurse(paren.alt, segments, parent, config)

case other => invalidPathSegment(config, other, segment)
Expand Down Expand Up @@ -311,25 +314,25 @@ private[ducktape] object PlanConfigurer {
plan.updateEach(regional(_, modifier, plan))

case plan: BetweenTuples[Erroneous, F] =>
plan.copy(plans = plan.plans.map(fieldPlan => regional(fieldPlan, modifier, plan)))
plan.updateEach(regional(_, modifier, plan))

case plan: BetweenCoproducts[Erroneous, F] =>
plan.copy(casePlans = plan.casePlans.map(regional(_, modifier, plan)))
plan.updateEach(regional(_, modifier, plan))

case plan: BetweenOptions[Erroneous, F] =>
plan.copy(plan = regional(plan.plan, modifier, plan))
plan.update(regional(_, modifier, plan))

case plan: BetweenNonOptionOption[Erroneous, F] =>
plan.copy(plan = regional(plan.plan, modifier, plan))
plan.update(regional(_, modifier, plan))

case plan: BetweenCollections[Erroneous, F] =>
plan.copy(plan = regional(plan.plan, modifier, plan))
plan.update(regional(_, modifier, plan))

case plan: BetweenFallibleNonFallible[Erroneous] =>
plan.copy(plan = regional(plan.plan, modifier, plan))
plan.update(regional(_, modifier, plan))

case plan @ BetweenFallibles(_, _, _, elemPlan) =>
plan.copy(plan = regional(elemPlan, modifier, plan))
plan.update(regional(_, modifier, plan))

case plan: Error =>
// TODO: Detect when a regional config doesn't do anything and emit an error
Expand Down

0 comments on commit 6ec73ec

Please sign in to comment.