TH: Abstract function for computing constructor names
[ganeti-local] / htools / Ganeti / THH.hs
index 87e6b0a..9fdf8ca 100644 (file)
@@ -10,7 +10,7 @@ needs in this module (except the one for unittests).
 
 {-
 
-Copyright (C) 2011 Google Inc.
+Copyright (C) 2011, 2012 Google Inc.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -42,32 +42,26 @@ module Ganeti.THH ( declareSADT
                   , defaultField
                   , optionalField
                   , renameField
-                  , containerField
                   , customField
                   , timeStampFields
                   , uuidFields
                   , serialFields
+                  , tagsFields
                   , buildObject
                   , buildObjectSerialisation
                   , buildParam
-                  , Container
                   ) where
 
-import Control.Arrow
-import Control.Monad (liftM, liftM2)
+import Control.Monad (liftM)
 import Data.Char
 import Data.List
-import qualified Data.Map as M
+import qualified Data.Set as Set
 import Language.Haskell.TH
 
 import qualified Text.JSON as JSON
 
-import Ganeti.HTools.JSON
-
 -- * Exported types
 
-type Container = M.Map String
-
 -- | Serialised field data type.
 data Field = Field { fieldName        :: String
                    , fieldType        :: Q Type
@@ -75,7 +69,6 @@ data Field = Field { fieldName        :: String
                    , fieldShow        :: Maybe (Q Exp)
                    , fieldDefault     :: Maybe (Q Exp)
                    , fieldConstr      :: Maybe String
-                   , fieldIsContainer :: Bool
                    , fieldIsOptional  :: Bool
                    }
 
@@ -88,7 +81,6 @@ simpleField fname ftype =
         , fieldShow        = Nothing
         , fieldDefault     = Nothing
         , fieldConstr      = Nothing
-        , fieldIsContainer = False
         , fieldIsOptional  = False
         }
 
@@ -105,14 +97,13 @@ defaultField defval field = field { fieldDefault = Just defval }
 optionalField :: Field -> Field
 optionalField field = field { fieldIsOptional = True }
 
--- | Marks a field as a container.
-containerField :: Field -> Field
-containerField field = field { fieldIsContainer = True }
-
 -- | Sets custom functions on a field.
-customField :: Q Exp -> Q Exp -> Field -> Field
+customField :: Name    -- ^ The name of the read function
+            -> Name    -- ^ The name of the show function
+            -> Field   -- ^ The original field
+            -> Field   -- ^ Updated field
 customField readfn showfn field =
-  field { fieldRead = Just readfn, fieldShow = Just showfn }
+  field { fieldRead = Just (varE readfn), fieldShow = Just (varE showfn) }
 
 fieldRecordName :: Field -> String
 fieldRecordName (Field { fieldName = name, fieldConstr = alias }) =
@@ -126,11 +117,10 @@ fieldVariable :: Field -> String
 fieldVariable f =
   case (fieldConstr f) of
     Just name -> ensureLower name
-    _ -> fieldName f
+    _ -> map (\c -> if c == '-' then '_' else c) $ fieldName f
 
 actualFieldType :: Field -> Q Type
-actualFieldType f | fieldIsContainer f = [t| Container $t |]
-                  | fieldIsOptional f  = [t| Maybe $t     |]
+actualFieldType f | fieldIsOptional f  = [t| Maybe $t     |]
                   | otherwise = t
                   where t = fieldType f
 
@@ -141,15 +131,16 @@ checkNonOptDef (Field { fieldDefault = (Just _), fieldName = name }) =
   fail $ "Default field " ++ name ++ " used in parameter declaration"
 checkNonOptDef _ = return ()
 
-loadFn :: Field -> Q Exp -> Q Exp
-loadFn (Field { fieldIsContainer = True }) expr = [| $expr >>= readContainer |]
-loadFn (Field { fieldRead = Just readfn }) expr = [| $expr >>= $readfn |]
-loadFn _ expr = expr
-
-saveFn :: Field -> Q Exp -> Q Exp
-saveFn (Field { fieldIsContainer = True }) expr = [| showContainer $expr |]
-saveFn (Field { fieldRead = Just readfn }) expr = [| $readfn $expr |]
-saveFn _ expr = expr
+-- | Produces the expression that will de-serialise a given
+-- field. Since some custom parsing functions might need to use the
+-- entire object, we do take and pass the object to any custom read
+-- functions.
+loadFn :: Field   -- ^ The field definition
+       -> Q Exp   -- ^ The value of the field as existing in the JSON message
+       -> Q Exp   -- ^ The entire object in JSON object format
+       -> Q Exp   -- ^ Resulting expression
+loadFn (Field { fieldRead = Just readfn }) expr o = [| $expr >>= $readfn $o |]
+loadFn _ expr _ = expr
 
 -- * Common field declarations
 
@@ -166,6 +157,11 @@ serialFields =
 uuidFields :: [Field]
 uuidFields = [ simpleField "uuid" [t| String |] ]
 
+-- | Tag field description.
+tagsFields :: [Field]
+tagsFields = [ defaultField [| Set.empty |] $
+               simpleField "tags" [t| Set.Set String |] ]
+
 -- * Helper functions
 
 -- | Ensure first letter is lowercase.
@@ -212,18 +208,6 @@ appFn :: Exp -> Exp -> Exp
 appFn f x | f == VarE 'id = x
           | otherwise = AppE f x
 
--- | Container loader
-readContainer :: (Monad m, JSON.JSON a) =>
-                 JSON.JSObject JSON.JSValue -> m (Container a)
-readContainer obj = do
-  let kjvlist = JSON.fromJSObject obj
-  kalist <- mapM (\(k, v) -> fromKeyValue k v >>= \a -> return (k, a)) kjvlist
-  return $ M.fromList kalist
-
--- | Container dumper
-showContainer :: (JSON.JSON a) => Container a -> JSON.JSValue
-showContainer = JSON.makeObj . map (second JSON.showJSON) . M.toList
-
 -- * Template code for simple raw type-equivalent ADTs
 
 -- | Generates a data type declaration.
@@ -246,7 +230,7 @@ strADTDecl name constructors =
 -- @
 genToRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
 genToRaw traw fname tname constructors = do
-  sigt <- [t| $(conT tname) -> $(conT traw) |]
+  let sigt = AppT (AppT ArrowT (ConT tname)) (ConT traw)
   -- the body clauses, matching on the constructor and returning the
   -- raw value
   clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
@@ -328,8 +312,10 @@ declareSADT = declareADT ''String
 -- @
 --
 -- in an instance JSON /name/ declaration
-genShowJSON :: String -> Q [Dec]
-genShowJSON name = [d| showJSON = JSON.showJSON . $(varE (toRawName name)) |]
+genShowJSON :: String -> Q Dec
+genShowJSON name = do
+  body <- [| JSON.showJSON . $(varE (toRawName name)) |]
+  return $ FunD (mkName "showJSON") [Clause [] (NormalB body) []]
 
 -- | Creates the readJSON member of a JSON instance declaration.
 --
@@ -363,7 +349,7 @@ makeJSONInstance name = do
   let base = nameBase name
   showJ <- genShowJSON base
   readJ <- genReadJSON base
-  return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) (readJ:showJ)]
+  return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) [readJ,showJ]]
 
 -- * Template code for opcodes
 
@@ -375,7 +361,7 @@ deCamelCase =
 -- | Transform an underscore_name into a CamelCase one.
 camelCase :: String -> String
 camelCase = concatMap (ensureUpper . drop 1) .
-            groupBy (\_ b -> b /= '_') . ('_':)
+            groupBy (\_ b -> b /= '_' && b /= '-') . ('_':)
 
 -- | Computes the name of a given constructor.
 constructorName :: Con -> Q Name
@@ -383,6 +369,15 @@ constructorName (NormalC name _) = return name
 constructorName (RecC name _)    = return name
 constructorName x                = fail $ "Unhandled constructor " ++ show x
 
+-- | Extract all constructor names from a given type.
+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 ++ "'"
+
 -- | Builds the generic constructor-to-string function.
 --
 -- This generates a simple function of the following form:
@@ -396,8 +391,7 @@ constructorName x                = fail $ "Unhandled constructor " ++ show x
 -- 'genToRaw' to actually generate the function
 genConstrToStr :: (String -> String) -> Name -> String -> Q [Dec]
 genConstrToStr trans_fun name fname = do
-  TyConI (DataD _ _ _ cons _) <- reify name
-  cnames <- mapM (liftM nameBase . constructorName) cons
+  cnames <- reifyConsNames name
   let svalues = map (Left . trans_fun) cnames
   genToRaw ''String (mkName fname) name $ zip cnames svalues
 
@@ -448,7 +442,7 @@ saveConstructor :: String    -- ^ The constructor name
                 -> Q Clause  -- ^ Resulting clause
 saveConstructor sname fields = do
   let cname = mkName sname
-  let fnames = map (mkName . fieldVariable) fields
+  fnames <- mapM (newName . fieldVariable) fields
   let pat = conP cname (map varP fnames)
   let felems = map (uncurry saveObjectField) (zip fnames fields)
       -- now build the OP_ID serialisation
@@ -513,7 +507,7 @@ genStrOfKey :: Name -> String -> Q [Dec]
 genStrOfKey = genConstrToStr ensureLower
 
 -- | LuxiOp parameter type.
-type LuxiParam = (String, Q Type, Q Exp)
+type LuxiParam = (String, Q Type)
 
 -- | Generates the LuxiOp data type.
 --
@@ -522,37 +516,37 @@ type LuxiParam = (String, Q Type, Q Exp)
 -- We can't use anything less generic, because the way different
 -- operations are serialized differs on both parameter- and top-level.
 --
--- There are three things to be defined for each parameter:
+-- There are two things to be defined for each parameter:
 --
 -- * name
 --
 -- * type
 --
--- * operation; this is the operation performed on the parameter before
---   serialization
---
 genLuxiOp :: String -> [(String, [LuxiParam])] -> Q [Dec]
 genLuxiOp name cons = do
   decl_d <- mapM (\(cname, fields) -> do
-                    fields' <- mapM (\(_, qt, _) ->
+                    fields' <- mapM (\(_, qt) ->
                                          qt >>= \t -> return (NotStrict, t))
                                fields
                     return $ NormalC (mkName cname) fields')
             cons
-  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read]
+  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read, ''Eq]
   (savesig, savefn) <- genSaveLuxiOp cons
-  return [declD, savesig, savefn]
+  req_defs <- declareSADT "LuxiReq" .
+              map (\(str, _) -> ("Req" ++ str, mkName ("luxiReq" ++ str))) $
+                  cons
+  return $ [declD, savesig, savefn] ++ req_defs
 
 -- | Generates the \"save\" expression for a single luxi parameter.
 saveLuxiField :: Name -> LuxiParam -> Q Exp
-saveLuxiField fvar (_, qt, fn) =
-    [| JSON.showJSON ( $(liftM2 appFn fn $ varE fvar) ) |]
+saveLuxiField fvar (_, qt) =
+    [| JSON.showJSON $(varE fvar) |]
 
 -- | Generates the \"save\" clause for entire LuxiOp constructor.
 saveLuxiConstructor :: (String, [LuxiParam]) -> Q Clause
 saveLuxiConstructor (sname, fields) = do
   let cname = mkName sname
-      fnames = map (\(nm, _, _) -> mkName nm) fields
+      fnames = map (mkName . fst) fields
       pat = conP cname (map varP fnames)
       flist = map (uncurry saveLuxiField) (zip fnames fields)
       finval = if null flist
@@ -595,14 +589,14 @@ buildObjectSerialisation sname fields = do
   shjson <- objectShowJSON sname
   rdjson <- objectReadJSON sname
   let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
-                 (rdjson:shjson)
+                 [rdjson, shjson]
   return $ savedecls ++ [loadsig, loadfn, instdecl]
 
 genSaveObject :: (Name -> Field -> Q Exp)
               -> String -> [Field] -> Q [Dec]
 genSaveObject save_fn sname fields = do
   let name = mkName sname
-  let fnames = map (mkName . fieldVariable) fields
+  fnames <- mapM (newName . fieldVariable) fields
   let pat = conP name (map varP fnames)
   let tdname = mkName ("toDict" ++ sname)
   tdsigt <- [t| $(conT name) -> [(String, JSON.JSValue)] |]
@@ -621,22 +615,23 @@ genSaveObject save_fn sname fields = do
 
 saveObjectField :: Name -> Field -> Q Exp
 saveObjectField fvar field
-  | isContainer = [| [( $nameE , JSON.showJSON . showContainer $ $fvarE)] |]
   | fisOptional = [| case $(varE fvar) of
                       Nothing -> []
                       Just v -> [( $nameE, JSON.showJSON v)]
                   |]
   | otherwise = case fieldShow field of
       Nothing -> [| [( $nameE, JSON.showJSON $fvarE)] |]
-      Just fn -> [| [( $nameE, JSON.showJSON . $fn $ $fvarE)] |]
-  where isContainer = fieldIsContainer field
-        fisOptional  = fieldIsOptional field
+      Just fn -> [| let (actual, extra) = $fn $fvarE
+                    in extra ++ [( $nameE, JSON.showJSON actual)]
+                  |]
+  where fisOptional  = fieldIsOptional field
         nameE = stringE (fieldName field)
         fvarE = varE fvar
 
-objectShowJSON :: String -> Q [Dec]
-objectShowJSON name =
-  [d| showJSON = JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
+objectShowJSON :: String -> Q Dec
+objectShowJSON name = do
+  body <- [| JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
+  return $ FunD (mkName "showJSON") [Clause [] (NormalB body) []]
 
 genLoadObject :: (Field -> Q (Name, Stmt))
               -> String -> [Field] -> Q (Dec, Dec)
@@ -659,7 +654,7 @@ genLoadObject load_fn sname fields = do
 loadObjectField :: Field -> Q (Name, Stmt)
 loadObjectField field = do
   let name = fieldVariable field
-      fvar = mkName name
+  fvar <- newName name
   -- these are used in all patterns below
   let objvar = varNameE "o"
       objfield = stringE (fieldName field)
@@ -671,7 +666,7 @@ loadObjectField field = do
                    [| $(varNameE "fromObjWithDefault") $objvar
                       $objfield $defv |]
                  Nothing -> [| $(varNameE "fromObj") $objvar $objfield |]
-  bexp <- loadFn field loadexp
+  bexp <- loadFn field loadexp objvar
 
   return (fvar, BindS (VarP fvar) bexp)
 
@@ -717,8 +712,8 @@ buildParam sname field_pfx fields = do
   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, ''Read]
-      declP = DataD [] name_p [] [decl_p] [''Show, ''Read]
+  let declF = DataD [] name_f [] [decl_f] [''Show, ''Read, ''Eq]
+      declP = DataD [] name_p [] [decl_p] [''Show, ''Read, ''Eq]
   ser_decls_f <- buildObjectSerialisation sname_f fields
   ser_decls_p <- buildPParamSerialisation sname_p fields
   fill_decls <- fillParam sname field_pfx fields
@@ -732,7 +727,7 @@ buildPParamSerialisation sname fields = do
   shjson <- objectShowJSON sname
   rdjson <- objectReadJSON sname
   let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
-                 (rdjson:shjson)
+                 [rdjson, shjson]
   return $ savedecls ++ [loadsig, loadfn, instdecl]
 
 savePParamField :: Name -> Field -> Q Exp
@@ -751,12 +746,12 @@ loadPParamField :: Field -> Q (Name, Stmt)
 loadPParamField field = do
   checkNonOptDef field
   let name = fieldName field
-      fvar = mkName name
+  fvar <- newName name
   -- these are used in all patterns below
   let objvar = varNameE "o"
       objfield = stringE name
       loadexp = [| $(varNameE "maybeFromObj") $objvar $objfield |]
-  bexp <- loadFn field loadexp
+  bexp <- loadFn field loadexp objvar
   return (fvar, BindS (VarP fvar) bexp)
 
 -- | Builds a simple declaration of type @n_x = fromMaybe f_x p_x@.