Commit a1505857 authored by Iustin Pop's avatar Iustin Pop
Browse files

Convert opcode TH code to the use of Field type



This makes more explicit the field behaviour - previously an optional
field was detected via a "Maybe" constructor, and an optional one via
a "Just defval" one. With this, field behaviour become more explicit
than auto-deduced.

In THH.hs, I slightly changed the fieldVariable function to use the
field name (if the field is not renamed), so that we have the exact
same output as before.
Signed-off-by: default avatarIustin Pop <iustin@google.com>
Reviewed-by: default avatarAgata Murawska <agatamurawska@google.com>
parent 656a7fdf
...@@ -54,28 +54,28 @@ $(makeJSONInstance ''ReplaceDisksMode) ...@@ -54,28 +54,28 @@ $(makeJSONInstance ''ReplaceDisksMode)
-- actually use in the htools codebase. -- actually use in the htools codebase.
$(genOpCode "OpCode" $(genOpCode "OpCode"
[ ("OpTestDelay", [ ("OpTestDelay",
[ ("duration", [t| Double |], noDefault) [ simpleField "duration" [t| Double |]
, ("on_master", [t| Bool |], noDefault) , simpleField "on_master" [t| Bool |]
, ("on_nodes", [t| [String] |], noDefault) , simpleField "on_nodes" [t| [String] |]
]) ])
, ("OpInstanceReplaceDisks", , ("OpInstanceReplaceDisks",
[ ("instance_name", [t| String |], noDefault) [ simpleField "instance_name" [t| String |]
, ("remote_node", [t| Maybe String |], noDefault) , optionalField $ simpleField "remote_node" [t| String |]
, ("mode", [t| ReplaceDisksMode |], noDefault) , simpleField "mode" [t| ReplaceDisksMode |]
, ("disks", [t| [Int] |], noDefault) , simpleField "disks" [t| [Int] |]
, ("iallocator", [t| Maybe String |], noDefault) , optionalField $ simpleField "iallocator" [t| String |]
]) ])
, ("OpInstanceFailover", , ("OpInstanceFailover",
[ ("instance_name", [t| String |], noDefault) [ simpleField "instance_name" [t| String |]
, ("ignore_consistency", [t| Bool |], noDefault) , simpleField "ignore_consistency" [t| Bool |]
, ("target_node", [t| Maybe String |], noDefault) , optionalField $ simpleField "target_node" [t| String |]
]) ])
, ("OpInstanceMigrate", , ("OpInstanceMigrate",
[ ("instance_name", [t| String |], noDefault) [ simpleField "instance_name" [t| String |]
, ("live", [t| Bool |], noDefault) , simpleField "live" [t| Bool |]
, ("cleanup", [t| Bool |], noDefault) , simpleField "cleanup" [t| Bool |]
, ("allow_failover", [t| Bool |], [| Just False |]) , defaultField [| False |] $ simpleField "allow_failover" [t| Bool |]
, ("target_node", [t| Maybe String |], noDefault) , optionalField $ simpleField "target_node" [t| String |]
]) ])
]) ])
......
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-}
{-| TemplateHaskell helper for HTools. {-| TemplateHaskell helper for HTools.
...@@ -34,7 +34,6 @@ module Ganeti.THH ( declareSADT ...@@ -34,7 +34,6 @@ module Ganeti.THH ( declareSADT
, makeJSONInstance , makeJSONInstance
, genOpID , genOpID
, genOpCode , genOpCode
, noDefault
, genStrOfOp , genStrOfOp
, genStrOfKey , genStrOfKey
, genLuxiOp , genLuxiOp
...@@ -119,8 +118,15 @@ fieldRecordName :: Field -> String ...@@ -119,8 +118,15 @@ fieldRecordName :: Field -> String
fieldRecordName (Field { fieldName = name, fieldConstr = alias }) = fieldRecordName (Field { fieldName = name, fieldConstr = alias }) =
maybe (camelCase name) id alias maybe (camelCase name) id alias
-- | Computes the preferred variable name to use for the value of this
-- field. If the field has a specific constructor name, then we use a
-- first-letter-lowercased version of that; otherwise, we simply use
-- the field name. See also 'fieldRecordName'.
fieldVariable :: Field -> String fieldVariable :: Field -> String
fieldVariable = map toLower . fieldRecordName fieldVariable f =
case (fieldConstr f) of
Just name -> ensureLower name
_ -> fieldName f
actualFieldType :: Field -> Q Type actualFieldType :: Field -> Q Type
actualFieldType f | fieldIsContainer f = [t| Container $t |] actualFieldType f | fieldIsContainer f = [t| Container $t |]
...@@ -408,27 +414,15 @@ type OpParam = (String, Q Type, Q Exp) ...@@ -408,27 +414,15 @@ type OpParam = (String, Q Type, Q Exp)
-- datatype and the JSON serialisation out of it. We can't use a -- datatype and the JSON serialisation out of it. We can't use a
-- generic serialisation since we need to be compatible with Ganeti's -- generic serialisation since we need to be compatible with Ganeti's
-- own, so we have a few quirks to work around. -- own, so we have a few quirks to work around.
--
-- There are three things to be defined for each parameter:
--
-- * name
--
-- * type; if this is 'Maybe', will only be serialised if it's a
-- 'Just' value
--
-- * default; if missing, won't raise an exception, but will instead
-- use the default
--
genOpCode :: String -- ^ Type name to use genOpCode :: String -- ^ Type name to use
-> [(String, [OpParam])] -- ^ Constructor name and parameters -> [(String, [Field])] -- ^ Constructor name and parameters
-> Q [Dec] -> Q [Dec]
genOpCode name cons = do genOpCode name cons = do
decl_d <- mapM (\(cname, fields) -> do decl_d <- mapM (\(cname, fields) -> do
-- we only need the type of the field, without Q -- we only need the type of the field, without Q
fields' <- mapM (\(_, qt, _) -> fields' <- mapM actualFieldType fields
qt >>= \t -> return (NotStrict, t)) let fields'' = zip (repeat NotStrict) fields'
fields return $ NormalC (mkName cname) fields'')
return $ NormalC (mkName cname) fields')
cons cons
let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read, ''Eq] let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read, ''Eq]
...@@ -443,39 +437,20 @@ isOptional :: Type -> Bool ...@@ -443,39 +437,20 @@ isOptional :: Type -> Bool
isOptional (AppT (ConT dt) _) | dt == ''Maybe = True isOptional (AppT (ConT dt) _) | dt == ''Maybe = True
isOptional _ = False isOptional _ = False
-- | Generates the \"save\" expression for a single opcode parameter.
--
-- There is only one special handling mode: if the parameter is of
-- 'Maybe' type, then we only save it if it's a 'Just' value,
-- otherwise we skip it.
saveField :: Name -- ^ The name of variable that contains the value
-> OpParam -- ^ Parameter definition
-> Q Exp
saveField fvar (fname, qt, _) = do
t <- qt
let fnexp = stringE fname
fvare = varE fvar
(if isOptional t
then [| case $fvare of
Just v' -> [( $fnexp, $showJSONE v')]
Nothing -> []
|]
else [| [( $fnexp, $showJSONE $fvare )] |])
-- | Generates the \"save\" clause for an entire opcode constructor. -- | Generates the \"save\" clause for an entire opcode constructor.
-- --
-- This matches the opcode with variables named the same as the -- This matches the opcode with variables named the same as the
-- constructor fields (just so that the spliced in code looks nicer), -- constructor fields (just so that the spliced in code looks nicer),
-- and passes those name plus the parameter definition to 'saveObjectField'.
saveConstructor :: String -- ^ The constructor name saveConstructor :: String -- ^ The constructor name
saveConstructor :: String -- ^ The constructor name -> [Field] -- ^ The parameter definitions for this
-- constructor -- constructor
-> Q Clause -- ^ Resulting clause -> Q Clause -- ^ Resulting clause
saveConstructor sname fields = do saveConstructor sname fields = do
let cname = mkName sname let cname = mkName sname
let cname = mkName sname let fnames = map (mkName . fieldVariable) fields
let pat = conP cname (map varP fnames) let pat = conP cname (map varP fnames)
let pat = conP cname (map varP fnames) let felems = map (uncurry saveObjectField) (zip fnames fields)
-- now build the OP_ID serialisation -- now build the OP_ID serialisation
opid = [| [( $(stringE "OP_ID"), opid = [| [( $(stringE "OP_ID"),
$showJSONE $(stringE . deCamelCase $ sname) )] |] $showJSONE $(stringE . deCamelCase $ sname) )] |]
...@@ -488,51 +463,23 @@ saveConstructor sname fields = do ...@@ -488,51 +463,23 @@ saveConstructor sname fields = do
-- --
-- This builds a per-constructor match clause that contains the -- This builds a per-constructor match clause that contains the
-- respective constructor-serialisation code. -- respective constructor-serialisation code.
genSaveOpCode :: [(String, [OpParam])] -> Q (Dec, Dec) genSaveOpCode :: [(String, [Field])] -> Q (Dec, Dec)
genSaveOpCode opdefs = do genSaveOpCode opdefs = do
cclauses <- mapM (uncurry saveConstructor) opdefs cclauses <- mapM (uncurry saveConstructor) opdefs
let fname = mkName "saveOpCode" let fname = mkName "saveOpCode"
sigt <- [t| $(conT (mkName "OpCode")) -> JSON.JSValue |] sigt <- [t| $(conT (mkName "OpCode")) -> JSON.JSValue |]
return $ (SigD fname sigt, FunD fname cclauses) return $ (SigD fname sigt, FunD fname cclauses)
-- | Generates the \"load\" field for a single parameter. loadConstructor :: String -> [Field] -> Q Exp
--
-- There is custom handling, depending on how the parameter is
-- specified. For a 'Maybe' type parameter, we allow that it is not
-- present (via 'Utils.maybeFromObj'). Otherwise, if there is a
-- default value, we allow the parameter to be abset, and finally if
-- there is no default value, we require its presence.
loadField :: OpParam -> Q (Name, Stmt)
loadField (fname, qt, qdefa) = do
let fvar = mkName fname
t <- qt
defa <- qdefa
-- these are used in all patterns below
let objvar = varNameE "o"
objfield = stringE fname
bexp <- if isOptional t
then [| $((varNameE "maybeFromObj")) $objvar $objfield |]
else case defa of
AppE (ConE dt) defval | dt == 'Just ->
-- but has a default value
[| $(varNameE "fromObjWithDefault")
$objvar $objfield $(return defval) |]
ConE dt | dt == 'Nothing ->
[| $(varNameE "fromObj") $objvar $objfield |]
s -> fail $ "Invalid default value " ++ show s ++
", expecting either 'Nothing' or a 'Just defval'"
return (fvar, BindS (VarP fvar) bexp)
loadConstructor :: String -> [OpParam] -> Q Exp
loadConstructor sname fields = do loadConstructor sname fields = do
let name = mkName sname let name = mkName sname
fbinds <- mapM loadField fields fbinds <- mapM loadObjectField fields
let (fnames, fstmts) = unzip fbinds let (fnames, fstmts) = unzip fbinds
let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)] fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
return $ DoE fstmts' return $ DoE fstmts'
genLoadOpCode :: [(String, [OpParam])] -> Q (Dec, Dec) genLoadOpCode :: [(String, [Field])] -> Q (Dec, Dec)
genLoadOpCode opdefs = do genLoadOpCode opdefs = do
let fname = mkName "loadOpCode" let fname = mkName "loadOpCode"
arg1 = mkName "v" arg1 = mkName "v"
...@@ -555,10 +502,6 @@ genLoadOpCode opdefs = do ...@@ -555,10 +502,6 @@ genLoadOpCode opdefs = do
sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |] sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []]) return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
-- | No default type.
noDefault :: Q Exp
noDefault = conE 'Nothing
-- * Template code for luxi -- * Template code for luxi
-- | Constructor-to-string for LuxiOp. -- | Constructor-to-string for LuxiOp.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment