Revision a1505857 htools/Ganeti/THH.hs

b/htools/Ganeti/THH.hs
1
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
1
{-# LANGUAGE TemplateHaskell #-}
2 2

  
3 3
{-| TemplateHaskell helper for HTools.
4 4

  
......
34 34
                  , makeJSONInstance
35 35
                  , genOpID
36 36
                  , genOpCode
37
                  , noDefault
38 37
                  , genStrOfOp
39 38
                  , genStrOfKey
40 39
                  , genLuxiOp
......
119 118
fieldRecordName (Field { fieldName = name, fieldConstr = alias }) =
120 119
  maybe (camelCase name) id alias
121 120

  
121
-- | Computes the preferred variable name to use for the value of this
122
-- field. If the field has a specific constructor name, then we use a
123
-- first-letter-lowercased version of that; otherwise, we simply use
124
-- the field name. See also 'fieldRecordName'.
122 125
fieldVariable :: Field -> String
123
fieldVariable = map toLower . fieldRecordName
126
fieldVariable f =
127
  case (fieldConstr f) of
128
    Just name -> ensureLower name
129
    _ -> fieldName f
124 130

  
125 131
actualFieldType :: Field -> Q Type
126 132
actualFieldType f | fieldIsContainer f = [t| Container $t |]
......
408 414
-- datatype and the JSON serialisation out of it. We can't use a
409 415
-- generic serialisation since we need to be compatible with Ganeti's
410 416
-- own, so we have a few quirks to work around.
411
--
412
-- There are three things to be defined for each parameter:
413
--
414
-- * name
415
--
416
-- * type; if this is 'Maybe', will only be serialised if it's a
417
--   'Just' value
418
--
419
-- * default; if missing, won't raise an exception, but will instead
420
--   use the default
421
--
422 417
genOpCode :: String                -- ^ Type name to use
423
          -> [(String, [OpParam])] -- ^ Constructor name and parameters
418
          -> [(String, [Field])]   -- ^ Constructor name and parameters
424 419
          -> Q [Dec]
425 420
genOpCode name cons = do
426 421
  decl_d <- mapM (\(cname, fields) -> do
427 422
                    -- we only need the type of the field, without Q
428
                    fields' <- mapM (\(_, qt, _) ->
429
                                         qt >>= \t -> return (NotStrict, t))
430
                               fields
431
                    return $ NormalC (mkName cname) fields')
423
                    fields' <- mapM actualFieldType fields
424
                    let fields'' = zip (repeat NotStrict) fields'
425
                    return $ NormalC (mkName cname) fields'')
432 426
            cons
433 427
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read, ''Eq]
434 428

  
......
443 437
isOptional (AppT (ConT dt) _) | dt == ''Maybe = True
444 438
isOptional _ = False
445 439

  
446
-- | Generates the \"save\" expression for a single opcode parameter.
447
--
448
-- There is only one special handling mode: if the parameter is of
449
-- 'Maybe' type, then we only save it if it's a 'Just' value,
450
-- otherwise we skip it.
451
saveField :: Name    -- ^ The name of variable that contains the value
452
          -> OpParam -- ^ Parameter definition
453
          -> Q Exp
454
saveField fvar (fname, qt, _) = do
455
  t <- qt
456
  let fnexp = stringE fname
457
      fvare = varE fvar
458
  (if isOptional t
459
   then [| case $fvare of
460
             Just v' -> [( $fnexp, $showJSONE v')]
461
             Nothing -> []
462
         |]
463
   else [| [( $fnexp, $showJSONE $fvare )] |])
464

  
465 440
-- | Generates the \"save\" clause for an entire opcode constructor.
466 441
--
467 442
-- This matches the opcode with variables named the same as the
468 443
-- constructor fields (just so that the spliced in code looks nicer),
469
-- and passes those name plus the parameter definition to 'saveField'.
444
-- and passes those name plus the parameter definition to 'saveObjectField'.
470 445
saveConstructor :: String    -- ^ The constructor name
471
                -> [OpParam] -- ^ The parameter definitions for this
446
                -> [Field]   -- ^ The parameter definitions for this
472 447
                             -- constructor
473 448
                -> Q Clause  -- ^ Resulting clause
474 449
saveConstructor sname fields = do
475 450
  let cname = mkName sname
476
  let fnames = map (\(n, _, _) -> mkName n) fields
451
  let fnames = map (mkName . fieldVariable) fields
477 452
  let pat = conP cname (map varP fnames)
478
  let felems = map (uncurry saveField) (zip fnames fields)
453
  let felems = map (uncurry saveObjectField) (zip fnames fields)
479 454
      -- now build the OP_ID serialisation
480 455
      opid = [| [( $(stringE "OP_ID"),
481 456
                   $showJSONE $(stringE . deCamelCase $ sname) )] |]
......
488 463
--
489 464
-- This builds a per-constructor match clause that contains the
490 465
-- respective constructor-serialisation code.
491
genSaveOpCode :: [(String, [OpParam])] -> Q (Dec, Dec)
466
genSaveOpCode :: [(String, [Field])] -> Q (Dec, Dec)
492 467
genSaveOpCode opdefs = do
493 468
  cclauses <- mapM (uncurry saveConstructor) opdefs
494 469
  let fname = mkName "saveOpCode"
495 470
  sigt <- [t| $(conT (mkName "OpCode")) -> JSON.JSValue |]
496 471
  return $ (SigD fname sigt, FunD fname cclauses)
497 472

  
498
-- | Generates the \"load\" field for a single parameter.
499
--
500
-- There is custom handling, depending on how the parameter is
501
-- specified. For a 'Maybe' type parameter, we allow that it is not
502
-- present (via 'Utils.maybeFromObj'). Otherwise, if there is a
503
-- default value, we allow the parameter to be abset, and finally if
504
-- there is no default value, we require its presence.
505
loadField :: OpParam -> Q (Name, Stmt)
506
loadField (fname, qt, qdefa) = do
507
  let fvar = mkName fname
508
  t <- qt
509
  defa <- qdefa
510
  -- these are used in all patterns below
511
  let objvar = varNameE "o"
512
      objfield = stringE fname
513
  bexp <- if isOptional t
514
          then [| $((varNameE "maybeFromObj")) $objvar $objfield |]
515
          else case defa of
516
                 AppE (ConE dt) defval | dt == 'Just ->
517
                   -- but has a default value
518
                   [| $(varNameE "fromObjWithDefault")
519
                      $objvar $objfield $(return defval) |]
520
                 ConE dt | dt == 'Nothing ->
521
                     [| $(varNameE "fromObj") $objvar $objfield |]
522
                 s -> fail $ "Invalid default value " ++ show s ++
523
                      ", expecting either 'Nothing' or a 'Just defval'"
524
  return (fvar, BindS (VarP fvar) bexp)
525

  
526
loadConstructor :: String -> [OpParam] -> Q Exp
473
loadConstructor :: String -> [Field] -> Q Exp
527 474
loadConstructor sname fields = do
528 475
  let name = mkName sname
529
  fbinds <- mapM loadField fields
476
  fbinds <- mapM loadObjectField fields
530 477
  let (fnames, fstmts) = unzip fbinds
531 478
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
532 479
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
533 480
  return $ DoE fstmts'
534 481

  
535
genLoadOpCode :: [(String, [OpParam])] -> Q (Dec, Dec)
482
genLoadOpCode :: [(String, [Field])] -> Q (Dec, Dec)
536 483
genLoadOpCode opdefs = do
537 484
  let fname = mkName "loadOpCode"
538 485
      arg1 = mkName "v"
......
555 502
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
556 503
  return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
557 504

  
558
-- | No default type.
559
noDefault :: Q Exp
560
noDefault = conE 'Nothing
561

  
562 505
-- * Template code for luxi
563 506

  
564 507
-- | Constructor-to-string for LuxiOp.

Also available in: Unified diff