1{-# LANGUAGE TemplateHaskell #-}
4-- Module : Streamly.Internal.Data.Serialize.TH
5-- Copyright : (c) 2023 Composewell Technologies
6-- License : BSD3-3-Clause
7-- Maintainer : streamly@composewell.com
8-- Stability : experimental
11-- XXX Instead of applying the product constructor in one go can we apply it
12-- one at a time in case of too many arguments, compiler may not have to save
15module Streamly.Internal.Data.Serialize.TH
22 , module Streamly.Internal.Data.Serialize.TH.Bottom
24 , module Streamly.Internal.Data.Serialize.TH.Common
26 , module Streamly.Internal.Data.Serialize.TH.RecHeader
29--------------------------------------------------------------------------------
31--------------------------------------------------------------------------------
33import Data.List (foldl')
34import Data.Word (Word16, Word32, Word64, Word8)
36import Language.Haskell.TH
37import Language.Haskell.TH.Syntax
38import Streamly.Internal.Data.Serialize.Type
40import Streamly.Internal.Data.Unbox.TH
46import qualified Streamly.Internal.Data.Serialize.TH.RecHeader as RecHeader
48import Streamly.Internal.Data.Serialize.TH.Bottom
49import Streamly.Internal.Data.Serialize.TH.Common
50import Streamly.Internal.Data.Serialize.TH.RecHeader
52--------------------------------------------------------------------------------
53-- Domain specific helpers
54--------------------------------------------------------------------------------
56exprGetSize :: Q Exp -> (Int, Type) -> Q Exp
57exprGetSize acc (i, _) = [|addSizeTo $(acc) $(varE (mkFieldName i))|]
59getTagSize :: Int -> Int
60getTagSize numConstructors
61 | numConstructors == 1 = 0
62 | fromIntegral (maxBound :: Word8) >= numConstructors = 1
63 | fromIntegral (maxBound :: Word16) >= numConstructors = 2
64 | fromIntegral (maxBound :: Word32) >= numConstructors = 4
65 | fromIntegral (maxBound :: Word64) >= numConstructors = 8
66 | otherwise = error "Too many constructors"
68getTagType :: Int -> Name
69getTagType numConstructors
70 | numConstructors == 1 = error "No tag for 1 constructor"
71 | fromIntegral (maxBound :: Word8) >= numConstructors = ''Word8
72 | fromIntegral (maxBound :: Word16) >= numConstructors = ''Word16
73 | fromIntegral (maxBound :: Word32) >= numConstructors = ''Word32
74 | fromIntegral (maxBound :: Word64) >= numConstructors = ''Word64
75 | otherwise = error "Too many constructors"
77--------------------------------------------------------------------------------
79--------------------------------------------------------------------------------
81getNameBaseLen :: Name -> Word8
83 let x = length (nameBase cname)
85 then error "Max Constructor Len: 63 characters"
88conEncLen :: Name -> Word8
89conEncLen cname = getNameBaseLen cname + 1
91--------------------------------------------------------------------------------
93--------------------------------------------------------------------------------
95mkSizeOfExpr :: Bool -> Bool -> TypeOfType -> Q Exp
96mkSizeOfExpr True False tyOfTy =
101 [|$(varE _acc) + $(litIntegral (conEncLen cname))|]
105 (caseE (varE _x) [matchCons (varE _acc) con])
106 MultiType constructors -> sizeOfHeadDt constructors
110 sizeOfFields acc fields =
111 foldl' exprGetSize acc $ zip [0..] fields
113 matchCons acc (SimpleDataCon cname fields) =
114 let a = litIntegral (conEncLen cname)
115 b = sizeOfFields acc (map snd fields)
116 expr = [|$(a) + $(b)|]
117 in matchConstructor cname (length fields) expr
120 let acc = [|$(varE _acc)|]
123 (caseE (varE _x) (fmap (matchCons acc) cons))
125mkSizeOfExpr False False tyOfTy =
127 UnitType _ -> lamE [varP _acc, wildP] [|$(varE _acc) + 1|]
131 (caseE (varE _x) [matchCons (varE _acc) con])
132 MultiType constructors -> sizeOfHeadDt constructors
136 tagSizeExp numConstructors =
137 litE (IntegerL (fromIntegral (getTagSize numConstructors)))
139 -- XXX fields of the same type can be folded together, will reduce the code
140 -- size when there are many fields of the same type.
141 -- XXX const size fields can be calculated statically.
142 -- XXX This can result in large compilation times due to nesting when there
143 -- are many constructors. We can create a list and sum the list at run time
144 -- to avoid that depending on the number of constructors. Or using a let
145 -- statement for each case may help?
146 -- appE (varE 'sum) (listE (acc : map (exprGetSize (litE (IntegerL 0))) (zip [0..] fields)))
147 sizeOfFields acc fields =
148 foldl' exprGetSize acc $ zip [0..] fields
150 matchCons acc (SimpleDataCon cname fields) =
151 let expr = sizeOfFields acc (map snd fields)
152 in matchConstructor cname (length fields) expr
154 -- XXX We fix VarSize for simplicity. Should be changed later.
156 let numCons = length cons
157 acc = [|$(varE _acc) + $(tagSizeExp numCons)|]
160 (caseE (varE _x) (fmap (matchCons acc) cons))
162mkSizeOfExpr False True (TheType con) = RecHeader.mkRecSizeOfExpr con
164mkSizeOfExpr _ _ _ = errorUnimplemented
166mkSizeDec :: SerializeConfig -> Type -> [DataCon] -> Q [Dec]
167mkSizeDec (SerializeConfig {..}) headTy cons = do
168 -- INLINE on sizeOf actually worsens some benchmarks, and improves none
171 cfgConstructorTagAsString
172 cfgRecordSyntaxWithHeader
173 (typeOfType headTy cons)
177 (\x -> [PragmaD (InlineP 'addSizeTo x FunLike AllPhases)])
179 ++ [FunD 'addSizeTo [Clause [] (NormalB sizeOfMethod) []]]
182--------------------------------------------------------------------------------
184--------------------------------------------------------------------------------
186mkDeserializeExpr :: Bool -> Bool -> Type -> TypeOfType -> Q Exp
187mkDeserializeExpr True False headTy tyOfTy =
189 UnitType cname -> deserializeConsExpr [SimpleDataCon cname []]
190 TheType con -> deserializeConsExpr [con]
191 MultiType cons -> deserializeConsExpr cons
195 deserializeConsExpr cons = do
196 conLen <- newName "conLen"
197 off1 <- newName "off1"
198 [|do ($(varP off1), $(varP conLen) :: Word8) <-
200 $(varE _initialOffset)
203 $(multiIfE (map (guardCon conLen off1) cons ++ [catchAll]))|]
209 ("Found invalid tag while peeking (" ++
210 $(lift (pprint headTy)) ++ ")")|]
212 guardCon conLen off con@(SimpleDataCon cname _) = do
213 let lenCname = getNameBaseLen cname
214 tag = map c2w (nameBase cname)
216 [|($(litIntegral lenCname) == $(varE conLen))
217 && $(xorCmp tag off _arr)|]
218 [|let $(varP (makeI 0)) = $(varE off) + $(litIntegral lenCname)
219 in $(mkDeserializeExprOne 'deserializeAt con)|]
221mkDeserializeExpr False False headTy tyOfTy =
224 -- XXX Should we peek and check if the byte value is 0?
226 [|pure ($(varE _initialOffset) + 1, $(conE cname))|]
230 [valD (varP (mkName "i0")) (normalB (varE _initialOffset)) []]
231 (mkDeserializeExprOne 'deserializeAt con)
234 let lenCons = length cons
235 tagType = getTagType lenCons
238 (tupP [varP (mkName "i0"), varP _tag])
239 [|deserializeAt $(varE _initialOffset) $(varE _arr) $(varE _endOffset)|]
242 (sigE (varE _tag) (conT tagType))
243 (map peekMatch (zip [0 ..] cons) ++ [peekErr]))
249 (normalB (mkDeserializeExprOne 'deserializeAt con)) []
256 ("Found invalid tag while peeking (" ++
257 $(lift (pprint headTy)) ++ ")")|])
260mkDeserializeExpr False True _ (TheType con@(SimpleDataCon _ fields)) = do
261 deserializeWithKeys <- newName "deserializeWithKeys"
262 updateFunc <- newName "updateFunc"
263 updateFuncDec <- RecHeader.conUpdateFuncDec updateFunc fields
264 deserializeWithKeysDec <-
265 RecHeader.mkDeserializeKeysDec deserializeWithKeys updateFunc con
267 (pure <$> (deserializeWithKeysDec ++ updateFuncDec))
268 (RecHeader.mkRecDeserializeExpr
274mkDeserializeExpr _ _ _ _ = errorUnimplemented
276mkDeserializeDec :: SerializeConfig -> Type -> [DataCon] -> Q [Dec]
277mkDeserializeDec (SerializeConfig {..}) headTy cons = do
280 cfgConstructorTagAsString
281 cfgRecordSyntaxWithHeader
283 (typeOfType headTy cons)
287 (\x -> [PragmaD (InlineP 'deserializeAt x FunLike AllPhases)])
293 (if isUnitType cons && not cfgConstructorTagAsString
294 then [VarP _initialOffset, WildP, WildP]
295 else [VarP _initialOffset, VarP _arr, VarP _endOffset])
302--------------------------------------------------------------------------------
304--------------------------------------------------------------------------------
306mkSerializeExprTag :: Name -> Int -> Q Exp
307mkSerializeExprTag tagType tagVal =
309 $(varE _initialOffset)
311 $((sigE (litE (IntegerL (fromIntegral tagVal))) (conT tagType)))|]
313mkSerializeExpr :: Bool -> Bool -> TypeOfType -> Q Exp
314mkSerializeExpr True False tyOfTy =
320 [serializeDataCon (SimpleDataCon cname [])]
325 [serializeDataCon con]
330 (map serializeDataCon cons)
334 serializeDataCon (SimpleDataCon cname fields) = do
335 let tagLen8 = getNameBaseLen cname
336 conEnc = tagLen8 : map c2w (nameBase cname)
342 (serializeW8List _initialOffset _arr conEnc)
343 , noBindS (mkSerializeExprFields 'serializeAt fields)
346mkSerializeExpr False False tyOfTy =
350 [|serializeAt $(varE _initialOffset) $(varE _arr) (0 :: Word8)|]
352 (TheType (SimpleDataCon cname fields)) ->
354 [valD (varP (mkName "i0")) (normalB (varE _initialOffset)) []]
360 (mkSerializeExprFields 'serializeAt fields)
363 (MultiType cons) -> do
364 let lenCons = length cons
365 tagType = getTagType lenCons
368 (map (\(tagVal, (SimpleDataCon cname fields)) ->
374 (mkSerializeExprTag tagType tagVal)
376 (mkSerializeExprFields
382mkSerializeExpr False True (TheType con) =
383 RecHeader.mkRecSerializeExpr _initialOffset con
385mkSerializeExpr _ _ _ = errorUnimplemented
387mkSerializeDec :: SerializeConfig -> Type -> [DataCon] -> Q [Dec]
388mkSerializeDec (SerializeConfig {..}) headTy cons = do
391 cfgConstructorTagAsString
392 cfgRecordSyntaxWithHeader
393 (typeOfType headTy cons)
397 (\x -> [PragmaD (InlineP 'serializeAt x FunLike AllPhases)])
403 (if isUnitType cons && not cfgConstructorTagAsString
404 then [VarP _initialOffset, VarP _arr, WildP]
405 else [VarP _initialOffset, VarP _arr, VarP _val])
412--------------------------------------------------------------------------------
414--------------------------------------------------------------------------------
416-- | A general function to derive Serialize instances where you can control
417-- which Constructors of the datatype to consider and what the Context for the
418-- 'Serialize' instance would be.
420-- Consider the datatype:
422-- data CustomDataType a b
424-- | CDTConstructor2 Bool
425-- | CDTConstructor3 Bool b
426-- deriving (Show, Eq)
431-- $(deriveSerializeInternal
433-- [AppT (ConT ''Serialize) (VarT (mkName "b"))]
435-- (AppT (ConT ''CustomDataType) (VarT (mkName "a")))
436-- (VarT (mkName "b")))
437-- [ DataCon 'CDTConstructor1 [] [] []
438-- , DataCon 'CDTConstructor2 [] [] [(Nothing, (ConT ''Bool))]
443-- [(Nothing, (ConT ''Bool)), (Nothing, (VarT (mkName "b")))]
446deriveSerializeInternal ::
447 SerializeConfig -> Type -> [DataCon] -> ([Dec] -> Q [Dec]) -> Q [Dec]
448deriveSerializeInternal conf headTy cons next = do
449 sizeDec <- mkSizeDec conf headTy cons
450 peekDec <- mkDeserializeDec conf headTy cons
451 pokeDec <- mkSerializeDec conf headTy cons
452 let methods = concat [sizeDec, peekDec, pokeDec]
455-- | @deriveSerializeWith config-modifier instance-dec@ generates a template
456-- Haskell splice consisting of a declaration of a 'Serialize' instance.
457-- @instance-dec@ is a template Haskell declaration splice consisting of a
458-- standard Haskell instance declaration without the type class methods (e.g.
459-- @[d|instance Serialize a => Serialize (Maybe a)|]@).
461-- The type class methods for the given instance are generated according to the
462-- supplied @config-modifier@ parameter. See 'SerializeConfig' for default
463-- configuration settings.
468-- \$(deriveSerializeWith
469-- ( inlineSerializeAt (Just NoInline)
470-- . inlineDeserializeAt (Just NoInline)
472-- [d|instance Serialize a => Serialize (Maybe a)|])
474deriveSerializeWith ::
475 (SerializeConfig -> SerializeConfig) -> Q [Dec] -> Q [Dec]
476deriveSerializeWith modifier mDecs = do
479 [InstanceD mo preds headTyWC []] -> do
480 let headTy = unwrap dec headTyWC
481 dt <- reifyDataType (getMainTypeName dec headTy)
483 deriveSerializeInternal
484 (modifier serializeConfig) headTy cons (next mo preds headTyWC)
485 _ -> errorMessage dec
489 next mo preds headTyWC methods = pure [InstanceD mo preds headTyWC methods]
493 [ "Error: deriveSerializeWith:"
495 , ">> " ++ pprint dec
497 , "The supplied declaration is not a valid instance declaration."
498 , "Provide a valid Haskell instance declaration without a body."
501 , "instance Serialize (Proxy a)"
502 , "instance Serialize a => Serialize (Identity a)"
503 , "instance Serialize (TableT Identity)"
506 unwrap _ (AppT (ConT _) r) = r
507 unwrap dec _ = errorMessage dec
509 getMainTypeName dec = go
515 go _ = errorMessage dec
517-- | Given an 'Serialize' instance declaration splice without the methods (e.g.
518-- @[d|instance Serialize a => Serialize (Maybe a)|]@), generate an instance
519-- declaration including all the type class method implementations.
521-- >>> deriveSerialize = deriveSerializeWith id
527-- [d|instance Serialize a => Serialize (Maybe a)|])
529deriveSerialize :: Q [Dec] -> Q [Dec]
530deriveSerialize = deriveSerializeWith id
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