All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.arainko.ducktape.internal.Configuration.scala Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
package io.github.arainko.ducktape.internal

import io.github.arainko.ducktape.*

import scala.quoted.*

private[ducktape] enum Configuration[+F <: Fallible] {
  def tpe: Type[?]

  case Const(value: Expr[Any], tpe: Type[?]) extends Configuration[Nothing]
  case CaseComputed(tpe: Type[?], function: Expr[Any => Any]) extends Configuration[Nothing]
  case FieldComputed(tpe: Type[?], function: Expr[Any => Any]) extends Configuration[Nothing]
  case FieldReplacement(source: Expr[Any], name: String, tpe: Type[?]) extends Configuration[Nothing]
  case FallibleConst(value: Expr[Any], tpe: Type[?]) extends Configuration[Fallible]
  case FallibleFieldComputed(tpe: Type[?], function: Expr[Any => Any]) extends Configuration[Fallible]
  case FallibleCaseComputed(tpe: Type[?], function: Expr[Any => Any]) extends Configuration[Fallible]
}

private[ducktape] object Configuration {

  given debug: Debug[Configuration[Fallible]] = Debug.derived

  trait ErrorModifier {
    def apply(parent: Plan[Erroneous, Fallible] | None.type, plan: Plan.Error)(using Quotes): Configuration[Nothing] | plan.type
  }

  object ErrorModifier {
    given Debug[ErrorModifier] = Debug.nonShowable

    val substituteOptionsWithNone = new ErrorModifier:
      def apply(parent: Plan[Erroneous, Fallible] | None.type, plan: Plan.Error)(using
        Quotes
      ): Configuration[Nothing] | plan.type =
        plan.dest.tpe match {
          case tpe @ '[Option[a]] => Configuration.Const('{ None }, tpe)
          case _                  => plan
        }

    val substituteWithDefaults = new ErrorModifier:
      def apply(parent: Plan[Erroneous, Fallible] | None.type, plan: Plan.Error)(using
        Quotes
      ): Configuration[Nothing] | plan.type =
        PartialFunction
          .condOpt(parent -> plan.destPath.segments.lastOption) {
            case (Plan.BetweenProducts(_, dest, _), Some(Path.Segment.Field(_, fieldName))) =>
              import quotes.reflect.*

              dest.defaults
                .get(fieldName)
                .collect { case expr if expr.asTerm.tpe <:< plan.dest.tpe.repr => Configuration.Const(expr, plan.dest.tpe) }
            case (Plan.BetweenTupleProduct(_, dest, _), Some(Path.Segment.Field(_, fieldName))) =>
              import quotes.reflect.*

              dest.defaults
                .get(fieldName)
                .collect { case expr if expr.asTerm.tpe <:< plan.dest.tpe.repr => Configuration.Const(expr, plan.dest.tpe) }
          }
          .flatten
          .getOrElse(plan)
  }

  trait FieldModifier {
    def apply(
      parent: Plan.BetweenProductFunction[Erroneous, Fallible] | Plan.BetweenProducts[Erroneous, Fallible] |
        Plan.BetweenTupleProduct[Erroneous, Fallible],
      field: String,
      plan: Plan[Erroneous, Fallible]
    )(using Quotes): Configuration[Nothing] | plan.type
  }

  object FieldModifier {
    given Debug[FieldModifier] = Debug.nonShowable
  }

  enum Instruction[+F <: Fallible] {
    def path: Path
    def side: Side
    def span: Span

    case Static(path: Path, side: Side, config: Configuration[F], span: Span) extends Instruction[F]

    case Dynamic(
      path: Path,
      side: Side,
      config: Plan[Erroneous, Fallible] | None.type => Either[String, Configuration[Nothing]],
      span: Span
    ) extends Instruction[Nothing]

    case Bulk(
      path: Path,
      side: Side,
      modifier: FieldModifier,
      span: Span
    ) extends Instruction[Nothing]

    case Regional(path: Path, side: Side, modifier: ErrorModifier, span: Span) extends Instruction[Nothing]

    case Failed(path: Path, side: Side, message: String, span: Span) extends Instruction[Nothing]
  }

  object Instruction {
    given debug: Debug[Instruction[Fallible]] = Debug.derived

    object Failed {
      def from(instruction: Instruction[Fallible], message: String): Instruction.Failed =
        Failed(instruction.path, instruction.side, message, instruction.span)
    }
  }

  def parse[G[+x], A: Type, B: Type, F <: Fallible](
    configs: Expr[Seq[Field.Fallible[G, A, B] | Case.Fallible[G, A, B]]],
    parsers: NonEmptyList[ConfigParser[F]]
  )(using Quotes, Context): List[Instruction[F]] = {
    import quotes.reflect.*
    def fallback(term: quotes.reflect.Term) =
      Configuration.Instruction.Failed(
        Path.empty(Type.of[Nothing]),
        Side.Dest,
        s"Unsupported config expression: ${term.show}",
        Span.fromPosition(term.pos)
      )
    val parser = ConfigParser.combine(parsers)

    Varargs
      .unapply(configs)
      .getOrElse(report.errorAndAbort("All of the transformation configs need to be inlined", configs))
      .map(expr => parser.applyOrElse(expr.asTerm, fallback))
      .toList
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy