Skip to content

Instantly share code, notes, and snippets.

@mpilquist
Last active February 11, 2020 13:56
Show Gist options
  • Save mpilquist/767c83b70524f10bdcd1db92b1b2ef05 to your computer and use it in GitHub Desktop.
Save mpilquist/767c83b70524f10bdcd1db92b1b2ef05 to your computer and use it in GitHub Desktop.
diff --git a/shared/src/main/scala/scodec/Codec.scala b/shared/src/main/scala/scodec/Codec.scala
index f3086f7..61e3e35 100644
--- a/shared/src/main/scala/scodec/Codec.scala
+++ b/shared/src/main/scala/scodec/Codec.scala
@@ -1,10 +1,10 @@
package scodec
-import shapeless._
-import shapeless.labelled.FieldType
-import shapeless.ops.record._
+import scala.deriving._
+import scala.compiletime._
-import scodec.bits.BitVector
+import scodec.bits.{BitVector, ByteVector}
+import scala.collection.mutable
/**
* Supports encoding a value of type `A` to a `BitVector` and decoding a `BitVector` to a value of `A`.
@@ -163,11 +163,6 @@ import scodec.bits.BitVector
*
* Full examples are available in the test directory of this project.
*
- * == Implicit Codecs ==
- *
- * If authoring combinators that require implicit codec arguments, use `shapeless.Lazy[Codec[A]]` instead of
- * `Codec[A]`. This prevents the occurrence of diverging implicit expansion errors.
- *
* @groupname tuple Tuple Support
* @groupprio tuple 11
*
@@ -202,44 +197,10 @@ trait Codec[A] extends GenCodec[A, A] { self =>
}
/**
- * Transforms using two functions, `A => Attempt[B]` and `B => A`.
- *
- * The supplied functions form an injection from `B` to `A`. Hence, this method converts from
- * a larger to a smaller type. Hence, the name `narrow`.
- * @group combinators
- */
- final def narrow[B](f: A => Attempt[B], g: B => A): Codec[B] =
- exmap(f, b => Attempt.successful(g(b)))
-
- /**
- * Transforms using two functions, `A => B` and `B => Attempt[A]`.
- *
- * The supplied functions form an injection from `A` to `B`. Hence, this method converts from
- * a smaller to a larger type. Hence, the name `widen`.
- * @group combinators
- */
- final def widen[B](f: A => B, g: B => Attempt[A]): Codec[B] =
- exmap(a => Attempt.successful(f(a)), g)
-
- /**
- * Lifts this codec in to a codec of a singleton hlist.
- * @group hlist
- */
- final def hlist: Codec[A :: HNil] = xmap(_ :: HNil, _.head)
-
- /**
- * Creates a `Codec[(A, B)]` that first encodes/decodes an `A` followed by a `B`.
- * @group tuple
- */
- final def pairedWith[B](codecB: Codec[B]): Codec[(A, B)] = new codecs.TupleCodec(this, codecB)
-
- /**
- * Creates a `Codec[(A, B)]` that first encodes/decodes an `A` followed by a `B`.
- *
- * Operator alias for [[pairedWith]].
+ * Lifts this codec in to a codec of a singleton tuple.
* @group tuple
*/
- final def ~[B](codecB: Codec[B]): Codec[(A, B)] = pairedWith(codecB)
+ final def tuple: Codec[A *: Unit] = xmap(_ *: (), _.head)
/**
* Assuming `A` is `Unit`, creates a `Codec[B]` that: encodes the unit followed by a `B`;
@@ -248,7 +209,7 @@ trait Codec[A] extends GenCodec[A, A] { self =>
* @group tuple
*/
final def dropLeft[B](codecB: Codec[B])(implicit ev: Unit =:= A): Codec[B] =
- pairedWith(codecB).xmap[B]({ case (a, b) => b }, b => (ev(()), b))
+ (this :: codecB).xmap[B]({ (_, b) => b }, b => (ev(()), b))
/**
* Assuming `A` is `Unit`, creates a `Codec[B]` that: encodes the unit followed by a `B`;
@@ -266,7 +227,7 @@ trait Codec[A] extends GenCodec[A, A] { self =>
* @group tuple
*/
final def dropRight[B](codecB: Codec[B])(implicit ev: Unit =:= B): Codec[A] =
- pairedWith(codecB).xmap[A]({ case (a, b) => a }, a => (a, ev(())))
+ (this :: codecB).xmap[A]({ (a, _) => a }, a => (a, ev(())))
/**
* Assuming `B` is `Unit`, creates a `Codec[A]` that: encodes the `A` followed by a unit;
@@ -277,17 +238,6 @@ trait Codec[A] extends GenCodec[A, A] { self =>
*/
final def <~[B](codecB: Codec[B])(implicit ev: Unit =:= B): Codec[A] = dropRight(codecB)
- /**
- * Converts this codec to an `HList` based codec by flattening all left nested pairs.
- * For example, `flattenLeftPairs` on a `Codec[(((A, B), C), D)]` results in a
- * `Codec[A :: B :: C :: D :: HNil]`. This is particularly useful when combined
- * with `~`, `~>`, and `<~`.
- *
- * @group tuple
- */
- final def flattenLeftPairs(implicit f: codecs.FlattenLeftPairs[A]): Codec[f.Out] =
- xmap(a => f.flatten(a), l => f.unflatten(l))
-
/**
* Converts this to a `Codec[Unit]` that encodes using the specified zero value and
* decodes a unit value when this codec decodes an `A` successfully.
@@ -364,11 +314,11 @@ trait Codec[A] extends GenCodec[A, A] { self =>
*
* @group combinators
*/
- final def upcast[B >: A](implicit ta: Typeable[A]): Codec[B] = new Codec[B] {
+ final def upcast[B >: A](implicit ct: reflect.ClassTag[A]): Codec[B] = new Codec[B] {
def sizeBound: SizeBound = self.sizeBound
- def encode(b: B) = ta.cast(b) match {
- case Some(a) => self.encode(a)
- case None => Attempt.failure(Err(s"not a value of type ${ta.describe}"))
+ def encode(b: B) = b match {
+ case a: A => self.encode(a)
+ case _ => Attempt.failure(Err(s"not a value of type ${ct.runtimeClass.getSimpleName}"))
}
def decode(bv: BitVector) = self.decode(bv)
override def toString = self.toString
@@ -382,13 +332,13 @@ trait Codec[A] extends GenCodec[A, A] { self =>
*
* @group combinators
*/
- final def downcast[B <: A](implicit tb: Typeable[B]): Codec[B] = new Codec[B] {
+ final def downcast[B <: A](implicit ct: reflect.ClassTag[B]): Codec[B] = new Codec[B] {
def sizeBound: SizeBound = self.sizeBound
def encode(b: B) = self.encode(b)
def decode(bv: BitVector) = self.decode(bv).flatMap { result =>
- tb.cast(result.value) match {
- case Some(b) => Attempt.successful(DecodeResult(b, result.remainder))
- case None => Attempt.failure(Err(s"not a value of type ${tb.describe}"))
+ result.value match {
+ case b: B => Attempt.successful(DecodeResult(b, result.remainder))
+ case _ => Attempt.failure(Err(s"not a value of type ${ct.runtimeClass.getSimpleName}"))
}
}
override def toString = self.toString
@@ -417,29 +367,29 @@ trait Codec[A] extends GenCodec[A, A] { self =>
override def toString = str
}
- /**
- * Supports creation of a coproduct codec. See [[scodec.codecs.CoproductCodecBuilder]] for details.
- * @group coproduct
- */
- def :+:[B](
- left: Codec[B]
- ): codecs.CoproductCodecBuilder[B :+: A :+: CNil, Codec[B] :: Codec[A] :: HNil, B :+: A :+: CNil] =
- codecs.CoproductCodecBuilder(left :: self :: HNil)
-
- /**
- * Lifts this codec to a codec of a shapeless field -- allowing it to be used in records and unions.
- * @group combinators
- */
- def toField[K]: Codec[FieldType[K, A]] =
- xmap[FieldType[K, A]](a => labelled.field[K](a), identity)
-
- /**
- * Lifts this codec to a codec of a shapeless field -- allowing it to be used in records and unions.
- * The specified key is pushed in to the context of any errors that are returned from the resulting codec.
- * @group combinators
- */
- def toFieldWithContext[K <: Symbol](k: K): Codec[FieldType[K, A]] =
- toField[K].withContext(k.name)
+ // /**
+ // * Supports creation of a coproduct codec. See [[scodec.codecs.CoproductCodecBuilder]] for details.
+ // * @group coproduct
+ // */
+ // def :+:[B](
+ // left: Codec[B]
+ // ): codecs.CoproductCodecBuilder[B :+: A :+: CNil, Codec[B] :: Codec[A] :: HNil, B :+: A :+: CNil] =
+ // codecs.CoproductCodecBuilder(left :: self :: HNil)
+
+ // /**
+ // * Lifts this codec to a codec of a shapeless field -- allowing it to be used in records and unions.
+ // * @group combinators
+ // */
+ // def toField[K]: Codec[FieldType[K, A]] =
+ // xmap[FieldType[K, A]](a => labelled.field[K](a), identity)
+
+ // /**
+ // * Lifts this codec to a codec of a shapeless field -- allowing it to be used in records and unions.
+ // * The specified key is pushed in to the context of any errors that are returned from the resulting codec.
+ // * @group combinators
+ // */
+ // def toFieldWithContext[K <: Symbol](k: K): Codec[FieldType[K, A]] =
+ // toField[K].withContext(k.name)
override def decodeOnly[AA >: A]: Codec[AA] = {
val sup = super.decodeOnly[AA]
@@ -488,22 +438,6 @@ object Codec extends EncoderFunctions with DecoderFunctions {
override def decode(bits: BitVector) = decoder.decode(bits)
}
- /**
- * Gets an implicitly available codec for type `A`.
- *
- * If an implicit `Codec[A]` is not available, one might be able to be derived automatically.
- * Codecs can be derived for:
- * - case classes (and hlists and records), where each component type of the case class either has an
- * implicitly available codec or one can be automatically derived
- * - sealed class hierarchies (and coproducts and unions), where:
- * - the root type, `A`, has an implicitly available `Discriminated[A, D]` for some `D`
- * - each subtype has an implicitly available codec or can have one derived
- * - each subtype `X` has an implicitly available `Discriminator[A, X, D]`
- *
- * @group ctor
- */
- def apply[A](implicit c: Lazy[Codec[A]]): Codec[A] = c.value
-
/**
* Provides a `Codec[A]` that delegates to a lazily evaluated `Codec[A]`.
* Typically used to consruct codecs for recursive structures.
@@ -527,70 +461,103 @@ object Codec extends EncoderFunctions with DecoderFunctions {
override def toString = s"lazily($c)"
}
- /**
- * Encodes the specified value to a bit vector using an implicitly available codec.
- * @group conv
- */
- def encode[A](a: A)(implicit c: Lazy[Codec[A]]): Attempt[BitVector] = c.value.encode(a)
+ extension on [T <: Tuple, U <: Tuple](t: Codec[T])(given u: codecs.DropUnits[T] { type L = U }) {
+ def dropUnits: Codec[U] = t.xmap(u.removeUnits, u.addUnits)
+ }
- /**
- * Decodes the specified bit vector in to a value of type `A` using an implicitly available codec.
- * @group conv
- */
- def decode[A](bits: BitVector)(implicit c: Lazy[Codec[A]]): Attempt[DecodeResult[A]] =
- c.value.decode(bits)
+ extension on [H, T <: Tuple](t: Codec[T]) {
+ /**
+ * Builds a `Codec[H *: T]` from a `Codec[H]` and a `Codec[T]` where `T` is a tuple type.
+ * That is, this operator is a codec-level tuple prepend operation.
+ * @param codec codec to prepend
+ * @group tuple
+ */
+ def ::(h: Codec[H]): Codec[H *: T] =
+ new Codec[H *: T] {
+ def sizeBound = h.sizeBound + t.sizeBound
+ def encode(ht: H *: T) = encodeBoth(h, t)(ht.head, ht.tail)
+ def decode(bv: BitVector) = decodeBoth(h, t)(bv).map(_.map(_ *: _))
+ override def toString = s"$h :: $t"
+ }
+ }
- /**
- * Supports derived codecs.
- * @group ctor
- */
- implicit val deriveHNil: Codec[HNil] =
- codecs.HListCodec.hnilCodec
+ extension on [A, B](b: Codec[B]) {
+ /**
+ * When called on a `Codec[A]` where `A` is not a tuple, creates a new codec that encodes/decodes a tuple of `(B, A)`.
+ * For example, {{{uint8 :: utf8}}} has type `Codec[(Int, Int)]`.
+ * @group tuple
+ */
+ def ::(a: Codec[A]): Codec[(A, B)] =
+ new Codec[(A, B)] {
+ def sizeBound = a.sizeBound + b.sizeBound
+ def encode(ab: (A, B)) = Codec.encodeBoth(a, b)(ab._1, ab._2)
+ def decode(bv: BitVector) = Codec.decodeBoth(a, b)(bv)
+ override def toString = s"$a :: $b"
+ }
+ }
- /**
- * Supports derived codecs.
- * @group ctor
- */
- implicit def deriveProduct[H, T <: HList](
- implicit headCodec: Lazy[Codec[H]],
- tailAux: Lazy[Codec[T]]
- ): Codec[H :: T] =
- headCodec.value :: tailAux.value
+ extension on [A, B <: Tuple](codecA: Codec[A]) {
+ /**
+ * Creates a new codec that encodes/decodes a tuple of `A :: B` given a function `A => Codec[B]`.
+ * This allows later parts of a tuple codec to be dependent on earlier values.
+ * @group tuple
+ */
+ def flatPrepend(f: A => Codec[B]): Codec[A *: B] =
+ new Codec[A *: B] {
+ def sizeBound = codecA.sizeBound.atLeast
+ def encode(ab: A *: B) = encodeBoth(codecA, f(ab.head))(ab.head, ab.tail)
+ def decode(b: BitVector) =
+ (for {
+ a <- codecA
+ l <- f(a)
+ } yield a *: l).decode(b)
+ override def toString = s"flatPrepend($codecA, $f)"
+ }
+
+ /**
+ * Creates a new codec that encodes/decodes a tuple of `A :: B` given a function `A => Codec[B]`.
+ * This allows later parts of a tuple codec to be dependent on earlier values.
+ * Operator alias for `flatPrepend`.
+ * @group tuple
+ */
+ def >>:~(f: A => Codec[B]): Codec[A *: B] = codecA.flatPrepend(f)
+ }
- /**
- * Supports derived codecs.
- * @group ctor
- */
- implicit def deriveRecord[KH <: Symbol, VH, TRec <: HList, KT <: HList](
- implicit
- keys: Keys.Aux[FieldType[KH, VH] :: TRec, KH :: KT],
- headCodec: Lazy[Codec[VH]],
- tailAux: Lazy[Codec[TRec]]
- ): Codec[FieldType[KH, VH] :: TRec] = lazily {
- val headFieldCodec: Codec[FieldType[KH, VH]] = headCodec.value.toFieldWithContext(keys().head)
- headFieldCodec :: tailAux.value
+ extension on [B <: Tuple](rhs: Codec[B]) {
+ /**
+ * When called on a `Codec[L]` for some `L <: HList`, returns a new codec that encodes/decodes
+ * `B :: L` but only returns `L`. HList equivalent of `~>`.
+ * @group hlist
+ */
+ def :~>:(lhs: Codec[Unit]): Codec[B] = lhs.dropLeft(rhs)
}
+ // /**
+ // * When called on a `Codec[A]`, returns a new codec that encodes/decodes `B :: A :: HNil`.
+ // * HList equivalent of `~>`.
+ // * @group hlist
+ // */
+ // def :~>:[B](codecB: Codec[B])(implicit ev: Unit =:= B): Codec[A :: HNil] =
+ // codecB :~>: self.hlist
+
- /**
- * Supports derived codecs.
- * @group ctor
- */
- implicit def deriveLabelledGeneric[A, Rec <: HList](
- implicit
- lgen: LabelledGeneric.Aux[A, Rec],
- auto: Lazy[Codec[Rec]]
- ): Codec[A] = auto.value.xmap(lgen.from, lgen.to)
- /**
- * Supports derived codecs.
- * @group ctor
- */
- implicit def deriveCoproduct[A, D, C0 <: Coproduct](
- implicit
- discriminated: codecs.Discriminated[A, D],
- auto: codecs.CoproductBuilderAuto[A] { type C = C0 },
- auto2: codecs.CoproductBuilderAutoDiscriminators[A, C0, D]
- ): Codec[A] = auto.apply.auto
+
+
+ // def [H, T <: Tuple] (h: Codec[H]) :: (t: Codec[T]): Codec[H *: T] =
+ // new Codec[H *: T] {
+ // def sizeBound = h.sizeBound + t.sizeBound
+ // def encode(ht: H *: T) = Codec.encodeBoth(h, t)(ht.head, ht.tail)
+ // def decode(bv: BitVector) = Codec.decodeBoth(h, t)(bv).map(_.map(_ *: _))
+ // override def toString = s"$h :: $t"
+ // }
+
+ // def [A, B] (a: Codec[A]) :: (b: Codec[B])(given DummyImplicit): Codec[(A, B)] =
+ // new Codec[(A, B)] {
+ // def sizeBound = a.sizeBound + b.sizeBound
+ // def encode(ab: (A, B)) = Codec.encodeBoth(a, b)(ab._1, ab._2)
+ // def decode(bv: BitVector) = Codec.decodeBoth(a, b)(bv)
+ // override def toString = s"$a :: $b"
+ // }
/**
* Creates a coproduct codec builder for the specified type.
@@ -606,17 +573,123 @@ object Codec extends EncoderFunctions with DecoderFunctions {
}}}
* @group ctor
*/
- def coproduct[A](implicit auto: codecs.CoproductBuilderAuto[A]): auto.Out = auto.apply
+ // def coproduct[A](implicit auto: codecs.CoproductBuilderAuto[A]): auto.Out = auto.apply
- /**
- * Transform typeclass instance.
- * @group inst
- */
- implicit val transformInstance: Transform[Codec] = new Transform[Codec] {
- def exmap[A, B](codec: Codec[A], f: A => Attempt[B], g: B => Attempt[A]): Codec[B] =
- codec.exmap(f, g)
+ inline given derivedTuple[T <: Tuple] as Codec[T] = {
+ val codecs = summonCodecs[T].toArray.asInstanceOf[Array[Codec[_]]]
+ deriveProduct(codecs, _.toArray.iterator, p => Tuple.fromProduct(p).asInstanceOf[T])
+ }
+
+ inline def derived[A](given m: Mirror.Of[A]): Codec[A] = {
+ val elemCodecs = summonCodecs[m.MirroredElemTypes].toArray.asInstanceOf[Array[Codec[_]]]
+ val elemLabels = summonLabels[m.MirroredElemLabels]
+ val codecs = elemCodecs.zip(elemLabels).map { (c, l) =>
+ c.withContext(l).asInstanceOf[Codec[_]]
+ }
+ inline m match {
+ case p: Mirror.ProductOf[A] =>
+ deriveProduct(codecs, a => a.asInstanceOf[Product].productIterator, t => p.fromProduct(t).asInstanceOf[A])
+ case s: Mirror.SumOf[A] =>
+ deriveSum(s, codecs)
+ }
+ }
+
+ private inline def summonOne[A]: A = summonFrom { case a: A => a }
+
+ private inline def summonCodecs[T <: Tuple]: List[Codec[_]] = inline erasedValue[T] match {
+ case _: Unit => Nil
+ case _: (t *: ts) => summonOne[Codec[t]] :: summonCodecs[ts]
+ }
+
+ private inline def summonLabels[T <: Tuple]: List[String] = inline erasedValue[T] match {
+ case _: Unit => Nil
+ case _: (t *: ts) => constValue[t].asInstanceOf[String] :: summonLabels[ts]
+ }
+
+ private def deriveProduct[A](codecs: Array[Codec[_]], toElems: A => Iterator[Any], mk: Product => A): Codec[A] = {
+ new Codec[A] {
+ def sizeBound = codecs.foldLeft(SizeBound.exact(0))(_ + _.sizeBound)
+ def encode(a: A) = {
+ var i = 0
+ val elems = toElems(a)
+ var result = BitVector.empty
+ var err: Err = null
+ while (i < codecs.size && (err eq null)) {
+ val elem = elems.next.asInstanceOf[Any]
+ val codec = codecs(i).asInstanceOf[Codec[Any]]
+ codec.encode(elem) match {
+ case Attempt.Successful(out) =>
+ result = result ++ out
+ case Attempt.Failure(e) => err = e
+ }
+ i += 1
+ }
+ if (err eq null) Attempt.successful(result) else Attempt.failure(err)
+ }
+ def decode(b: BitVector) = {
+ var i = 0
+ val bldr = mutable.ArrayBuilder.make[AnyRef]
+ var buf = b
+ var err: Err = null
+ while (i < codecs.size && (err eq null)) {
+ val codec = codecs(i).asInstanceOf[Codec[AnyRef]]
+ codec.decode(buf) match {
+ case Attempt.Successful(DecodeResult(a, rem)) =>
+ bldr += a
+ buf = rem
+ case Attempt.Failure(e) => err = e
+ }
+ i += 1
+ }
+ if (err eq null) Attempt.successful(DecodeResult(mk(new ArrayProduct(bldr.result)), buf))
+ else Attempt.failure(err)
+ }
+ }
+ }
+
+ private def deriveSum[A](
+ s: Mirror.SumOf[A],
+ elemCodecs: Array[Codec[_]],
+ ): Codec[A] = new Codec[A] {
+ private val discriminator = codecs.uint8
+ def sizeBound = discriminator.sizeBound + SizeBound.choice(elemCodecs.map(_.sizeBound))
+ def encode(a: A) = {
+ val idx = s.ordinal(a)
+ (discriminator :: elemCodecs(idx).asInstanceOf[Codec[A]]).encode(idx, a)
+ }
+ def decode(b: BitVector) = {
+ discriminator.flatMap(idx => elemCodecs(idx).asInstanceOf[Codec[A]]).decode(b)
+ }
+ }
+
+ inline given derivedSingleton[A <: Singleton](given m: Mirror.Of[A]) as Codec[A] = {
+ inline m match {
+ case s: Mirror.Singleton => codecs.provide(s.fromProduct(null).asInstanceOf[A])
+ }
+ }
+
+ given Codec[Byte] = codecs.byte
+ given Codec[Short] = codecs.short16
+ given Codec[Int] = codecs.int32
+ given Codec[Long] = codecs.int64
+ given Codec[Float] = codecs.float
+ given Codec[Double] = codecs.double
+ given Codec[String] = codecs.utf8_32
+ given Codec[Boolean] = codecs.bool(8)
+ given Codec[BitVector] = codecs.variableSizeBitsLong(codecs.int64, codecs.bits)
+ given Codec[ByteVector] = codecs.variableSizeBytesLong(codecs.int64, codecs.bytes)
+ given Codec[java.util.UUID] = codecs.uuid
+
+ given [A](given ccount: Codec[Int], ca: Codec[A]) as Codec[List[A]] = codecs.listOfN(ccount, ca)
+ given [A](given ccount: Codec[Int], ca: Codec[A]) as Codec[Vector[A]] = codecs.vectorOfN(ccount, ca)
+ given [A](given cguard: Codec[Boolean], ca: Codec[A]) as Codec[Option[A]] = codecs.optional(cguard, ca)
+
+ given Transform[Codec] {
+ def [A, B](fa: Codec[A]).exmap(f: A => Attempt[B], g: B => Attempt[A]): Codec[B] =
+ fa.exmap(f, g)
+ }
- override def xmap[A, B](codec: Codec[A], f: A => B, g: B => A): Codec[B] =
- codec.xmap(f, g)
+ implicit class AsSyntax[A](private val self: Codec[A]) extends AnyVal {
+ def as[B](given t: Transformer[A, B]): Codec[B] = t(self)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment