{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
module Data.Type.BitRecords.Writer.ByteStringBuilder where
import Data.Type.BitRecords.BitBuffer64
import Data.FunctionBuilder
import Data.Type.BitRecords.Structure
import Data.Word
import Data.Bits
import Data.Kind.Extra
import Data.Proxy
import Data.Monoid
import Control.Category
import Prelude hiding ((.), id)
import qualified Data.ByteString.Builder as SB
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString as SB
import Text.Printf
import Data.FunctionBuilder ( FunctionBuilder )
import qualified Data.FunctionBuilder as FunctionBuilder
instance HasFunctionBuilder BitBuilder BitBuffer64 where
toFunctionBuilder = immediate . appendBitBuffer64
newtype BitBuilder =
BitBuilder {unBitBuilder :: Dual (Endo BitBuilderState)}
deriving (Monoid, Semigroup)
data BitBuilderState where
BitBuilderState ::
!SB.Builder -> !BitBuffer64 -> !Word64 -> BitBuilderState
data BuilderWithSize where
MkBuilderWithSize :: !Word64 -> !SB.Builder -> BuilderWithSize
instance Semigroup BuilderWithSize where
(MkBuilderWithSize !ls !lb) <> (MkBuilderWithSize !rs !rb) =
MkBuilderWithSize (ls + rs) (lb <> rb)
instance Monoid BuilderWithSize where
mempty = MkBuilderWithSize 0 mempty
bitBuilderWithSize ::
forall (struct :: Extends (Structure sizeType)) .
HasFunctionBuilder BitBuilder (Proxy struct)
=> Proxy struct
-> ToFunction BitBuilder (Proxy struct) BuilderWithSize
bitBuilderWithSize = toFunction . builderBoxConstructor
wrapBitBuilderWithSize ::
forall (struct :: Extends (Structure sizeType)) wrapped .
HasFunctionBuilder BitBuilder (Proxy struct)
=> (BuilderWithSize -> wrapped)
-> Proxy struct
-> ToFunction BitBuilder (Proxy struct) wrapped
wrapBitBuilderWithSize !f !p = toFunction (mapAccumulator f (builderBoxConstructor p))
builderBoxConstructor ::
forall (struct :: Extends (Structure sizeType)) r .
HasFunctionBuilder BitBuilder (Proxy struct)
=> Proxy struct
-> FunctionBuilder BuilderWithSize r (ToFunction BitBuilder (Proxy struct) r)
builderBoxConstructor !p =
let fromBitBuilder !h =
let (BitBuilderState !builder _ !wsize) =
flushBitBuilder
$ appBitBuilder h initialBitBuilderState
!out = MkBuilderWithSize wsize builder
in out
in mapAccumulator fromBitBuilder (toFunctionBuilder p)
runBitBuilder
:: BitBuilder -> SB.Builder
runBitBuilder !w =
getBitBuilderStateBuilder $
flushBitBuilder $ appBitBuilder w initialBitBuilderState
bitBuffer64Builder :: (BitBuilderState -> BitBuilderState)
-> BitBuilder
bitBuffer64Builder = BitBuilder . Dual . Endo
appBitBuilder :: BitBuilder
-> BitBuilderState
-> BitBuilderState
appBitBuilder !w = appEndo (getDual (unBitBuilder w))
getBitBuilderStateBuilder
:: BitBuilderState -> SB.Builder
getBitBuilderStateBuilder (BitBuilderState !builder _ _) = builder
initialBitBuilderState
:: BitBuilderState
initialBitBuilderState =
BitBuilderState mempty emptyBitBuffer64 0
flushBitBuilder
:: BitBuilderState -> BitBuilderState
flushBitBuilder (BitBuilderState !bldr !buff !totalSize) =
BitBuilderState (writeRestBytes bldr 0)
emptyBitBuffer64
totalSize'
where !off = bitBuffer64Length buff
!off_ = (fromIntegral off :: Word64)
!totalSize' = totalSize + signum (off_ `rem` 8) + (off_ `div` 8)
!part = bitBuffer64Content buff
writeRestBytes !bldr' !flushOffset =
if off <= flushOffset
then bldr'
else let !flushOffset' = flushOffset + 8
!bldr'' =
bldr' <>
SB.word8 (fromIntegral
((part `unsafeShiftR`
(bitBuffer64MaxLength - flushOffset')) .&.
0xFF))
in writeRestBytes bldr'' flushOffset'
appendBitBuffer64 :: BitBuffer64 -> BitBuilder
appendBitBuffer64 !x' =
bitBuffer64Builder $
\(BitBuilderState !builder !buff !totalSizeIn) -> go x' builder buff totalSizeIn
where go !x !builder !buff !totalSize
| bitBuffer64Length x == 0 = BitBuilderState builder buff totalSize
| otherwise =
let (!rest, !buff') = bufferBits x buff
in if bitBuffer64SpaceLeft buff' > 0
then BitBuilderState builder buff' totalSize
else let !nextBuilder =
builder <>
SB.word64BE (bitBuffer64Content buff')
!totalSize' = totalSize + bitBuffer64MaxLengthBytes
in go rest nextBuilder emptyBitBuffer64 totalSize'
appendStrictByteString :: SB.ByteString -> BitBuilder
appendStrictByteString !sb =
foldMap (appendBitBuffer64 . bitBuffer64 8 . fromIntegral) (SB.unpack sb)
runBitBuilderHoley
:: FunctionBuilder BitBuilder SB.Builder a -> a
runBitBuilderHoley (FB !x) = x runBitBuilder
printBuilder :: SB.Builder -> String
printBuilder b =
("<< " ++) $
(++ " >>") $ unwords $ printf "%0.2x" <$> B.unpack (SB.toLazyByteString b)
bitBuffer64Printer :: HasFunctionBuilder BitBuilder a => a -> ToFunction BitBuilder a String
bitBuffer64Printer =
toFunction . mapAccumulator (printBuilder . runBitBuilder) . toFunctionBuilder