Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / THH.hs @ 596d7b4f

History | View | Annotate | Download (46.9 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, 2013, 2014 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.Arrow ((&&&))
72
import Control.Applicative
73
import Control.Monad
74
import Data.Attoparsec () -- Needed to prevent spurious GHC 7.4 linking errors.
75
  -- See issue #683 and https://ghc.haskell.org/trac/ghc/ticket/4899
76
import Data.Char
77
import Data.List
78
import Data.Maybe
79
import qualified Data.Set as Set
80
import Language.Haskell.TH
81

    
82
import qualified Text.JSON as JSON
83
import Text.JSON.Pretty (pp_value)
84

    
85
import Ganeti.JSON
86
import Ganeti.PyValue
87
import Ganeti.THH.PyType
88

    
89

    
90
-- * Exported types
91

    
92
-- | Class of objects that can be converted to 'JSObject'
93
-- lists-format.
94
class DictObject a where
95
  toDict :: a -> [(String, JSON.JSValue)]
96

    
97
-- | Optional field information.
98
data OptionalType
99
  = NotOptional           -- ^ Field is not optional
100
  | OptionalOmitNull      -- ^ Field is optional, null is not serialised
101
  | OptionalSerializeNull -- ^ Field is optional, null is serialised
102
  deriving (Show, Eq)
103

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

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

    
130
withDoc :: String -> Field -> Field
131
withDoc doc field =
132
  field { fieldDoc = doc }
133

    
134
-- | Sets the renamed constructor field.
135
renameField :: String -> Field -> Field
136
renameField constrName field = field { fieldConstr = Just constrName }
137

    
138
-- | Sets the default value on a field (makes it optional with a
139
-- default value).
140
defaultField :: Q Exp -> Field -> Field
141
defaultField defval field = field { fieldDefault = Just defval }
142

    
143
-- | Marks a field optional (turning its base type into a Maybe).
144
optionalField :: Field -> Field
145
optionalField field = field { fieldIsOptional = OptionalOmitNull }
146

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

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

    
161
-- | Sets the read function to also accept string parsable by the given
162
-- function.
163
specialNumericalField :: Name -> Field -> Field
164
specialNumericalField f field =
165
     field { fieldRead = Just (appE (varE 'numericalReadFn) (varE f)) }
166

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

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

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

    
194
-- | Compute the actual field type (taking into account possible
195
-- optional status).
196
actualFieldType :: Field -> Q Type
197
actualFieldType f | fieldIsOptional f /= NotOptional = [t| Maybe $t |]
198
                  | otherwise = t
199
                  where t = fieldType f
200

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

    
214
-- | Construct a function that parses a field value. If the field has
215
-- a custom 'fieldRead', it's applied to @o@ and used. Otherwise
216
-- @JSON.readJSON@ is used.
217
parseFn :: Field   -- ^ The field definition
218
        -> Q Exp   -- ^ The entire object in JSON object format
219
        -> Q Exp   -- ^ The resulting function that parses a JSON message
220
parseFn field o = maybe [| JSON.readJSON |] (`appE` o) (fieldRead field)
221

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

    
232
-- | Just as 'loadFn', but for optional fields.
233
loadFnOpt :: Field   -- ^ The field definition
234
          -> Q Exp   -- ^ The value of the field as existing in the JSON message
235
                     -- as Maybe
236
          -> Q Exp   -- ^ The entire object in JSON object format
237
          -> Q Exp   -- ^ Resulting expression
238
loadFnOpt field@(Field { fieldDefault = Just def }) expr o
239
  = case fieldIsOptional field of
240
      NotOptional -> [| $expr >>= maybe (return $def) $(parseFn field o) |]
241
      _           -> fail $ "Field " ++ fieldName field ++ ":\
242
                            \ A field can't be optional and\
243
                            \ have a default value at the same time."
244
loadFnOpt field expr o
245
  = [| $expr >>= maybe (return Nothing) (liftM Just . $(parseFn field o)) |]
246

    
247
-- * Common field declarations
248

    
249
-- | Timestamp fields description.
250
timeStampFields :: [Field]
251
timeStampFields =
252
    [ defaultField [| 0::Double |] $ simpleField "ctime" [t| Double |]
253
    , defaultField [| 0::Double |] $ simpleField "mtime" [t| Double |]
254
    ]
255

    
256
-- | Serial number fields description.
257
serialFields :: [Field]
258
serialFields =
259
    [ renameField  "Serial" $ simpleField "serial_no" [t| Int |] ]
260

    
261
-- | UUID fields description.
262
uuidFields :: [Field]
263
uuidFields = [ simpleField "uuid" [t| String |] ]
264

    
265
-- | Tag set type alias.
266
type TagSet = Set.Set String
267

    
268
-- | Tag field description.
269
tagsFields :: [Field]
270
tagsFields = [ defaultField [| Set.empty |] $
271
               simpleField "tags" [t| TagSet |] ]
272

    
273
-- * Internal types
274

    
275
-- | A simple field, in constrast to the customisable 'Field' type.
276
type SimpleField = (String, Q Type)
277

    
278
-- | A definition for a single constructor for a simple object.
279
type SimpleConstructor = (String, [SimpleField])
280

    
281
-- | A definition for ADTs with simple fields.
282
type SimpleObject = [SimpleConstructor]
283

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

    
287
-- | A type alias for a Luxi constructor of a regular object.
288
type LuxiConstructor = (String, [Field])
289

    
290
-- * Helper functions
291

    
292
-- | Ensure first letter is lowercase.
293
--
294
-- Used to convert type name to function prefix, e.g. in @data Aa ->
295
-- aaToRaw@.
296
ensureLower :: String -> String
297
ensureLower [] = []
298
ensureLower (x:xs) = toLower x:xs
299

    
300
-- | Ensure first letter is uppercase.
301
--
302
-- Used to convert constructor name to component
303
ensureUpper :: String -> String
304
ensureUpper [] = []
305
ensureUpper (x:xs) = toUpper x:xs
306

    
307
-- | Helper for quoted expressions.
308
varNameE :: String -> Q Exp
309
varNameE = varE . mkName
310

    
311
-- | showJSON as an expression, for reuse.
312
showJSONE :: Q Exp
313
showJSONE = varE 'JSON.showJSON
314

    
315
-- | makeObj as an expression, for reuse.
316
makeObjE :: Q Exp
317
makeObjE = varE 'JSON.makeObj
318

    
319
-- | fromObj (Ganeti specific) as an expression, for reuse.
320
fromObjE :: Q Exp
321
fromObjE = varE 'fromObj
322

    
323
-- | ToRaw function name.
324
toRawName :: String -> Name
325
toRawName = mkName . (++ "ToRaw") . ensureLower
326

    
327
-- | FromRaw function name.
328
fromRawName :: String -> Name
329
fromRawName = mkName . (++ "FromRaw") . ensureLower
330

    
331
-- | Converts a name to it's varE\/litE representations.
332
reprE :: Either String Name -> Q Exp
333
reprE = either stringE varE
334

    
335
-- | Smarter function application.
336
--
337
-- This does simply f x, except that if is 'id', it will skip it, in
338
-- order to generate more readable code when using -ddump-splices.
339
appFn :: Exp -> Exp -> Exp
340
appFn f x | f == VarE 'id = x
341
          | otherwise = AppE f x
342

    
343
-- | Builds a field for a normal constructor.
344
buildConsField :: Q Type -> StrictTypeQ
345
buildConsField ftype = do
346
  ftype' <- ftype
347
  return (NotStrict, ftype')
348

    
349
-- | Builds a constructor based on a simple definition (not field-based).
350
buildSimpleCons :: Name -> SimpleObject -> Q Dec
351
buildSimpleCons tname cons = do
352
  decl_d <- mapM (\(cname, fields) -> do
353
                    fields' <- mapM (buildConsField . snd) fields
354
                    return $ NormalC (mkName cname) fields') cons
355
  return $ DataD [] tname [] decl_d [''Show, ''Eq]
356

    
357
-- | Generate the save function for a given type.
358
genSaveSimpleObj :: Name                            -- ^ Object type
359
                 -> String                          -- ^ Function name
360
                 -> SimpleObject                    -- ^ Object definition
361
                 -> (SimpleConstructor -> Q Clause) -- ^ Constructor save fn
362
                 -> Q (Dec, Dec)
363
genSaveSimpleObj tname sname opdefs fn = do
364
  let sigt = AppT (AppT ArrowT (ConT tname)) (ConT ''JSON.JSValue)
365
      fname = mkName sname
366
  cclauses <- mapM fn opdefs
367
  return $ (SigD fname sigt, FunD fname cclauses)
368

    
369
-- * Template code for simple raw type-equivalent ADTs
370

    
371
-- | Generates a data type declaration.
372
--
373
-- The type will have a fixed list of instances.
374
strADTDecl :: Name -> [String] -> Dec
375
strADTDecl name constructors =
376
  DataD [] name []
377
          (map (flip NormalC [] . mkName) constructors)
378
          [''Show, ''Eq, ''Enum, ''Bounded, ''Ord]
379

    
380
-- | Generates a toRaw function.
381
--
382
-- This generates a simple function of the form:
383
--
384
-- @
385
-- nameToRaw :: Name -> /traw/
386
-- nameToRaw Cons1 = var1
387
-- nameToRaw Cons2 = \"value2\"
388
-- @
389
genToRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
390
genToRaw traw fname tname constructors = do
391
  let sigt = AppT (AppT ArrowT (ConT tname)) (ConT traw)
392
  -- the body clauses, matching on the constructor and returning the
393
  -- raw value
394
  clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
395
                             (normalB (reprE v)) []) constructors
396
  return [SigD fname sigt, FunD fname clauses]
397

    
398
-- | Generates a fromRaw function.
399
--
400
-- The function generated is monadic and can fail parsing the
401
-- raw value. It is of the form:
402
--
403
-- @
404
-- nameFromRaw :: (Monad m) => /traw/ -> m Name
405
-- nameFromRaw s | s == var1       = Cons1
406
--               | s == \"value2\" = Cons2
407
--               | otherwise = fail /.../
408
-- @
409
genFromRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
410
genFromRaw traw fname tname constructors = do
411
  -- signature of form (Monad m) => String -> m $name
412
  sigt <- [t| (Monad m) => $(conT traw) -> m $(conT tname) |]
413
  -- clauses for a guarded pattern
414
  let varp = mkName "s"
415
      varpe = varE varp
416
  clauses <- mapM (\(c, v) -> do
417
                     -- the clause match condition
418
                     g <- normalG [| $varpe == $(reprE v) |]
419
                     -- the clause result
420
                     r <- [| return $(conE (mkName c)) |]
421
                     return (g, r)) constructors
422
  -- the otherwise clause (fallback)
423
  oth_clause <- do
424
    g <- normalG [| otherwise |]
425
    r <- [|fail ("Invalid string value for type " ++
426
                 $(litE (stringL (nameBase tname))) ++ ": " ++ show $varpe) |]
427
    return (g, r)
428
  let fun = FunD fname [Clause [VarP varp]
429
                        (GuardedB (clauses++[oth_clause])) []]
430
  return [SigD fname sigt, fun]
431

    
432
-- | Generates a data type from a given raw format.
433
--
434
-- The format is expected to multiline. The first line contains the
435
-- type name, and the rest of the lines must contain two words: the
436
-- constructor name and then the string representation of the
437
-- respective constructor.
438
--
439
-- The function will generate the data type declaration, and then two
440
-- functions:
441
--
442
-- * /name/ToRaw, which converts the type to a raw type
443
--
444
-- * /name/FromRaw, which (monadically) converts from a raw type to the type
445
--
446
-- Note that this is basically just a custom show\/read instance,
447
-- nothing else.
448
declareADT
449
  :: (a -> Either String Name) -> Name -> String -> [(String, a)] -> Q [Dec]
450
declareADT fn traw sname cons = do
451
  let name = mkName sname
452
      ddecl = strADTDecl name (map fst cons)
453
      -- process cons in the format expected by genToRaw
454
      cons' = map (\(a, b) -> (a, fn b)) cons
455
  toraw <- genToRaw traw (toRawName sname) name cons'
456
  fromraw <- genFromRaw traw (fromRawName sname) name cons'
457
  return $ ddecl:toraw ++ fromraw
458

    
459
declareLADT :: Name -> String -> [(String, String)] -> Q [Dec]
460
declareLADT = declareADT Left
461

    
462
declareILADT :: String -> [(String, Int)] -> Q [Dec]
463
declareILADT sname cons = do
464
  consNames <- sequence [ newName ('_':n) | (n, _) <- cons ]
465
  consFns <- concat <$> sequence
466
             [ do sig <- sigD n [t| Int |]
467
                  let expr = litE (IntegerL (toInteger i))
468
                  fn <- funD n [clause [] (normalB expr) []]
469
                  return [sig, fn]
470
             | n <- consNames
471
             | (_, i) <- cons ]
472
  let cons' = [ (n, n') | (n, _) <- cons | n' <- consNames ]
473
  (consFns ++) <$> declareADT Right ''Int sname cons'
474

    
475
declareIADT :: String -> [(String, Name)] -> Q [Dec]
476
declareIADT = declareADT Right ''Int
477

    
478
declareSADT :: String -> [(String, Name)] -> Q [Dec]
479
declareSADT = declareADT Right ''String
480

    
481
-- | Creates the showJSON member of a JSON instance declaration.
482
--
483
-- This will create what is the equivalent of:
484
--
485
-- @
486
-- showJSON = showJSON . /name/ToRaw
487
-- @
488
--
489
-- in an instance JSON /name/ declaration
490
genShowJSON :: String -> Q Dec
491
genShowJSON name = do
492
  body <- [| JSON.showJSON . $(varE (toRawName name)) |]
493
  return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
494

    
495
-- | Creates the readJSON member of a JSON instance declaration.
496
--
497
-- This will create what is the equivalent of:
498
--
499
-- @
500
-- readJSON s = case readJSON s of
501
--                Ok s' -> /name/FromRaw s'
502
--                Error e -> Error /description/
503
-- @
504
--
505
-- in an instance JSON /name/ declaration
506
genReadJSON :: String -> Q Dec
507
genReadJSON name = do
508
  let s = mkName "s"
509
  body <- [| case JSON.readJSON $(varE s) of
510
               JSON.Ok s' -> $(varE (fromRawName name)) s'
511
               JSON.Error e ->
512
                   JSON.Error $ "Can't parse raw value for type " ++
513
                           $(stringE name) ++ ": " ++ e ++ " from " ++
514
                           show $(varE s)
515
           |]
516
  return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
517

    
518
-- | Generates a JSON instance for a given type.
519
--
520
-- This assumes that the /name/ToRaw and /name/FromRaw functions
521
-- have been defined as by the 'declareSADT' function.
522
makeJSONInstance :: Name -> Q [Dec]
523
makeJSONInstance name = do
524
  let base = nameBase name
525
  showJ <- genShowJSON base
526
  readJ <- genReadJSON base
527
  return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) [readJ,showJ]]
528

    
529
-- * Template code for opcodes
530

    
531
-- | Transforms a CamelCase string into an_underscore_based_one.
532
deCamelCase :: String -> String
533
deCamelCase =
534
    intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
535

    
536
-- | Transform an underscore_name into a CamelCase one.
537
camelCase :: String -> String
538
camelCase = concatMap (ensureUpper . drop 1) .
539
            groupBy (\_ b -> b /= '_' && b /= '-') . ('_':)
540

    
541
-- | Computes the name of a given constructor.
542
constructorName :: Con -> Q Name
543
constructorName (NormalC name _) = return name
544
constructorName (RecC name _)    = return name
545
constructorName x                = fail $ "Unhandled constructor " ++ show x
546

    
547
-- | Extract all constructor names from a given type.
548
reifyConsNames :: Name -> Q [String]
549
reifyConsNames name = do
550
  reify_result <- reify name
551
  case reify_result of
552
    TyConI (DataD _ _ _ cons _) -> mapM (liftM nameBase . constructorName) cons
553
    o -> fail $ "Unhandled name passed to reifyConsNames, expected\
554
                \ type constructor but got '" ++ show o ++ "'"
555

    
556
-- | Builds the generic constructor-to-string function.
557
--
558
-- This generates a simple function of the following form:
559
--
560
-- @
561
-- fname (ConStructorOne {}) = trans_fun("ConStructorOne")
562
-- fname (ConStructorTwo {}) = trans_fun("ConStructorTwo")
563
-- @
564
--
565
-- This builds a custom list of name\/string pairs and then uses
566
-- 'genToRaw' to actually generate the function.
567
genConstrToStr :: (String -> String) -> Name -> String -> Q [Dec]
568
genConstrToStr trans_fun name fname = do
569
  cnames <- reifyConsNames name
570
  let svalues = map (Left . trans_fun) cnames
571
  genToRaw ''String (mkName fname) name $ zip cnames svalues
572

    
573
-- | Constructor-to-string for OpCode.
574
genOpID :: Name -> String -> Q [Dec]
575
genOpID = genConstrToStr deCamelCase
576

    
577
-- | Builds a list with all defined constructor names for a type.
578
--
579
-- @
580
-- vstr :: String
581
-- vstr = [...]
582
-- @
583
--
584
-- Where the actual values of the string are the constructor names
585
-- mapped via @trans_fun@.
586
genAllConstr :: (String -> String) -> Name -> String -> Q [Dec]
587
genAllConstr trans_fun name vstr = do
588
  cnames <- reifyConsNames name
589
  let svalues = sort $ map trans_fun cnames
590
      vname = mkName vstr
591
      sig = SigD vname (AppT ListT (ConT ''String))
592
      body = NormalB (ListE (map (LitE . StringL) svalues))
593
  return $ [sig, ValD (VarP vname) body []]
594

    
595
-- | Generates a list of all defined opcode IDs.
596
genAllOpIDs :: Name -> String -> Q [Dec]
597
genAllOpIDs = genAllConstr deCamelCase
598

    
599
-- | OpCode parameter (field) type.
600
type OpParam = (String, Q Type, Q Exp)
601

    
602
-- * Python code generation
603

    
604
data OpCodeField = OpCodeField { ocfName :: String
605
                               , ocfType :: PyType
606
                               , ocfDefl :: Maybe PyValueEx
607
                               , ocfDoc  :: String
608
                               }
609

    
610
-- | Transfers opcode data between the opcode description (through
611
-- @genOpCode@) and the Python code generation functions.
612
data OpCodeDescriptor = OpCodeDescriptor { ocdName   :: String
613
                                         , ocdType   :: PyType
614
                                         , ocdDoc    :: String
615
                                         , ocdFields :: [OpCodeField]
616
                                         , ocdDescr  :: String
617
                                         }
618

    
619
-- | Optionally encapsulates default values in @PyValueEx@.
620
--
621
-- @maybeApp exp typ@ returns a quoted expression that encapsulates
622
-- the default value @exp@ of an opcode parameter cast to @typ@ in a
623
-- @PyValueEx@, if @exp@ is @Just@.  Otherwise, it returns a quoted
624
-- expression with @Nothing@.
625
maybeApp :: Maybe (Q Exp) -> Q Type -> Q Exp
626
maybeApp Nothing _ =
627
  [| Nothing |]
628

    
629
maybeApp (Just expr) typ =
630
  [| Just ($(conE (mkName "PyValueEx")) ($expr :: $typ)) |]
631

    
632
-- | Generates a Python type according to whether the field is
633
-- optional.
634
--
635
-- The type of created expression is PyType.
636
genPyType' :: OptionalType -> Q Type -> Q PyType
637
genPyType' opt typ = typ >>= pyOptionalType (opt /= NotOptional)
638

    
639
-- | Generates Python types from opcode parameters.
640
genPyType :: Field -> Q PyType
641
genPyType f = genPyType' (fieldIsOptional f) (fieldType f)
642

    
643
-- | Generates Python default values from opcode parameters.
644
genPyDefault :: Field -> Q Exp
645
genPyDefault f = maybeApp (fieldDefault f) (fieldType f)
646

    
647
pyField :: Field -> Q Exp
648
pyField f = genPyType f >>= \t ->
649
            [| OpCodeField $(stringE (fieldName f))
650
                           t
651
                           $(genPyDefault f)
652
                           $(stringE (fieldDoc f)) |]
653

    
654
-- | Generates a Haskell function call to "showPyClass" with the
655
-- necessary information on how to build the Python class string.
656
pyClass :: OpCodeConstructor -> Q Exp
657
pyClass (consName, consType, consDoc, consFields, consDscField) =
658
  do let pyClassVar = varNameE "showPyClass"
659
         consName' = stringE consName
660
     consType' <- genPyType' NotOptional consType
661
     let consDoc' = stringE consDoc
662
     [| OpCodeDescriptor $consName'
663
                         consType'
664
                         $consDoc'
665
                         $(listE $ map pyField consFields)
666
                         consDscField |]
667

    
668
-- | Generates a function called "pyClasses" that holds the list of
669
-- all the opcode descriptors necessary for generating the Python
670
-- opcodes.
671
pyClasses :: [OpCodeConstructor] -> Q [Dec]
672
pyClasses cons =
673
  do let name = mkName "pyClasses"
674
         sig = SigD name (AppT ListT (ConT ''OpCodeDescriptor))
675
     fn <- FunD name <$> (:[]) <$> declClause cons
676
     return [sig, fn]
677
  where declClause c =
678
          clause [] (normalB (ListE <$> mapM pyClass c)) []
679

    
680
-- | Converts from an opcode constructor to a Luxi constructor.
681
opcodeConsToLuxiCons :: (a, b, c, d, e) -> (a, d)
682
opcodeConsToLuxiCons (x, _, _, y, _) = (x, y)
683

    
684
-- | Generates the OpCode data type.
685
--
686
-- This takes an opcode logical definition, and builds both the
687
-- datatype and the JSON serialisation out of it. We can't use a
688
-- generic serialisation since we need to be compatible with Ganeti's
689
-- own, so we have a few quirks to work around.
690
genOpCode :: String              -- ^ Type name to use
691
          -> [OpCodeConstructor] -- ^ Constructor name and parameters
692
          -> Q [Dec]
693
genOpCode name cons = do
694
  let tname = mkName name
695
  decl_d <- mapM (\(cname, _, _, fields, _) -> do
696
                    -- we only need the type of the field, without Q
697
                    fields' <- mapM (fieldTypeInfo "op") fields
698
                    return $ RecC (mkName cname) fields')
699
            cons
700
  let declD = DataD [] tname [] decl_d [''Show, ''Eq]
701
  let (allfsig, allffn) = genAllOpFields "allOpFields" cons
702
  save_decs <- genSaveOpCode tname "saveOpCode" "toDictOpCode"
703
               (map opcodeConsToLuxiCons cons) saveConstructor True
704
  (loadsig, loadfn) <- genLoadOpCode cons
705
  pyDecls <- pyClasses cons
706
  return $ [declD, allfsig, allffn, loadsig, loadfn] ++ save_decs ++ pyDecls
707

    
708
-- | Generates the function pattern returning the list of fields for a
709
-- given constructor.
710
genOpConsFields :: OpCodeConstructor -> Clause
711
genOpConsFields (cname, _, _, fields, _) =
712
  let op_id = deCamelCase cname
713
      fvals = map (LitE . StringL) . sort . nub $
714
              concatMap (\f -> fieldName f:fieldExtraKeys f) fields
715
  in Clause [LitP (StringL op_id)] (NormalB $ ListE fvals) []
716

    
717
-- | Generates a list of all fields of an opcode constructor.
718
genAllOpFields  :: String              -- ^ Function name
719
                -> [OpCodeConstructor] -- ^ Object definition
720
                -> (Dec, Dec)
721
genAllOpFields sname opdefs =
722
  let cclauses = map genOpConsFields opdefs
723
      other = Clause [WildP] (NormalB (ListE [])) []
724
      fname = mkName sname
725
      sigt = AppT  (AppT ArrowT (ConT ''String)) (AppT ListT (ConT ''String))
726
  in (SigD fname sigt, FunD fname (cclauses++[other]))
727

    
728
-- | Generates the \"save\" clause for an entire opcode constructor.
729
--
730
-- This matches the opcode with variables named the same as the
731
-- constructor fields (just so that the spliced in code looks nicer),
732
-- and passes those name plus the parameter definition to 'saveObjectField'.
733
saveConstructor :: LuxiConstructor -- ^ The constructor
734
                -> Q Clause        -- ^ Resulting clause
735
saveConstructor (sname, fields) = do
736
  let cname = mkName sname
737
  fnames <- mapM (newName . fieldVariable) fields
738
  let pat = conP cname (map varP fnames)
739
  let felems = map (uncurry saveObjectField) (zip fnames fields)
740
      -- now build the OP_ID serialisation
741
      opid = [| [( $(stringE "OP_ID"),
742
                   JSON.showJSON $(stringE . deCamelCase $ sname) )] |]
743
      flist = listE (opid:felems)
744
      -- and finally convert all this to a json object
745
      flist' = [| concat $flist |]
746
  clause [pat] (normalB flist') []
747

    
748
-- | Generates the main save opcode function.
749
--
750
-- This builds a per-constructor match clause that contains the
751
-- respective constructor-serialisation code.
752
genSaveOpCode :: Name                          -- ^ Object ype
753
              -> String                        -- ^ To 'JSValue' function name
754
              -> String                        -- ^ To 'JSObject' function name
755
              -> [LuxiConstructor]             -- ^ Object definition
756
              -> (LuxiConstructor -> Q Clause) -- ^ Constructor save fn
757
              -> Bool                          -- ^ Whether to generate
758
                                               -- obj or just a
759
                                               -- list\/tuple of values
760
              -> Q [Dec]
761
genSaveOpCode tname jvalstr tdstr opdefs fn gen_object = do
762
  tdclauses <- mapM fn opdefs
763
  let typecon = ConT tname
764
      jvalname = mkName jvalstr
765
      jvalsig = AppT  (AppT ArrowT typecon) (ConT ''JSON.JSValue)
766
      tdname = mkName tdstr
767
  tdsig <- [t| $(return typecon) -> [(String, JSON.JSValue)] |]
768
  jvalclause <- if gen_object
769
                  then [| $makeObjE . $(varE tdname) |]
770
                  else [| JSON.showJSON . map snd . $(varE tdname) |]
771
  return [ SigD tdname tdsig
772
         , FunD tdname tdclauses
773
         , SigD jvalname jvalsig
774
         , ValD (VarP jvalname) (NormalB jvalclause) []]
775

    
776
-- | Generates load code for a single constructor of the opcode data type.
777
loadConstructor :: OpCodeConstructor -> Q Exp
778
loadConstructor (sname, _, _, fields, _) = do
779
  let name = mkName sname
780
  fbinds <- mapM loadObjectField fields
781
  let (fnames, fstmts) = unzip fbinds
782
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
783
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
784
  return $ DoE fstmts'
785

    
786
-- | Generates the loadOpCode function.
787
genLoadOpCode :: [OpCodeConstructor] -> Q (Dec, Dec)
788
genLoadOpCode opdefs = do
789
  let fname = mkName "loadOpCode"
790
      arg1 = mkName "v"
791
      objname = mkName "o"
792
      opid = mkName "op_id"
793
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
794
                                 (JSON.readJSON $(varE arg1)) |]
795
  st2 <- bindS (varP opid) [| $fromObjE $(varE objname) $(stringE "OP_ID") |]
796
  -- the match results (per-constructor blocks)
797
  mexps <- mapM loadConstructor opdefs
798
  fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
799
  let mpats = map (\(me, (consName, _, _, _, _)) ->
800
                       let mp = LitP . StringL . deCamelCase $ consName
801
                       in Match mp (NormalB me) []
802
                  ) $ zip mexps opdefs
803
      defmatch = Match WildP (NormalB fails) []
804
      cst = NoBindS $ CaseE (VarE opid) $ mpats++[defmatch]
805
      body = DoE [st1, st2, cst]
806
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
807
  return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
808

    
809
-- * Template code for luxi
810

    
811
-- | Constructor-to-string for LuxiOp.
812
genStrOfOp :: Name -> String -> Q [Dec]
813
genStrOfOp = genConstrToStr id
814

    
815
-- | Constructor-to-string for MsgKeys.
816
genStrOfKey :: Name -> String -> Q [Dec]
817
genStrOfKey = genConstrToStr ensureLower
818

    
819
-- | Generates the LuxiOp data type.
820
--
821
-- This takes a Luxi operation definition and builds both the
822
-- datatype and the function transforming the arguments to JSON.
823
-- We can't use anything less generic, because the way different
824
-- operations are serialized differs on both parameter- and top-level.
825
--
826
-- There are two things to be defined for each parameter:
827
--
828
-- * name
829
--
830
-- * type
831
--
832
genLuxiOp :: String -> [LuxiConstructor] -> Q [Dec]
833
genLuxiOp name cons = do
834
  let tname = mkName name
835
  decl_d <- mapM (\(cname, fields) -> do
836
                    -- we only need the type of the field, without Q
837
                    fields' <- mapM actualFieldType fields
838
                    let fields'' = zip (repeat NotStrict) fields'
839
                    return $ NormalC (mkName cname) fields'')
840
            cons
841
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Eq]
842
  save_decs <- genSaveOpCode tname "opToArgs" "opToDict"
843
               cons saveLuxiConstructor False
844
  req_defs <- declareSADT "LuxiReq" .
845
              map (\(str, _) -> ("Req" ++ str, mkName ("luxiReq" ++ str))) $
846
                  cons
847
  return $ declD:save_decs ++ req_defs
848

    
849
-- | Generates the \"save\" clause for entire LuxiOp constructor.
850
saveLuxiConstructor :: LuxiConstructor -> Q Clause
851
saveLuxiConstructor (sname, fields) = do
852
  let cname = mkName sname
853
  fnames <- mapM (newName . fieldVariable) fields
854
  let pat = conP cname (map varP fnames)
855
  let felems = map (uncurry saveObjectField) (zip fnames fields)
856
      flist = [| concat $(listE felems) |]
857
  clause [pat] (normalB flist) []
858

    
859
-- * "Objects" functionality
860

    
861
-- | Extract the field's declaration from a Field structure.
862
fieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
863
fieldTypeInfo field_pfx fd = do
864
  t <- actualFieldType fd
865
  let n = mkName . (field_pfx ++) . fieldRecordName $ fd
866
  return (n, NotStrict, t)
867

    
868
-- | Build an object declaration.
869
buildObject :: String -> String -> [Field] -> Q [Dec]
870
buildObject sname field_pfx fields = do
871
  let name = mkName sname
872
  fields_d <- mapM (fieldTypeInfo field_pfx) fields
873
  let decl_d = RecC name fields_d
874
  let declD = DataD [] name [] [decl_d] [''Show, ''Eq]
875
  ser_decls <- buildObjectSerialisation sname fields
876
  return $ declD:ser_decls
877

    
878
-- | Generates an object definition: data type and its JSON instance.
879
buildObjectSerialisation :: String -> [Field] -> Q [Dec]
880
buildObjectSerialisation sname fields = do
881
  let name = mkName sname
882
  savedecls <- genSaveObject saveObjectField sname fields
883
  (loadsig, loadfn) <- genLoadObject loadObjectField sname fields
884
  shjson <- objectShowJSON sname
885
  rdjson <- objectReadJSON sname
886
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
887
                 [rdjson, shjson]
888
  return $ savedecls ++ [loadsig, loadfn, instdecl]
889

    
890
-- | The toDict function name for a given type.
891
toDictName :: String -> Name
892
toDictName sname = mkName ("toDict" ++ sname)
893

    
894
-- | Generates the save object functionality.
895
genSaveObject :: (Name -> Field -> Q Exp)
896
              -> String -> [Field] -> Q [Dec]
897
genSaveObject save_fn sname fields = do
898
  let name = mkName sname
899
  fnames <- mapM (newName . fieldVariable) fields
900
  let pat = conP name (map varP fnames)
901
  let tdname = toDictName sname
902
  tdsigt <- [t| $(conT name) -> [(String, JSON.JSValue)] |]
903

    
904
  let felems = map (uncurry save_fn) (zip fnames fields)
905
      flist = listE felems
906
      -- and finally convert all this to a json object
907
      tdlist = [| concat $flist |]
908
      iname = mkName "i"
909
  tclause <- clause [pat] (normalB tdlist) []
910
  cclause <- [| $makeObjE . $(varE tdname) |]
911
  let fname = mkName ("save" ++ sname)
912
  sigt <- [t| $(conT name) -> JSON.JSValue |]
913
  return [SigD tdname tdsigt, FunD tdname [tclause],
914
          SigD fname sigt, ValD (VarP fname) (NormalB cclause) []]
915

    
916
-- | Generates the code for saving an object's field, handling the
917
-- various types of fields that we have.
918
saveObjectField :: Name -> Field -> Q Exp
919
saveObjectField fvar field =
920
  let formatFn = fromMaybe [| JSON.showJSON &&& (const []) |] $
921
                           fieldShow field
922
      formatCode v = [| let (actual, extra) = $formatFn $(v)
923
                         in ($nameE, actual) : extra |]
924
  in case fieldIsOptional field of
925
    OptionalOmitNull ->       [| case $(fvarE) of
926
                                   Nothing -> []
927
                                   Just v  -> $(formatCode [| v |])
928
                              |]
929
    OptionalSerializeNull ->  [| case $(fvarE) of
930
                                   Nothing -> [( $nameE, JSON.JSNull )]
931
                                   Just v  -> $(formatCode [| v |])
932
                              |]
933
    NotOptional ->            formatCode fvarE
934
  where nameE = stringE (fieldName field)
935
        fvarE = varE fvar
936

    
937
-- | Generates the showJSON clause for a given object name.
938
objectShowJSON :: String -> Q Dec
939
objectShowJSON name = do
940
  body <- [| JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
941
  return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
942

    
943
-- | Generates the load object functionality.
944
genLoadObject :: (Field -> Q (Name, Stmt))
945
              -> String -> [Field] -> Q (Dec, Dec)
946
genLoadObject load_fn sname fields = do
947
  let name = mkName sname
948
      funname = mkName $ "load" ++ sname
949
      arg1 = mkName $ if null fields then "_" else "v"
950
      objname = mkName "o"
951
      opid = mkName "op_id"
952
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
953
                                 (JSON.readJSON $(varE arg1)) |]
954
  fbinds <- mapM load_fn fields
955
  let (fnames, fstmts) = unzip fbinds
956
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
957
      retstmt = [NoBindS (AppE (VarE 'return) cval)]
958
      -- FIXME: should we require an empty dict for an empty type?
959
      -- this allows any JSValue right now
960
      fstmts' = if null fields
961
                  then retstmt
962
                  else st1:fstmts ++ retstmt
963
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT name) |]
964
  return $ (SigD funname sigt,
965
            FunD funname [Clause [VarP arg1] (NormalB (DoE fstmts')) []])
966

    
967
-- | Generates code for loading an object's field.
968
loadObjectField :: Field -> Q (Name, Stmt)
969
loadObjectField field = do
970
  let name = fieldVariable field
971
  fvar <- newName name
972
  -- these are used in all patterns below
973
  let objvar = varNameE "o"
974
      objfield = stringE (fieldName field)
975
  bexp <- case fieldDefault field of
976
            -- Only non-optional fields without defaults must have a value;
977
            -- we treat both optional types the same, since
978
            -- 'maybeFromObj' can deal with both missing and null values
979
            -- appropriately (the same)
980
            Nothing | fieldIsOptional field == NotOptional ->
981
                 loadFn field [| fromObj $objvar $objfield |] objvar
982
            _ -> loadFnOpt field [| maybeFromObj $objvar $objfield |] objvar
983

    
984
  return (fvar, BindS (VarP fvar) bexp)
985

    
986
-- | Builds the readJSON instance for a given object name.
987
objectReadJSON :: String -> Q Dec
988
objectReadJSON name = do
989
  let s = mkName "s"
990
  body <- [| case JSON.readJSON $(varE s) of
991
               JSON.Ok s' -> $(varE .mkName $ "load" ++ name) s'
992
               JSON.Error e ->
993
                 JSON.Error $ "Can't parse value for type " ++
994
                       $(stringE name) ++ ": " ++ e
995
           |]
996
  return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
997

    
998
-- * Inheritable parameter tables implementation
999

    
1000
-- | Compute parameter type names.
1001
paramTypeNames :: String -> (String, String)
1002
paramTypeNames root = ("Filled"  ++ root ++ "Params",
1003
                       "Partial" ++ root ++ "Params")
1004

    
1005
-- | Compute information about the type of a parameter field.
1006
paramFieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
1007
paramFieldTypeInfo field_pfx fd = do
1008
  t <- actualFieldType fd
1009
  let n = mkName . (++ "P") . (field_pfx ++) .
1010
          fieldRecordName $ fd
1011
  return (n, NotStrict, AppT (ConT ''Maybe) t)
1012

    
1013
-- | Build a parameter declaration.
1014
--
1015
-- This function builds two different data structures: a /filled/ one,
1016
-- in which all fields are required, and a /partial/ one, in which all
1017
-- fields are optional. Due to the current record syntax issues, the
1018
-- fields need to be named differrently for the two structures, so the
1019
-- partial ones get a /P/ suffix.
1020
buildParam :: String -> String -> [Field] -> Q [Dec]
1021
buildParam sname field_pfx fields = do
1022
  let (sname_f, sname_p) = paramTypeNames sname
1023
      name_f = mkName sname_f
1024
      name_p = mkName sname_p
1025
  fields_f <- mapM (fieldTypeInfo field_pfx) fields
1026
  fields_p <- mapM (paramFieldTypeInfo field_pfx) fields
1027
  let decl_f = RecC name_f fields_f
1028
      decl_p = RecC name_p fields_p
1029
  let declF = DataD [] name_f [] [decl_f] [''Show, ''Eq]
1030
      declP = DataD [] name_p [] [decl_p] [''Show, ''Eq]
1031
  ser_decls_f <- buildObjectSerialisation sname_f fields
1032
  ser_decls_p <- buildPParamSerialisation sname_p fields
1033
  fill_decls <- fillParam sname field_pfx fields
1034
  return $ [declF, declP] ++ ser_decls_f ++ ser_decls_p ++ fill_decls ++
1035
           buildParamAllFields sname fields ++
1036
           buildDictObjectInst name_f sname_f
1037

    
1038
-- | Builds a list of all fields of a parameter.
1039
buildParamAllFields :: String -> [Field] -> [Dec]
1040
buildParamAllFields sname fields =
1041
  let vname = mkName ("all" ++ sname ++ "ParamFields")
1042
      sig = SigD vname (AppT ListT (ConT ''String))
1043
      val = ListE $ map (LitE . StringL . fieldName) fields
1044
  in [sig, ValD (VarP vname) (NormalB val) []]
1045

    
1046
-- | Builds the 'DictObject' instance for a filled parameter.
1047
buildDictObjectInst :: Name -> String -> [Dec]
1048
buildDictObjectInst name sname =
1049
  [InstanceD [] (AppT (ConT ''DictObject) (ConT name))
1050
   [ValD (VarP 'toDict) (NormalB (VarE (toDictName sname))) []]]
1051

    
1052
-- | Generates the serialisation for a partial parameter.
1053
buildPParamSerialisation :: String -> [Field] -> Q [Dec]
1054
buildPParamSerialisation sname fields = do
1055
  let name = mkName sname
1056
  savedecls <- genSaveObject savePParamField sname fields
1057
  (loadsig, loadfn) <- genLoadObject loadPParamField sname fields
1058
  shjson <- objectShowJSON sname
1059
  rdjson <- objectReadJSON sname
1060
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
1061
                 [rdjson, shjson]
1062
  return $ savedecls ++ [loadsig, loadfn, instdecl]
1063

    
1064
-- | Generates code to save an optional parameter field.
1065
savePParamField :: Name -> Field -> Q Exp
1066
savePParamField fvar field = do
1067
  checkNonOptDef field
1068
  let actualVal = mkName "v"
1069
  normalexpr <- saveObjectField actualVal field
1070
  -- we have to construct the block here manually, because we can't
1071
  -- splice-in-splice
1072
  return $ CaseE (VarE fvar) [ Match (ConP 'Nothing [])
1073
                                       (NormalB (ConE '[])) []
1074
                             , Match (ConP 'Just [VarP actualVal])
1075
                                       (NormalB normalexpr) []
1076
                             ]
1077

    
1078
-- | Generates code to load an optional parameter field.
1079
loadPParamField :: Field -> Q (Name, Stmt)
1080
loadPParamField field = do
1081
  checkNonOptDef field
1082
  let name = fieldName field
1083
  fvar <- newName name
1084
  -- these are used in all patterns below
1085
  let objvar = varNameE "o"
1086
      objfield = stringE name
1087
      loadexp = [| $(varE 'maybeFromObj) $objvar $objfield |]
1088
  bexp <- loadFnOpt field loadexp objvar
1089
  return (fvar, BindS (VarP fvar) bexp)
1090

    
1091
-- | Builds a simple declaration of type @n_x = fromMaybe f_x p_x@.
1092
buildFromMaybe :: String -> Q Dec
1093
buildFromMaybe fname =
1094
  valD (varP (mkName $ "n_" ++ fname))
1095
         (normalB [| $(varE 'fromMaybe)
1096
                        $(varNameE $ "f_" ++ fname)
1097
                        $(varNameE $ "p_" ++ fname) |]) []
1098

    
1099
-- | Builds a function that executes the filling of partial parameter
1100
-- from a full copy (similar to Python's fillDict).
1101
fillParam :: String -> String -> [Field] -> Q [Dec]
1102
fillParam sname field_pfx fields = do
1103
  let fnames = map (\fd -> field_pfx ++ fieldRecordName fd) fields
1104
      (sname_f, sname_p) = paramTypeNames sname
1105
      oname_f = "fobj"
1106
      oname_p = "pobj"
1107
      name_f = mkName sname_f
1108
      name_p = mkName sname_p
1109
      fun_name = mkName $ "fill" ++ sname ++ "Params"
1110
      le_full = ValD (ConP name_f (map (VarP . mkName . ("f_" ++)) fnames))
1111
                (NormalB . VarE . mkName $ oname_f) []
1112
      le_part = ValD (ConP name_p (map (VarP . mkName . ("p_" ++)) fnames))
1113
                (NormalB . VarE . mkName $ oname_p) []
1114
      obj_new = foldl (\accu vname -> AppE accu (VarE vname)) (ConE name_f)
1115
                $ map (mkName . ("n_" ++)) fnames
1116
  le_new <- mapM buildFromMaybe fnames
1117
  funt <- [t| $(conT name_f) -> $(conT name_p) -> $(conT name_f) |]
1118
  let sig = SigD fun_name funt
1119
      fclause = Clause [VarP (mkName oname_f), VarP (mkName oname_p)]
1120
                (NormalB $ LetE (le_full:le_part:le_new) obj_new) []
1121
      fun = FunD fun_name [fclause]
1122
  return [sig, fun]
1123

    
1124
-- * Template code for exceptions
1125

    
1126
-- | Exception simple error message field.
1127
excErrMsg :: (String, Q Type)
1128
excErrMsg = ("errMsg", [t| String |])
1129

    
1130
-- | Builds an exception type definition.
1131
genException :: String                  -- ^ Name of new type
1132
             -> SimpleObject -- ^ Constructor name and parameters
1133
             -> Q [Dec]
1134
genException name cons = do
1135
  let tname = mkName name
1136
  declD <- buildSimpleCons tname cons
1137
  (savesig, savefn) <- genSaveSimpleObj tname ("save" ++ name) cons $
1138
                         uncurry saveExcCons
1139
  (loadsig, loadfn) <- genLoadExc tname ("load" ++ name) cons
1140
  return [declD, loadsig, loadfn, savesig, savefn]
1141

    
1142
-- | Generates the \"save\" clause for an entire exception constructor.
1143
--
1144
-- This matches the exception with variables named the same as the
1145
-- constructor fields (just so that the spliced in code looks nicer),
1146
-- and calls showJSON on it.
1147
saveExcCons :: String        -- ^ The constructor name
1148
            -> [SimpleField] -- ^ The parameter definitions for this
1149
                             -- constructor
1150
            -> Q Clause      -- ^ Resulting clause
1151
saveExcCons sname fields = do
1152
  let cname = mkName sname
1153
  fnames <- mapM (newName . fst) fields
1154
  let pat = conP cname (map varP fnames)
1155
      felems = if null fnames
1156
                 then conE '() -- otherwise, empty list has no type
1157
                 else listE $ map (\f -> [| JSON.showJSON $(varE f) |]) fnames
1158
  let tup = tupE [ litE (stringL sname), felems ]
1159
  clause [pat] (normalB [| JSON.showJSON $tup |]) []
1160

    
1161
-- | Generates load code for a single constructor of an exception.
1162
--
1163
-- Generates the code (if there's only one argument, we will use a
1164
-- list, not a tuple:
1165
--
1166
-- @
1167
-- do
1168
--  (x1, x2, ...) <- readJSON args
1169
--  return $ Cons x1 x2 ...
1170
-- @
1171
loadExcConstructor :: Name -> String -> [SimpleField] -> Q Exp
1172
loadExcConstructor inname sname fields = do
1173
  let name = mkName sname
1174
  f_names <- mapM (newName . fst) fields
1175
  let read_args = AppE (VarE 'JSON.readJSON) (VarE inname)
1176
  let binds = case f_names of
1177
                [x] -> BindS (ListP [VarP x])
1178
                _   -> BindS (TupP (map VarP f_names))
1179
      cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) f_names
1180
  return $ DoE [binds read_args, NoBindS (AppE (VarE 'return) cval)]
1181

    
1182
{-| Generates the loadException function.
1183

    
1184
This generates a quite complicated function, along the lines of:
1185

    
1186
@
1187
loadFn (JSArray [JSString name, args]) = case name of
1188
   "A1" -> do
1189
     (x1, x2, ...) <- readJSON args
1190
     return $ A1 x1 x2 ...
1191
   "a2" -> ...
1192
   s -> fail $ "Unknown exception" ++ s
1193
loadFn v = fail $ "Expected array but got " ++ show v
1194
@
1195
-}
1196
genLoadExc :: Name -> String -> SimpleObject -> Q (Dec, Dec)
1197
genLoadExc tname sname opdefs = do
1198
  let fname = mkName sname
1199
  exc_name <- newName "name"
1200
  exc_args <- newName "args"
1201
  exc_else <- newName "s"
1202
  arg_else <- newName "v"
1203
  fails <- [| fail $ "Unknown exception '" ++ $(varE exc_else) ++ "'" |]
1204
  -- default match for unknown exception name
1205
  let defmatch = Match (VarP exc_else) (NormalB fails) []
1206
  -- the match results (per-constructor blocks)
1207
  str_matches <-
1208
    mapM (\(s, params) -> do
1209
            body_exp <- loadExcConstructor exc_args s params
1210
            return $ Match (LitP (StringL s)) (NormalB body_exp) [])
1211
    opdefs
1212
  -- the first function clause; we can't use [| |] due to TH
1213
  -- limitations, so we have to build the AST by hand
1214
  let clause1 = Clause [ConP 'JSON.JSArray
1215
                               [ListP [ConP 'JSON.JSString [VarP exc_name],
1216
                                            VarP exc_args]]]
1217
                (NormalB (CaseE (AppE (VarE 'JSON.fromJSString)
1218
                                        (VarE exc_name))
1219
                          (str_matches ++ [defmatch]))) []
1220
  -- the fail expression for the second function clause
1221
  fail_type <- [| fail $ "Invalid exception: expected '(string, [args])' " ++
1222
                  "      but got " ++ show (pp_value $(varE arg_else)) ++ "'"
1223
                |]
1224
  -- the second function clause
1225
  let clause2 = Clause [VarP arg_else] (NormalB fail_type) []
1226
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT tname) |]
1227
  return $ (SigD fname sigt, FunD fname [clause1, clause2])