On Mon, Sep 26, 2011 at 11:57 AM, Iustin Pop <[email protected]> wrote:
> This replaces the hand-coded opID with one automatically generated
> from the constructor names, similar to the way Python does it, except
> it's done at compilation time as opposed to runtime.
>
> Again, the code line delta does not favour this patch, but this
> eliminates error-prone, manual code with auto-generated one; in case
> we add more opcode support, this will help a lot.
> ---
>  htools/Ganeti/OpCodes.hs |    7 +------
>  htools/Ganeti/THH.hs     |   46 
> +++++++++++++++++++++++++++++++++++++++++++---
>  2 files changed, 44 insertions(+), 9 deletions(-)
>
> diff --git a/htools/Ganeti/OpCodes.hs b/htools/Ganeti/OpCodes.hs
> index b3fc1f3..af973f1 100644
> --- a/htools/Ganeti/OpCodes.hs
> +++ b/htools/Ganeti/OpCodes.hs
> @@ -62,12 +62,7 @@ data OpCode = OpTestDelay Double Bool [String]
>             deriving (Show, Read, Eq)
>
>
> --- | Computes the OP_ID for an OpCode.
> -opID :: OpCode -> String
> -opID (OpTestDelay _ _ _) = "OP_TEST_DELAY"
> -opID (OpInstanceReplaceDisks _ _ _ _ _) = "OP_INSTANCE_REPLACE_DISKS"
> -opID (OpInstanceFailover {}) = "OP_INSTANCE_FAILOVER"
> -opID (OpInstanceMigrate  {}) = "OP_INSTANCE_MIGRATE"
> +$(THH.genOpID ''OpCode "opID")
>
>  -- | Loads an OpCode from the JSON serialised form.
>  loadOpCode :: JSValue -> J.Result OpCode
> diff --git a/htools/Ganeti/THH.hs b/htools/Ganeti/THH.hs
> index fe5e4ae..5b85492 100644
> --- a/htools/Ganeti/THH.hs
> +++ b/htools/Ganeti/THH.hs
> @@ -31,9 +31,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
> MA
>
>  module Ganeti.THH ( declareSADT
>                   , makeJSONInstance
> +                  , genOpID
>                   ) where
>
> +import Control.Monad (liftM)
>  import Data.Char
> +import Data.List
>  import Language.Haskell.TH
>
>  import qualified Text.JSON as JSON
> @@ -54,6 +57,12 @@ toStrName = mkName . (++ "ToString") . ensureLower
>  fromStrName :: String -> Name
>  fromStrName = mkName . (++ "FromString") . ensureLower
>
> +-- | Converts a name to it's varE/litE representations.
> +--
> +reprE :: Either String Name -> Q Exp
> +reprE (Left name) = litE (StringL name)
> +reprE (Right name) = varE name
> +
>  -- | Generates a data type declaration.
>  --
>  -- The type will have a fixed list of instances
> @@ -72,13 +81,13 @@ strADTDecl name constructors =
>  -- nameToString Cons1 = var1
>  -- nameToString Cons2 = \"value2\"
>  -- @
> -genToString :: Name -> Name -> [(String, Name)] -> Q [Dec]
> +genToString :: Name -> Name -> [(String, Either String Name)] -> Q [Dec]
>  genToString fname tname constructors = do
>   sigt <- [t| $(conT tname) -> String |]
>   -- the body clauses, matching on the constructor and returning the
>   -- string value
>   clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
> -                             (normalB (varE  v)) []) constructors
> +                             (normalB (reprE v)) []) constructors
>   return [SigD fname sigt, FunD fname clauses]
>
>  -- | Generates a fromString function.
> @@ -135,7 +144,8 @@ declareSADT :: String -> [(String, Name)] -> Q [Dec]
>  declareSADT sname cons = do
>   let name = mkName sname
>   let ddecl = strADTDecl sname (map fst cons)
> -  tostr <- genToString (toStrName sname) name cons
> +  tostr <- genToString (toStrName sname) name $
> +           map (\(a, b) -> (a, Right b)) cons
I think I'dput the map into separate variable for readability reasons

>   fromstr <- genFromString (fromStrName sname) name cons
>   return $ ddecl:tostr ++ fromstr
>
> @@ -185,3 +195,33 @@ makeJSONInstance name = do
>   readJ <- genReadJSON base
>
>   return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) (readJ:showJ)]
> +
> +-- | Transforms a CamelCase string into an_underscore_based_one.
> +deCamelCase :: String -> String
> +deCamelCase =
> +    intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
> +
> +-- | Computes the name of a given constructor
> +constructorName :: Con -> Q Name
> +constructorName (NormalC name _) = return name
> +constructorName (RecC name _)    = return name
> +constructorName x                = fail $ "Unhandled constructor " ++ show x
> +
> +-- | Builds the constructor-to-string function.
> +--
> +-- This generates a simple function of the following form:
> +--
> +-- @
> +-- fname (ConStructorOne {}) = "CON_STRUCTOR_ONE"
> +-- fname (ConStructorTwo {}) = "CON_STRUCTOR_TWO"
> +-- @
> +--
> +-- This builds a custom list of name/string pairs and then uses
> +-- 'genToString' to actually generate the function
> +genOpID :: Name -> String -> Q [Dec]
> +genOpID name fname = do
> +  info <- reify name
> +  let TyConI (DataD _ _ _ cons _) = info
Why not TyConl (DataD _ _ _ cons _) <- reinfy name? You do not seem to
use info anywhere else..

> +  cnames <- mapM (liftM nameBase . constructorName) cons
> +  let svalues = map (Left . deCamelCase) cnames
> +  genToString (mkName fname) name $ zip cnames svalues
> --
> 1.7.3.1
>
>

Rest LGTM

Reply via email to