packages feed

binrep-0.2.0: src/Binrep/Example/FileTable.hs

module Binrep.Example.FileTable where

import Binrep
import Refined hiding ( Weaken )
import Refined.Unsafe
import Binrep.Type.Common ( Endianness(..) )
import Binrep.Type.Int
import Binrep.Type.LenPfx
import FlatParse.Basic qualified as FP
import Data.ByteString qualified as B
import Strongweak
import Strongweak.Generic
--import Data.Map ( Map )
import GHC.Generics ( Generic )
import GHC.Exts
import Data.Vector.Sized qualified as V
import Data.Word

type BS = B.ByteString

-- We're unable to put one invariant in the types: an entry can't be placed past
-- the maximum offset. Validating that requires quite a lot of work: we have to
-- do much of the layouting work, which will be repeated for serializing. This
-- is a downside of phase separation, it crops up every now and then.
--
-- The BLen instance will similarly be a bit complex, but it could probably be
-- implemented with similar code to strengthening.
newtype Table s a = Table { unTable :: SW s (LenPfx 'I1 'LE (Entry s a)) }

instance (Put a, BLen a) => Put (Table 'Strong a) where put = putFileTable

putFileTable :: (Put a, BLen a) => Table 'Strong a -> Builder
putFileTable (Table a@(LenPfx es)) =
    let es' = V.map prepEntry es
        osBase = V.sum $ V.map (\(l, _, _) -> l) es'
    in  case V.foldl go ((fromIntegral osBase) - 1, mempty, mempty) es' of
          (_, bh, bd) -> put (lenPfxSize a) <> bh <> bd
  where
    go :: (Word8, Builder, Builder) -> (BLenT, Word8 -> Builder, BS) -> (Word8, Builder, Builder)
    go (os, bh, bd) (_, eh, ed) = (os+fromIntegral (B.length ed), bh<>eh os, bd<>put ed)

prepEntry :: (Put a, BLen a) => Entry 'Strong a -> (BLenT, Word8 -> Builder, BS)
prepEntry (Entry nm bs) = (l, b, bs')
  where
    bs' = unrefine bs
    b os =
        put nm <> put os <> put (fromIntegral (B.length bs') :: Word8)
    l = blen nm + 1 + 1 + blen bs'

instance Get a => Get (Table 'Strong a) where get = getFileTable

getFileTable :: Get a => Getter (Table 'Strong a)
getFileTable = FP.withAddr# $ \addr# -> Table <$> getLenPfx (getWith addr#)

{-
This is certainly a weird type.

  * Can use regular strongweak generics
  * Has no 'Get' instance
  * Has a 'GetWith Addr#' instance
  * Has no 'Put' instance
  * Has no 'PutWith' instance

You can't serialize an 'Entry' by itself, because it serializes to two
artifacts, a header entry and the associated data. Now I see why Kaitai Struct
was having trouble with serializing this sort of type.
-}
data Entry s a = Entry
  { entryName :: a
  , entryData :: SW s (Refined (SizeLessThan (IMax 'U 'I1)) BS)
  } deriving stock (Generic)
deriving stock instance Show a => Show (Entry 'Weak   a)
deriving stock instance Eq   a => Eq   (Entry 'Weak   a)
deriving stock instance Show a => Show (Entry 'Strong a)
deriving stock instance Eq   a => Eq   (Entry 'Strong a)
instance Weaken (Entry 'Strong a) where
    type Weak (Entry 'Strong a) = Entry 'Weak a
    weaken = weakenGeneric
instance Strengthen (Entry 'Strong a) where
    strengthen = strengthenGeneric

instance Get a => GetWith Addr# (Entry 'Strong a) where getWith = getEntry

getEntry :: Get a => Addr# -> Getter (Entry 'Strong a)
getEntry addr# = do
    name <- get
    dat  <- FP.withAnyWord8# $ \offset# -> FP.withAnyWord8# $ \len# ->
        FP.takeBsOffAddr# addr# (w8i# offset#) (w8i# len#)
    return $ Entry name (reallyUnsafeRefine dat)

w8i# :: Word8# -> Int#
w8i# w# = word2Int# (word8ToWord# w#)

exBs :: BS
exBs = B.pack
  [ 0x02
  , 0x30, 0x31, 0x32, 0x00
  , 12 -- <- offset!!
  , 0x01
  , 0x39, 0x38, 0x00
  , 13
  , 0x01
  , 0xFF
  , 0xF0
  ]