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