1{-# LANGUAGE TemplateHaskell #-}
4-- Module : Streamly.Internal.Data.Serialize.TH.RecHeader
5-- Copyright : (c) 2023 Composewell Technologies
6-- License : BSD3-3-Clause
7-- Maintainer : streamly@composewell.com
8-- Stability : experimental
11module Streamly.Internal.Data.Serialize.TH.RecHeader
16 , mkDeserializeKeysDec
19--------------------------------------------------------------------------------
21--------------------------------------------------------------------------------
23import Control.Monad (void)
24import Data.List (foldl')
25import Data.Word (Word32, Word8)
26import Data.Maybe (fromJust)
27import Language.Haskell.TH
28import Streamly.Internal.Data.Serialize.Type (Serialize(..))
29import Data.Foldable (foldlM)
30import Streamly.Internal.Data.Unbox (MutableByteArray)
31import Data.Proxy (Proxy(..))
33import qualified Streamly.Internal.Data.Unbox as Unbox
35import Streamly.Internal.Data.Serialize.TH.Bottom
36import Streamly.Internal.Data.Serialize.TH.Common
38--------------------------------------------------------------------------------
40--------------------------------------------------------------------------------
42-- Compatibility Algorithm
43-- =======================
45-- The algorithm is written without any low level implementation details. See
46-- the code for any low level implementation details.
51-- To serialize the data,
53-- * Get the list of keys for the record as @keyList@.
54-- * Serialize the @keyList@.
55-- * Serialize the @fields@ one-by-one after serializing the @keyList@.
60-- To deserialize the data to type @T@,
62-- __Checking for type match__:
64-- * Get the list of keys for type @T@ as @targetKeyList@.
65-- * Get the list of keys encoded as @encodedKeyList@.
66-- * If @targetKeyList == encodedKeyList@ see the __Type Match__ section else
67-- see the __No Type Match__ section.
71-- * Decode the fields one-by-one and construct the type @T@ in the end.
75-- * Decode the list of keys encoded into @encodedKeyList@.
76-- * Get the list of keys for type @T@ as @targetKeyList@.
77-- * Loop through @encodedKeyList@ and start deserializing the encoded data.
78-- * If the key is present in @encodedKeyList@ and not in @targetKeyList@
79-- then skip parsing the corresponding value.
80-- * If the key is present in @targetKeyList@ and not in @encodedKeyList@
81-- then set the value for that key as @Nothing@.
82-- * If the key is present in both @encodedKeyList@ and in @targetKeyList@
84-- * Construct @T@ after parsing all the data.
89-- * Record update syntax is not robust across language extensions and common
90-- record plugins (like record-dot-processor, large-records, etc.).
92--------------------------------------------------------------------------------
94--------------------------------------------------------------------------------
96-- Like haskell list but the maximum length of the list is 255
97newtype CompactList a =
99 { unCompactList :: [a]
102-- We use 'Word8' to encode the length, hence the maximim number of elements in
104instance forall a. Serialize a => Serialize (CompactList a) where
106 -- {-# INLINE size #-}
107 size acc (CompactList xs) =
108 foldl' size (acc + (Unbox.sizeOf (Proxy :: Proxy Word8))) xs
110 -- Inlining this causes large compilation times for tests
111 {-# INLINABLE deserialize #-}
112 deserialize off arr sz = do
113 (off1, len8) <- deserialize off arr sz :: IO (Int, Word8)
114 let len = w8_int len8
115 peekList f o i | i >= 3 = do
116 -- Unfold the loop three times
117 (o1, x1) <- deserialize o arr sz
118 (o2, x2) <- deserialize o1 arr sz
119 (o3, x3) <- deserialize o2 arr sz
120 peekList (f . (\xs -> x1:x2:x3:xs)) o3 (i - 3)
121 peekList f o 0 = pure (o, f [])
123 (o1, x) <- deserialize o arr sz
124 peekList (f . (x:)) o1 (i - 1)
125 (nextOff, lst) <- peekList id off1 len
126 pure (nextOff, CompactList lst)
128 -- Inlining this causes large compilation times for tests
129 {-# INLINABLE serialize #-}
130 serialize off arr (CompactList val) = do
131 void $ serialize off arr (int_w8 (length val) :: Word8)
132 let off1 = off + Unbox.sizeOf (Proxy :: Proxy Word8)
133 let pokeList o [] = pure o
134 pokeList o (x:xs) = do
135 o1 <- serialize o arr x
139--------------------------------------------------------------------------------
141--------------------------------------------------------------------------------
143fieldToNameBase :: Field -> String
144fieldToNameBase = nameBase . fromJust . fst
146isMaybeType :: Type -> Bool
147isMaybeType (AppT (ConT m) _) = m == ''Maybe
150--------------------------------------------------------------------------------
152--------------------------------------------------------------------------------
154-- We add 4 here because we use 'serializeWithSize' for serializing.
155exprGetSize :: Q Exp -> (Int, Type) -> Q Exp
156exprGetSize acc (i, _) =
157 [|size $(acc) $(varE (mkFieldName i)) + 4|]
159sizeOfHeader :: SimpleDataCon -> Int
160sizeOfHeader (SimpleDataCon _ fields) =
161 sizeForFinalOff + sizeForHeaderLength + sizeForNumFields
162 + sum (map ((+ sizeForFieldLen) . length . fieldToNameBase) fields)
167 sizeForHeaderLength = 4 -- Max header length is (255 * (255 + 1) + 1) and
168 -- hence 2 bytes is enough to store it. But we still
169 -- use 4 bytes as using 2 bytes introduces
171 sizeForNumFields = 1 -- At max 255 fields in the record constructor
172 sizeForFieldLen = 1 -- At max 255 letters in the key
174mkSizeOfExpr :: SimpleDataCon -> Q Exp
176 n_acc <- newName "acc"
179 [varP n_acc, varP n_x]
180 [|$(litIntegral hlen) +
181 $(caseE (varE n_x) [matchCons (varE n_acc) con])|])
185 hlen = sizeOfHeader con
186 sizeOfFields acc fields = foldl' exprGetSize acc $ zip [0 ..] fields
187 matchCons acc (SimpleDataCon cname fields) =
188 let expr = sizeOfFields acc (map snd fields)
189 in matchConstructor cname (length fields) expr
191--------------------------------------------------------------------------------
193--------------------------------------------------------------------------------
195headerValue :: SimpleDataCon -> [Word8]
196headerValue (SimpleDataCon _ fields) =
197 int_w8 numFields : concat (fmap lengthPrependedFieldEncoding fields)
201 -- Error out if the number of fields or the length of key is >= 256. We use
202 -- Word8 for encoding the info and hence the max value is 255.
204 let lenFields = length fields
205 in if lenFields <= 255
207 else errorUnsupported
208 "Number of fields in the record should be <= 255."
209 lengthPrependedFieldEncoding field =
211 let fEnc_ = map c2w (fieldToNameBase field)
212 lenFEnc = length fEnc_
217 "Length of any key should be <= 255."
218 in (int_w8 (length fEnc)) : fEnc
220--------------------------------------------------------------------------------
222--------------------------------------------------------------------------------
224-- Encoding the size is required if we want to skip the field without knowing
225-- its type. We encode the size as 'Word32' hence there is a 4 bytes increase
227{-# INLINE serializeWithSize #-}
228serializeWithSize :: Serialize a => Int -> MutableByteArray -> a -> IO Int
229serializeWithSize off arr val = do
230 off1 <- serialize (off + 4) arr val
231 Unbox.pokeByteIndex off arr (int_w32 (off1 - off - 4) :: Word32)
234mkSerializeExpr :: Name -> SimpleDataCon -> Q Exp
235mkSerializeExpr initialOffset (con@(SimpleDataCon cname fields)) = do
236 afterHLen <- newName "afterHLen"
237 -- Encoding the header length is required.
238 -- We first compare the header length encoded and the current header
239 -- length. Only if the header lengths match, we compare the headers.
240 [|do $(varP afterHLen) <-
242 ($(varE initialOffset) + 4)
244 ($(litIntegral hlen) :: Word32)
245 $(varP (makeI 0)) <- $(serializeW8List afterHLen _arr hval)
246 let $(openConstructor cname (length fields)) = $(varE _val)
247 finalOff <- $(mkSerializeExprFields 'serializeWithSize fields)
249 $(varE initialOffset)
251 ((fromIntegral :: Int -> Word32)
252 (finalOff - $(varE initialOffset)))
257 hval = headerValue con
260--------------------------------------------------------------------------------
262--------------------------------------------------------------------------------
264{-# INLINE deserializeWithSize #-}
265deserializeWithSize ::
266 Serialize a => Int -> MutableByteArray -> Int -> IO (Int, a)
267deserializeWithSize off arr endOff = deserialize (off + 4) arr endOff
269conUpdateFuncDec :: Name -> [Field] -> Q [Dec]
270conUpdateFuncDec funcName fields = do
271 prevAcc <- newName "prevAcc"
272 curOff <- newName "curOff"
273 endOff <- newName "endOff"
280 [ map (matchField arr endOff (prevAcc, curOff)) fnames
284 [|do (valOff, valLen :: Word32) <-
291 , valOff + w32_int valLen)|])
296 [ PragmaD (InlineP funcName NoInline FunLike AllPhases)
302 , TupP [VarP prevAcc, VarP curOff]
312 fnames = fmap (fromJust . fst) fields
313 matchField :: Name -> Name -> (Name, Name) -> Name -> Q Match
314 matchField arr endOff (acc, currOff) fname = do
315 let fnameLit = StringL (nameBase fname)
319 [|do (valOff, valLen :: Word32) <-
325 ( ($(litE fnameLit), $(varE currOff)) : $(varE acc)
326 , valOff + w32_int valLen)|])
329mkDeserializeKeysDec :: Name -> Name -> SimpleDataCon -> Q [Dec]
330mkDeserializeKeysDec funcName updateFunc (SimpleDataCon cname fields) = do
331 hOff <- newName "hOff"
332 finalOff <- newName "finalOff"
334 endOff <- newName "endOff"
335 kvEncoded <- newName "kvEncoded"
336 finalRec <- newName "finalRec"
337 let deserializeFieldExpr (Just name, ty) = do
338 let nameLit = litE (StringL (nameBase name))
339 [|case lookup $(nameLit) $(varE kvEncoded) of
340 Nothing -> $(emptyTy name ty)
342 val <- deserializeWithSize off $(varE arr) $(varE endOff)
344 deserializeFieldExpr _ =
345 errorUnsupported "The datatype should use record syntax."
347 [|do (dataOff, hlist :: CompactList (CompactList Word8)) <-
348 deserialize $(varE hOff) $(varE arr) $(varE endOff)
349 let keys = wListToString . unCompactList <$> unCompactList hlist
350 ($(varP kvEncoded), _) <-
352 ($(varE updateFunc) $(varE arr) $(varE endOff))
359 $(deserializeFieldExpr i)|])
360 [|pure $(conE cname)|]
362 pure ($(varE finalOff), $(varE finalRec))|]
364 [ PragmaD (InlineP funcName NoInline FunLike AllPhases)
382 then [|pure Nothing|]
383 else [|error $(litE (StringL (nameBase k ++ " is not found.")))|]
386mkDeserializeExpr :: Name -> Name -> Name -> SimpleDataCon -> Q Exp
387mkDeserializeExpr initialOff endOff deserializeWithKeys con = do
388 hOff <- newName "hOff"
389 let sizeForFinalOff = 4 -- Word32
390 sizeForHeaderLength = 4 -- Word32
391 sizePreData = sizeForFinalOff + sizeForHeaderLength + hlen
392 [|do (hlenOff, encLen :: Word32) <-
393 deserialize $(varE initialOff) $(varE _arr) $(varE endOff)
394 ($(varP hOff), hlen1 :: Word32) <-
395 deserialize hlenOff $(varE _arr) $(varE endOff)
396 if (hlen1 == $(litIntegral hlen)) && $(xorCmp hval hOff _arr)
398 let $(varP (makeI 0)) =
400 $(litIntegral sizePreData)
401 $(mkDeserializeExprOne 'deserializeWithSize con)
402 else $(varE deserializeWithKeys)
404 ($(varE initialOff) + w32_int encLen)
410 hval = headerValue con
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list