module Ganeti.THH ( declareSADT
, declareLADT
, declareILADT
, declareIADT
, makeJSONInstance
, deCamelCase
, genOpID
, genOpLowerStrip
, genAllConstr
, genAllOpIDs
, PyValue(..)
, PyValueEx(..)
, OpCodeField(..)
, OpCodeDescriptor(..)
, genOpCode
, genStrOfOp
, genStrOfKey
, genLuxiOp
, Field (..)
, simpleField
, andRestArguments
, withDoc
, defaultField
, notSerializeDefaultField
, optionalField
, optionalNullSerField
, renameField
, customField
, buildObject
, buildObjectSerialisation
, buildParam
, genException
, excErrMsg
) where
import Control.Arrow ((&&&))
import Control.Applicative
import Control.Monad
import Control.Monad.Base ()
import Control.Monad.Writer (tell)
import qualified Control.Monad.Trans as MT
import Data.Attoparsec.Text ()
import Data.Char
import Data.Function (on)
import Data.List
import Data.Maybe
import qualified Data.Map as M
import qualified Data.Set as S
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (lift)
import qualified Text.JSON as JSON
import Text.JSON.Pretty (pp_value)
import Ganeti.JSON
import Ganeti.PyValue
import Ganeti.THH.PyType
data OptionalType
= NotOptional
| OptionalOmitNull
| OptionalSerializeNull
| AndRestArguments
deriving (Show, Eq)
data Field = Field { fieldName :: String
, fieldType :: Q Type
, fieldRead :: Maybe (Q Exp)
, fieldShow :: Maybe (Q Exp)
, fieldExtraKeys :: [String]
, fieldDefault :: Maybe (Q Exp)
, fieldSerializeDefault :: Bool
, fieldConstr :: Maybe String
, fieldIsOptional :: OptionalType
, fieldDoc :: String
}
simpleField :: String -> Q Type -> Field
simpleField fname ftype =
Field { fieldName = fname
, fieldType = ftype
, fieldRead = Nothing
, fieldShow = Nothing
, fieldExtraKeys = []
, fieldDefault = Nothing
, fieldSerializeDefault = True
, fieldConstr = Nothing
, fieldIsOptional = NotOptional
, fieldDoc = ""
}
andRestArguments :: String -> Field
andRestArguments fname =
Field { fieldName = fname
, fieldType = [t| M.Map String JSON.JSValue |]
, fieldRead = Nothing
, fieldShow = Nothing
, fieldExtraKeys = []
, fieldDefault = Nothing
, fieldSerializeDefault = True
, fieldConstr = Nothing
, fieldIsOptional = AndRestArguments
, fieldDoc = ""
}
withDoc :: String -> Field -> Field
withDoc doc field =
field { fieldDoc = doc }
renameField :: String -> Field -> Field
renameField constrName field = field { fieldConstr = Just constrName }
defaultField :: Q Exp -> Field -> Field
defaultField defval field = field { fieldDefault = Just defval }
notSerializeDefaultField :: Q Exp -> Field -> Field
notSerializeDefaultField defval field =
field { fieldDefault = Just defval
, fieldSerializeDefault = False }
optionalField :: Field -> Field
optionalField field = field { fieldIsOptional = OptionalOmitNull }
optionalNullSerField :: Field -> Field
optionalNullSerField field = field { fieldIsOptional = OptionalSerializeNull }
customField :: Name
-> Name
-> [String]
-> Field
-> Field
customField readfn showfn extra field =
field { fieldRead = Just (varE readfn), fieldShow = Just (varE showfn)
, fieldExtraKeys = extra }
fieldRecordName :: Field -> String
fieldRecordName (Field { fieldName = name, fieldConstr = alias }) =
fromMaybe (camelCase name) alias
fieldVariable :: Field -> String
fieldVariable f =
case (fieldConstr f) of
Just name -> ensureLower name
_ -> map (\c -> if c == '-' then '_' else c) $ fieldName f
actualFieldType :: Field -> Q Type
actualFieldType f | fieldIsOptional f `elem` [NotOptional, AndRestArguments] = t
| otherwise = [t| Maybe $t |]
where t = fieldType f
checkNonOptDef :: (Monad m) => Field -> m ()
checkNonOptDef (Field { fieldIsOptional = OptionalOmitNull
, fieldName = name }) =
fail $ "Optional field " ++ name ++ " used in parameter declaration"
checkNonOptDef (Field { fieldIsOptional = OptionalSerializeNull
, fieldName = name }) =
fail $ "Optional field " ++ name ++ " used in parameter declaration"
checkNonOptDef (Field { fieldDefault = (Just _), fieldName = name }) =
fail $ "Default field " ++ name ++ " used in parameter declaration"
checkNonOptDef _ = return ()
parseFn :: Field
-> Q Exp
-> Q Exp
parseFn field o =
let fnType = [t| JSON.JSValue -> JSON.Result $(fieldType field) |]
expr = maybe [| readJSONWithDesc $(stringE $ fieldName field) False |]
(`appE` o) (fieldRead field)
in sigE expr fnType
loadFn :: Field
-> Q Exp
-> Q Exp
-> Q Exp
loadFn field expr o = [| $expr >>= $(parseFn field o) |]
loadFnOpt :: Field
-> Q Exp
-> Q Exp
-> Q Exp
loadFnOpt field@(Field { fieldDefault = Just def }) expr o
= case fieldIsOptional field of
NotOptional -> [| $expr >>= maybe (return $def) $(parseFn field o) |]
_ -> fail $ "Field " ++ fieldName field ++ ":\
\ A field can't be optional and\
\ have a default value at the same time."
loadFnOpt field expr o
= [| $expr >>= maybe (return Nothing) (liftM Just . $(parseFn field o)) |]
type SimpleField = (String, Q Type)
type SimpleConstructor = (String, [SimpleField])
type SimpleObject = [SimpleConstructor]
type OpCodeConstructor = (String, Q Type, String, [Field], String)
type LuxiConstructor = (String, [Field])
ensureLower :: String -> String
ensureLower [] = []
ensureLower (x:xs) = toLower x:xs
ensureUpper :: String -> String
ensureUpper [] = []
ensureUpper (x:xs) = toUpper x:xs
varNameE :: String -> Q Exp
varNameE = varE . mkName
showJSONE :: Q Exp
showJSONE = varE 'JSON.showJSON
makeObjE :: Q Exp
makeObjE = varE 'JSON.makeObj
fromObjE :: Q Exp
fromObjE = varE 'fromObj
toRawName :: String -> Name
toRawName = mkName . (++ "ToRaw") . ensureLower
fromRawName :: String -> Name
fromRawName = mkName . (++ "FromRaw") . ensureLower
reprE :: Either String Name -> Q Exp
reprE = either stringE varE
appFn :: Exp -> Exp -> Exp
appFn f x | f == VarE 'id = x
| otherwise = AppE f x
appCons :: Name -> [Exp] -> Exp
appCons cname = foldl AppE (ConE cname)
appConsApp :: Name -> [Exp] -> Exp
appConsApp cname =
foldl (\accu e -> InfixE (Just accu) (VarE '(<*>)) (Just e))
(AppE (VarE 'pure) (ConE cname))
buildConsField :: Q Type -> StrictTypeQ
buildConsField ftype = do
ftype' <- ftype
return (NotStrict, ftype')
buildSimpleCons :: Name -> SimpleObject -> Q Dec
buildSimpleCons tname cons = do
decl_d <- mapM (\(cname, fields) -> do
fields' <- mapM (buildConsField . snd) fields
return $ NormalC (mkName cname) fields') cons
return $ DataD [] tname [] decl_d [''Show, ''Eq]
genSaveSimpleObj :: Name
-> String
-> SimpleObject
-> (SimpleConstructor -> Q Clause)
-> Q (Dec, Dec)
genSaveSimpleObj tname sname opdefs fn = do
let sigt = AppT (AppT ArrowT (ConT tname)) (ConT ''JSON.JSValue)
fname = mkName sname
cclauses <- mapM fn opdefs
return $ (SigD fname sigt, FunD fname cclauses)
strADTDecl :: Name -> [String] -> Dec
strADTDecl name constructors =
DataD [] name []
(map (flip NormalC [] . mkName) constructors)
[''Show, ''Eq, ''Enum, ''Bounded, ''Ord]
genToRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
genToRaw traw fname tname constructors = do
let sigt = AppT (AppT ArrowT (ConT tname)) (ConT traw)
clauses <- mapM (\(c, v) -> clause [recP (mkName c) []]
(normalB (reprE v)) []) constructors
return [SigD fname sigt, FunD fname clauses]
genFromRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
genFromRaw traw fname tname constructors = do
sigt <- [t| (Monad m) => $(conT traw) -> m $(conT tname) |]
let varp = mkName "s"
varpe = varE varp
clauses <- mapM (\(c, v) -> do
g <- normalG [| $varpe == $(reprE v) |]
r <- [| return $(conE (mkName c)) |]
return (g, r)) constructors
oth_clause <- do
g <- normalG [| otherwise |]
r <- [|fail ("Invalid string value for type " ++
$(litE (stringL (nameBase tname))) ++ ": " ++ show $varpe) |]
return (g, r)
let fun = FunD fname [Clause [VarP varp]
(GuardedB (clauses++[oth_clause])) []]
return [SigD fname sigt, fun]
declareADT
:: (a -> Either String Name) -> Name -> String -> [(String, a)] -> Q [Dec]
declareADT fn traw sname cons = do
let name = mkName sname
ddecl = strADTDecl name (map fst cons)
cons' = map (\(a, b) -> (a, fn b)) cons
toraw <- genToRaw traw (toRawName sname) name cons'
fromraw <- genFromRaw traw (fromRawName sname) name cons'
return $ ddecl:toraw ++ fromraw
declareLADT :: Name -> String -> [(String, String)] -> Q [Dec]
declareLADT = declareADT Left
declareILADT :: String -> [(String, Int)] -> Q [Dec]
declareILADT sname cons = do
consNames <- sequence [ newName ('_':n) | (n, _) <- cons ]
consFns <- concat <$> sequence
[ do sig <- sigD n [t| Int |]
let expr = litE (IntegerL (toInteger i))
fn <- funD n [clause [] (normalB expr) []]
return [sig, fn]
| n <- consNames
| (_, i) <- cons ]
let cons' = [ (n, n') | (n, _) <- cons | n' <- consNames ]
(consFns ++) <$> declareADT Right ''Int sname cons'
declareIADT :: String -> [(String, Name)] -> Q [Dec]
declareIADT = declareADT Right ''Int
declareSADT :: String -> [(String, Name)] -> Q [Dec]
declareSADT = declareADT Right ''String
genShowJSON :: String -> Q Dec
genShowJSON name = do
body <- [| JSON.showJSON . $(varE (toRawName name)) |]
return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
genReadJSON :: String -> Q Dec
genReadJSON name = do
let s = mkName "s"
body <- [| $(varE (fromRawName name)) =<<
readJSONWithDesc $(stringE name) True $(varE s) |]
return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
makeJSONInstance :: Name -> Q [Dec]
makeJSONInstance name = do
let base = nameBase name
showJ <- genShowJSON base
readJ <- genReadJSON base
return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) [readJ,showJ]]
deCamelCase :: String -> String
deCamelCase =
intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
camelCase :: String -> String
camelCase = concatMap (ensureUpper . drop 1) .
groupBy (\_ b -> b /= '_' && b /= '-') . ('_':)
constructorName :: Con -> Q Name
constructorName (NormalC name _) = return name
constructorName (RecC name _) = return name
constructorName x = fail $ "Unhandled constructor " ++ show x
reifyConsNames :: Name -> Q [String]
reifyConsNames name = do
reify_result <- reify name
case reify_result of
TyConI (DataD _ _ _ cons _) -> mapM (liftM nameBase . constructorName) cons
o -> fail $ "Unhandled name passed to reifyConsNames, expected\
\ type constructor but got '" ++ show o ++ "'"
genConstrToStr :: (String -> Q String) -> Name -> String -> Q [Dec]
genConstrToStr trans_fun name fname = do
cnames <- reifyConsNames name
svalues <- mapM (liftM Left . trans_fun) cnames
genToRaw ''String (mkName fname) name $ zip cnames svalues
genOpID :: Name -> String -> Q [Dec]
genOpID = genConstrToStr (return . deCamelCase)
genOpLowerStrip :: String -> Name -> String -> Q [Dec]
genOpLowerStrip prefix =
genConstrToStr (liftM ((prefix ++) . map toLower . deCamelCase)
. stripPrefixM "Op")
where
stripPrefixM :: String -> String -> Q String
stripPrefixM pfx s = maybe (fail $ s ++ " doesn't start with " ++ pfx)
return
$ stripPrefix pfx s
genAllConstr :: (String -> String) -> Name -> String -> Q [Dec]
genAllConstr trans_fun name vstr = do
cnames <- reifyConsNames name
let svalues = sort $ map trans_fun cnames
vname = mkName vstr
sig = SigD vname (AppT ListT (ConT ''String))
body = NormalB (ListE (map (LitE . StringL) svalues))
return $ [sig, ValD (VarP vname) body []]
genAllOpIDs :: Name -> String -> Q [Dec]
genAllOpIDs = genAllConstr deCamelCase
type OpParam = (String, Q Type, Q Exp)
data OpCodeField = OpCodeField { ocfName :: String
, ocfType :: PyType
, ocfDefl :: Maybe PyValueEx
, ocfDoc :: String
}
data OpCodeDescriptor = OpCodeDescriptor { ocdName :: String
, ocdType :: PyType
, ocdDoc :: String
, ocdFields :: [OpCodeField]
, ocdDescr :: String
}
maybeApp :: Maybe (Q Exp) -> Q Type -> Q Exp
maybeApp Nothing _ =
[| Nothing |]
maybeApp (Just expr) typ =
[| Just ($(conE (mkName "PyValueEx")) ($expr :: $typ)) |]
genPyType' :: OptionalType -> Q Type -> Q PyType
genPyType' opt typ = typ >>= pyOptionalType (opt /= NotOptional)
genPyType :: Field -> Q PyType
genPyType f = genPyType' (fieldIsOptional f) (fieldType f)
genPyDefault :: Field -> Q Exp
genPyDefault f = maybeApp (fieldDefault f) (fieldType f)
pyField :: Field -> Q Exp
pyField f = genPyType f >>= \t ->
[| OpCodeField $(stringE (fieldName f))
t
$(genPyDefault f)
$(stringE (fieldDoc f)) |]
pyClass :: OpCodeConstructor -> Q Exp
pyClass (consName, consType, consDoc, consFields, consDscField) =
do let pyClassVar = varNameE "showPyClass"
consName' = stringE consName
consType' <- genPyType' NotOptional consType
let consDoc' = stringE consDoc
[| OpCodeDescriptor $consName'
consType'
$consDoc'
$(listE $ map pyField consFields)
consDscField |]
pyClasses :: [OpCodeConstructor] -> Q [Dec]
pyClasses cons =
do let name = mkName "pyClasses"
sig = SigD name (AppT ListT (ConT ''OpCodeDescriptor))
fn <- FunD name <$> (:[]) <$> declClause cons
return [sig, fn]
where declClause c =
clause [] (normalB (ListE <$> mapM pyClass c)) []
opcodeConsToLuxiCons :: OpCodeConstructor -> LuxiConstructor
opcodeConsToLuxiCons (x, _, _, y, _) = (x, y)
genOpCodeDictObject :: Name
-> (LuxiConstructor -> Q Clause)
-> (LuxiConstructor -> Q Exp)
-> [LuxiConstructor]
-> Q [Dec]
genOpCodeDictObject tname savefn loadfn cons = do
tdclauses <- genSaveOpCode cons savefn
fdclauses <- genLoadOpCode cons loadfn
return [ InstanceD [] (AppT (ConT ''DictObject) (ConT tname))
[ FunD 'toDict tdclauses
, FunD 'fromDictWKeys fdclauses
]]
genOpCode :: String
-> [OpCodeConstructor]
-> Q [Dec]
genOpCode name cons = do
let tname = mkName name
decl_d <- mapM (\(cname, _, _, fields, _) -> do
fields' <- mapM (fieldTypeInfo "op") fields
return $ RecC (mkName cname) fields')
cons
let declD = DataD [] tname [] decl_d [''Show, ''Eq]
let (allfsig, allffn) = genAllOpFields "allOpFields" cons
let luxiCons = map opcodeConsToLuxiCons cons
dictObjInst <- genOpCodeDictObject tname saveConstructor loadOpConstructor
luxiCons
pyDecls <- pyClasses cons
return $ [declD, allfsig, allffn] ++ dictObjInst ++ pyDecls
genOpConsFields :: OpCodeConstructor -> Clause
genOpConsFields (cname, _, _, fields, _) =
let op_id = deCamelCase cname
fvals = map (LitE . StringL) . sort . nub $
concatMap (\f -> fieldName f:fieldExtraKeys f) fields
in Clause [LitP (StringL op_id)] (NormalB $ ListE fvals) []
genAllOpFields :: String
-> [OpCodeConstructor]
-> (Dec, Dec)
genAllOpFields sname opdefs =
let cclauses = map genOpConsFields opdefs
other = Clause [WildP] (NormalB (ListE [])) []
fname = mkName sname
sigt = AppT (AppT ArrowT (ConT ''String)) (AppT ListT (ConT ''String))
in (SigD fname sigt, FunD fname (cclauses++[other]))
saveConstructor :: LuxiConstructor
-> Q Clause
saveConstructor (sname, fields) = do
let cname = mkName sname
fnames <- mapM (newName . fieldVariable) fields
let pat = conP cname (map varP fnames)
let felems = zipWith saveObjectField fnames fields
opid = [| [( $(stringE "OP_ID"),
JSON.showJSON $(stringE . deCamelCase $ sname) )] |]
flist = listE (opid:felems)
flist' = [| concat $flist |]
clause [pat] (normalB flist') []
genSaveOpCode :: [LuxiConstructor]
-> (LuxiConstructor -> Q Clause)
-> Q [Clause]
genSaveOpCode opdefs fn = mapM fn opdefs
loadConstructor :: Name -> (Field -> Q Exp) -> [Field] -> Q Exp
loadConstructor name loadfn fields =
[| MT.lift $(appConsApp name <$> mapM loadfn fields)
<* tell $(fieldsUsedKeysQ fields) |]
loadOpConstructor :: LuxiConstructor -> Q Exp
loadOpConstructor (sname, fields) =
loadConstructor (mkName sname) (loadObjectField fields) fields
genLoadOpCode :: [LuxiConstructor]
-> (LuxiConstructor -> Q Exp)
-> Q [Clause]
genLoadOpCode opdefs fn = do
let objname = objVarName
opidKey = "OP_ID"
opid = mkName $ map toLower opidKey
st <- bindS (varP opid) [| $fromObjE $(varE objname) $(stringE opidKey) |]
mexps <- mapM fn opdefs
fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
let mpats = map (\(me, op) ->
let mp = LitP . StringL . deCamelCase . fst $ op
in Match mp (NormalB me) []
) $ zip mexps opdefs
defmatch = Match WildP (NormalB fails) []
cst = NoBindS $ CaseE (VarE opid) $ mpats++[defmatch]
body = DoE [st, cst]
bodyAndOpId <- [| $(return body)
<* tell (mkUsedKeys $ S.singleton opidKey) |]
return [Clause [VarP objname] (NormalB bodyAndOpId) []]
genStrOfOp :: Name -> String -> Q [Dec]
genStrOfOp = genConstrToStr return
genStrOfKey :: Name -> String -> Q [Dec]
genStrOfKey = genConstrToStr (return . ensureLower)
genLuxiOp :: String -> [LuxiConstructor] -> Q [Dec]
genLuxiOp name cons = do
let tname = mkName name
decl_d <- mapM (\(cname, fields) -> do
fields' <- mapM actualFieldType fields
let fields'' = zip (repeat NotStrict) fields'
return $ NormalC (mkName cname) fields'')
cons
let declD = DataD [] (mkName name) [] decl_d [''Show, ''Eq]
dictObjInst <- genOpCodeDictObject tname saveLuxiConstructor
loadOpConstructor cons
opToArgsType <- [t| $(conT tname) -> JSON.JSValue |]
opToArgsExp <- [| JSON.showJSON . map snd . toDict |]
let opToArgsName = mkName "opToArgs"
opToArgsDecs = [ SigD opToArgsName opToArgsType
, ValD (VarP opToArgsName) (NormalB opToArgsExp) []
]
req_defs <- declareSADT "LuxiReq" .
map (\(str, _) -> ("Req" ++ str, mkName ("luxiReq" ++ str))) $
cons
return $ [declD] ++ dictObjInst ++ opToArgsDecs ++ req_defs
saveLuxiConstructor :: LuxiConstructor -> Q Clause
saveLuxiConstructor (sname, fields) = do
let cname = mkName sname
fnames <- mapM (newName . fieldVariable) fields
let pat = conP cname (map varP fnames)
let felems = zipWith saveObjectField fnames fields
flist = [| concat $(listE felems) |]
clause [pat] (normalB flist) []
fieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
fieldTypeInfo field_pfx fd = do
t <- actualFieldType fd
let n = mkName . (field_pfx ++) . fieldRecordName $ fd
return (n, NotStrict, t)
buildObject :: String -> String -> [Field] -> Q [Dec]
buildObject sname field_pfx fields = do
when (any ((==) AndRestArguments . fieldIsOptional)
. drop 1 $ reverse fields)
$ fail "Objects may have only one AndRestArguments field,\
\ and it must be the last one."
let name = mkName sname
fields_d <- mapM (fieldTypeInfo field_pfx) fields
let decl_d = RecC name fields_d
let declD = DataD [] name [] [decl_d] [''Show, ''Eq]
ser_decls <- buildObjectSerialisation sname fields
return $ declD:ser_decls
buildObjectSerialisation :: String -> [Field] -> Q [Dec]
buildObjectSerialisation sname fields = do
let name = mkName sname
dictdecls <- genDictObject saveObjectField
(loadObjectField fields) sname fields
savedecls <- genSaveObject sname
(loadsig, loadfn) <- genLoadObject sname
shjson <- objectShowJSON sname
rdjson <- objectReadJSON sname
let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
[rdjson, shjson]
return $ dictdecls ++ savedecls ++ [loadsig, loadfn, instdecl]
objVarName :: Name
objVarName = mkName "_o"
defaultToJSArray :: (DictObject a) => [String] -> a -> [JSON.JSValue]
defaultToJSArray keys o =
let m = M.fromList $ toDict o
in map (fromMaybe JSON.JSNull . flip M.lookup m) keys
defaultFromJSArray :: (DictObject a)
=> [String] -> [JSON.JSValue] -> JSON.Result a
defaultFromJSArray keys xs = do
let xslen = length xs
explen = length keys
unless (xslen == explen) (fail $ "Expected " ++ show explen
++ " arguments, got " ++ show xslen)
fromDict $ zip keys xs
genArrayObjectInstance :: Name -> [Field] -> Q Dec
genArrayObjectInstance name fields = do
let fnames = concatMap (liftA2 (:) fieldName fieldExtraKeys) fields
instanceD (return []) (appT (conT ''ArrayObject) (conT name))
[ valD (varP 'toJSArray) (normalB [| defaultToJSArray $(lift fnames) |]) []
, valD (varP 'fromJSArray) (normalB [| defaultFromJSArray fnames |]) []
]
genDictObject :: (Name -> Field -> Q Exp)
-> (Field -> Q Exp)
-> String
-> [Field]
-> Q [Dec]
genDictObject save_fn load_fn sname fields = do
let name = mkName sname
fnames <- mapM (newName . fieldVariable) fields
let pat = conP name (map varP fnames)
tdexp = [| concat $(listE $ zipWith save_fn fnames fields) |]
tdclause <- clause [pat] (normalB tdexp) []
fdexp <- loadConstructor name load_fn fields
let fdclause = Clause [VarP objVarName] (NormalB fdexp) []
arrdec <- genArrayObjectInstance name fields
return $ [InstanceD [] (AppT (ConT ''DictObject) (ConT name))
[ FunD 'toDict [tdclause]
, FunD 'fromDictWKeys [fdclause]
]]
++ [arrdec]
genSaveObject :: String -> Q [Dec]
genSaveObject sname = do
let fname = mkName ("save" ++ sname)
sigt <- [t| $(conT $ mkName sname) -> JSON.JSValue |]
cclause <- [| showJSONtoDict |]
return [SigD fname sigt, ValD (VarP fname) (NormalB cclause) []]
saveObjectField :: Name -> Field -> Q Exp
saveObjectField fvar field = do
let formatFn = fromMaybe [| JSON.showJSON &&& (const []) |] $
fieldShow field
formatFnTyped = sigE formatFn
[t| $(fieldType field) -> (JSON.JSValue, [(String, JSON.JSValue)]) |]
let formatCode v = [| let (actual, extra) = $formatFnTyped $(v)
in ($nameE, actual) : extra |]
case fieldIsOptional field of
OptionalOmitNull -> [| case $(fvarE) of
Nothing -> []
Just v -> $(formatCode [| v |])
|]
OptionalSerializeNull -> [| case $(fvarE) of
Nothing -> [( $nameE, JSON.JSNull )]
Just v -> $(formatCode [| v |])
|]
NotOptional -> case (fieldDefault field, fieldSerializeDefault field) of
(Just v, False) -> [| if $v /= $fvarE
then $(formatCode fvarE)
else [] |]
_ -> formatCode fvarE
AndRestArguments -> [| M.toList $(varE fvar) |]
where nameE = stringE (fieldName field)
fvarE = varE fvar
objectShowJSON :: String -> Q Dec
objectShowJSON name = do
body <- [| JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
genLoadObject :: String -> Q (Dec, Dec)
genLoadObject sname = do
let fname = mkName $ "load" ++ sname
sigt <- [t| JSON.JSValue -> JSON.Result $(conT $ mkName sname) |]
cclause <- [| readJSONfromDict |]
return $ (SigD fname sigt,
FunD fname [Clause [] (NormalB cclause) []])
loadObjectField :: [Field] -> Field -> Q Exp
loadObjectField allFields field = do
let otherNames = fieldsDictKeysQ . filter (on (/=) fieldName field)
$ allFields
let objvar = varE objVarName
objfield = stringE (fieldName field)
case (fieldDefault field, fieldIsOptional field) of
(Nothing, NotOptional) ->
loadFn field [| fromObj $objvar $objfield |] objvar
(Nothing, AndRestArguments) ->
[| return . M.fromList
. filter (not . (`S.member` $(otherNames)) . fst)
$ $objvar |]
_ -> loadFnOpt field [| maybeFromObj $objvar $objfield |] objvar
fieldDictKeys :: Field -> Exp
fieldDictKeys field = AppE (VarE 'S.fromList)
. ListE . map (LitE . StringL) $ liftA2 (:) fieldName fieldExtraKeys field
fieldsDictKeys :: [Field] -> Exp
fieldsDictKeys fields =
AppE (VarE 'S.unions) . ListE . map fieldDictKeys $ fields
fieldsDictKeysQ :: [Field] -> Q Exp
fieldsDictKeysQ = return . fieldsDictKeys
fieldsUsedKeysQ :: [Field] -> Q Exp
fieldsUsedKeysQ fields
| any ((==) AndRestArguments . fieldIsOptional) fields
= [| allUsedKeys |]
| otherwise = [| mkUsedKeys $(fieldsDictKeysQ fields) |]
objectReadJSON :: String -> Q Dec
objectReadJSON name = do
let s = mkName "s"
body <- [| $(varE . mkName $ "load" ++ name) =<<
readJSONWithDesc $(stringE name) False $(varE s) |]
return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
paramTypeNames :: String -> (String, String)
paramTypeNames root = ("Filled" ++ root ++ "Params",
"Partial" ++ root ++ "Params")
paramFieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
paramFieldTypeInfo field_pfx fd = do
t <- actualFieldType fd
let n = mkName . (++ "P") . (field_pfx ++) .
fieldRecordName $ fd
return (n, NotStrict, AppT (ConT ''Maybe) t)
buildParam :: String -> String -> [Field] -> Q [Dec]
buildParam sname field_pfx fields = do
let (sname_f, sname_p) = paramTypeNames sname
name_f = mkName sname_f
name_p = mkName sname_p
fields_f <- mapM (fieldTypeInfo field_pfx) fields
fields_p <- mapM (paramFieldTypeInfo field_pfx) fields
let decl_f = RecC name_f fields_f
decl_p = RecC name_p fields_p
let declF = DataD [] name_f [] [decl_f] [''Show, ''Eq]
declP = DataD [] name_p [] [decl_p] [''Show, ''Eq]
ser_decls_f <- buildObjectSerialisation sname_f fields
ser_decls_p <- buildPParamSerialisation sname_p fields
fill_decls <- fillParam sname field_pfx fields
return $ [declF, declP] ++ ser_decls_f ++ ser_decls_p ++ fill_decls ++
buildParamAllFields sname fields
buildParamAllFields :: String -> [Field] -> [Dec]
buildParamAllFields sname fields =
let vname = mkName ("all" ++ sname ++ "ParamFields")
sig = SigD vname (AppT ListT (ConT ''String))
val = ListE $ map (LitE . StringL . fieldName) fields
in [sig, ValD (VarP vname) (NormalB val) []]
buildPParamSerialisation :: String -> [Field] -> Q [Dec]
buildPParamSerialisation sname fields = do
let name = mkName sname
dictdecls <- genDictObject savePParamField loadPParamField sname fields
savedecls <- genSaveObject sname
(loadsig, loadfn) <- genLoadObject sname
shjson <- objectShowJSON sname
rdjson <- objectReadJSON sname
let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
[rdjson, shjson]
return $ dictdecls ++ savedecls ++ [loadsig, loadfn, instdecl]
savePParamField :: Name -> Field -> Q Exp
savePParamField fvar field = do
checkNonOptDef field
let actualVal = mkName "v"
normalexpr <- saveObjectField actualVal field
return $ CaseE (VarE fvar) [ Match (ConP 'Nothing [])
(NormalB (ConE '[])) []
, Match (ConP 'Just [VarP actualVal])
(NormalB normalexpr) []
]
loadPParamField :: Field -> Q Exp
loadPParamField field = do
checkNonOptDef field
let name = fieldName field
let objvar = varE objVarName
objfield = stringE name
loadexp = [| $(varE 'maybeFromObj) $objvar $objfield |]
loadFnOpt field loadexp objvar
buildFromMaybe :: String -> Q Dec
buildFromMaybe fname =
valD (varP (mkName $ "n_" ++ fname))
(normalB [| $(varE 'fromMaybe)
$(varNameE $ "f_" ++ fname)
$(varNameE $ "p_" ++ fname) |]) []
fillParam :: String -> String -> [Field] -> Q [Dec]
fillParam sname field_pfx fields = do
let fnames = map (\fd -> field_pfx ++ fieldRecordName fd) fields
(sname_f, sname_p) = paramTypeNames sname
oname_f = "fobj"
oname_p = "pobj"
name_f = mkName sname_f
name_p = mkName sname_p
fun_name = mkName $ "fill" ++ sname ++ "Params"
le_full = ValD (ConP name_f (map (VarP . mkName . ("f_" ++)) fnames))
(NormalB . VarE . mkName $ oname_f) []
le_part = ValD (ConP name_p (map (VarP . mkName . ("p_" ++)) fnames))
(NormalB . VarE . mkName $ oname_p) []
obj_new = appCons name_f $ map (VarE . mkName . ("n_" ++)) fnames
le_new <- mapM buildFromMaybe fnames
funt <- [t| $(conT name_f) -> $(conT name_p) -> $(conT name_f) |]
let sig = SigD fun_name funt
fclause = Clause [VarP (mkName oname_f), VarP (mkName oname_p)]
(NormalB $ LetE (le_full:le_part:le_new) obj_new) []
fun = FunD fun_name [fclause]
return [sig, fun]
excErrMsg :: (String, Q Type)
excErrMsg = ("errMsg", [t| String |])
genException :: String
-> SimpleObject
-> Q [Dec]
genException name cons = do
let tname = mkName name
declD <- buildSimpleCons tname cons
(savesig, savefn) <- genSaveSimpleObj tname ("save" ++ name) cons $
uncurry saveExcCons
(loadsig, loadfn) <- genLoadExc tname ("load" ++ name) cons
return [declD, loadsig, loadfn, savesig, savefn]
saveExcCons :: String
-> [SimpleField]
-> Q Clause
saveExcCons sname fields = do
let cname = mkName sname
fnames <- mapM (newName . fst) fields
let pat = conP cname (map varP fnames)
felems = if null fnames
then conE '()
else listE $ map (\f -> [| JSON.showJSON $(varE f) |]) fnames
let tup = tupE [ litE (stringL sname), felems ]
clause [pat] (normalB [| JSON.showJSON $tup |]) []
loadExcConstructor :: Name -> String -> [SimpleField] -> Q Exp
loadExcConstructor inname sname fields = do
let name = mkName sname
f_names <- mapM (newName . fst) fields
let read_args = AppE (VarE 'JSON.readJSON) (VarE inname)
let binds = case f_names of
[x] -> BindS (ListP [VarP x])
_ -> BindS (TupP (map VarP f_names))
cval = appCons name $ map VarE f_names
return $ DoE [binds read_args, NoBindS (AppE (VarE 'return) cval)]
genLoadExc :: Name -> String -> SimpleObject -> Q (Dec, Dec)
genLoadExc tname sname opdefs = do
let fname = mkName sname
exc_name <- newName "name"
exc_args <- newName "args"
exc_else <- newName "s"
arg_else <- newName "v"
fails <- [| fail $ "Unknown exception '" ++ $(varE exc_else) ++ "'" |]
let defmatch = Match (VarP exc_else) (NormalB fails) []
str_matches <-
mapM (\(s, params) -> do
body_exp <- loadExcConstructor exc_args s params
return $ Match (LitP (StringL s)) (NormalB body_exp) [])
opdefs
let clause1 = Clause [ConP 'JSON.JSArray
[ListP [ConP 'JSON.JSString [VarP exc_name],
VarP exc_args]]]
(NormalB (CaseE (AppE (VarE 'JSON.fromJSString)
(VarE exc_name))
(str_matches ++ [defmatch]))) []
fail_type <- [| fail $ "Invalid exception: expected '(string, [args])' " ++
" but got " ++ show (pp_value $(varE arg_else)) ++ "'"
|]
let clause2 = Clause [VarP arg_else] (NormalB fail_type) []
sigt <- [t| JSON.JSValue -> JSON.Result $(conT tname) |]
return $ (SigD fname sigt, FunD fname [clause1, clause2])