1{-# LANGUAGE TemplateHaskell #-}
4-- Module : Streamly.Internal.Data.Unbox.TH
5-- Copyright : (c) 2023 Composewell Technologies
6-- License : BSD3-3-Clause
7-- Maintainer : streamly@composewell.com
8-- Stability : experimental
11module Streamly.Internal.Data.Unbox.TH
16--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
20import Data.Word (Word16, Word32, Word64, Word8)
21import Data.Proxy (Proxy(..))
23import Language.Haskell.TH
24import Language.Haskell.TH.Syntax
25import Streamly.Internal.Data.Unbox
27--------------------------------------------------------------------------------
29--------------------------------------------------------------------------------
31-- The following are copied to remove the dependency on th-utilities.
32-- The code has been copied from th-abstraction and th-utilities.
34-- Some CPP macros in the following code are not required but are kept
35-- anyway. They can be removed if deemed as a maintainance burden.
37#if MIN_VERSION_template_haskell(2,17,0)
38type TyVarBndr_ flag = TyVarBndr flag
40type TyVarBndr_ flag = TyVarBndr
43-- | Case analysis for a 'TyVarBndr'. If the value is a @'PlainTV' n _@, apply
44-- the first function to @n@; if it is @'KindedTV' n _ k@, apply the second
45-- function to @n@ and @k@.
46elimTV :: (Name -> r) -> (Name -> Kind -> r) -> TyVarBndr_ flag -> r
47#if MIN_VERSION_template_haskell(2,17,0)
48elimTV ptv _ktv (PlainTV n _) = ptv n
49elimTV _ptv ktv (KindedTV n _ k) = ktv n k
51elimTV ptv _ktv (PlainTV n) = ptv n
52elimTV _ptv ktv (KindedTV n k) = ktv n k
55-- | Extract the type variable name from a 'TyVarBndr', ignoring the
56-- kind signature if one exists.
57tvName :: TyVarBndr_ flag -> Name
58tvName = elimTV id (\n _ -> n)
60-- | Get the 'Name' of a 'TyVarBndr'
61tyVarBndrName :: TyVarBndr_ flag -> Name
64appsT :: Type -> [Type] -> Type
66appsT x (y:xs) = appsT (AppT x y) xs
68-- | Utility to conveniently handle change to 'InstanceD' API in
69-- template-haskell-2.11.0
70plainInstanceD :: Cxt -> Type -> [Dec] -> Dec
72#if MIN_VERSION_template_haskell(2,11,0)
78-- | Simplified info about a 'DataD'. Omits deriving, strictness,
79-- kind info, and whether it's @data@ or @newtype@.
80data DataType = DataType
85 } deriving (Eq, Show, Ord) --, Data, Typeable, Generic)
87-- | Simplified info about a 'Con'. Omits deriving, strictness, and kind
88-- info. This is much nicer than consuming 'Con' directly, because it
89-- unifies all the constructors into one.
94 , dcFields :: [(Maybe Name, Type)]
95 } deriving (Eq, Show, Ord) --, Data, Typeable, Generic)
98-- | Convert a 'Con' to a list of 'DataCon'. The result is a list
99-- because 'GadtC' and 'RecGadtC' can define multiple constructors.
100conToDataCons :: Con -> [DataCon]
102 NormalC name slots ->
103 [DataCon name [] [] (map (\(_, ty) -> (Nothing, ty)) slots)]
105 [DataCon name [] [] (map (\(n, _, ty) -> (Just n, ty)) fields)]
106 InfixC (_, ty1) name (_, ty2) ->
107 [DataCon name [] [] [(Nothing, ty1), (Nothing, ty2)]]
108 ForallC tvs preds con ->
109 map (\(DataCon name tvs0 preds0 fields) ->
110 DataCon name (tvs0 ++ map tyVarBndrName tvs) (preds0 ++ preds) fields) (conToDataCons con)
111#if MIN_VERSION_template_haskell(2,11,0)
113 map (\dn -> DataCon dn [] [] (map (\(_, ty) -> (Nothing, ty)) slots)) ns
114 RecGadtC ns fields _ ->
115 map (\dn -> DataCon dn [] [] (map (\(fn, _, ty) -> (Just fn, ty)) fields)) ns
118-- | Reify the given data or newtype declaration, and yields its
119-- 'DataType' representation.
120reifyDataType :: Name -> Q DataType
121reifyDataType name = do
123 case infoToDataType info of
124 Nothing -> fail $ "Expected to reify a datatype. Instead got:\n" ++ pprint info
127infoToDataType :: Info -> Maybe DataType
128infoToDataType info = case info of
129#if MIN_VERSION_template_haskell(2,11,0)
130 TyConI (DataD preds name tvs _kind cons _deriving) ->
132 TyConI (DataD preds name tvs cons _deriving) ->
134 Just $ DataType name (map tyVarBndrName tvs) preds (concatMap conToDataCons cons)
135#if MIN_VERSION_template_haskell(2,11,0)
136 TyConI (NewtypeD preds name tvs _kind con _deriving) ->
138 TyConI (NewtypeD preds name tvs con _deriving) ->
140 Just $ DataType name (map tyVarBndrName tvs) preds (conToDataCons con)
143--------------------------------------------------------------------------------
145--------------------------------------------------------------------------------
147type Field = (Maybe Name, Type)
155_initialOffset :: Name
156_initialOffset = mkName "initialOffset"
161mkOffsetName :: Int -> Name
162mkOffsetName i = mkName ("offset" ++ show i)
164mkFieldName :: Int -> Name
165mkFieldName i = mkName ("field" ++ show i)
167--------------------------------------------------------------------------------
168-- Domain specific helpers
169--------------------------------------------------------------------------------
171exprGetSize :: Type -> Q Exp
172exprGetSize ty = appE (varE 'sizeOf) [|Proxy :: Proxy $(pure ty)|]
174getTagSize :: Int -> Int
175getTagSize numConstructors
176 | numConstructors == 1 = 0
177 | fromIntegral (maxBound :: Word8) >= numConstructors = 1
178 | fromIntegral (maxBound :: Word16) >= numConstructors = 2
179 | fromIntegral (maxBound :: Word32) >= numConstructors = 4
180 | fromIntegral (maxBound :: Word64) >= numConstructors = 8
181 | otherwise = error "Too many constructors"
183getTagType :: Int -> Name
184getTagType numConstructors
185 | numConstructors == 1 = error "No tag for 1 constructor"
186 | fromIntegral (maxBound :: Word8) >= numConstructors = ''Word8
187 | fromIntegral (maxBound :: Word16) >= numConstructors = ''Word16
188 | fromIntegral (maxBound :: Word32) >= numConstructors = ''Word32
189 | fromIntegral (maxBound :: Word64) >= numConstructors = ''Word64
190 | otherwise = error "Too many constructors"
192mkOffsetDecls :: Int -> [Field] -> [Q Dec]
193mkOffsetDecls tagSize fields =
196 (varP (mkOffsetName 0))
198 [|$(litE (IntegerL (fromIntegral tagSize))) +
199 $(varE _initialOffset)|])
201 (map mkOffsetExpr (zip [1 ..] fields)))
205 mkOffsetExpr (i, (_, ty)) =
207 (varP (mkOffsetName i))
208 (normalB [|$(varE (mkOffsetName (i - 1))) + $(exprGetSize ty)|])
211--------------------------------------------------------------------------------
213--------------------------------------------------------------------------------
215isUnitType :: [DataCon] -> Bool
216isUnitType [DataCon _ _ _ []] = True
219mkSizeOfExpr :: Type -> [DataCon] -> Q Exp
220mkSizeOfExpr headTy constructors =
224 ("Attempting to get size with no constructors (" ++
225 $(lift (pprint headTy)) ++ ")")|]
226 -- One constructor with no fields is a unit type. Size of a unit type is
228 [con@(DataCon _ _ _ fields)] ->
230 [] -> litE (IntegerL 1)
231 _ -> [|$(sizeOfConstructor con)|]
232 _ -> [|$(litE (IntegerL (fromIntegral tagSize))) + $(sizeOfHeadDt)|]
236 tagSize = getTagSize (length constructors)
237 sizeOfField (_, ty) = exprGetSize ty
238 sizeOfConstructor (DataCon _ _ _ fields) =
239 appE (varE 'sum) (listE (map sizeOfField fields))
240 -- The size of any Unbox type is atleast 1
242 appE (varE 'maximum) (listE (map sizeOfConstructor constructors))
244--------------------------------------------------------------------------------
246--------------------------------------------------------------------------------
248mkPeekExprOne :: Int -> DataCon -> Q Exp
249mkPeekExprOne tagSize (DataCon cname _ _ fields) =
251 [] -> [|pure $(conE cname)|]
254 (mkOffsetDecls tagSize fields)
256 (\acc i -> [|$(acc) <*> $(peekField i)|])
257 [|$(conE cname) <$> $(peekField 0)|]
258 [1 .. (length fields - 1)])
262 peekField i = [|peekByteIndex $(varE (mkOffsetName i)) $(varE _arr)|]
264mkPeekExpr :: Type -> [DataCon] -> Q Exp
265mkPeekExpr headTy cons =
269 ("Attempting to peek type with no constructors (" ++
270 $(lift (pprint headTy)) ++ ")")|]
271 [con] -> mkPeekExprOne 0 con
276 [|peekByteIndex $(varE _initialOffset) $(varE _arr)|]
279 (sigE (varE _tag) (conT tagType))
280 (map peekMatch (zip [0 ..] cons) ++ [peekErr]))
285 lenCons = length cons
286 tagType = getTagType lenCons
287 tagSize = getTagSize lenCons
289 match (litP (IntegerL i)) (normalB (mkPeekExprOne tagSize con)) []
295 ("Found invalid tag while peeking (" ++
296 $(lift (pprint headTy)) ++ ")")|])
299--------------------------------------------------------------------------------
301--------------------------------------------------------------------------------
303mkPokeExprTag :: Name -> Int -> Q Exp
304mkPokeExprTag tagType tagVal = pokeTag
310 $(varE _initialOffset)
312 $((sigE (litE (IntegerL (fromIntegral tagVal))) (conT tagType)))|]
314mkPokeExprFields :: Int -> [Field] -> Q Exp
315mkPokeExprFields tagSize fields = do
320 (mkOffsetDecls tagSize fields)
321 (doE $ map (noBindS . pokeField) [0 .. (numFields - 1)])
325 numFields = length fields
328 $(varE (mkOffsetName i))
330 $(varE (mkFieldName i))|]
332mkPokeMatch :: Name -> Int -> Q Exp -> Q Match
333mkPokeMatch cname numFields exp0 =
335 (conP cname (map varP (map mkFieldName [0 .. (numFields - 1)])))
339mkPokeExpr :: Type -> [DataCon] -> Q Exp
340mkPokeExpr headTy cons =
344 ("Attempting to poke type with no constructors (" ++
345 $(lift (pprint headTy)) ++ ")")|]
346 [(DataCon _ _ _ [])] -> [|pure ()|]
347 [(DataCon cname _ _ fields)] ->
350 [mkPokeMatch cname (length fields) (mkPokeExprFields 0 fields)]
354 (map (\(tagVal, (DataCon cname _ _ fields)) ->
358 (doE [ noBindS $ mkPokeExprTag tagType tagVal
359 , noBindS $ mkPokeExprFields tagSize fields
365 lenCons = length cons
366 tagType = getTagType lenCons
367 tagSize = getTagSize lenCons
369--------------------------------------------------------------------------------
371--------------------------------------------------------------------------------
373-- | A general function to derive Unbox instances where you can control which
374-- Constructors of the datatype to consider and what the Context for the 'Unbox'
377-- Consider the datatype:
379-- data CustomDataType a b
381-- | CDTConstructor2 Bool
382-- | CDTConstructor3 Bool b
383-- deriving (Show, Eq)
388-- $(deriveUnboxInternal
389-- [AppT (ConT ''Unbox) (VarT (mkName "b"))]
391-- (AppT (ConT ''CustomDataType) (VarT (mkName "a")))
392-- (VarT (mkName "b")))
393-- [ DataCon 'CDTConstructor1 [] [] []
394-- , DataCon 'CDTConstructor2 [] [] [(Nothing, (ConT ''Bool))]
399-- [(Nothing, (ConT ''Bool)), (Nothing, (VarT (mkName "b")))]
402deriveUnboxInternal :: Cxt -> Type -> [DataCon] -> Q [Dec]
403deriveUnboxInternal preds headTy cons = do
404 sizeOfMethod <- mkSizeOfExpr headTy cons
405 peekMethod <- mkPeekExpr headTy cons
406 pokeMethod <- mkPokeExpr headTy cons
408 [ FunD 'sizeOf [Clause [WildP] (NormalB sizeOfMethod) []]
414 else [VarP _initialOffset, VarP _arr])
422 then [WildP, WildP, WildP]
423 else [VarP _initialOffset, VarP _arr, VarP _val])
428 return [plainInstanceD preds (AppT (ConT ''Unbox) headTy) methods]
430-- | Template haskell helper to create instances of 'Unbox' automatically.
432-- Consider the datatype:
434-- data CustomDataType a b c = ...
437-- Usage: @$(deriveUnbox ''CustomDataType)@
439-- Note: All type variables automatcally get an "Unbox" constraint.
440-- The derived code will look like the following,
442-- instance (Unbox a, Unbox b, Unbox c) => Unbox (CustomDataType a b c) where
446-- To control which type variables get the Unbox constraint, use
448deriveUnbox :: Name -> Q [Dec]
450 dt <- reifyDataType name
451 let preds = map (unboxPred . VarT) (dtTvs dt)
452 headTy = appsT (ConT name) (map VarT (dtTvs dt))
454 deriveUnboxInternal preds headTy cons
459#if MIN_VERSION_template_haskell(2,10,0)
460 AppT (ConT ''Unbox) ty
465-- | Like 'deriveUnbox' but control which type variables get the 'Unbox'
468-- Consider the datatype:
470-- data CustomDataType a b c = ...
473-- Usage: @$(deriveUnboxWith ["a", "c"] ''CustomDataType)@
476-- instance (Unbox a, Unbox c) => Unbox (CustomDataType a b c) where
480deriveUnboxWith :: [String] -> Name -> Q [Dec]
481deriveUnboxWith vars name = do
482 dt <- reifyDataType name
483 let preds = map (unboxPred . VarT) (fmap mkName vars)
484 headTy = appsT (ConT name) (map VarT (dtTvs dt))
486 deriveUnboxInternal preds headTy cons
491#if MIN_VERSION_template_haskell(2,10,0)
492 AppT (ConT ''Unbox) 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