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
, presentInForthcoming
, optionalField
, optionalNullSerField
, makeOptional
, renameField
, customField
, buildObject
, buildObjectWithForthcoming
, buildObjectSerialisation
, buildParam
, genException
, excErrMsg
) where
import Control.Arrow ((&&&), second)
import Control.Applicative
import Control.Lens.Type (Lens')
import Control.Lens (lens, set, element)
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 Data.Monoid
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.PartialParams
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
, fieldPresentInForthcoming :: Bool
}
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 = ""
, fieldPresentInForthcoming = False
}
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 = ""
, fieldPresentInForthcoming = True
}
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 }
presentInForthcoming :: Field -> Field
presentInForthcoming field = field { fieldPresentInForthcoming = True }
optionalField :: Field -> Field
optionalField field = field { fieldIsOptional = OptionalOmitNull }
optionalNullSerField :: Field -> Field
optionalNullSerField field = field { fieldIsOptional = OptionalSerializeNull }
makeOptional :: Field -> Field
makeOptional field = if and [ fieldIsOptional field == NotOptional
, isNothing $ fieldDefault field
, not $ fieldPresentInForthcoming field
]
then optionalField field
else field
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
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
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 (second fn) 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
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 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
buildAccessor :: Name
-> String
-> Name
-> String
-> Name
-> String
-> Field
-> Q [Dec]
buildAccessor fnm fpfx rnm rpfx nm pfx field = do
let optField = makeOptional field
x <- newName "x"
(rpfx_name, _, _) <- fieldTypeInfo rpfx field
(fpfx_name, _, ftype) <- fieldTypeInfo fpfx optField
(pfx_name, _, _) <- fieldTypeInfo pfx field
let r_body_core = AppE (VarE rpfx_name) $ VarE x
r_body = if fieldIsOptional field == fieldIsOptional optField
then r_body_core
else AppE (VarE 'return) r_body_core
f_body = AppE (VarE fpfx_name) $ VarE x
return $ [ SigD pfx_name $ ArrowT `AppT` ConT nm `AppT` ftype
, FunD pfx_name
[ Clause [ConP rnm [VarP x]] (NormalB r_body) []
, Clause [ConP fnm [VarP x]] (NormalB f_body) []
]]
buildLens :: (Name, Name)
-> (Name, Name)
-> Name
-> String
-> Int
-> (Field, Int)
-> Q [Dec]
buildLens (fnm, fdnm) (rnm, rdnm) nm pfx ar (field, i) = do
let optField = makeOptional field
if fieldIsOptional field /= fieldIsOptional optField
then return []
else do
let lensnm = mkName $ pfx ++ fieldRecordName field ++ "L"
(accnm, _, ftype) <- fieldTypeInfo pfx field
vars <- replicateM ar (newName "x")
var <- newName "val"
context <- newName "val"
let body cn cdn = NormalB
. (ConE cn `AppE`)
. foldl (\e (j, x) -> AppE e . VarE
$ if i == j then var else x)
(ConE cdn)
$ zip [0..] vars
let setterE = LamE [VarP context, VarP var] $ CaseE (VarE context)
[ Match (ConP fnm [ConP fdnm . set (element i) WildP
$ map VarP vars])
(body fnm fdnm) []
, Match (ConP rnm [ConP rdnm . set (element i) WildP
$ map VarP vars])
(body rnm rdnm) []
]
return [ SigD lensnm $ ConT ''Lens' `AppT` ConT nm `AppT` ftype
, ValD (VarP lensnm)
(NormalB $ VarE 'lens `AppE` VarE accnm `AppE` setterE) []
]
buildObjectWithForthcoming ::
String
-> String
-> [Field]
-> Q [Dec]
buildObjectWithForthcoming sname field_pfx fields = do
let capitalPrefix = ensureUpper field_pfx
forth_nm = "Forthcoming" ++ sname
forth_data_nm = forth_nm ++ "Data"
forth_pfx = "forthcoming" ++ capitalPrefix
real_nm = "Real" ++ sname
real_data_nm = real_nm ++ "Data"
real_pfx = "real" ++ capitalPrefix
concreteDecls <- buildObject real_data_nm real_pfx fields
forthcomingDecls <- buildObject forth_data_nm forth_pfx
(map makeOptional fields)
let name = mkName sname
real_d = NormalC (mkName real_nm)
[(NotStrict, ConT (mkName real_data_nm))]
forth_d = NormalC (mkName forth_nm)
[(NotStrict, ConT (mkName forth_data_nm))]
declD = DataD [] name [] [real_d, forth_d] [''Show, ''Eq]
read_body <- [| branchOnField "forthcoming"
(liftM $(conE $ mkName forth_nm) . JSON.readJSON)
(liftM $(conE $ mkName real_nm) . JSON.readJSON) |]
x <- newName "x"
show_real_body <- [| JSON.showJSON $(varE x) |]
show_forth_body <- [| addField ("forthcoming", JSON.JSBool True)
$ JSON.showJSON $(varE x) |]
let rdjson = FunD 'JSON.readJSON [Clause [] (NormalB read_body) []]
shjson = FunD 'JSON.showJSON
[ Clause [ConP (mkName real_nm) [VarP x]]
(NormalB show_real_body) []
, Clause [ConP (mkName forth_nm) [VarP x]]
(NormalB show_forth_body) []
]
instJSONdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
[rdjson, shjson]
accessors <- liftM concat . flip mapM fields
$ buildAccessor (mkName forth_nm) forth_pfx
(mkName real_nm) real_pfx
name field_pfx
lenses <- liftM concat . flip mapM (zip fields [0..])
$ buildLens (mkName forth_nm, mkName forth_data_nm)
(mkName real_nm, mkName real_data_nm)
name field_pfx (length fields)
xs <- newName "xs"
fromDictWKeysbody <- [| if ("forthcoming", JSON.JSBool True) `elem` $(varE xs)
then liftM $(conE $ mkName forth_nm)
(fromDictWKeys $(varE xs))
else liftM $(conE $ mkName real_nm)
(fromDictWKeys $(varE xs)) |]
todictx_r <- [| toDict $(varE x) |]
todictx_f <- [| ("forthcoming", JSON.JSBool True) : toDict $(varE x) |]
let todict = FunD 'toDict [ Clause [ConP (mkName real_nm) [VarP x]]
(NormalB todictx_r) []
, Clause [ConP (mkName forth_nm) [VarP x]]
(NormalB todictx_f) []
]
fromdict = FunD 'fromDictWKeys [ Clause [VarP xs]
(NormalB fromDictWKeysbody) [] ]
instDict = InstanceD [] (AppT (ConT ''DictObject) (ConT name))
[todict, fromdict]
instArray <- genArrayObjectInstance name
(simpleField "forthcoming" [t| Bool |] : fields)
let forthPredName = mkName $ field_pfx ++ "Forthcoming"
let forthPredDecls = [ SigD forthPredName
$ ArrowT `AppT` ConT name `AppT` ConT ''Bool
, FunD forthPredName
[ Clause [ConP (mkName real_nm) [WildP]]
(NormalB $ ConE 'False) []
, Clause [ConP (mkName forth_nm) [WildP]]
(NormalB $ ConE 'True) []
]
]
return $ concreteDecls ++ forthcomingDecls ++ [declD, instJSONdecl]
++ forthPredDecls ++ accessors ++ lenses ++ [instDict, instArray]
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")
paramFieldNames :: String -> Field -> (Name, Name)
paramFieldNames field_pfx fd =
let base = field_pfx ++ fieldRecordName fd
in (mkName base, mkName (base ++ "P"))
paramFieldTypeInfo :: String -> Field -> VarStrictTypeQ
paramFieldTypeInfo field_pfx fd = do
t <- actualFieldType fd
return (snd $ paramFieldNames field_pfx fd, 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
fillParam :: String -> String -> [Field] -> Q [Dec]
fillParam sname field_pfx fields = do
let (sname_f, sname_p) = paramTypeNames sname
name_f = mkName sname_f
name_p = mkName sname_p
let (fnames, pnames) = unzip $ map (paramFieldNames field_pfx) fields
fbinds <- mapM (newName . ("f_" ++) . nameBase) fnames
let fConP = ConP name_f (map VarP fbinds)
pbinds <- mapM (newName . ("p_" ++) . nameBase) pnames
let pConP = ConP name_p (map VarP pbinds)
let fromMaybeExp fn pn = AppE (AppE (VarE 'fromMaybe) (VarE fn)) (VarE pn)
fupdates = appCons name_f $ zipWith fromMaybeExp fbinds pbinds
fclause = Clause [fConP, pConP] (NormalB fupdates) []
let tpupdates = appCons name_p $ map (AppE (ConE 'Just) . VarE) fbinds
tpclause = Clause [fConP] (NormalB tpupdates) []
let tfupdates = appConsApp name_f $ map VarE pbinds
tfclause = Clause [pConP] (NormalB tfupdates) []
let instType = AppT (AppT (ConT ''PartialParams) (ConT name_f)) (ConT name_p)
let memptyExp = appCons name_p $ map (const $ VarE 'empty) fields
memptyClause = Clause [] (NormalB memptyExp) []
pbinds2 <- mapM (newName . ("p2_" ++) . nameBase) pnames
let pConP2 = ConP name_p (map VarP pbinds2)
let altExp = zipWith (\l r -> AppE (AppE (VarE '(<|>)) (VarE r)) (VarE l))
mappendExp = appCons name_p $ altExp pbinds pbinds2
mappendClause = Clause [pConP, pConP2] (NormalB mappendExp) []
let monoidType = AppT (ConT ''Monoid) (ConT name_p)
return [ InstanceD [] instType
[ FunD 'fillParams [fclause]
, FunD 'toPartial [tpclause]
, FunD 'toFilled [tfclause]
]
, InstanceD [] monoidType
[ FunD 'mempty [memptyClause]
, FunD 'mappend [mappendClause]
]]
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])