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

scodec.protocols.pipes.scala Maven / Gradle / Ivy

The newest version!
package scodec.protocols

import fs2._
import fs2.pipe.Stepper

/** General purpose combinators for working with pipes that are not included in fs2. */
object pipes { outer =>

  implicit class PipeOps[A, B](val self: Pipe[Pure, A, B]) extends AnyVal {
    def conditionallyFeed[X](f: X => Either[A, B]): Pipe[Pure, X, B] = outer.conditionallyFeed(self, f)
    def liftL[C]: Pipe[Pure, Either[A, C], Either[B, C]] = outer.liftL[A, B, C](self)
    def liftR[C]: Pipe[Pure, Either[C, A], Either[C, B]] = outer.liftR[A, B, C](self)
  }

  /**
   * Accepts values of type `X` and converts them to an `A` or `B`. If `A`, the `A`
   * is fed to `p`. If `B`, the `B` is emitted directly.
   */
  def conditionallyFeed[A, B, X](p: Pipe[Pure, A, B], f: X => Either[A, B]): Pipe[Pure, X, B] =
    pipe.lift(f).andThen(liftL(p)).andThen(pipe.lift(_.fold(identity, identity)))

  def liftL[A, B, C](p: Pipe[Pure, A, B]): Pipe[Pure, Either[A, C], Either[B, C]] = {
    def go(stepper: Stepper[A, B]): Stream.Handle[Pure, Either[A, C]] => Pull[Pure, Either[B, C], Stream.Handle[Pure, Either[A, C]]] = h => {
      stepper.step match {
        case Stepper.Done => Pull.done
        case Stepper.Fail(err) => Pull.fail(err)
        case Stepper.Emits(chunk, next) =>
          Pull.output(chunk.map { b => Left(b): Either[B, C] }) >> go(next)(h)
        case Stepper.Await(receive) =>
          h.receive {
            case chunk #: tl =>
              chunk.uncons match {
                case None =>
                  go(stepper)(tl)
                case Some((head @ Right(c), tail)) =>
                  val numHeadRights = {
                    val indexOfFirstLeft = tail.indexWhere(_.isLeft)
                    indexOfFirstLeft match {
                      case None => chunk.size
                      case Some(idx) => 1 + idx
                    }
                  }
                  val toOutput = chunk.take(numHeadRights).asInstanceOf[Chunk[Either[B, C]]]
                  val remainder = chunk.drop(numHeadRights)
                  Pull.output(toOutput) >> go(stepper)(if (remainder.isEmpty) tl else tl.push(remainder))
                case Some((Left(a), tail)) =>
                  val numHeadLefts = {
                    val indexOfFirstRight = tail.indexWhere(_.isRight)
                    indexOfFirstRight match {
                      case None => chunk.size
                      case Some(idx) => 1 + idx
                    }
                  }
                  val toFeed = chunk.take(numHeadLefts).map { case Left(a) => a; case Right(_) => sys.error("Chunk is all lefts!") }
                  val remainder = chunk.drop(numHeadLefts)
                  go(receive(Some(toFeed)))(if (remainder.isEmpty) tl else tl.push(remainder))
              }
          }
      }
    }
    _ pull go(pipe.stepper(p))
  }

  def liftR[A, B, C](p: Pipe[Pure, A, B]): Pipe[Pure, Either[C, A], Either[C, B]] = {
    def swap[X, Y]: Pipe[Pure, Either[X, Y], Either[Y, X]] = pipe.lift((_: Either[X, Y]).swap)
    swap[C, A].andThen(liftL(p)).andThen(swap[B, C])
  }

  implicit class StepperOps[A, B](val self: Stepper[A, B]) extends AnyVal {
    def stepToAwait[I, R](
      cont: (Vector[B], Option[Chunk[A]] => Stepper[A, B]) => Pull[Pure, I, R]
    ): Pull[Pure, I, R] = outer.stepToAwait(self)(cont)
  }

  def stepToAwait[A, B, I, R](s: Stepper[A, B], acc: Vector[B] = Vector.empty)(
    cont: (Vector[B], Option[Chunk[A]] => Stepper[A, B]) => Pull[Pure, I, R]
  ): Pull[Pure, I, R] = {
    s.step match {
      case Stepper.Done => Pull.done
      case Stepper.Fail(err) => Pull.fail(err)
      case Stepper.Emits(chunk, next) =>
        stepToAwait(next, acc ++ chunk.toVector)(cont)
      case Stepper.Await(receive) =>
        cont(acc, receive)
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy