domain-cereal-0.1: library/DomainCereal/TH.hs
module DomainCereal.TH where
import qualified Data.Serialize as Cereal
import qualified Data.Serialize.LEB128.Lenient as Leb128
import DomainCereal.Prelude
import qualified DomainCore.Model as Model
import qualified DomainCore.TH as DomainTH
import Language.Haskell.TH.Syntax
import THLego.Helpers
import qualified THLego.Lambdas as Lambdas
import qualified TemplateHaskell.Compat.V0208 as Compat
-- *
serializeInstanceD :: Model.TypeDec -> Dec
serializeInstanceD (Model.TypeDec typeName typeDef) =
InstanceD Nothing [] headType [putFunD, getFunD]
where
headType =
AppT (ConT ''Cereal.Serialize) (ConT (textName typeName))
(putFunD, getFunD) =
case typeDef of
Model.SumTypeDef members ->
(sumPutFunD preparedMembers, sumGetFunD preparedMembers)
where
preparedMembers =
fmap prepare members
where
prepare (memberName, memberComponentTypes) =
( DomainTH.sumConstructorName typeName memberName,
length memberComponentTypes
)
Model.ProductTypeDef members ->
(productPutFunD conName components, productGetFunD conName components)
where
conName =
textName typeName
components =
length members
-- *
sumPutFunD :: [(Name, Int)] -> Dec
sumPutFunD members =
FunD 'Cereal.put clauses
where
clauses =
zipWith memberClause members [0 ..]
where
memberClause (conName, components) conIdx =
Clause [Compat.conp conName componentPList] (NormalB body) []
where
componentNameList = enumAlphabeticNames components
componentPList = componentNameList & fmap VarP
body = mconcatE $ tagE : fmap namePutE componentNameList
where
tagE = AppE (VarE 'Leb128.putLEB128) conIdxLitE
where
conIdxLitE = signedAsWord32E $ LitE $ IntegerL $ fromIntegral conIdx
productPutFunD :: Name -> Int -> Dec
productPutFunD conName components =
FunD 'Cereal.put [clause]
where
clause =
Clause [Compat.conp conName componentPList] (NormalB body) []
where
componentNameList = enumAlphabeticNames components
componentPList = componentNameList & fmap VarP
body = nameListPutE componentNameList
sumGetFunD :: [(Name, Int)] -> Dec
sumGetFunD members =
FunD 'Cereal.get [clause]
where
clause =
Clause [] (NormalB body) []
where
body =
AppE (AppE (VarE '(>>=)) word32GetLEB128E) tagMatchE
where
tagMatchE = Lambdas.matcher $ zipWith memberMatch members [0 ..] <> [defaultMatch]
where
memberMatch (conName, components) conIdx =
Match (LitP (IntegerL conIdx)) (NormalB body) []
where
body = applicativeChainE (ConE conName) (replicate components (VarE 'Cereal.get))
defaultMatch =
Match WildP (NormalB body) []
where
body = AppE (VarE 'fail) (LitE (StringL "Unsupported tag"))
productGetFunD :: Name -> Int -> Dec
productGetFunD conName components =
FunD 'Cereal.get [clause]
where
clause =
Clause [] (NormalB body) []
where
body =
applicativeChainE (ConE conName) (replicate components (VarE 'Cereal.get))
-- *
mconcatE :: [Exp] -> Exp
mconcatE = AppE (VarE 'mconcat) . ListE
nameListPutE :: [Name] -> Exp
nameListPutE = mconcatE . fmap namePutE
namePutE :: Name -> Exp
namePutE name = AppE (VarE 'Cereal.put) (VarE name)
signedAsWord32E :: Exp -> Exp
signedAsWord32E exp = SigE exp (ConT ''Word32)
word32GetLEB128E :: Exp
word32GetLEB128E = SigE (VarE 'Leb128.getLEB128) (AppT (ConT ''Cereal.Get) (ConT ''Word32))