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
11module Streamly.Internal.Data.Serialize.TH
18--------------------------------------------------------------------------------
20--------------------------------------------------------------------------------
22import Data.List (foldl')
23import Data.Word (Word16, Word32, Word64, Word8)
25import Language.Haskell.TH
26import Language.Haskell.TH.Syntax
27import Streamly.Internal.Data.Serialize.Type
29import Streamly.Internal.Data.Unbox.TH
36import qualified Streamly.Internal.Data.Serialize.TH.RecHeader as RecHeader
38import Streamly.Internal.Data.Serialize.TH.Bottom
39import Streamly.Internal.Data.Serialize.TH.Common
41--------------------------------------------------------------------------------
42-- Domain specific helpers
43--------------------------------------------------------------------------------
45exprGetSize :: Q Exp -> (Int, Type) -> Q Exp
46exprGetSize acc (i, _) = [|size $(acc) $(varE (mkFieldName i))|]
48getTagSize :: Int -> Int
49getTagSize numConstructors
50 | numConstructors == 1 = 0
51 | fromIntegral (maxBound :: Word8) >= numConstructors = 1
52 | fromIntegral (maxBound :: Word16) >= numConstructors = 2
53 | fromIntegral (maxBound :: Word32) >= numConstructors = 4
54 | fromIntegral (maxBound :: Word64) >= numConstructors = 8
55 | otherwise = error "Too many constructors"
57getTagType :: Int -> Name
58getTagType numConstructors
59 | numConstructors == 1 = error "No tag for 1 constructor"
60 | fromIntegral (maxBound :: Word8) >= numConstructors = ''Word8
61 | fromIntegral (maxBound :: Word16) >= numConstructors = ''Word16
62 | fromIntegral (maxBound :: Word32) >= numConstructors = ''Word32
63 | fromIntegral (maxBound :: Word64) >= numConstructors = ''Word64
64 | otherwise = error "Too many constructors"
66--------------------------------------------------------------------------------
68--------------------------------------------------------------------------------
70getNameBaseLen :: Name -> Word8
72 let x = length (nameBase cname)
74 then error "Max Constructor Len: 63 characters"
77conEncLen :: Name -> Word8
78conEncLen cname = getNameBaseLen cname + 1
80--------------------------------------------------------------------------------
82--------------------------------------------------------------------------------
84mkSizeOfExpr :: Bool -> Bool -> TypeOfType -> Q Exp
85mkSizeOfExpr True False tyOfTy =
90 [|$(varE _acc) + $(litIntegral (conEncLen cname))|]
94 (caseE (varE _x) [matchCons (varE _acc) con])
95 MultiType constructors -> sizeOfHeadDt constructors
99 sizeOfFields acc fields =
100 foldl' exprGetSize acc $ zip [0..] fields
102 matchCons acc (SimpleDataCon cname fields) =
103 let a = litIntegral (conEncLen cname)
104 b = sizeOfFields acc (map snd fields)
105 expr = [|$(a) + $(b)|]
106 in matchConstructor cname (length fields) expr
109 let acc = [|$(varE _acc)|]
112 (caseE (varE _x) (fmap (matchCons acc) cons))
114mkSizeOfExpr False False tyOfTy =
116 UnitType _ -> lamE [varP _acc, wildP] [|$(varE _acc) + 1|]
120 (caseE (varE _x) [matchCons (varE _acc) con])
121 MultiType constructors -> sizeOfHeadDt constructors
125 tagSizeExp numConstructors =
126 litE (IntegerL (fromIntegral (getTagSize numConstructors)))
128 -- XXX fields of the same type can be folded together, will reduce the code
129 -- size when there are many fields of the same type.
130 -- XXX const size fields can be calculated statically.
131 -- XXX This can result in large compilation times due to nesting when there
132 -- are many constructors. We can create a list and sum the list at run time
133 -- to avoid that depending on the number of constructors. Or using a let
134 -- statement for each case may help?
135 -- appE (varE 'sum) (listE (acc : map (exprGetSize (litE (IntegerL 0))) (zip [0..] fields)))
136 sizeOfFields acc fields =
137 foldl' exprGetSize acc $ zip [0..] fields
139 matchCons acc (SimpleDataCon cname fields) =
140 let expr = sizeOfFields acc (map snd fields)
141 in matchConstructor cname (length fields) expr
143 -- XXX We fix VarSize for simplicity. Should be changed later.
145 let numCons = length cons
146 acc = [|$(varE _acc) + $(tagSizeExp numCons)|]
149 (caseE (varE _x) (fmap (matchCons acc) cons))
151mkSizeOfExpr False True (TheType con) = RecHeader.mkSizeOfExpr con
153mkSizeOfExpr _ _ _ = errorUnimplemented
155mkSizeDec :: Config -> Type -> [DataCon] -> Q [Dec]
156mkSizeDec (Config {..}) headTy cons = do
157 -- INLINE on sizeOf actually worsens some benchmarks, and improves none
160 constructorTagAsString
161 recordSyntaxWithHeader
162 (typeOfType headTy cons)
164 [ PragmaD (InlineP 'size inlineSize FunLike AllPhases)
165 , FunD 'size [Clause [] (NormalB sizeOfMethod) []]
168--------------------------------------------------------------------------------
170--------------------------------------------------------------------------------
172mkDeserializeExpr :: Bool -> Bool -> Type -> TypeOfType -> Q Exp
173mkDeserializeExpr True False headTy tyOfTy =
175 UnitType cname -> deserializeConsExpr [SimpleDataCon cname []]
176 TheType con -> deserializeConsExpr [con]
177 MultiType cons -> deserializeConsExpr cons
181 deserializeConsExpr cons = do
182 conLen <- newName "conLen"
183 off1 <- newName "off1"
184 [|do ($(varP off1), $(varP conLen) :: Word8) <-
186 $(varE _initialOffset)
189 $(multiIfE (map (guardCon conLen off1) cons ++ [catchAll]))|]
195 ("Found invalid tag while peeking (" ++
196 $(lift (pprint headTy)) ++ ")")|]
198 guardCon conLen off con@(SimpleDataCon cname _) = do
199 let lenCname = getNameBaseLen cname
200 tag = map c2w (nameBase cname)
202 [|($(litIntegral lenCname) == $(varE conLen))
203 && $(xorCmp tag off _arr)|]
204 [|let $(varP (makeI 0)) = $(varE off) + $(litIntegral lenCname)
205 in $(mkDeserializeExprOne 'deserialize con)|]
207mkDeserializeExpr False False headTy tyOfTy =
211 [|pure ($(varE _initialOffset) + 1, $(conE cname))|]
215 [valD (varP (mkName "i0")) (normalB (varE _initialOffset)) []]
216 (mkDeserializeExprOne 'deserialize con)
219 let lenCons = length cons
220 tagType = getTagType lenCons
223 (tupP [varP (mkName "i0"), varP _tag])
224 [|deserialize $(varE _initialOffset) $(varE _arr) $(varE _endOffset)|]
227 (sigE (varE _tag) (conT tagType))
228 (map peekMatch (zip [0 ..] cons) ++ [peekErr]))
234 (normalB (mkDeserializeExprOne 'deserialize con)) []
241 ("Found invalid tag while peeking (" ++
242 $(lift (pprint headTy)) ++ ")")|])
245mkDeserializeExpr False True _ (TheType con@(SimpleDataCon _ fields)) = do
246 deserializeWithKeys <- newName "deserializeWithKeys"
247 updateFunc <- newName "updateFunc"
248 updateFuncDec <- RecHeader.conUpdateFuncDec updateFunc fields
249 deserializeWithKeysDec <-
250 RecHeader.mkDeserializeKeysDec deserializeWithKeys updateFunc con
252 (pure <$> (deserializeWithKeysDec ++ updateFuncDec))
253 (RecHeader.mkDeserializeExpr
259mkDeserializeExpr _ _ _ _ = errorUnimplemented
261mkDeserializeDec :: Config -> Type -> [DataCon] -> Q [Dec]
262mkDeserializeDec (Config {..}) headTy cons = do
265 constructorTagAsString
266 recordSyntaxWithHeader
268 (typeOfType headTy cons)
270 [ PragmaD (InlineP 'deserialize inlineDeserialize FunLike AllPhases)
274 (if isUnitType cons && not constructorTagAsString
275 then [VarP _initialOffset, WildP, WildP]
276 else [VarP _initialOffset, VarP _arr, VarP _endOffset])
282--------------------------------------------------------------------------------
284--------------------------------------------------------------------------------
286mkSerializeExprTag :: Name -> Int -> Q Exp
287mkSerializeExprTag tagType tagVal =
289 $(varE _initialOffset)
291 $((sigE (litE (IntegerL (fromIntegral tagVal))) (conT tagType)))|]
293mkSerializeExpr :: Bool -> Bool -> TypeOfType -> Q Exp
294mkSerializeExpr True False tyOfTy =
300 [serializeDataCon (SimpleDataCon cname [])]
305 [serializeDataCon con]
310 (map serializeDataCon cons)
314 serializeDataCon (SimpleDataCon cname fields) = do
315 let tagLen8 = getNameBaseLen cname
316 conEnc = tagLen8 : map c2w (nameBase cname)
322 (serializeW8List _initialOffset _arr conEnc)
323 , noBindS (mkSerializeExprFields 'serialize fields)
326mkSerializeExpr False False tyOfTy =
329 UnitType _ -> [|pure ($(varE _initialOffset) + 1)|]
331 (TheType (SimpleDataCon cname fields)) ->
333 [valD (varP (mkName "i0")) (normalB (varE _initialOffset)) []]
339 (mkSerializeExprFields 'serialize fields)
342 (MultiType cons) -> do
343 let lenCons = length cons
344 tagType = getTagType lenCons
347 (map (\(tagVal, (SimpleDataCon cname fields)) ->
353 (mkSerializeExprTag tagType tagVal)
355 (mkSerializeExprFields
361mkSerializeExpr False True (TheType con) =
362 RecHeader.mkSerializeExpr _initialOffset con
364mkSerializeExpr _ _ _ = errorUnimplemented
366mkSerializeDec :: Config -> Type -> [DataCon] -> Q [Dec]
367mkSerializeDec (Config {..}) headTy cons = do
370 constructorTagAsString
371 recordSyntaxWithHeader
372 (typeOfType headTy cons)
374 [ PragmaD (InlineP 'serialize inlineSerialize FunLike AllPhases)
378 (if isUnitType cons && not constructorTagAsString
379 then [VarP _initialOffset, WildP, WildP]
380 else [VarP _initialOffset, VarP _arr, VarP _val])
386--------------------------------------------------------------------------------
388--------------------------------------------------------------------------------
390-- | A general function to derive Serialize instances where you can control
391-- which Constructors of the datatype to consider and what the Context for the
392-- 'Serialize' instance would be.
394-- Consider the datatype:
396-- data CustomDataType a b
398-- | CDTConstructor2 Bool
399-- | CDTConstructor3 Bool b
400-- deriving (Show, Eq)
405-- $(deriveSerializeInternal
407-- [AppT (ConT ''Serialize) (VarT (mkName "b"))]
409-- (AppT (ConT ''CustomDataType) (VarT (mkName "a")))
410-- (VarT (mkName "b")))
411-- [ DataCon 'CDTConstructor1 [] [] []
412-- , DataCon 'CDTConstructor2 [] [] [(Nothing, (ConT ''Bool))]
417-- [(Nothing, (ConT ''Bool)), (Nothing, (VarT (mkName "b")))]
420deriveSerializeInternal ::
421 Config -> Type -> [DataCon] -> ([Dec] -> Q [Dec]) -> Q [Dec]
422deriveSerializeInternal conf headTy cons next = do
423 sizeDec <- mkSizeDec conf headTy cons
424 peekDec <- mkDeserializeDec conf headTy cons
425 pokeDec <- mkSerializeDec conf headTy cons
426 let methods = concat [sizeDec, peekDec, pokeDec]
429-- | Similar to 'deriveSerialize' but take a 'Config' to control how the
430-- instance is generated.
434-- $(deriveSerializeWith
436-- [d|instance Serialize a => Serialize (Maybe a)|])
438deriveSerializeWith :: Config -> Q [Dec] -> Q [Dec]
439deriveSerializeWith conf mDecs = do
442 [inst@(InstanceD _ _ headTyWC [])] -> do
443 let headTy = unwrap dec headTyWC
444 dt <- reifyDataType (getMainTypeName dec headTy)
446 deriveSerializeInternal conf headTy cons (next inst)
447 _ -> errorUnsupported (errorMessage dec)
450 next (InstanceD mo preds headTyWC []) methods =
451 pure [InstanceD mo preds headTyWC methods]
452 next dec _ = errorUnsupported (errorMessage dec)
456 [ "Error: deriveSerializeWith:"
458 , ">> " ++ pprint dec
460 , "The above is not a valid instance declaration."
461 , "Any haskell instance declaration without a body is valid."
464 , "instance Serialize (Proxy a)"
465 , "instance Serialize a => Serialize (Identity a)"
466 , "instance Serialize (TableT Identity)"
469 unwrap _ (AppT (ConT _) r) = r
470 unwrap dec _ = errorMessage dec
472 getMainTypeName dec = go
478 go _ = errorMessage dec
481-- | Template haskell helper to create instances of 'Serialize' automatically.
483-- Consider the datatype:
485-- data CustomDataType a b c = ...
488-- Usage: @$(deriveSerialize ''CustomDataType)@
490-- Note: All type variables automatcally get an "Serialize" constraint.
491-- The derived code will look like the following,
493-- instance (Serialize a, Serialize b, Serialize c) => Serialize (CustomDataType a b c) where
497-- To control which type variables don't get the Serialize constraint, use
498-- 'deriveSerializeWith'.
499deriveSerialize :: Name -> Q [Dec]
500deriveSerialize name = do
501 dt <- reifyDataType name
502 let preds = map (unboxPred . VarT) (dtTvs dt)
503 headTy = appsT (ConT name) (map VarT (dtTvs dt))
505 deriveSerializeInternal defaultConfig headTy cons (next preds headTy)
510 next preds headTy methods =
511 pure [InstanceD Nothing preds (AppT (ConT ''Serialize) headTy) methods]
513 unboxPred ty = AppT (ConT ''Serialize) ty
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