packages feed

packstream-bolt-0.1.0.0: src/Data/PackStream/Generic.hs

{-# LANGUAGE UndecidableInstances #-}

-- | Internal module. Not part of the public API.
module Data.PackStream.Generic
    ( GPackStream
    , genericToPs
    , genericFromPs
    , GenericPackStream(..)
    ) where

import           Compat.Prelude

import           Data.Kind (Constraint, Type)
import           GHC.Generics

import           Data.PackStream.Ps
import           Data.PackStream.Result

-- | Convert a value with a 'Generic' instance to a 'Ps' value.
genericToPs :: (Generic a, GPackStream (Rep a)) => a -> Ps
genericToPs = gToPs . from

-- | Decode a 'Ps' value into a type with a 'Generic' instance.
genericFromPs :: (Generic a, GPackStream (Rep a)) => Ps -> Result a
genericFromPs x = to <$> gFromPs x

-- | Newtype wrapper providing a 'PackStream' instance via 'GHC.Generics'.
type GenericPackStream :: Type -> Type
type role GenericPackStream representational
newtype GenericPackStream a = GenericPackStream a

instance (Generic a, GPackStream (Rep a)) => PackStream (GenericPackStream a) where
  toPs (GenericPackStream a) = genericToPs a
  fromPs a = GenericPackStream <$> genericFromPs a

-- | Generic representation class for PackStream encoding\/decoding.
type GPackStream :: (Type -> Type) -> Constraint
class GPackStream f where
  gToPs   :: f a -> Ps
  gFromPs :: Ps -> Result (f a)

instance GPackStream U1 where
  gToPs U1 = PsNull
  gFromPs PsNull = return U1
  gFromPs _      = fail "invalid encoding for custom unit type"

instance (GPackStream a, GProdPack b) => GPackStream (a :*: b) where
  gToPs = toPs . prodToPs
  gFromPs = fromPs >=> prodFromPs

instance (GSumPack a, GSumPack b, SumSize a, SumSize b) => GPackStream (a :+: b) where
  gToPs = sumToPs 0 size
    where
      size = unTagged (sumSize :: Tagged (a :+: b) Int64)

  gFromPs = \case
    PsInteger code -> checkSumFromPs0 size (fromIntegral code)
    o              -> fromPs o >>= uncurry (checkSumFromPs size)
    where
      size = unTagged (sumSize :: Tagged (a :+: b) Int64)

instance GPackStream a => GPackStream (M1 t c a) where
  gToPs (M1 x) = gToPs x
  gFromPs x = M1 <$> gFromPs x

instance PackStream a => GPackStream (K1 i a) where
  gToPs (K1 x) = toPs x
  gFromPs o = K1 <$> fromPs o


-- Product type packing.

type GProdPack :: (Type -> Type) -> Constraint
class GProdPack f where
  prodToPs :: f a -> [Ps]
  prodFromPs :: [Ps] -> Result (f a)


instance (GPackStream a, GProdPack b) => GProdPack (a :*: b) where
  prodToPs (a :*: b) = gToPs a : prodToPs b
  prodFromPs (a:b) = (:*:) <$> gFromPs a <*> prodFromPs b
  prodFromPs _     = fail "invalid encoding for product type"

instance GPackStream a => GProdPack (M1 t c a) where
  prodToPs (M1 x) = [gToPs x]
  prodFromPs [x] = M1 <$> gFromPs x
  prodFromPs _   = fail "invalid encoding for product type"


-- Sum type packing.

checkSumFromPs0 :: GSumPack f => Int64 -> Int64 -> Result (f a)
checkSumFromPs0 size code
  | code < size = sumFromPs code size PsNull
  | otherwise   = fail "invalid encoding for sum type"


checkSumFromPs :: (GSumPack f) => Int64 -> Int64 -> Ps -> Result (f a)
checkSumFromPs size code x
  | code < size = sumFromPs code size x
  | otherwise   = fail "invalid encoding for sum type"


type GSumPack :: (Type -> Type) -> Constraint
class GSumPack f where
  sumToPs :: Int64 -> Int64 -> f a -> Ps
  sumFromPs :: Int64 -> Int64 -> Ps -> Result (f a)


instance (GSumPack a, GSumPack b) => GSumPack (a :+: b) where
  sumToPs code size = \case
    L1 x -> sumToPs code           sizeL x
    R1 x -> sumToPs (code + sizeL) sizeR x
    where
      sizeL = size `shiftR` 1
      sizeR = size - sizeL

  sumFromPs code size x
    | code < sizeL = L1 <$> sumFromPs code           sizeL x
    | otherwise    = R1 <$> sumFromPs (code - sizeL) sizeR x
    where
      sizeL = size `shiftR` 1
      sizeR = size - sizeL


instance {-# OVERLAPPING #-} GSumPack (C1 c U1) where
  sumToPs code _ _ = toPs code
  sumFromPs _ _ = gFromPs


instance {-# OVERLAPPABLE #-} GPackStream a => GSumPack (C1 c a) where
  sumToPs code _ x = toPs (code, gToPs x)
  sumFromPs _ _ = gFromPs


-- Sum size.

type SumSize :: (Type -> Type) -> Constraint
class SumSize f where
  sumSize :: Tagged f Int64

type Tagged :: (Type -> Type) -> Type -> Type
type role Tagged phantom representational
newtype Tagged (s :: Type -> Type) b = Tagged { unTagged :: b }

instance (SumSize a, SumSize b) => SumSize (a :+: b) where
  sumSize = Tagged $ unTagged (sumSize :: Tagged a Int64) +
                     unTagged (sumSize :: Tagged b Int64)

instance SumSize (C1 c a) where
  sumSize = Tagged 1