Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / THH.hs @ 0d78accc

History | View | Annotate | Download (48.3 kB)

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

    
3
{-| TemplateHaskell helper for Ganeti Haskell code.
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, 2012 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
                  , declareLADT
34
                  , declareILADT
35
                  , declareIADT
36
                  , makeJSONInstance
37
                  , deCamelCase
38
                  , genOpID
39
                  , genAllConstr
40
                  , genAllOpIDs
41
                  , PyValue(..)
42
                  , PyValueEx(..)
43
                  , OpCodeField(..)
44
                  , OpCodeDescriptor(..)
45
                  , genOpCode
46
                  , genStrOfOp
47
                  , genStrOfKey
48
                  , genLuxiOp
49
                  , Field (..)
50
                  , simpleField
51
                  , specialNumericalField
52
                  , withDoc
53
                  , defaultField
54
                  , optionalField
55
                  , optionalNullSerField
56
                  , renameField
57
                  , customField
58
                  , timeStampFields
59
                  , uuidFields
60
                  , serialFields
61
                  , tagsFields
62
                  , TagSet
63
                  , buildObject
64
                  , buildObjectSerialisation
65
                  , buildParam
66
                  , DictObject(..)
67
                  , genException
68
                  , excErrMsg
69
                  ) where
70

    
71
import Control.Applicative
72
import Control.Monad
73
import Data.Char
74
import Data.List
75
import Data.Maybe
76
import qualified Data.Set as Set
77
import Language.Haskell.TH
78

    
79
import qualified Text.JSON as JSON
80
import Text.JSON.Pretty (pp_value)
81

    
82
import Ganeti.JSON
83
import Ganeti.PyValue
84

    
85

    
86
-- * Exported types
87

    
88
-- | Class of objects that can be converted to 'JSObject'
89
-- lists-format.
90
class DictObject a where
91
  toDict :: a -> [(String, JSON.JSValue)]
92

    
93
-- | Optional field information.
94
data OptionalType
95
  = NotOptional           -- ^ Field is not optional
96
  | OptionalOmitNull      -- ^ Field is optional, null is not serialised
97
  | OptionalSerializeNull -- ^ Field is optional, null is serialised
98
  deriving (Show, Eq)
99

    
100
-- | Serialised field data type.
101
data Field = Field { fieldName        :: String
102
                   , fieldType        :: Q Type
103
                   , fieldRead        :: Maybe (Q Exp)
104
                   , fieldShow        :: Maybe (Q Exp)
105
                   , fieldExtraKeys   :: [String]
106
                   , fieldDefault     :: Maybe (Q Exp)
107
                   , fieldConstr      :: Maybe String
108
                   , fieldIsOptional  :: OptionalType
109
                   , fieldDoc         :: String
110
                   }
111

    
112
-- | Generates a simple field.
113
simpleField :: String -> Q Type -> Field
114
simpleField fname ftype =
115
  Field { fieldName        = fname
116
        , fieldType        = ftype
117
        , fieldRead        = Nothing
118
        , fieldShow        = Nothing
119
        , fieldExtraKeys   = []
120
        , fieldDefault     = Nothing
121
        , fieldConstr      = Nothing
122
        , fieldIsOptional  = NotOptional
123
        , fieldDoc         = ""
124
        }
125

    
126
withDoc :: String -> Field -> Field
127
withDoc doc field =
128
  field { fieldDoc = doc }
129

    
130
-- | Sets the renamed constructor field.
131
renameField :: String -> Field -> Field
132
renameField constrName field = field { fieldConstr = Just constrName }
133

    
134
-- | Sets the default value on a field (makes it optional with a
135
-- default value).
136
defaultField :: Q Exp -> Field -> Field
137
defaultField defval field = field { fieldDefault = Just defval }
138

    
139
-- | Marks a field optional (turning its base type into a Maybe).
140
optionalField :: Field -> Field
141
optionalField field = field { fieldIsOptional = OptionalOmitNull }
142

    
143
-- | Marks a field optional (turning its base type into a Maybe), but
144
-- with 'Nothing' serialised explicitly as /null/.
145
optionalNullSerField :: Field -> Field
146
optionalNullSerField field = field { fieldIsOptional = OptionalSerializeNull }
147

    
148
-- | Wrapper around a special parse function, suitable as field-parsing
149
-- function.
150
numericalReadFn :: JSON.JSON a => (String -> JSON.Result a)
151
                   -> [(String, JSON.JSValue)] -> JSON.JSValue -> JSON.Result a
152
numericalReadFn _ _ v@(JSON.JSRational _ _) = JSON.readJSON v
153
numericalReadFn f _ (JSON.JSString x) = f $ JSON.fromJSString x
154
numericalReadFn _ _ _ = JSON.Error "A numerical field has to be a number or\ 
155
                                   \ a string."
156

    
157
-- | Wrapper to lift a read function to optional values
158
makeReadOptional :: ([(String, JSON.JSValue)] -> JSON.JSValue -> JSON.Result a)
159
                    -> [(String, JSON.JSValue)]
160
                    -> Maybe JSON.JSValue -> JSON.Result (Maybe a)
161
makeReadOptional _ _ Nothing = JSON.Ok Nothing
162
makeReadOptional f o (Just x) = fmap Just $ f o x
163

    
164
-- | Sets the read function to also accept string parsable by the given
165
-- function.
166
specialNumericalField :: Name -> Field -> Field
167
specialNumericalField f field =
168
  if (fieldIsOptional field == NotOptional)
169
     then field { fieldRead = Just (appE (varE 'numericalReadFn) (varE f)) }
170
     else field { fieldRead = Just (appE (varE 'makeReadOptional)
171
                                         (appE (varE 'numericalReadFn)
172
                                               (varE f))) }
173

    
174
-- | Sets custom functions on a field.
175
customField :: Name      -- ^ The name of the read function
176
            -> Name      -- ^ The name of the show function
177
            -> [String]  -- ^ The name of extra field keys
178
            -> Field     -- ^ The original field
179
            -> Field     -- ^ Updated field
180
customField readfn showfn extra field =
181
  field { fieldRead = Just (varE readfn), fieldShow = Just (varE showfn)
182
        , fieldExtraKeys = extra }
183

    
184
-- | Computes the record name for a given field, based on either the
185
-- string value in the JSON serialisation or the custom named if any
186
-- exists.
187
fieldRecordName :: Field -> String
188
fieldRecordName (Field { fieldName = name, fieldConstr = alias }) =
189
  fromMaybe (camelCase name) alias
190

    
191
-- | Computes the preferred variable name to use for the value of this
192
-- field. If the field has a specific constructor name, then we use a
193
-- first-letter-lowercased version of that; otherwise, we simply use
194
-- the field name. See also 'fieldRecordName'.
195
fieldVariable :: Field -> String
196
fieldVariable f =
197
  case (fieldConstr f) of
198
    Just name -> ensureLower name
199
    _ -> map (\c -> if c == '-' then '_' else c) $ fieldName f
200

    
201
-- | Compute the actual field type (taking into account possible
202
-- optional status).
203
actualFieldType :: Field -> Q Type
204
actualFieldType f | fieldIsOptional f /= NotOptional = [t| Maybe $t |]
205
                  | otherwise = t
206
                  where t = fieldType f
207

    
208
-- | Checks that a given field is not optional (for object types or
209
-- fields which should not allow this case).
210
checkNonOptDef :: (Monad m) => Field -> m ()
211
checkNonOptDef (Field { fieldIsOptional = OptionalOmitNull
212
                      , fieldName = name }) =
213
  fail $ "Optional field " ++ name ++ " used in parameter declaration"
214
checkNonOptDef (Field { fieldIsOptional = OptionalSerializeNull
215
                      , fieldName = name }) =
216
  fail $ "Optional field " ++ name ++ " used in parameter declaration"
217
checkNonOptDef (Field { fieldDefault = (Just _), fieldName = name }) =
218
  fail $ "Default field " ++ name ++ " used in parameter declaration"
219
checkNonOptDef _ = return ()
220

    
221
-- | Produces the expression that will de-serialise a given
222
-- field. Since some custom parsing functions might need to use the
223
-- entire object, we do take and pass the object to any custom read
224
-- functions.
225
loadFn :: Field   -- ^ The field definition
226
       -> Q Exp   -- ^ The value of the field as existing in the JSON message
227
       -> Q Exp   -- ^ The entire object in JSON object format
228
       -> Q Exp   -- ^ Resulting expression
229
loadFn (Field { fieldRead = Just readfn }) expr o = [| $expr >>= $readfn $o |]
230
loadFn _ expr _ = expr
231

    
232
-- * Common field declarations
233

    
234
-- | Timestamp fields description.
235
timeStampFields :: [Field]
236
timeStampFields =
237
    [ defaultField [| 0::Double |] $ simpleField "ctime" [t| Double |]
238
    , defaultField [| 0::Double |] $ simpleField "mtime" [t| Double |]
239
    ]
240

    
241
-- | Serial number fields description.
242
serialFields :: [Field]
243
serialFields =
244
    [ renameField  "Serial" $ simpleField "serial_no" [t| Int |] ]
245

    
246
-- | UUID fields description.
247
uuidFields :: [Field]
248
uuidFields = [ simpleField "uuid" [t| String |] ]
249

    
250
-- | Tag set type alias.
251
type TagSet = Set.Set String
252

    
253
-- | Tag field description.
254
tagsFields :: [Field]
255
tagsFields = [ defaultField [| Set.empty |] $
256
               simpleField "tags" [t| TagSet |] ]
257

    
258
-- * Internal types
259

    
260
-- | A simple field, in constrast to the customisable 'Field' type.
261
type SimpleField = (String, Q Type)
262

    
263
-- | A definition for a single constructor for a simple object.
264
type SimpleConstructor = (String, [SimpleField])
265

    
266
-- | A definition for ADTs with simple fields.
267
type SimpleObject = [SimpleConstructor]
268

    
269
-- | A type alias for an opcode constructor of a regular object.
270
type OpCodeConstructor = (String, Q Type, String, [Field], String)
271

    
272
-- | A type alias for a Luxi constructor of a regular object.
273
type LuxiConstructor = (String, [Field])
274

    
275
-- * Helper functions
276

    
277
-- | Ensure first letter is lowercase.
278
--
279
-- Used to convert type name to function prefix, e.g. in @data Aa ->
280
-- aaToRaw@.
281
ensureLower :: String -> String
282
ensureLower [] = []
283
ensureLower (x:xs) = toLower x:xs
284

    
285
-- | Ensure first letter is uppercase.
286
--
287
-- Used to convert constructor name to component
288
ensureUpper :: String -> String
289
ensureUpper [] = []
290
ensureUpper (x:xs) = toUpper x:xs
291

    
292
-- | Helper for quoted expressions.
293
varNameE :: String -> Q Exp
294
varNameE = varE . mkName
295

    
296
-- | showJSON as an expression, for reuse.
297
showJSONE :: Q Exp
298
showJSONE = varE 'JSON.showJSON
299

    
300
-- | makeObj as an expression, for reuse.
301
makeObjE :: Q Exp
302
makeObjE = varE 'JSON.makeObj
303

    
304
-- | fromObj (Ganeti specific) as an expression, for reuse.
305
fromObjE :: Q Exp
306
fromObjE = varE 'fromObj
307

    
308
-- | ToRaw function name.
309
toRawName :: String -> Name
310
toRawName = mkName . (++ "ToRaw") . ensureLower
311

    
312
-- | FromRaw function name.
313
fromRawName :: String -> Name
314
fromRawName = mkName . (++ "FromRaw") . ensureLower
315

    
316
-- | Converts a name to it's varE\/litE representations.
317
reprE :: Either String Name -> Q Exp
318
reprE = either stringE varE
319

    
320
-- | Smarter function application.
321
--
322
-- This does simply f x, except that if is 'id', it will skip it, in
323
-- order to generate more readable code when using -ddump-splices.
324
appFn :: Exp -> Exp -> Exp
325
appFn f x | f == VarE 'id = x
326
          | otherwise = AppE f x
327

    
328
-- | Builds a field for a normal constructor.
329
buildConsField :: Q Type -> StrictTypeQ
330
buildConsField ftype = do
331
  ftype' <- ftype
332
  return (NotStrict, ftype')
333

    
334
-- | Builds a constructor based on a simple definition (not field-based).
335
buildSimpleCons :: Name -> SimpleObject -> Q Dec
336
buildSimpleCons tname cons = do
337
  decl_d <- mapM (\(cname, fields) -> do
338
                    fields' <- mapM (buildConsField . snd) fields
339
                    return $ NormalC (mkName cname) fields') cons
340
  return $ DataD [] tname [] decl_d [''Show, ''Eq]
341

    
342
-- | Generate the save function for a given type.
343
genSaveSimpleObj :: Name                            -- ^ Object type
344
                 -> String                          -- ^ Function name
345
                 -> SimpleObject                    -- ^ Object definition
346
                 -> (SimpleConstructor -> Q Clause) -- ^ Constructor save fn
347
                 -> Q (Dec, Dec)
348
genSaveSimpleObj tname sname opdefs fn = do
349
  let sigt = AppT (AppT ArrowT (ConT tname)) (ConT ''JSON.JSValue)
350
      fname = mkName sname
351
  cclauses <- mapM fn opdefs
352
  return $ (SigD fname sigt, FunD fname cclauses)
353

    
354
-- * Template code for simple raw type-equivalent ADTs
355

    
356
-- | Generates a data type declaration.
357
--
358
-- The type will have a fixed list of instances.
359
strADTDecl :: Name -> [String] -> Dec
360
strADTDecl name constructors =
361
  DataD [] name []
362
          (map (flip NormalC [] . mkName) constructors)
363
          [''Show, ''Eq, ''Enum, ''Bounded, ''Ord]
364

    
365
-- | Generates a toRaw function.
366
--
367
-- This generates a simple function of the form:
368
--
369
-- @
370
-- nameToRaw :: Name -> /traw/
371
-- nameToRaw Cons1 = var1
372
-- nameToRaw Cons2 = \"value2\"
373
-- @
374
genToRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
375
genToRaw traw fname tname constructors = do
376
  let sigt = AppT (AppT ArrowT (ConT tname)) (ConT traw)
377
  -- the body clauses, matching on the constructor and returning the
378
  -- raw value
379
  clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
380
                             (normalB (reprE v)) []) constructors
381
  return [SigD fname sigt, FunD fname clauses]
382

    
383
-- | Generates a fromRaw function.
384
--
385
-- The function generated is monadic and can fail parsing the
386
-- raw value. It is of the form:
387
--
388
-- @
389
-- nameFromRaw :: (Monad m) => /traw/ -> m Name
390
-- nameFromRaw s | s == var1       = Cons1
391
--               | s == \"value2\" = Cons2
392
--               | otherwise = fail /.../
393
-- @
394
genFromRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
395
genFromRaw traw fname tname constructors = do
396
  -- signature of form (Monad m) => String -> m $name
397
  sigt <- [t| (Monad m) => $(conT traw) -> m $(conT tname) |]
398
  -- clauses for a guarded pattern
399
  let varp = mkName "s"
400
      varpe = varE varp
401
  clauses <- mapM (\(c, v) -> do
402
                     -- the clause match condition
403
                     g <- normalG [| $varpe == $(reprE v) |]
404
                     -- the clause result
405
                     r <- [| return $(conE (mkName c)) |]
406
                     return (g, r)) constructors
407
  -- the otherwise clause (fallback)
408
  oth_clause <- do
409
    g <- normalG [| otherwise |]
410
    r <- [|fail ("Invalid string value for type " ++
411
                 $(litE (stringL (nameBase tname))) ++ ": " ++ show $varpe) |]
412
    return (g, r)
413
  let fun = FunD fname [Clause [VarP varp]
414
                        (GuardedB (clauses++[oth_clause])) []]
415
  return [SigD fname sigt, fun]
416

    
417
-- | Generates a data type from a given raw format.
418
--
419
-- The format is expected to multiline. The first line contains the
420
-- type name, and the rest of the lines must contain two words: the
421
-- constructor name and then the string representation of the
422
-- respective constructor.
423
--
424
-- The function will generate the data type declaration, and then two
425
-- functions:
426
--
427
-- * /name/ToRaw, which converts the type to a raw type
428
--
429
-- * /name/FromRaw, which (monadically) converts from a raw type to the type
430
--
431
-- Note that this is basically just a custom show\/read instance,
432
-- nothing else.
433
declareADT
434
  :: (a -> Either String Name) -> Name -> String -> [(String, a)] -> Q [Dec]
435
declareADT fn traw sname cons = do
436
  let name = mkName sname
437
      ddecl = strADTDecl name (map fst cons)
438
      -- process cons in the format expected by genToRaw
439
      cons' = map (\(a, b) -> (a, fn b)) cons
440
  toraw <- genToRaw traw (toRawName sname) name cons'
441
  fromraw <- genFromRaw traw (fromRawName sname) name cons'
442
  return $ ddecl:toraw ++ fromraw
443

    
444
declareLADT :: Name -> String -> [(String, String)] -> Q [Dec]
445
declareLADT = declareADT Left
446

    
447
declareILADT :: String -> [(String, Int)] -> Q [Dec]
448
declareILADT sname cons = do
449
  consNames <- sequence [ newName ('_':n) | (n, _) <- cons ]
450
  consFns <- concat <$> sequence
451
             [ do sig <- sigD n [t| Int |]
452
                  let expr = litE (IntegerL (toInteger i))
453
                  fn <- funD n [clause [] (normalB expr) []]
454
                  return [sig, fn]
455
             | n <- consNames
456
             | (_, i) <- cons ]
457
  let cons' = [ (n, n') | (n, _) <- cons | n' <- consNames ]
458
  (consFns ++) <$> declareADT Right ''Int sname cons'
459

    
460
declareIADT :: String -> [(String, Name)] -> Q [Dec]
461
declareIADT = declareADT Right ''Int
462

    
463
declareSADT :: String -> [(String, Name)] -> Q [Dec]
464
declareSADT = declareADT Right ''String
465

    
466
-- | Creates the showJSON member of a JSON instance declaration.
467
--
468
-- This will create what is the equivalent of:
469
--
470
-- @
471
-- showJSON = showJSON . /name/ToRaw
472
-- @
473
--
474
-- in an instance JSON /name/ declaration
475
genShowJSON :: String -> Q Dec
476
genShowJSON name = do
477
  body <- [| JSON.showJSON . $(varE (toRawName name)) |]
478
  return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
479

    
480
-- | Creates the readJSON member of a JSON instance declaration.
481
--
482
-- This will create what is the equivalent of:
483
--
484
-- @
485
-- readJSON s = case readJSON s of
486
--                Ok s' -> /name/FromRaw s'
487
--                Error e -> Error /description/
488
-- @
489
--
490
-- in an instance JSON /name/ declaration
491
genReadJSON :: String -> Q Dec
492
genReadJSON name = do
493
  let s = mkName "s"
494
  body <- [| case JSON.readJSON $(varE s) of
495
               JSON.Ok s' -> $(varE (fromRawName name)) s'
496
               JSON.Error e ->
497
                   JSON.Error $ "Can't parse raw value for type " ++
498
                           $(stringE name) ++ ": " ++ e ++ " from " ++
499
                           show $(varE s)
500
           |]
501
  return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
502

    
503
-- | Generates a JSON instance for a given type.
504
--
505
-- This assumes that the /name/ToRaw and /name/FromRaw functions
506
-- have been defined as by the 'declareSADT' function.
507
makeJSONInstance :: Name -> Q [Dec]
508
makeJSONInstance name = do
509
  let base = nameBase name
510
  showJ <- genShowJSON base
511
  readJ <- genReadJSON base
512
  return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) [readJ,showJ]]
513

    
514
-- * Template code for opcodes
515

    
516
-- | Transforms a CamelCase string into an_underscore_based_one.
517
deCamelCase :: String -> String
518
deCamelCase =
519
    intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
520

    
521
-- | Transform an underscore_name into a CamelCase one.
522
camelCase :: String -> String
523
camelCase = concatMap (ensureUpper . drop 1) .
524
            groupBy (\_ b -> b /= '_' && b /= '-') . ('_':)
525

    
526
-- | Computes the name of a given constructor.
527
constructorName :: Con -> Q Name
528
constructorName (NormalC name _) = return name
529
constructorName (RecC name _)    = return name
530
constructorName x                = fail $ "Unhandled constructor " ++ show x
531

    
532
-- | Extract all constructor names from a given type.
533
reifyConsNames :: Name -> Q [String]
534
reifyConsNames name = do
535
  reify_result <- reify name
536
  case reify_result of
537
    TyConI (DataD _ _ _ cons _) -> mapM (liftM nameBase . constructorName) cons
538
    o -> fail $ "Unhandled name passed to reifyConsNames, expected\
539
                \ type constructor but got '" ++ show o ++ "'"
540

    
541
-- | Builds the generic constructor-to-string function.
542
--
543
-- This generates a simple function of the following form:
544
--
545
-- @
546
-- fname (ConStructorOne {}) = trans_fun("ConStructorOne")
547
-- fname (ConStructorTwo {}) = trans_fun("ConStructorTwo")
548
-- @
549
--
550
-- This builds a custom list of name\/string pairs and then uses
551
-- 'genToRaw' to actually generate the function.
552
genConstrToStr :: (String -> String) -> Name -> String -> Q [Dec]
553
genConstrToStr trans_fun name fname = do
554
  cnames <- reifyConsNames name
555
  let svalues = map (Left . trans_fun) cnames
556
  genToRaw ''String (mkName fname) name $ zip cnames svalues
557

    
558
-- | Constructor-to-string for OpCode.
559
genOpID :: Name -> String -> Q [Dec]
560
genOpID = genConstrToStr deCamelCase
561

    
562
-- | Builds a list with all defined constructor names for a type.
563
--
564
-- @
565
-- vstr :: String
566
-- vstr = [...]
567
-- @
568
--
569
-- Where the actual values of the string are the constructor names
570
-- mapped via @trans_fun@.
571
genAllConstr :: (String -> String) -> Name -> String -> Q [Dec]
572
genAllConstr trans_fun name vstr = do
573
  cnames <- reifyConsNames name
574
  let svalues = sort $ map trans_fun cnames
575
      vname = mkName vstr
576
      sig = SigD vname (AppT ListT (ConT ''String))
577
      body = NormalB (ListE (map (LitE . StringL) svalues))
578
  return $ [sig, ValD (VarP vname) body []]
579

    
580
-- | Generates a list of all defined opcode IDs.
581
genAllOpIDs :: Name -> String -> Q [Dec]
582
genAllOpIDs = genAllConstr deCamelCase
583

    
584
-- | OpCode parameter (field) type.
585
type OpParam = (String, Q Type, Q Exp)
586

    
587
-- * Python code generation
588

    
589
data OpCodeField = OpCodeField { ocfName :: String
590
                               , ocfType :: String
591
                               , ocfDefl :: Maybe PyValueEx
592
                               , ocfDoc  :: String
593
                               }
594

    
595
-- | Transfers opcode data between the opcode description (through
596
-- @genOpCode@) and the Python code generation functions.
597
data OpCodeDescriptor = OpCodeDescriptor { ocdName   :: String
598
                                         , ocdType   :: String
599
                                         , ocdDoc    :: String
600
                                         , ocdFields :: [OpCodeField]
601
                                         , ocdDescr  :: String
602
                                         }
603

    
604
-- | Strips out the module name
605
--
606
-- @
607
-- pyBaseName "Data.Map" = "Map"
608
-- @
609
pyBaseName :: String -> String
610
pyBaseName str =
611
  case span (/= '.') str of
612
    (x, []) -> x
613
    (_, _:x) -> pyBaseName x
614

    
615
-- | Converts a Haskell type name into a Python type name.
616
--
617
-- @
618
-- pyTypename "Bool" = "ht.TBool"
619
-- @
620
pyTypeName :: Show a => a -> String
621
pyTypeName name =
622
  "ht.T" ++ (case pyBaseName (show name) of
623
                "()" -> "None"
624
                "Map" -> "DictOf"
625
                "Set" -> "SetOf"
626
                "ListSet" -> "SetOf"
627
                "Either" -> "Or"
628
                "GenericContainer" -> "DictOf"
629
                "JSValue" -> "Any"
630
                "JSObject" -> "Object"
631
                str -> str)
632

    
633
-- | Converts a Haskell type into a Python type.
634
--
635
-- @
636
-- pyType [Int] = "ht.TListOf(ht.TInt)"
637
-- @
638
pyType :: Type -> Q String
639
pyType (AppT typ1 typ2) =
640
  do t <- pyCall typ1 typ2
641
     return $ t ++ ")"
642

    
643
pyType (ConT name) = return (pyTypeName name)
644
pyType ListT = return "ht.TListOf"
645
pyType (TupleT 0) = return "ht.TNone"
646
pyType (TupleT _) = return "ht.TTupleOf"
647
pyType typ = error $ "unhandled case for type " ++ show typ
648
        
649
-- | Converts a Haskell type application into a Python type.
650
--
651
-- @
652
-- Maybe Int = "ht.TMaybe(ht.TInt)"
653
-- @
654
pyCall :: Type -> Type -> Q String
655
pyCall (AppT typ1 typ2) arg =
656
  do t <- pyCall typ1 typ2
657
     targ <- pyType arg
658
     return $ t ++ ", " ++ targ
659

    
660
pyCall typ1 typ2 =
661
  do t1 <- pyType typ1
662
     t2 <- pyType typ2
663
     return $ t1 ++ "(" ++ t2
664

    
665
-- | @pyType opt typ@ converts Haskell type @typ@ into a Python type,
666
-- where @opt@ determines if the converted type is optional (i.e.,
667
-- Maybe).
668
--
669
-- @
670
-- pyType False [Int] = "ht.TListOf(ht.TInt)" (mandatory)
671
-- pyType True [Int] = "ht.TMaybe(ht.TListOf(ht.TInt))" (optional)
672
-- @
673
pyOptionalType :: Bool -> Type -> Q String
674
pyOptionalType opt typ
675
  | opt = do t <- pyType typ
676
             return $ "ht.TMaybe(" ++ t ++ ")"
677
  | otherwise = pyType typ
678

    
679
-- | Optionally encapsulates default values in @PyValueEx@.
680
--
681
-- @maybeApp exp typ@ returns a quoted expression that encapsulates
682
-- the default value @exp@ of an opcode parameter cast to @typ@ in a
683
-- @PyValueEx@, if @exp@ is @Just@.  Otherwise, it returns a quoted
684
-- expression with @Nothing@.
685
maybeApp :: Maybe (Q Exp) -> Q Type -> Q Exp
686
maybeApp Nothing _ =
687
  [| Nothing |]
688

    
689
maybeApp (Just expr) typ =
690
  [| Just ($(conE (mkName "PyValueEx")) ($expr :: $typ)) |]
691

    
692

    
693
-- | Generates a Python type according to whether the field is
694
-- optional
695
genPyType' :: OptionalType -> Q Type -> Q Exp
696
genPyType' opt typ = typ >>= pyOptionalType (opt /= NotOptional) >>= stringE
697

    
698
-- | Generates Python types from opcode parameters.
699
genPyType :: Field -> Q Exp
700
genPyType f = genPyType' (fieldIsOptional f) (fieldType f)
701

    
702
-- | Generates Python default values from opcode parameters.
703
genPyDefault :: Field -> Q Exp
704
genPyDefault f = maybeApp (fieldDefault f) (fieldType f)
705

    
706
pyField :: Field -> Q Exp
707
pyField f = [| OpCodeField $(stringE (fieldName f))
708
                           $(genPyType f)
709
                           $(genPyDefault f)
710
                           $(stringE (fieldDoc f)) |]
711

    
712
-- | Generates a Haskell function call to "showPyClass" with the
713
-- necessary information on how to build the Python class string.
714
pyClass :: OpCodeConstructor -> Q Exp
715
pyClass (consName, consType, consDoc, consFields, consDscField) =
716
  do let pyClassVar = varNameE "showPyClass"
717
         consName' = stringE consName
718
     let consType' = genPyType' NotOptional consType
719
     let consDoc' = stringE consDoc
720
     [| OpCodeDescriptor $consName'
721
                         $consType'
722
                         $consDoc'
723
                         $(listE $ map pyField consFields)
724
                         consDscField |]
725

    
726
-- | Generates a function called "pyClasses" that holds the list of
727
-- all the opcode descriptors necessary for generating the Python
728
-- opcodes.
729
pyClasses :: [OpCodeConstructor] -> Q [Dec]
730
pyClasses cons =
731
  do let name = mkName "pyClasses"
732
         sig = SigD name (AppT ListT (ConT ''OpCodeDescriptor))
733
     fn <- FunD name <$> (:[]) <$> declClause cons
734
     return [sig, fn]
735
  where declClause c =
736
          clause [] (normalB (ListE <$> mapM pyClass c)) []
737

    
738
-- | Converts from an opcode constructor to a Luxi constructor.
739
opcodeConsToLuxiCons :: (a, b, c, d, e) -> (a, d)
740
opcodeConsToLuxiCons (x, _, _, y, _) = (x, y)
741

    
742
-- | Generates the OpCode data type.
743
--
744
-- This takes an opcode logical definition, and builds both the
745
-- datatype and the JSON serialisation out of it. We can't use a
746
-- generic serialisation since we need to be compatible with Ganeti's
747
-- own, so we have a few quirks to work around.
748
genOpCode :: String              -- ^ Type name to use
749
          -> [OpCodeConstructor] -- ^ Constructor name and parameters
750
          -> Q [Dec]
751
genOpCode name cons = do
752
  let tname = mkName name
753
  decl_d <- mapM (\(cname, _, _, fields, _) -> do
754
                    -- we only need the type of the field, without Q
755
                    fields' <- mapM (fieldTypeInfo "op") fields
756
                    return $ RecC (mkName cname) fields')
757
            cons
758
  let declD = DataD [] tname [] decl_d [''Show, ''Eq]
759
  let (allfsig, allffn) = genAllOpFields "allOpFields" cons
760
  save_decs <- genSaveOpCode tname "saveOpCode" "toDictOpCode"
761
               (map opcodeConsToLuxiCons cons) saveConstructor True
762
  (loadsig, loadfn) <- genLoadOpCode cons
763
  pyDecls <- pyClasses cons
764
  return $ [declD, allfsig, allffn, loadsig, loadfn] ++ save_decs ++ pyDecls
765

    
766
-- | Generates the function pattern returning the list of fields for a
767
-- given constructor.
768
genOpConsFields :: OpCodeConstructor -> Clause
769
genOpConsFields (cname, _, _, fields, _) =
770
  let op_id = deCamelCase cname
771
      fvals = map (LitE . StringL) . sort . nub $
772
              concatMap (\f -> fieldName f:fieldExtraKeys f) fields
773
  in Clause [LitP (StringL op_id)] (NormalB $ ListE fvals) []
774

    
775
-- | Generates a list of all fields of an opcode constructor.
776
genAllOpFields  :: String              -- ^ Function name
777
                -> [OpCodeConstructor] -- ^ Object definition
778
                -> (Dec, Dec)
779
genAllOpFields sname opdefs =
780
  let cclauses = map genOpConsFields opdefs
781
      other = Clause [WildP] (NormalB (ListE [])) []
782
      fname = mkName sname
783
      sigt = AppT  (AppT ArrowT (ConT ''String)) (AppT ListT (ConT ''String))
784
  in (SigD fname sigt, FunD fname (cclauses++[other]))
785

    
786
-- | Generates the \"save\" clause for an entire opcode constructor.
787
--
788
-- This matches the opcode with variables named the same as the
789
-- constructor fields (just so that the spliced in code looks nicer),
790
-- and passes those name plus the parameter definition to 'saveObjectField'.
791
saveConstructor :: LuxiConstructor -- ^ The constructor
792
                -> Q Clause        -- ^ Resulting clause
793
saveConstructor (sname, fields) = do
794
  let cname = mkName sname
795
  fnames <- mapM (newName . fieldVariable) fields
796
  let pat = conP cname (map varP fnames)
797
  let felems = map (uncurry saveObjectField) (zip fnames fields)
798
      -- now build the OP_ID serialisation
799
      opid = [| [( $(stringE "OP_ID"),
800
                   JSON.showJSON $(stringE . deCamelCase $ sname) )] |]
801
      flist = listE (opid:felems)
802
      -- and finally convert all this to a json object
803
      flist' = [| concat $flist |]
804
  clause [pat] (normalB flist') []
805

    
806
-- | Generates the main save opcode function.
807
--
808
-- This builds a per-constructor match clause that contains the
809
-- respective constructor-serialisation code.
810
genSaveOpCode :: Name                          -- ^ Object ype
811
              -> String                        -- ^ To 'JSValue' function name
812
              -> String                        -- ^ To 'JSObject' function name
813
              -> [LuxiConstructor]             -- ^ Object definition
814
              -> (LuxiConstructor -> Q Clause) -- ^ Constructor save fn
815
              -> Bool                          -- ^ Whether to generate
816
                                               -- obj or just a
817
                                               -- list\/tuple of values
818
              -> Q [Dec]
819
genSaveOpCode tname jvalstr tdstr opdefs fn gen_object = do
820
  tdclauses <- mapM fn opdefs
821
  let typecon = ConT tname
822
      jvalname = mkName jvalstr
823
      jvalsig = AppT  (AppT ArrowT typecon) (ConT ''JSON.JSValue)
824
      tdname = mkName tdstr
825
  tdsig <- [t| $(return typecon) -> [(String, JSON.JSValue)] |]
826
  jvalclause <- if gen_object
827
                  then [| $makeObjE . $(varE tdname) |]
828
                  else [| JSON.showJSON . map snd . $(varE tdname) |]
829
  return [ SigD tdname tdsig
830
         , FunD tdname tdclauses
831
         , SigD jvalname jvalsig
832
         , ValD (VarP jvalname) (NormalB jvalclause) []]
833

    
834
-- | Generates load code for a single constructor of the opcode data type.
835
loadConstructor :: OpCodeConstructor -> Q Exp
836
loadConstructor (sname, _, _, fields, _) = do
837
  let name = mkName sname
838
  fbinds <- mapM loadObjectField fields
839
  let (fnames, fstmts) = unzip fbinds
840
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
841
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
842
  return $ DoE fstmts'
843

    
844
-- | Generates the loadOpCode function.
845
genLoadOpCode :: [OpCodeConstructor] -> Q (Dec, Dec)
846
genLoadOpCode opdefs = do
847
  let fname = mkName "loadOpCode"
848
      arg1 = mkName "v"
849
      objname = mkName "o"
850
      opid = mkName "op_id"
851
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
852
                                 (JSON.readJSON $(varE arg1)) |]
853
  st2 <- bindS (varP opid) [| $fromObjE $(varE objname) $(stringE "OP_ID") |]
854
  -- the match results (per-constructor blocks)
855
  mexps <- mapM loadConstructor opdefs
856
  fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
857
  let mpats = map (\(me, (consName, _, _, _, _)) ->
858
                       let mp = LitP . StringL . deCamelCase $ consName
859
                       in Match mp (NormalB me) []
860
                  ) $ zip mexps opdefs
861
      defmatch = Match WildP (NormalB fails) []
862
      cst = NoBindS $ CaseE (VarE opid) $ mpats++[defmatch]
863
      body = DoE [st1, st2, cst]
864
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
865
  return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
866

    
867
-- * Template code for luxi
868

    
869
-- | Constructor-to-string for LuxiOp.
870
genStrOfOp :: Name -> String -> Q [Dec]
871
genStrOfOp = genConstrToStr id
872

    
873
-- | Constructor-to-string for MsgKeys.
874
genStrOfKey :: Name -> String -> Q [Dec]
875
genStrOfKey = genConstrToStr ensureLower
876

    
877
-- | Generates the LuxiOp data type.
878
--
879
-- This takes a Luxi operation definition and builds both the
880
-- datatype and the function transforming the arguments to JSON.
881
-- We can't use anything less generic, because the way different
882
-- operations are serialized differs on both parameter- and top-level.
883
--
884
-- There are two things to be defined for each parameter:
885
--
886
-- * name
887
--
888
-- * type
889
--
890
genLuxiOp :: String -> [LuxiConstructor] -> Q [Dec]
891
genLuxiOp name cons = do
892
  let tname = mkName name
893
  decl_d <- mapM (\(cname, fields) -> do
894
                    -- we only need the type of the field, without Q
895
                    fields' <- mapM actualFieldType fields
896
                    let fields'' = zip (repeat NotStrict) fields'
897
                    return $ NormalC (mkName cname) fields'')
898
            cons
899
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Eq]
900
  save_decs <- genSaveOpCode tname "opToArgs" "opToDict"
901
               cons saveLuxiConstructor False
902
  req_defs <- declareSADT "LuxiReq" .
903
              map (\(str, _) -> ("Req" ++ str, mkName ("luxiReq" ++ str))) $
904
                  cons
905
  return $ declD:save_decs ++ req_defs
906

    
907
-- | Generates the \"save\" clause for entire LuxiOp constructor.
908
saveLuxiConstructor :: LuxiConstructor -> Q Clause
909
saveLuxiConstructor (sname, fields) = do
910
  let cname = mkName sname
911
  fnames <- mapM (newName . fieldVariable) fields
912
  let pat = conP cname (map varP fnames)
913
  let felems = map (uncurry saveObjectField) (zip fnames fields)
914
      flist = [| concat $(listE felems) |]
915
  clause [pat] (normalB flist) []
916

    
917
-- * "Objects" functionality
918

    
919
-- | Extract the field's declaration from a Field structure.
920
fieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
921
fieldTypeInfo field_pfx fd = do
922
  t <- actualFieldType fd
923
  let n = mkName . (field_pfx ++) . fieldRecordName $ fd
924
  return (n, NotStrict, t)
925

    
926
-- | Build an object declaration.
927
buildObject :: String -> String -> [Field] -> Q [Dec]
928
buildObject sname field_pfx fields = do
929
  let name = mkName sname
930
  fields_d <- mapM (fieldTypeInfo field_pfx) fields
931
  let decl_d = RecC name fields_d
932
  let declD = DataD [] name [] [decl_d] [''Show, ''Eq]
933
  ser_decls <- buildObjectSerialisation sname fields
934
  return $ declD:ser_decls
935

    
936
-- | Generates an object definition: data type and its JSON instance.
937
buildObjectSerialisation :: String -> [Field] -> Q [Dec]
938
buildObjectSerialisation sname fields = do
939
  let name = mkName sname
940
  savedecls <- genSaveObject saveObjectField sname fields
941
  (loadsig, loadfn) <- genLoadObject loadObjectField sname fields
942
  shjson <- objectShowJSON sname
943
  rdjson <- objectReadJSON sname
944
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
945
                 [rdjson, shjson]
946
  return $ savedecls ++ [loadsig, loadfn, instdecl]
947

    
948
-- | The toDict function name for a given type.
949
toDictName :: String -> Name
950
toDictName sname = mkName ("toDict" ++ sname)
951

    
952
-- | Generates the save object functionality.
953
genSaveObject :: (Name -> Field -> Q Exp)
954
              -> String -> [Field] -> Q [Dec]
955
genSaveObject save_fn sname fields = do
956
  let name = mkName sname
957
  fnames <- mapM (newName . fieldVariable) fields
958
  let pat = conP name (map varP fnames)
959
  let tdname = toDictName sname
960
  tdsigt <- [t| $(conT name) -> [(String, JSON.JSValue)] |]
961

    
962
  let felems = map (uncurry save_fn) (zip fnames fields)
963
      flist = listE felems
964
      -- and finally convert all this to a json object
965
      tdlist = [| concat $flist |]
966
      iname = mkName "i"
967
  tclause <- clause [pat] (normalB tdlist) []
968
  cclause <- [| $makeObjE . $(varE tdname) |]
969
  let fname = mkName ("save" ++ sname)
970
  sigt <- [t| $(conT name) -> JSON.JSValue |]
971
  return [SigD tdname tdsigt, FunD tdname [tclause],
972
          SigD fname sigt, ValD (VarP fname) (NormalB cclause) []]
973

    
974
-- | Generates the code for saving an object's field, handling the
975
-- various types of fields that we have.
976
saveObjectField :: Name -> Field -> Q Exp
977
saveObjectField fvar field =
978
  case fieldIsOptional field of
979
    OptionalOmitNull -> [| case $(varE fvar) of
980
                             Nothing -> []
981
                             Just v  -> [( $nameE, JSON.showJSON v )]
982
                         |]
983
    OptionalSerializeNull -> [| case $(varE fvar) of
984
                                  Nothing -> [( $nameE, JSON.JSNull )]
985
                                  Just v  -> [( $nameE, JSON.showJSON v )]
986
                              |]
987
    NotOptional ->
988
      case fieldShow field of
989
        -- Note: the order of actual:extra is important, since for
990
        -- some serialisation types (e.g. Luxi), we use tuples
991
        -- (positional info) rather than object (name info)
992
        Nothing -> [| [( $nameE, JSON.showJSON $fvarE)] |]
993
        Just fn -> [| let (actual, extra) = $fn $fvarE
994
                      in ($nameE, JSON.showJSON actual):extra
995
                    |]
996
  where nameE = stringE (fieldName field)
997
        fvarE = varE fvar
998

    
999
-- | Generates the showJSON clause for a given object name.
1000
objectShowJSON :: String -> Q Dec
1001
objectShowJSON name = do
1002
  body <- [| JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
1003
  return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
1004

    
1005
-- | Generates the load object functionality.
1006
genLoadObject :: (Field -> Q (Name, Stmt))
1007
              -> String -> [Field] -> Q (Dec, Dec)
1008
genLoadObject load_fn sname fields = do
1009
  let name = mkName sname
1010
      funname = mkName $ "load" ++ sname
1011
      arg1 = mkName $ if null fields then "_" else "v"
1012
      objname = mkName "o"
1013
      opid = mkName "op_id"
1014
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
1015
                                 (JSON.readJSON $(varE arg1)) |]
1016
  fbinds <- mapM load_fn fields
1017
  let (fnames, fstmts) = unzip fbinds
1018
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
1019
      retstmt = [NoBindS (AppE (VarE 'return) cval)]
1020
      -- FIXME: should we require an empty dict for an empty type?
1021
      -- this allows any JSValue right now
1022
      fstmts' = if null fields
1023
                  then retstmt
1024
                  else st1:fstmts ++ retstmt
1025
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT name) |]
1026
  return $ (SigD funname sigt,
1027
            FunD funname [Clause [VarP arg1] (NormalB (DoE fstmts')) []])
1028

    
1029
-- | Generates code for loading an object's field.
1030
loadObjectField :: Field -> Q (Name, Stmt)
1031
loadObjectField field = do
1032
  let name = fieldVariable field
1033
  fvar <- newName name
1034
  -- these are used in all patterns below
1035
  let objvar = varNameE "o"
1036
      objfield = stringE (fieldName field)
1037
      loadexp =
1038
        if fieldIsOptional field /= NotOptional
1039
          -- we treat both optional types the same, since
1040
          -- 'maybeFromObj' can deal with both missing and null values
1041
          -- appropriately (the same)
1042
          then [| $(varE 'maybeFromObj) $objvar $objfield |]
1043
          else case fieldDefault field of
1044
                 Just defv ->
1045
                   [| $(varE 'fromObjWithDefault) $objvar
1046
                      $objfield $defv |]
1047
                 Nothing -> [| $fromObjE $objvar $objfield |]
1048
  bexp <- loadFn field loadexp objvar
1049

    
1050
  return (fvar, BindS (VarP fvar) bexp)
1051

    
1052
-- | Builds the readJSON instance for a given object name.
1053
objectReadJSON :: String -> Q Dec
1054
objectReadJSON name = do
1055
  let s = mkName "s"
1056
  body <- [| case JSON.readJSON $(varE s) of
1057
               JSON.Ok s' -> $(varE .mkName $ "load" ++ name) s'
1058
               JSON.Error e ->
1059
                 JSON.Error $ "Can't parse value for type " ++
1060
                       $(stringE name) ++ ": " ++ e
1061
           |]
1062
  return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
1063

    
1064
-- * Inheritable parameter tables implementation
1065

    
1066
-- | Compute parameter type names.
1067
paramTypeNames :: String -> (String, String)
1068
paramTypeNames root = ("Filled"  ++ root ++ "Params",
1069
                       "Partial" ++ root ++ "Params")
1070

    
1071
-- | Compute information about the type of a parameter field.
1072
paramFieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
1073
paramFieldTypeInfo field_pfx fd = do
1074
  t <- actualFieldType fd
1075
  let n = mkName . (++ "P") . (field_pfx ++) .
1076
          fieldRecordName $ fd
1077
  return (n, NotStrict, AppT (ConT ''Maybe) t)
1078

    
1079
-- | Build a parameter declaration.
1080
--
1081
-- This function builds two different data structures: a /filled/ one,
1082
-- in which all fields are required, and a /partial/ one, in which all
1083
-- fields are optional. Due to the current record syntax issues, the
1084
-- fields need to be named differrently for the two structures, so the
1085
-- partial ones get a /P/ suffix.
1086
buildParam :: String -> String -> [Field] -> Q [Dec]
1087
buildParam sname field_pfx fields = do
1088
  let (sname_f, sname_p) = paramTypeNames sname
1089
      name_f = mkName sname_f
1090
      name_p = mkName sname_p
1091
  fields_f <- mapM (fieldTypeInfo field_pfx) fields
1092
  fields_p <- mapM (paramFieldTypeInfo field_pfx) fields
1093
  let decl_f = RecC name_f fields_f
1094
      decl_p = RecC name_p fields_p
1095
  let declF = DataD [] name_f [] [decl_f] [''Show, ''Eq]
1096
      declP = DataD [] name_p [] [decl_p] [''Show, ''Eq]
1097
  ser_decls_f <- buildObjectSerialisation sname_f fields
1098
  ser_decls_p <- buildPParamSerialisation sname_p fields
1099
  fill_decls <- fillParam sname field_pfx fields
1100
  return $ [declF, declP] ++ ser_decls_f ++ ser_decls_p ++ fill_decls ++
1101
           buildParamAllFields sname fields ++
1102
           buildDictObjectInst name_f sname_f
1103

    
1104
-- | Builds a list of all fields of a parameter.
1105
buildParamAllFields :: String -> [Field] -> [Dec]
1106
buildParamAllFields sname fields =
1107
  let vname = mkName ("all" ++ sname ++ "ParamFields")
1108
      sig = SigD vname (AppT ListT (ConT ''String))
1109
      val = ListE $ map (LitE . StringL . fieldName) fields
1110
  in [sig, ValD (VarP vname) (NormalB val) []]
1111

    
1112
-- | Builds the 'DictObject' instance for a filled parameter.
1113
buildDictObjectInst :: Name -> String -> [Dec]
1114
buildDictObjectInst name sname =
1115
  [InstanceD [] (AppT (ConT ''DictObject) (ConT name))
1116
   [ValD (VarP 'toDict) (NormalB (VarE (toDictName sname))) []]]
1117

    
1118
-- | Generates the serialisation for a partial parameter.
1119
buildPParamSerialisation :: String -> [Field] -> Q [Dec]
1120
buildPParamSerialisation sname fields = do
1121
  let name = mkName sname
1122
  savedecls <- genSaveObject savePParamField sname fields
1123
  (loadsig, loadfn) <- genLoadObject loadPParamField sname fields
1124
  shjson <- objectShowJSON sname
1125
  rdjson <- objectReadJSON sname
1126
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
1127
                 [rdjson, shjson]
1128
  return $ savedecls ++ [loadsig, loadfn, instdecl]
1129

    
1130
-- | Generates code to save an optional parameter field.
1131
savePParamField :: Name -> Field -> Q Exp
1132
savePParamField fvar field = do
1133
  checkNonOptDef field
1134
  let actualVal = mkName "v"
1135
  normalexpr <- saveObjectField actualVal field
1136
  -- we have to construct the block here manually, because we can't
1137
  -- splice-in-splice
1138
  return $ CaseE (VarE fvar) [ Match (ConP 'Nothing [])
1139
                                       (NormalB (ConE '[])) []
1140
                             , Match (ConP 'Just [VarP actualVal])
1141
                                       (NormalB normalexpr) []
1142
                             ]
1143

    
1144
-- | Generates code to load an optional parameter field.
1145
loadPParamField :: Field -> Q (Name, Stmt)
1146
loadPParamField field = do
1147
  checkNonOptDef field
1148
  let name = fieldName field
1149
  fvar <- newName name
1150
  -- these are used in all patterns below
1151
  let objvar = varNameE "o"
1152
      objfield = stringE name
1153
      loadexp = [| $(varE 'maybeFromObj) $objvar $objfield |]
1154
      field' = field {fieldRead=fmap (appE (varE 'makeReadOptional))
1155
                                  $ fieldRead field}
1156
  bexp <- loadFn field' loadexp objvar
1157
  return (fvar, BindS (VarP fvar) bexp)
1158

    
1159
-- | Builds a simple declaration of type @n_x = fromMaybe f_x p_x@.
1160
buildFromMaybe :: String -> Q Dec
1161
buildFromMaybe fname =
1162
  valD (varP (mkName $ "n_" ++ fname))
1163
         (normalB [| $(varE 'fromMaybe)
1164
                        $(varNameE $ "f_" ++ fname)
1165
                        $(varNameE $ "p_" ++ fname) |]) []
1166

    
1167
-- | Builds a function that executes the filling of partial parameter
1168
-- from a full copy (similar to Python's fillDict).
1169
fillParam :: String -> String -> [Field] -> Q [Dec]
1170
fillParam sname field_pfx fields = do
1171
  let fnames = map (\fd -> field_pfx ++ fieldRecordName fd) fields
1172
      (sname_f, sname_p) = paramTypeNames sname
1173
      oname_f = "fobj"
1174
      oname_p = "pobj"
1175
      name_f = mkName sname_f
1176
      name_p = mkName sname_p
1177
      fun_name = mkName $ "fill" ++ sname ++ "Params"
1178
      le_full = ValD (ConP name_f (map (VarP . mkName . ("f_" ++)) fnames))
1179
                (NormalB . VarE . mkName $ oname_f) []
1180
      le_part = ValD (ConP name_p (map (VarP . mkName . ("p_" ++)) fnames))
1181
                (NormalB . VarE . mkName $ oname_p) []
1182
      obj_new = foldl (\accu vname -> AppE accu (VarE vname)) (ConE name_f)
1183
                $ map (mkName . ("n_" ++)) fnames
1184
  le_new <- mapM buildFromMaybe fnames
1185
  funt <- [t| $(conT name_f) -> $(conT name_p) -> $(conT name_f) |]
1186
  let sig = SigD fun_name funt
1187
      fclause = Clause [VarP (mkName oname_f), VarP (mkName oname_p)]
1188
                (NormalB $ LetE (le_full:le_part:le_new) obj_new) []
1189
      fun = FunD fun_name [fclause]
1190
  return [sig, fun]
1191

    
1192
-- * Template code for exceptions
1193

    
1194
-- | Exception simple error message field.
1195
excErrMsg :: (String, Q Type)
1196
excErrMsg = ("errMsg", [t| String |])
1197

    
1198
-- | Builds an exception type definition.
1199
genException :: String                  -- ^ Name of new type
1200
             -> SimpleObject -- ^ Constructor name and parameters
1201
             -> Q [Dec]
1202
genException name cons = do
1203
  let tname = mkName name
1204
  declD <- buildSimpleCons tname cons
1205
  (savesig, savefn) <- genSaveSimpleObj tname ("save" ++ name) cons $
1206
                         uncurry saveExcCons
1207
  (loadsig, loadfn) <- genLoadExc tname ("load" ++ name) cons
1208
  return [declD, loadsig, loadfn, savesig, savefn]
1209

    
1210
-- | Generates the \"save\" clause for an entire exception constructor.
1211
--
1212
-- This matches the exception with variables named the same as the
1213
-- constructor fields (just so that the spliced in code looks nicer),
1214
-- and calls showJSON on it.
1215
saveExcCons :: String        -- ^ The constructor name
1216
            -> [SimpleField] -- ^ The parameter definitions for this
1217
                             -- constructor
1218
            -> Q Clause      -- ^ Resulting clause
1219
saveExcCons sname fields = do
1220
  let cname = mkName sname
1221
  fnames <- mapM (newName . fst) fields
1222
  let pat = conP cname (map varP fnames)
1223
      felems = if null fnames
1224
                 then conE '() -- otherwise, empty list has no type
1225
                 else listE $ map (\f -> [| JSON.showJSON $(varE f) |]) fnames
1226
  let tup = tupE [ litE (stringL sname), felems ]
1227
  clause [pat] (normalB [| JSON.showJSON $tup |]) []
1228

    
1229
-- | Generates load code for a single constructor of an exception.
1230
--
1231
-- Generates the code (if there's only one argument, we will use a
1232
-- list, not a tuple:
1233
--
1234
-- @
1235
-- do
1236
--  (x1, x2, ...) <- readJSON args
1237
--  return $ Cons x1 x2 ...
1238
-- @
1239
loadExcConstructor :: Name -> String -> [SimpleField] -> Q Exp
1240
loadExcConstructor inname sname fields = do
1241
  let name = mkName sname
1242
  f_names <- mapM (newName . fst) fields
1243
  let read_args = AppE (VarE 'JSON.readJSON) (VarE inname)
1244
  let binds = case f_names of
1245
                [x] -> BindS (ListP [VarP x])
1246
                _   -> BindS (TupP (map VarP f_names))
1247
      cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) f_names
1248
  return $ DoE [binds read_args, NoBindS (AppE (VarE 'return) cval)]
1249

    
1250
{-| Generates the loadException function.
1251

    
1252
This generates a quite complicated function, along the lines of:
1253

    
1254
@
1255
loadFn (JSArray [JSString name, args]) = case name of
1256
   "A1" -> do
1257
     (x1, x2, ...) <- readJSON args
1258
     return $ A1 x1 x2 ...
1259
   "a2" -> ...
1260
   s -> fail $ "Unknown exception" ++ s
1261
loadFn v = fail $ "Expected array but got " ++ show v
1262
@
1263
-}
1264
genLoadExc :: Name -> String -> SimpleObject -> Q (Dec, Dec)
1265
genLoadExc tname sname opdefs = do
1266
  let fname = mkName sname
1267
  exc_name <- newName "name"
1268
  exc_args <- newName "args"
1269
  exc_else <- newName "s"
1270
  arg_else <- newName "v"
1271
  fails <- [| fail $ "Unknown exception '" ++ $(varE exc_else) ++ "'" |]
1272
  -- default match for unknown exception name
1273
  let defmatch = Match (VarP exc_else) (NormalB fails) []
1274
  -- the match results (per-constructor blocks)
1275
  str_matches <-
1276
    mapM (\(s, params) -> do
1277
            body_exp <- loadExcConstructor exc_args s params
1278
            return $ Match (LitP (StringL s)) (NormalB body_exp) [])
1279
    opdefs
1280
  -- the first function clause; we can't use [| |] due to TH
1281
  -- limitations, so we have to build the AST by hand
1282
  let clause1 = Clause [ConP 'JSON.JSArray
1283
                               [ListP [ConP 'JSON.JSString [VarP exc_name],
1284
                                            VarP exc_args]]]
1285
                (NormalB (CaseE (AppE (VarE 'JSON.fromJSString)
1286
                                        (VarE exc_name))
1287
                          (str_matches ++ [defmatch]))) []
1288
  -- the fail expression for the second function clause
1289
  fail_type <- [| fail $ "Invalid exception: expected '(string, [args])' " ++
1290
                  "      but got " ++ show (pp_value $(varE arg_else)) ++ "'"
1291
                |]
1292
  -- the second function clause
1293
  let clause2 = Clause [VarP arg_else] (NormalB fail_type) []
1294
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT tname) |]
1295
  return $ (SigD fname sigt, FunD fname [clause1, clause2])