Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / THH.hs @ a0090487

History | View | Annotate | Download (16.3 kB)

1
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
2

    
3
{-| TemplateHaskell helper for HTools.
4

    
5
As TemplateHaskell require that splices be defined in a separate
6
module, we combine all the TemplateHaskell functionality that HTools
7
needs in this module (except the one for unittests).
8

    
9
-}
10

    
11
{-
12

    
13
Copyright (C) 2011 Google Inc.
14

    
15
This program is free software; you can redistribute it and/or modify
16
it under the terms of the GNU General Public License as published by
17
the Free Software Foundation; either version 2 of the License, or
18
(at your option) any later version.
19

    
20
This program is distributed in the hope that it will be useful, but
21
WITHOUT ANY WARRANTY; without even the implied warranty of
22
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23
General Public License for more details.
24

    
25
You should have received a copy of the GNU General Public License
26
along with this program; if not, write to the Free Software
27
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28
02110-1301, USA.
29

    
30
-}
31

    
32
module Ganeti.THH ( declareSADT
33
                  , makeJSONInstance
34
                  , genOpID
35
                  , genOpCode
36
                  , noDefault
37
                  , genStrOfOp
38
                  , genStrOfKey
39
                  , genLuxiOp
40
                  ) where
41

    
42
import Control.Monad (liftM)
43
import Data.Char
44
import Data.List
45
import Language.Haskell.TH
46

    
47
import qualified Text.JSON as JSON
48

    
49
-- * Helper functions
50

    
51
-- | Ensure first letter is lowercase.
52
--
53
-- Used to convert type name to function prefix, e.g. in @data Aa ->
54
-- aaToString@.
55
ensureLower :: String -> String
56
ensureLower [] = []
57
ensureLower (x:xs) = toLower x:xs
58

    
59
-- | Helper for quoted expressions.
60
varNameE :: String -> Q Exp
61
varNameE = varE . mkName
62

    
63
-- | showJSON as an expression, for reuse.
64
showJSONE :: Q Exp
65
showJSONE = varNameE "showJSON"
66

    
67
-- | ToString function name.
68
toStrName :: String -> Name
69
toStrName = mkName . (++ "ToString") . ensureLower
70

    
71
-- | FromString function name.
72
fromStrName :: String -> Name
73
fromStrName = mkName . (++ "FromString") . ensureLower
74

    
75
-- | Converts a name to it's varE/litE representations.
76
--
77
reprE :: Either String Name -> Q Exp
78
reprE = either stringE varE
79

    
80
-- * Template code for simple string-equivalent ADTs
81

    
82
-- | Generates a data type declaration.
83
--
84
-- The type will have a fixed list of instances.
85
strADTDecl :: Name -> [String] -> Dec
86
strADTDecl name constructors =
87
    DataD [] name []
88
              (map (flip NormalC [] . mkName) constructors)
89
              [''Show, ''Read, ''Eq, ''Enum, ''Bounded, ''Ord]
90

    
91
-- | Generates a toString function.
92
--
93
-- This generates a simple function of the form:
94
--
95
-- @
96
-- nameToString :: Name -> String
97
-- nameToString Cons1 = var1
98
-- nameToString Cons2 = \"value2\"
99
-- @
100
genToString :: Name -> Name -> [(String, Either String Name)] -> Q [Dec]
101
genToString fname tname constructors = do
102
  sigt <- [t| $(conT tname) -> String |]
103
  -- the body clauses, matching on the constructor and returning the
104
  -- string value
105
  clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
106
                             (normalB (reprE v)) []) constructors
107
  return [SigD fname sigt, FunD fname clauses]
108

    
109
-- | Generates a fromString function.
110
--
111
-- The function generated is monadic and can fail parsing the
112
-- string. It is of the form:
113
--
114
-- @
115
-- nameFromString :: (Monad m) => String -> m Name
116
-- nameFromString s | s == var1       = Cons1
117
--                  | s == \"value2\" = Cons2
118
--                  | otherwise = fail /.../
119
-- @
120
genFromString :: Name -> Name -> [(String, Name)] -> Q [Dec]
121
genFromString fname tname constructors = do
122
  -- signature of form (Monad m) => String -> m $name
123
  sigt <- [t| (Monad m) => String -> m $(conT tname) |]
124
  -- clauses for a guarded pattern
125
  let varp = mkName "s"
126
      varpe = varE varp
127
  clauses <- mapM (\(c, v) -> do
128
                     -- the clause match condition
129
                     g <- normalG [| $varpe == $(varE v) |]
130
                     -- the clause result
131
                     r <- [| return $(conE (mkName c)) |]
132
                     return (g, r)) constructors
133
  -- the otherwise clause (fallback)
134
  oth_clause <- do
135
    g <- normalG [| otherwise |]
136
    r <- [|fail ("Invalid string value for type " ++
137
                 $(litE (stringL (nameBase tname))) ++ ": " ++ $varpe) |]
138
    return (g, r)
139
  let fun = FunD fname [Clause [VarP varp]
140
                        (GuardedB (clauses++[oth_clause])) []]
141
  return [SigD fname sigt, fun]
142

    
143
-- | Generates a data type from a given string format.
144
--
145
-- The format is expected to multiline. The first line contains the
146
-- type name, and the rest of the lines must contain two words: the
147
-- constructor name and then the string representation of the
148
-- respective constructor.
149
--
150
-- The function will generate the data type declaration, and then two
151
-- functions:
152
--
153
-- * /name/ToString, which converts the type to a string
154
--
155
-- * /name/FromString, which (monadically) converts from a string to the type
156
--
157
-- Note that this is basically just a custom show/read instance,
158
-- nothing else.
159
declareSADT :: String -> [(String, Name)] -> Q [Dec]
160
declareSADT sname cons = do
161
  let name = mkName sname
162
      ddecl = strADTDecl name (map fst cons)
163
      -- process cons in the format expected by genToString
164
      cons' = map (\(a, b) -> (a, Right b)) cons
165
  tostr <- genToString (toStrName sname) name cons'
166
  fromstr <- genFromString (fromStrName sname) name cons
167
  return $ ddecl:tostr ++ fromstr
168

    
169

    
170
-- | Creates the showJSON member of a JSON instance declaration.
171
--
172
-- This will create what is the equivalent of:
173
--
174
-- @
175
-- showJSON = showJSON . /name/ToString
176
-- @
177
--
178
-- in an instance JSON /name/ declaration
179
genShowJSON :: String -> Q [Dec]
180
genShowJSON name = [d| showJSON = JSON.showJSON . $(varE (toStrName name)) |]
181

    
182
-- | Creates the readJSON member of a JSON instance declaration.
183
--
184
-- This will create what is the equivalent of:
185
--
186
-- @
187
-- readJSON s = case readJSON s of
188
--                Ok s' -> /name/FromString s'
189
--                Error e -> Error /description/
190
-- @
191
--
192
-- in an instance JSON /name/ declaration
193
genReadJSON :: String -> Q Dec
194
genReadJSON name = do
195
  let s = mkName "s"
196
  body <- [| case JSON.readJSON $(varE s) of
197
               JSON.Ok s' -> $(varE (fromStrName name)) s'
198
               JSON.Error e ->
199
                   JSON.Error $ "Can't parse string value for type " ++
200
                           $(stringE name) ++ ": " ++ e
201
           |]
202
  return $ FunD (mkName "readJSON") [Clause [VarP s] (NormalB body) []]
203

    
204
-- | Generates a JSON instance for a given type.
205
--
206
-- This assumes that the /name/ToString and /name/FromString functions
207
-- have been defined as by the 'declareSADT' function.
208
makeJSONInstance :: Name -> Q [Dec]
209
makeJSONInstance name = do
210
  let base = nameBase name
211
  showJ <- genShowJSON base
212
  readJ <- genReadJSON base
213
  return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) (readJ:showJ)]
214

    
215
-- * Template code for opcodes
216

    
217
-- | Transforms a CamelCase string into an_underscore_based_one.
218
deCamelCase :: String -> String
219
deCamelCase =
220
    intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
221

    
222
-- | Computes the name of a given constructor
223
constructorName :: Con -> Q Name
224
constructorName (NormalC name _) = return name
225
constructorName (RecC name _)    = return name
226
constructorName x                = fail $ "Unhandled constructor " ++ show x
227

    
228
-- | Builds the generic constructor-to-string function.
229
--
230
-- This generates a simple function of the following form:
231
--
232
-- @
233
-- fname (ConStructorOne {}) = trans_fun("ConStructorOne")
234
-- fname (ConStructorTwo {}) = trans_fun("ConStructorTwo")
235
-- @
236
--
237
-- This builds a custom list of name/string pairs and then uses
238
-- 'genToString' to actually generate the function
239
genConstrToStr :: (String -> String) -> Name -> String -> Q [Dec]
240
genConstrToStr trans_fun name fname = do
241
  TyConI (DataD _ _ _ cons _) <- reify name
242
  cnames <- mapM (liftM nameBase . constructorName) cons
243
  let svalues = map (Left . trans_fun) cnames
244
  genToString (mkName fname) name $ zip cnames svalues
245

    
246
-- | Constructor-to-string for OpCode.
247
genOpID :: Name -> String -> Q [Dec]
248
genOpID = genConstrToStr deCamelCase
249

    
250
-- | OpCode parameter (field) type
251
type OpParam = (String, Q Type, Q Exp)
252

    
253
-- | Generates the OpCode data type.
254
--
255
-- This takes an opcode logical definition, and builds both the
256
-- datatype and the JSON serialisation out of it. We can't use a
257
-- generic serialisation since we need to be compatible with Ganeti's
258
-- own, so we have a few quirks to work around.
259
--
260
-- There are three things to be defined for each parameter:
261
--
262
-- * name
263
--
264
-- * type; if this is 'Maybe', will only be serialised if it's a
265
--   'Just' value
266
--
267
-- * default; if missing, won't raise an exception, but will instead
268
--   use the default
269
--
270
genOpCode :: String                -- ^ Type name to use
271
          -> [(String, [OpParam])] -- ^ Constructor name and parameters
272
          -> Q [Dec]
273
genOpCode name cons = do
274
  decl_d <- mapM (\(cname, fields) -> do
275
                    -- we only need the type of the field, without Q
276
                    fields' <- mapM (\(_, qt, _) ->
277
                                         qt >>= \t -> return (NotStrict, t))
278
                               fields
279
                    return $ NormalC (mkName cname) fields')
280
            cons
281
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read, ''Eq]
282

    
283
  (savesig, savefn) <- genSaveOpCode cons
284
  (loadsig, loadfn) <- genLoadOpCode cons
285
  return [declD, loadsig, loadfn, savesig, savefn]
286

    
287
-- | Checks whether a given parameter is options
288
--
289
-- This requires that it's a 'Maybe'.
290
isOptional :: Type -> Bool
291
isOptional (AppT (ConT dt) _) | dt == ''Maybe = True
292
isOptional _ = False
293

    
294
-- | Generates the \"save\" expression for a single opcode parameter.
295
--
296
-- There is only one special handling mode: if the parameter is of
297
-- 'Maybe' type, then we only save it if it's a 'Just' value,
298
-- otherwise we skip it.
299
saveField :: Name    -- ^ The name of variable that contains the value
300
          -> OpParam -- ^ Parameter definition
301
          -> Q Exp
302
saveField fvar (fname, qt, _) = do
303
  t <- qt
304
  let fnexp = stringE fname
305
      fvare = varE fvar
306
  (if isOptional t
307
   then [| case $fvare of
308
             Just v' -> [( $fnexp, $showJSONE v')]
309
             Nothing -> []
310
         |]
311
   else [| [( $fnexp, $showJSONE $fvare )] |])
312

    
313
-- | Generates the \"save\" clause for an entire opcode constructor.
314
--
315
-- This matches the opcode with variables named the same as the
316
-- constructor fields (just so that the spliced in code looks nicer),
317
-- and passes those name plus the parameter definition to 'saveField'.
318
saveConstructor :: String    -- ^ The constructor name
319
                -> [OpParam] -- ^ The parameter definitions for this
320
                             -- constructor
321
                -> Q Clause  -- ^ Resulting clause
322
saveConstructor sname fields = do
323
  let cname = mkName sname
324
  let fnames = map (\(n, _, _) -> mkName n) fields
325
  let pat = conP cname (map varP fnames)
326
  let felems = map (uncurry saveField) (zip fnames fields)
327
      -- now build the OP_ID serialisation
328
      opid = [| [( $(stringE "OP_ID"),
329
                   $showJSONE $(stringE . deCamelCase $ sname) )] |]
330
      flist = listE (opid:felems)
331
      -- and finally convert all this to a json object
332
      flist' = [| $(varNameE "makeObj") (concat $flist) |]
333
  clause [pat] (normalB flist') []
334

    
335
-- | Generates the main save opcode function.
336
--
337
-- This builds a per-constructor match clause that contains the
338
-- respective constructor-serialisation code.
339
genSaveOpCode :: [(String, [OpParam])] -> Q (Dec, Dec)
340
genSaveOpCode opdefs = do
341
  cclauses <- mapM (uncurry saveConstructor) opdefs
342
  let fname = mkName "saveOpCode"
343
  sigt <- [t| $(conT (mkName "OpCode")) -> JSON.JSValue |]
344
  return $ (SigD fname sigt, FunD fname cclauses)
345

    
346
-- | Generates the \"load\" field for a single parameter.
347
--
348
-- There is custom handling, depending on how the parameter is
349
-- specified. For a 'Maybe' type parameter, we allow that it is not
350
-- present (via 'Utils.maybeFromObj'). Otherwise, if there is a
351
-- default value, we allow the parameter to be abset, and finally if
352
-- there is no default value, we require its presence.
353
loadField :: OpParam -> Q (Name, Stmt)
354
loadField (fname, qt, qdefa) = do
355
  let fvar = mkName fname
356
  t <- qt
357
  defa <- qdefa
358
  -- these are used in all patterns below
359
  let objvar = varNameE "o"
360
      objfield = stringE fname
361
  bexp <- if isOptional t
362
          then [| $((varNameE "maybeFromObj")) $objvar $objfield |]
363
          else case defa of
364
                 AppE (ConE dt) defval | dt == 'Just ->
365
                   -- but has a default value
366
                   [| $(varNameE "fromObjWithDefault")
367
                      $objvar $objfield $(return defval) |]
368
                 ConE dt | dt == 'Nothing ->
369
                     [| $(varNameE "fromObj") $objvar $objfield |]
370
                 s -> fail $ "Invalid default value " ++ show s ++
371
                      ", expecting either 'Nothing' or a 'Just defval'"
372
  return (fvar, BindS (VarP fvar) bexp)
373

    
374
loadConstructor :: String -> [OpParam] -> Q Exp
375
loadConstructor sname fields = do
376
  let name = mkName sname
377
  fbinds <- mapM loadField fields
378
  let (fnames, fstmts) = unzip fbinds
379
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
380
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
381
  return $ DoE fstmts'
382

    
383
genLoadOpCode :: [(String, [OpParam])] -> Q (Dec, Dec)
384
genLoadOpCode opdefs = do
385
  let fname = mkName "loadOpCode"
386
      arg1 = mkName "v"
387
      objname = mkName "o"
388
      opid = mkName "op_id"
389
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
390
                                 (JSON.readJSON $(varE arg1)) |]
391
  st2 <- bindS (varP opid) [| $(varNameE "fromObj")
392
                              $(varE objname) $(stringE "OP_ID") |]
393
  -- the match results (per-constructor blocks)
394
  mexps <- mapM (uncurry loadConstructor) opdefs
395
  fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
396
  let mpats = map (\(me, c) ->
397
                       let mp = LitP . StringL . deCamelCase . fst $ c
398
                       in Match mp (NormalB me) []
399
                  ) $ zip mexps opdefs
400
      defmatch = Match WildP (NormalB fails) []
401
      cst = NoBindS $ CaseE (VarE opid) $ mpats++[defmatch]
402
      body = DoE [st1, st2, cst]
403
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
404
  return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
405

    
406
-- | No default type.
407
noDefault :: Q Exp
408
noDefault = conE 'Nothing
409

    
410
-- * Template code for luxi
411

    
412
-- | Constructor-to-string for LuxiOp.
413
genStrOfOp :: Name -> String -> Q [Dec]
414
genStrOfOp = genConstrToStr id
415

    
416
-- | Constructor-to-string for MsgKeys.
417
genStrOfKey :: Name -> String -> Q [Dec]
418
genStrOfKey = genConstrToStr ensureLower
419

    
420
-- | LuxiOp parameter type.
421
type LuxiParam = (String, Q Type, Q Exp)
422

    
423
-- | Generates the LuxiOp data type.
424
--
425
-- This takes a Luxi operation definition and builds both the
426
-- datatype and the function trnasforming the arguments to JSON.
427
-- We can't use anything less generic, because the way different
428
-- operations are serialized differs on both parameter- and top-level.
429
--
430
-- There are three things to be defined for each parameter:
431
--
432
-- * name
433
--
434
-- * type
435
--
436
-- * operation; this is the operation performed on the parameter before
437
--   serialization
438
--
439
genLuxiOp :: String -> [(String, [LuxiParam], Q Exp)] -> Q [Dec]
440
genLuxiOp name cons = do
441
  decl_d <- mapM (\(cname, fields, _) -> do
442
                    fields' <- mapM (\(_, qt, _) ->
443
                                         qt >>= \t -> return (NotStrict, t))
444
                               fields
445
                    return $ NormalC (mkName cname) fields')
446
            cons
447
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read]
448
  (savesig, savefn) <- genSaveLuxiOp cons
449
  return [declD, savesig, savefn]
450

    
451
-- | Generates the \"save\" clause for entire LuxiOp constructor.
452
saveLuxiConstructor :: (String, [LuxiParam], Q Exp) -> Q Clause
453
saveLuxiConstructor (sname, fields, finfn) =
454
  let cname = mkName sname
455
      fnames = map (\(nm, _, _) -> mkName nm) fields
456
      pat = conP cname (map varP fnames)
457
      flist = map (\(nm, _, fn) -> appE fn $ varNameE nm) fields
458
      finval = appE finfn (tupE flist)
459
  in
460
    clause [pat] (normalB finval) []
461

    
462
-- | Generates the main save LuxiOp function.
463
genSaveLuxiOp :: [(String, [LuxiParam], Q Exp)] -> Q (Dec, Dec)
464
genSaveLuxiOp opdefs = do
465
  sigt <- [t| $(conT (mkName "LuxiOp")) -> JSON.JSValue |]
466
  let fname = mkName "opToArgs"
467
  cclauses <- mapM saveLuxiConstructor opdefs
468
  return $ (SigD fname sigt, FunD fname cclauses)