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

fs2.interop.scodec.StreamDecoder.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2013 Functional Streams for Scala
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

// Adapted from scodec-protocols, licensed under 3-clause BSD

package fs2
package interop
package scodec

import _root_.scodec.{Attempt, DecodeResult, Decoder, Err, codecs}
import _root_.scodec.bits.BitVector

import cats.MonadThrow

/** Supports binary decoding of a stream that emits elements as they are decoded.
  *
  * The main purpose of using a `StreamDecoder` over a `scodec.Decoder` is mixing
  * decoding with processing. For example, `scodec.codecs.vector(decoderA): Decoder[Vector[A]]`
  * could be used to decode a bit stream but the decoded `Vector[A]` would not be
  * emitted until the end of the bit stream. With `StreamDecoder.many(decoderA): StreamDecoder[A]`,
  * each decoded `A` value is emitted as soon as it is decoded.
  *
  * The `StreamDecoder` companion has various constructors -- most importantly, `once`
  * and `many`, that allow a `Decoder[A]` to be lifted to a `StreamDecoder[A]`.
  *
  * Given a `StreamDecoder[A]`, a bit stream can be decoded via the `decode` method or
  * by calling a variant of `toPipe`.
  */
final class StreamDecoder[+A] private (private val step: StreamDecoder.Step[A]) { self =>

  import StreamDecoder._

  /** Converts this decoder to a `Pipe[F, BitVector, A]`. */
  def toPipe[F[_]: RaiseThrowable]: Pipe[F, BitVector, A] = decode(_)

  /** Converts this decoder to a `Pipe[F, Byte, A]`. */
  def toPipeByte[F[_]: RaiseThrowable]: Pipe[F, Byte, A] =
    in => in.chunks.map(_.toBitVector).through(toPipe)

  /** Returns a `Stream[F, A]` given a `Stream[F, BitVector]`. */
  def decode[F[_]: RaiseThrowable](s: Stream[F, BitVector]): Stream[F, A] =
    apply(s).void.stream

  /** Returns a `Pull[F, A, Option[Stream[F, BitVector]]]` given a `Stream[F, BitVector]`.
    * The result of the returned pull is the remainder of the input stream that was not used in decoding.
    */
  def apply[F[_]: RaiseThrowable](
      s: Stream[F, BitVector]
  ): Pull[F, A, Option[Stream[F, BitVector]]] =
    step match {
      case Empty         => Pull.pure(Some(s))
      case Result(a)     => Pull.output1(a).as(Some(s))
      case Failed(cause) => Pull.raiseError(cause)
      case Append(x, y) =>
        x(s).flatMap {
          case None      => Pull.pure(None)
          case Some(rem) => y()(rem)
        }

      case Decode(decoder, once, failOnErr) =>
        def loop(
            carry: BitVector,
            s: Stream[F, BitVector],
            carriedError: Option[Err]
        ): Pull[F, A, Option[Stream[F, BitVector]]] =
          s.pull.uncons1.flatMap {
            case Some((hd, tl)) =>
              val buffer = carry ++ hd
              decoder(buffer) match {
                case Attempt.Successful(DecodeResult(value, remainder)) =>
                  val next = if (remainder.isEmpty) tl else tl.cons1(remainder)
                  val p = value(next)
                  if (once) p
                  else
                    p.flatMap {
                      case Some(next) =>
                        loop(BitVector.empty, next, None)
                      case None => Pull.pure(None)
                    }
                case Attempt.Failure(e: Err.InsufficientBits) =>
                  loop(buffer, tl, Some(e))
                case Attempt.Failure(comp: Err.Composite)
                    if comp.errs.exists(_.isInstanceOf[Err.InsufficientBits]) =>
                  loop(buffer, tl, Some(comp))
                case Attempt.Failure(e) =>
                  if (failOnErr) Pull.raiseError(CodecError(e))
                  else Pull.pure(Some(tl.cons1(buffer)))
              }
            case None =>
              def done = if (carry.isEmpty) Pull.pure(None) else Pull.pure(Some(Stream(carry)))
              carriedError.filter(_ => failOnErr) match {
                case Some(_: Err.InsufficientBits) if !once =>
                  done
                case Some(e: Err.Composite)
                    if !once && e.errs.exists(_.isInstanceOf[Err.InsufficientBits]) =>
                  done
                case Some(e) => Pull.raiseError(CodecError(e))
                case None    => done
              }
          }
        loop(BitVector.empty, s, None)

      case Isolate(bits, decoder) =>
        def loop(
            carry: BitVector,
            s: Stream[F, BitVector],
            carriedError: Option[Err]
        ): Pull[F, A, Option[Stream[F, BitVector]]] =
          s.pull.uncons1.flatMap {
            case Some((hd, tl)) =>
              val (buffer, remainder) = (carry ++ hd).splitAt(bits)
              if (buffer.size == bits)
                decoder[F](Stream(buffer)) >> Pull.pure(Some(tl.cons1(remainder)))
              else {
                assert(
                  remainder.isEmpty,
                  s"remainder should be empty; ${bits} ${carry.size} ${hd.size} ${buffer.size} ${remainder.size}"
                )
                loop(buffer, tl, Some(Err.InsufficientBits(bits, buffer.size, Nil)))
              }
            case None =>
              carriedError match {
                case Some(e) => Pull.raiseError(CodecError(e))
                case None =>
                  if (carry.isEmpty) Pull.pure(None) else Pull.pure(Some(Stream(carry)))
              }
          }
        loop(BitVector.empty, s, None)
    }

  /** Creates a stream decoder that, upon decoding an `A`, applies it to the supplied function and decodes
    * the next part of the input with the returned decoder. When that decoder finishes, the remainder of
    * the input is returned to the original decoder for further decoding.
    */
  def flatMap[B](f: A => StreamDecoder[B]): StreamDecoder[B] =
    new StreamDecoder[B](
      self.step match {
        case Empty         => Empty
        case Result(a)     => f(a).step
        case Failed(cause) => Failed(cause)
        case Decode(g, once, failOnErr) =>
          Decode(in => g(in).map(_.map(_.flatMap(f))), once, failOnErr)
        case Isolate(bits, decoder) => Isolate(bits, decoder.flatMap(f))
        case Append(x, y)           => Append(x.flatMap(f), () => y().flatMap(f))
      }
    )

  def filter(p: A => Boolean): StreamDecoder[A] =
    flatMap(a => if (p(a)) StreamDecoder.emit(a) else StreamDecoder.empty)

  def handleErrorWith[A2 >: A](f: Throwable => StreamDecoder[A2]): StreamDecoder[A2] =
    new StreamDecoder[A2](
      self.step match {
        case Empty         => Empty
        case Result(a)     => Result(a)
        case Failed(cause) => f(cause).step
        case Decode(g, once, failOnErr) =>
          Decode(in => g(in).map(_.map(_.handleErrorWith(f))), once, failOnErr)
        case Isolate(bits, decoder) => Isolate(bits, decoder.handleErrorWith(f))
        case Append(x, y)           => Append(x.handleErrorWith(f), () => y().handleErrorWith(f))
      }
    )

  /** Maps the supplied function over each output of this decoder. */
  def map[B](f: A => B): StreamDecoder[B] = flatMap(a => StreamDecoder.emit(f(a)))

  /** Creates a stream decoder that first decodes until this decoder finishes and then decodes
    * using the supplied decoder.
    *
    * Note: this should not be used to write recursive decoders (e.g., `def ints: StreamDecoder[A] = once(int32) ++ ints`)
    * if each incremental decoding step can fail with `InsufficientBits`. Otherwise, it decoding can get stuck in
    * an infinite loop, where the remaining bits are fed to the recursive call.
    */
  def ++[A2 >: A](that: => StreamDecoder[A2]): StreamDecoder[A2] =
    new StreamDecoder(Append(this, () => that))

  /** Alias for `StreamDecoder.isolate(bits)(this)`. */
  def isolate(bits: Long): StreamDecoder[A] = StreamDecoder.isolate(bits)(this)

  /** Converts this stream decoder to a `Decoder[Vector[A]]`. */
  def strict: Decoder[Vector[A]] =
    new Decoder[Vector[A]] {
      def decode(bits: BitVector): Attempt[DecodeResult[Vector[A]]] = {
        type ET[X] = Either[Throwable, X]
        self
          .map(Left(_))
          .apply[Fallible](Stream(bits))
          .flatMap { remainder =>
            remainder
              .map(r => r.map(Right(_)).pull.echo)
              .getOrElse(Pull.done)
          }
          .stream
          .compile[Fallible, ET, Either[A, BitVector]]
          .fold((Vector.empty[A], BitVector.empty)) { case ((acc, rem), entry) =>
            entry match {
              case Left(a)   => (acc :+ a, rem)
              case Right(r2) => (acc, rem ++ r2)
            }
          }
          .fold(
            {
              case CodecError(e) => Attempt.failure(e)
              case other         => Attempt.failure(Err.General(other.getMessage, Nil))
            },
            { case (acc, rem) =>
              Attempt.successful(DecodeResult(acc, rem))
            }
          )
      }
    }

  /** Alias for [[filter]].
    *
    * Implemented to enable filtering in for comprehensions
    */
  def withFilter(p: A => Boolean): StreamDecoder[A] = filter(p)
}

object StreamDecoder {
  private sealed trait Step[+A]
  private case object Empty extends Step[Nothing]
  private case class Result[A](value: A) extends Step[A]
  private case class Failed(cause: Throwable) extends Step[Nothing]
  private case class Decode[A](
      f: BitVector => Attempt[DecodeResult[StreamDecoder[A]]],
      once: Boolean,
      failOnErr: Boolean
  ) extends Step[A]
  private case class Isolate[A](bits: Long, decoder: StreamDecoder[A]) extends Step[A]
  private case class Append[A](x: StreamDecoder[A], y: () => StreamDecoder[A]) extends Step[A]

  /** Stream decoder that emits no elements. */
  val empty: StreamDecoder[Nothing] = new StreamDecoder[Nothing](Empty)

  /** Stream decoder that emits a single `A` and consumes no bits from the input. */
  def emit[A](a: A): StreamDecoder[A] = new StreamDecoder[A](Result(a))

  /** Stream decoder that emits the supplied `A` values and consumes no bits from the input. */
  def emits[A](as: Iterable[A]): StreamDecoder[A] =
    as.foldLeft(empty: StreamDecoder[A])((acc, a) => acc ++ emit(a))

  /** Creates a stream decoder that decodes one `A` using the supplied decoder.
    * Input bits are buffered until the decoder is able to decode an `A`.
    */
  def once[A](decoder: Decoder[A]): StreamDecoder[A] =
    new StreamDecoder[A](
      Decode(in => decoder.decode(in).map(_.map(emit)), once = true, failOnErr = true)
    )

  /** Creates a stream decoder that repeatedly decodes `A` values using the supplied decoder.
    * Note: insufficient bit errors raised from the supplied decoder do **not** get re-raised.
    */
  def many[A](decoder: Decoder[A]): StreamDecoder[A] =
    new StreamDecoder[A](
      Decode(in => decoder.decode(in).map(_.map(emit)), once = false, failOnErr = true)
    )

  /** Creates a stream decoder that attempts to decode one `A` using the supplied decoder.
    * Input bits are buffered until the decoder is able to decode an `A`.
    * If decoding fails, the bits are not consumed and the stream decoder yields no values.
    */
  def tryOnce[A](decoder: Decoder[A]): StreamDecoder[A] =
    new StreamDecoder[A](
      Decode(in => decoder.decode(in).map(_.map(emit)), once = true, failOnErr = false)
    )

  /** Creates a stream decoder that repeatedly decodes `A` values until decoding fails.
    * If decoding fails, the read bits are not consumed and the stream decoder terminates,
    * having emitted any successfully decoded values earlier.
    * Note: insufficient bit errors raised from the supplied decoder do **not** get re-raised.
    */
  def tryMany[A](decoder: Decoder[A]): StreamDecoder[A] =
    new StreamDecoder[A](
      Decode(in => decoder.decode(in).map(_.map(emit)), once = false, failOnErr = false)
    )

  /** Creates a stream decoder that fails decoding with the specified exception. */
  def raiseError(cause: Throwable): StreamDecoder[Nothing] = new StreamDecoder(Failed(cause))

  /** Creates a stream decoder that fails decoding with the specified error. */
  def raiseError(err: Err): StreamDecoder[Nothing] = raiseError(CodecError(err))

  /** Creates a stream decoder that reads the specified number of bits and then decodes
    * them with the supplied stream decoder. Any remainder from the inner stream decoder is
    * discarded.
    */
  def isolate[A](bits: Long)(decoder: StreamDecoder[A]): StreamDecoder[A] =
    new StreamDecoder(Isolate(if (bits < 0) 0 else bits, decoder))

  /** Creates a stream decoder that ignores the specified number of bits. */
  def ignore(bits: Long): StreamDecoder[Nothing] =
    once(codecs.ignore(bits)).flatMap(_ => empty)

  implicit val instance: MonadThrow[StreamDecoder] = new MonadThrow[StreamDecoder] {
    def pure[A](a: A) = StreamDecoder.emit(a)
    def flatMap[A, B](da: StreamDecoder[A])(f: A => StreamDecoder[B]) = da.flatMap(f)
    def tailRecM[A, B](a: A)(f: A => StreamDecoder[Either[A, B]]): StreamDecoder[B] =
      f(a).flatMap {
        case Left(a)  => tailRecM(a)(f)
        case Right(b) => pure(b)
      }
    def handleErrorWith[A](da: StreamDecoder[A])(
        f: Throwable => StreamDecoder[A]
    ): StreamDecoder[A] =
      da.handleErrorWith(f)
    def raiseError[A](e: Throwable): StreamDecoder[A] =
      StreamDecoder.raiseError(e)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy