Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / THH.hs @ 5b4ed56e

History | View | Annotate | Download (48.3 kB)

1
{-# LANGUAGE ExistentialQuantification, 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
                  , OpCodeDescriptor
44
                  , genOpCode
45
                  , genStrOfOp
46
                  , genStrOfKey
47
                  , genLuxiOp
48
                  , Field (..)
49
                  , simpleField
50
                  , specialNumericalField
51
                  , withDoc
52
                  , defaultField
53
                  , optionalField
54
                  , optionalNullSerField
55
                  , renameField
56
                  , customField
57
                  , timeStampFields
58
                  , uuidFields
59
                  , serialFields
60
                  , tagsFields
61
                  , TagSet
62
                  , buildObject
63
                  , buildObjectSerialisation
64
                  , buildParam
65
                  , DictObject(..)
66
                  , genException
67
                  , excErrMsg
68
                  ) where
69

    
70
import Control.Monad (liftM)
71
import Data.Char
72
import Data.List
73
import qualified Data.Set as Set
74
import Language.Haskell.TH
75

    
76
import qualified Text.JSON as JSON
77
import Text.JSON.Pretty (pp_value)
78

    
79
import Ganeti.JSON
80

    
81
import Data.Maybe
82
import Data.Functor ((<$>))
83

    
84
-- * Exported types
85

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

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

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

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

    
124
withDoc :: String -> Field -> Field
125
withDoc doc field =
126
  field { fieldDoc = doc }
127

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

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

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

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

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

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

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

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

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

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

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

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

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

    
230
-- * Common field declarations
231

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

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

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

    
248
-- | Tag set type alias.
249
type TagSet = Set.Set String
250

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

    
256
-- * Internal types
257

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

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

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

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

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

    
273
-- * Helper functions
274

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

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

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

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

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

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

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

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

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

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

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

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

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

    
352
-- * Template code for simple raw type-equivalent ADTs
353

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

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

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

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

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

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

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

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

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

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

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

    
512
-- * Template code for opcodes
513

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

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

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

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

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

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

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

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

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

    
585
-- * Python code generation
586

    
587
-- | Converts Haskell values into Python values
588
--
589
-- This is necessary for the default values of opcode parameters and
590
-- return values.  For example, if a default value or return type is a
591
-- Data.Map, then it must be shown as a Python dictioanry.
592
class PyValue a where
593
  showValue :: a -> String
594

    
595
-- | Encapsulates Python default values
596
data PyValueEx = forall a. PyValue a => PyValueEx a
597

    
598
instance PyValue PyValueEx where
599
  showValue (PyValueEx x) = showValue x
600

    
601
-- | Transfers opcode data between the opcode description (through
602
-- @genOpCode@) and the Python code generation functions.
603
type OpCodeDescriptor =
604
  (String, String, String, [String],
605
   [String], [Maybe PyValueEx], [String], String)
606

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

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

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

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

    
663
pyCall typ1 typ2 =
664
  do t1 <- pyType typ1
665
     t2 <- pyType typ2
666
     return $ t1 ++ "(" ++ t2
667

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

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

    
692
maybeApp (Just expr) typ =
693
  [| Just ($(conE (mkName "PyValueEx")) ($expr :: $typ)) |]
694

    
695

    
696
-- | Generates a Python type according to whether the field is
697
-- optional
698
genPyType :: OptionalType -> Q Type -> Q ExpQ
699
genPyType opt typ =
700
  do t <- typ
701
     stringE <$> pyOptionalType (opt /= NotOptional) t
702

    
703
-- | Generates Python types from opcode parameters.
704
genPyTypes :: [Field] -> Q ExpQ
705
genPyTypes fs =
706
  listE <$> mapM (\f -> genPyType (fieldIsOptional f) (fieldType f)) fs
707

    
708
-- | Generates Python default values from opcode parameters.
709
genPyDefaults :: [Field] -> ExpQ
710
genPyDefaults fs =
711
  listE $ map (\f -> maybeApp (fieldDefault f) (fieldType f)) fs
712

    
713
-- | Generates a Haskell function call to "showPyClass" with the
714
-- necessary information on how to build the Python class string.
715
pyClass :: OpCodeConstructor -> ExpQ
716
pyClass (consName, consType, consDoc, consFields, consDscField) =
717
  do let pyClassVar = varNameE "showPyClass"
718
         consName' = stringE consName
719
     consType' <- genPyType NotOptional consType
720
     let consDoc' = stringE consDoc
721
         consFieldNames = listE $ map (stringE . fieldName) consFields
722
         consFieldDocs = listE $ map (stringE . fieldDoc) consFields
723
     consFieldTypes <- genPyTypes consFields
724
     let consFieldDefaults = genPyDefaults consFields
725
     [| ($consName',
726
         $consType',
727
         $consDoc',
728
         $consFieldNames,
729
         $consFieldTypes,
730
         $consFieldDefaults,
731
         $consFieldDocs,
732
         consDscField) |]
733

    
734
-- | Generates a function called "pyClasses" that holds the list of
735
-- all the opcode descriptors necessary for generating the Python
736
-- opcodes.
737
pyClasses :: [OpCodeConstructor] -> Q [Dec]
738
pyClasses cons =
739
  do let name = mkName "pyClasses"
740
         sig = SigD name (AppT ListT (ConT ''OpCodeDescriptor))
741
     fn <- FunD name <$> (:[]) <$> declClause cons
742
     return [sig, fn]
743
  where declClause c =
744
          clause [] (normalB (ListE <$> mapM pyClass c)) []
745

    
746
-- | Converts from an opcode constructor to a Luxi constructor.
747
opcodeConsToLuxiCons :: (a, b, c, d, e) -> (a, d)
748
opcodeConsToLuxiCons (x, _, _, y, _) = (x, y)
749

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

    
774
-- | Generates the function pattern returning the list of fields for a
775
-- given constructor.
776
genOpConsFields :: OpCodeConstructor -> Clause
777
genOpConsFields (cname, _, _, fields, _) =
778
  let op_id = deCamelCase cname
779
      fvals = map (LitE . StringL) . sort . nub $
780
              concatMap (\f -> fieldName f:fieldExtraKeys f) fields
781
  in Clause [LitP (StringL op_id)] (NormalB $ ListE fvals) []
782

    
783
-- | Generates a list of all fields of an opcode constructor.
784
genAllOpFields  :: String              -- ^ Function name
785
                -> [OpCodeConstructor] -- ^ Object definition
786
                -> (Dec, Dec)
787
genAllOpFields sname opdefs =
788
  let cclauses = map genOpConsFields opdefs
789
      other = Clause [WildP] (NormalB (ListE [])) []
790
      fname = mkName sname
791
      sigt = AppT  (AppT ArrowT (ConT ''String)) (AppT ListT (ConT ''String))
792
  in (SigD fname sigt, FunD fname (cclauses++[other]))
793

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

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

    
842
-- | Generates load code for a single constructor of the opcode data type.
843
loadConstructor :: OpCodeConstructor -> Q Exp
844
loadConstructor (sname, _, _, fields, _) = do
845
  let name = mkName sname
846
  fbinds <- mapM loadObjectField fields
847
  let (fnames, fstmts) = unzip fbinds
848
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
849
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
850
  return $ DoE fstmts'
851

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

    
875
-- * Template code for luxi
876

    
877
-- | Constructor-to-string for LuxiOp.
878
genStrOfOp :: Name -> String -> Q [Dec]
879
genStrOfOp = genConstrToStr id
880

    
881
-- | Constructor-to-string for MsgKeys.
882
genStrOfKey :: Name -> String -> Q [Dec]
883
genStrOfKey = genConstrToStr ensureLower
884

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

    
915
-- | Generates the \"save\" clause for entire LuxiOp constructor.
916
saveLuxiConstructor :: LuxiConstructor -> Q Clause
917
saveLuxiConstructor (sname, fields) = do
918
  let cname = mkName sname
919
  fnames <- mapM (newName . fieldVariable) fields
920
  let pat = conP cname (map varP fnames)
921
  let felems = map (uncurry saveObjectField) (zip fnames fields)
922
      flist = [| concat $(listE felems) |]
923
  clause [pat] (normalB flist) []
924

    
925
-- * "Objects" functionality
926

    
927
-- | Extract the field's declaration from a Field structure.
928
fieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
929
fieldTypeInfo field_pfx fd = do
930
  t <- actualFieldType fd
931
  let n = mkName . (field_pfx ++) . fieldRecordName $ fd
932
  return (n, NotStrict, t)
933

    
934
-- | Build an object declaration.
935
buildObject :: String -> String -> [Field] -> Q [Dec]
936
buildObject sname field_pfx fields = do
937
  let name = mkName sname
938
  fields_d <- mapM (fieldTypeInfo field_pfx) fields
939
  let decl_d = RecC name fields_d
940
  let declD = DataD [] name [] [decl_d] [''Show, ''Eq]
941
  ser_decls <- buildObjectSerialisation sname fields
942
  return $ declD:ser_decls
943

    
944
-- | Generates an object definition: data type and its JSON instance.
945
buildObjectSerialisation :: String -> [Field] -> Q [Dec]
946
buildObjectSerialisation sname fields = do
947
  let name = mkName sname
948
  savedecls <- genSaveObject saveObjectField sname fields
949
  (loadsig, loadfn) <- genLoadObject loadObjectField sname fields
950
  shjson <- objectShowJSON sname
951
  rdjson <- objectReadJSON sname
952
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
953
                 [rdjson, shjson]
954
  return $ savedecls ++ [loadsig, loadfn, instdecl]
955

    
956
-- | The toDict function name for a given type.
957
toDictName :: String -> Name
958
toDictName sname = mkName ("toDict" ++ sname)
959

    
960
-- | Generates the save object functionality.
961
genSaveObject :: (Name -> Field -> Q Exp)
962
              -> String -> [Field] -> Q [Dec]
963
genSaveObject save_fn sname fields = do
964
  let name = mkName sname
965
  fnames <- mapM (newName . fieldVariable) fields
966
  let pat = conP name (map varP fnames)
967
  let tdname = toDictName sname
968
  tdsigt <- [t| $(conT name) -> [(String, JSON.JSValue)] |]
969

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

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

    
1007
-- | Generates the showJSON clause for a given object name.
1008
objectShowJSON :: String -> Q Dec
1009
objectShowJSON name = do
1010
  body <- [| JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
1011
  return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
1012

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

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

    
1058
  return (fvar, BindS (VarP fvar) bexp)
1059

    
1060
-- | Builds the readJSON instance for a given object name.
1061
objectReadJSON :: String -> Q Dec
1062
objectReadJSON name = do
1063
  let s = mkName "s"
1064
  body <- [| case JSON.readJSON $(varE s) of
1065
               JSON.Ok s' -> $(varE .mkName $ "load" ++ name) s'
1066
               JSON.Error e ->
1067
                 JSON.Error $ "Can't parse value for type " ++
1068
                       $(stringE name) ++ ": " ++ e
1069
           |]
1070
  return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
1071

    
1072
-- * Inheritable parameter tables implementation
1073

    
1074
-- | Compute parameter type names.
1075
paramTypeNames :: String -> (String, String)
1076
paramTypeNames root = ("Filled"  ++ root ++ "Params",
1077
                       "Partial" ++ root ++ "Params")
1078

    
1079
-- | Compute information about the type of a parameter field.
1080
paramFieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
1081
paramFieldTypeInfo field_pfx fd = do
1082
  t <- actualFieldType fd
1083
  let n = mkName . (++ "P") . (field_pfx ++) .
1084
          fieldRecordName $ fd
1085
  return (n, NotStrict, AppT (ConT ''Maybe) t)
1086

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

    
1112
-- | Builds a list of all fields of a parameter.
1113
buildParamAllFields :: String -> [Field] -> [Dec]
1114
buildParamAllFields sname fields =
1115
  let vname = mkName ("all" ++ sname ++ "ParamFields")
1116
      sig = SigD vname (AppT ListT (ConT ''String))
1117
      val = ListE $ map (LitE . StringL . fieldName) fields
1118
  in [sig, ValD (VarP vname) (NormalB val) []]
1119

    
1120
-- | Builds the 'DictObject' instance for a filled parameter.
1121
buildDictObjectInst :: Name -> String -> [Dec]
1122
buildDictObjectInst name sname =
1123
  [InstanceD [] (AppT (ConT ''DictObject) (ConT name))
1124
   [ValD (VarP 'toDict) (NormalB (VarE (toDictName sname))) []]]
1125

    
1126
-- | Generates the serialisation for a partial parameter.
1127
buildPParamSerialisation :: String -> [Field] -> Q [Dec]
1128
buildPParamSerialisation sname fields = do
1129
  let name = mkName sname
1130
  savedecls <- genSaveObject savePParamField sname fields
1131
  (loadsig, loadfn) <- genLoadObject loadPParamField sname fields
1132
  shjson <- objectShowJSON sname
1133
  rdjson <- objectReadJSON sname
1134
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
1135
                 [rdjson, shjson]
1136
  return $ savedecls ++ [loadsig, loadfn, instdecl]
1137

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

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

    
1167
-- | Builds a simple declaration of type @n_x = fromMaybe f_x p_x@.
1168
buildFromMaybe :: String -> Q Dec
1169
buildFromMaybe fname =
1170
  valD (varP (mkName $ "n_" ++ fname))
1171
         (normalB [| $(varE 'fromMaybe)
1172
                        $(varNameE $ "f_" ++ fname)
1173
                        $(varNameE $ "p_" ++ fname) |]) []
1174

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

    
1200
-- * Template code for exceptions
1201

    
1202
-- | Exception simple error message field.
1203
excErrMsg :: (String, Q Type)
1204
excErrMsg = ("errMsg", [t| String |])
1205

    
1206
-- | Builds an exception type definition.
1207
genException :: String                  -- ^ Name of new type
1208
             -> SimpleObject -- ^ Constructor name and parameters
1209
             -> Q [Dec]
1210
genException name cons = do
1211
  let tname = mkName name
1212
  declD <- buildSimpleCons tname cons
1213
  (savesig, savefn) <- genSaveSimpleObj tname ("save" ++ name) cons $
1214
                         uncurry saveExcCons
1215
  (loadsig, loadfn) <- genLoadExc tname ("load" ++ name) cons
1216
  return [declD, loadsig, loadfn, savesig, savefn]
1217

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

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

    
1258
{-| Generates the loadException function.
1259

    
1260
This generates a quite complicated function, along the lines of:
1261

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