binrep-0.5.0: src/Binrep/Put/Mason.hs
{-# OPTIONS_GHC -fno-warn-orphans #-} -- for generic data op instance
-- TODO some instances are wrong, Void should be typeerror
module Binrep.Put.Mason where
import Mason.Builder qualified as Mason
import Data.ByteString qualified as B
import Data.Word
import Data.Int
import Data.Void ( Void, absurd )
import GHC.Generics
import Generic.Data.Function.FoldMap
import Generic.Data.Function.Common
import Generic.Data.Rep.Assert
type Builder = Mason.BuilderFor Mason.StrictByteStringBackend
class Put a where
-- | Serialize to binary.
put :: a -> Builder
-- | Run the serializer.
runPut :: Put a => a -> B.ByteString
runPut = runBuilder . put
runBuilder :: Builder -> B.ByteString
runBuilder = Mason.toStrictByteString
instance GenericFoldMap Builder where
type GenericFoldMapC Builder a = Put a
genericFoldMapF = put
-- | Serialize a term of the non-sum type @a@ via its 'Generic' instance.
putGenericNonSum
:: forall {cd} {f} {asserts} a
. ( Generic a, Rep a ~ D1 cd f, GFoldMapNonSum Builder f
, asserts ~ '[ 'NoEmpty, 'NoSum], ApplyGCAsserts asserts f)
=> a -> Builder
putGenericNonSum = genericFoldMapNonSum @asserts
-- | Serialize a term of the sum type @a@ via its 'Generic' instance.
--
-- You must provide a serializer for @a@'s constructors. This is regrettably
-- inefficient due to having to use 'String's. Alas. Do write your own instance
-- if you want better performance!
putGenericSum
:: forall {cd} {f} {asserts} a
. (Generic a, Rep a ~ D1 cd f, GFoldMapSum 'SumOnly Builder f
, asserts ~ '[ 'NoEmpty, 'NeedSum], ApplyGCAsserts asserts f)
=> (String -> Builder) -> a -> Builder
putGenericSum = genericFoldMapSum @'SumOnly @asserts
-- | Impossible to serialize 'Void'.
instance Put Void where
put = absurd
-- | Serialize each element in order. No length indicator, so parse until either
-- error or EOF. Usually not what you want, but sometimes used at the "top" of
-- binary formats.
instance Put a => Put [a] where
put = mconcat . map put
instance (Put a, Put b) => Put (a, b) where
put (a, b) = put a <> put b
-- | Serialize the bytestring as-is.
--
-- Careful -- the only way you're going to be able to parse this is to read
-- until EOF.
instance Put B.ByteString where
put = Mason.byteString
{-# INLINE put #-}
-- need to give args for RankNTypes reasons I don't understand
instance Put Word8 where
put w = Mason.word8 w
{-# INLINE put #-}
instance Put Int8 where
put w = Mason.int8 w
{-# INLINE put #-}
-- | Put with inlined checks via an environment.
class PutWith r a where
-- | Attempt to serialize to binary with the given environment.
putWith :: r -> a -> Either String Builder
default putWith :: Put a => r -> a -> Either String Builder
putWith _ = putWithout
-- | Helper for wrapping a 'BinRep' into a 'BinRepWith' (for encoding).
putWithout :: Put a => a -> Either String Builder
putWithout = Right . put
instance Put a => PutWith r [a]
-- | Run the serializer with the given environment.
runPutWith :: PutWith r a => r -> a -> Either String B.ByteString
runPutWith r a = case putWith r a of Left e -> Left e
Right x -> Right $ runBuilder x