Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / THH.hs @ c2442429

History | View | Annotate | Download (47 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 Google Inc.
14

    
15
This program is free software; you can redistribute it and/or modify
16
it under the terms of the GNU General Public License as published by
17
the Free Software Foundation; either version 2 of the License, or
18
(at your option) any later version.
19

    
20
This program is distributed in the hope that it will be useful, but
21
WITHOUT ANY WARRANTY; without even the implied warranty of
22
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23
General Public License for more details.
24

    
25
You should have received a copy of the GNU General Public License
26
along with this program; if not, write to the Free Software
27
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28
02110-1301, USA.
29

    
30
-}
31

    
32
module Ganeti.THH ( declareSADT
33
                  , declareLADT
34
                  , declareILADT
35
                  , declareIADT
36
                  , makeJSONInstance
37
                  , deCamelCase
38
                  , genOpID
39
                  , genAllConstr
40
                  , genAllOpIDs
41
                  , PyValue(..)
42
                  , PyValueEx(..)
43
                  , OpCodeField(..)
44
                  , OpCodeDescriptor(..)
45
                  , genOpCode
46
                  , genStrOfOp
47
                  , genStrOfKey
48
                  , genLuxiOp
49
                  , Field (..)
50
                  , simpleField
51
                  , specialNumericalField
52
                  , withDoc
53
                  , defaultField
54
                  , optionalField
55
                  , optionalNullSerField
56
                  , renameField
57
                  , customField
58
                  , timeStampFields
59
                  , uuidFields
60
                  , serialFields
61
                  , tagsFields
62
                  , TagSet
63
                  , buildObject
64
                  , buildObjectSerialisation
65
                  , buildParam
66
                  , DictObject(..)
67
                  , genException
68
                  , excErrMsg
69
                  ) where
70

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

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

    
83
import Ganeti.JSON
84
import Ganeti.PyValue
85
import Ganeti.THH.PyType
86

    
87

    
88
-- * Exported types
89

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

    
95
-- | Optional field information.
96
data OptionalType
97
  = NotOptional           -- ^ Field is not optional
98
  | OptionalOmitNull      -- ^ Field is optional, null is not serialised
99
  | OptionalSerializeNull -- ^ Field is optional, null is serialised
100
  | AndRestArguments      -- ^ Special field capturing all the remaining fields
101
                          -- as plain JSON values
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
-- | Wrapper to lift a read function to optional values
162
makeReadOptional :: ([(String, JSON.JSValue)] -> JSON.JSValue -> JSON.Result a)
163
                    -> [(String, JSON.JSValue)]
164
                    -> Maybe JSON.JSValue -> JSON.Result (Maybe a)
165
makeReadOptional _ _ Nothing = JSON.Ok Nothing
166
makeReadOptional f o (Just x) = fmap Just $ f o x
167

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

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

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

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

    
205
-- | Compute the actual field type (taking into account possible
206
-- optional status).
207
actualFieldType :: Field -> Q Type
208
actualFieldType f | fieldIsOptional f `elem` [NotOptional, AndRestArguments] = t
209
                  | otherwise =  [t| Maybe $t |]
210
                  where t = fieldType f
211

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

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

    
236
-- * Common field declarations
237

    
238
-- | Timestamp fields description.
239
timeStampFields :: [Field]
240
timeStampFields =
241
    [ defaultField [| 0::Double |] $ simpleField "ctime" [t| Double |]
242
    , defaultField [| 0::Double |] $ simpleField "mtime" [t| Double |]
243
    ]
244

    
245
-- | Serial number fields description.
246
serialFields :: [Field]
247
serialFields =
248
    [ renameField  "Serial" $ simpleField "serial_no" [t| Int |] ]
249

    
250
-- | UUID fields description.
251
uuidFields :: [Field]
252
uuidFields = [ simpleField "uuid" [t| String |] ]
253

    
254
-- | Tag set type alias.
255
type TagSet = Set.Set String
256

    
257
-- | Tag field description.
258
tagsFields :: [Field]
259
tagsFields = [ defaultField [| Set.empty |] $
260
               simpleField "tags" [t| TagSet |] ]
261

    
262
-- * Internal types
263

    
264
-- | A simple field, in constrast to the customisable 'Field' type.
265
type SimpleField = (String, Q Type)
266

    
267
-- | A definition for a single constructor for a simple object.
268
type SimpleConstructor = (String, [SimpleField])
269

    
270
-- | A definition for ADTs with simple fields.
271
type SimpleObject = [SimpleConstructor]
272

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

    
276
-- | A type alias for a Luxi constructor of a regular object.
277
type LuxiConstructor = (String, [Field])
278

    
279
-- * Helper functions
280

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

    
289
-- | Ensure first letter is uppercase.
290
--
291
-- Used to convert constructor name to component
292
ensureUpper :: String -> String
293
ensureUpper [] = []
294
ensureUpper (x:xs) = toUpper x:xs
295

    
296
-- | Helper for quoted expressions.
297
varNameE :: String -> Q Exp
298
varNameE = varE . mkName
299

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

    
304
-- | makeObj as an expression, for reuse.
305
makeObjE :: Q Exp
306
makeObjE = varE 'JSON.makeObj
307

    
308
-- | fromObj (Ganeti specific) as an expression, for reuse.
309
fromObjE :: Q Exp
310
fromObjE = varE 'fromObj
311

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

    
316
-- | FromRaw function name.
317
fromRawName :: String -> Name
318
fromRawName = mkName . (++ "FromRaw") . ensureLower
319

    
320
-- | Converts a name to it's varE\/litE representations.
321
reprE :: Either String Name -> Q Exp
322
reprE = either stringE varE
323

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

    
332
-- | Builds a field for a normal constructor.
333
buildConsField :: Q Type -> StrictTypeQ
334
buildConsField ftype = do
335
  ftype' <- ftype
336
  return (NotStrict, ftype')
337

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

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

    
358
-- * Template code for simple raw type-equivalent ADTs
359

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

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

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

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

    
448
declareLADT :: Name -> String -> [(String, String)] -> Q [Dec]
449
declareLADT = declareADT Left
450

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

    
464
declareIADT :: String -> [(String, Name)] -> Q [Dec]
465
declareIADT = declareADT Right ''Int
466

    
467
declareSADT :: String -> [(String, Name)] -> Q [Dec]
468
declareSADT = declareADT Right ''String
469

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

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

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

    
518
-- * Template code for opcodes
519

    
520
-- | Transforms a CamelCase string into an_underscore_based_one.
521
deCamelCase :: String -> String
522
deCamelCase =
523
    intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
524

    
525
-- | Transform an underscore_name into a CamelCase one.
526
camelCase :: String -> String
527
camelCase = concatMap (ensureUpper . drop 1) .
528
            groupBy (\_ b -> b /= '_' && b /= '-') . ('_':)
529

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

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

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

    
562
-- | Constructor-to-string for OpCode.
563
genOpID :: Name -> String -> Q [Dec]
564
genOpID = genConstrToStr deCamelCase
565

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

    
584
-- | Generates a list of all defined opcode IDs.
585
genAllOpIDs :: Name -> String -> Q [Dec]
586
genAllOpIDs = genAllConstr deCamelCase
587

    
588
-- | OpCode parameter (field) type.
589
type OpParam = (String, Q Type, Q Exp)
590

    
591
-- * Python code generation
592

    
593
data OpCodeField = OpCodeField { ocfName :: String
594
                               , ocfType :: PyType
595
                               , ocfDefl :: Maybe PyValueEx
596
                               , ocfDoc  :: String
597
                               }
598

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

    
608
-- | Optionally encapsulates default values in @PyValueEx@.
609
--
610
-- @maybeApp exp typ@ returns a quoted expression that encapsulates
611
-- the default value @exp@ of an opcode parameter cast to @typ@ in a
612
-- @PyValueEx@, if @exp@ is @Just@.  Otherwise, it returns a quoted
613
-- expression with @Nothing@.
614
maybeApp :: Maybe (Q Exp) -> Q Type -> Q Exp
615
maybeApp Nothing _ =
616
  [| Nothing |]
617

    
618
maybeApp (Just expr) typ =
619
  [| Just ($(conE (mkName "PyValueEx")) ($expr :: $typ)) |]
620

    
621
-- | Generates a Python type according to whether the field is
622
-- optional.
623
--
624
-- The type of created expression is PyType.
625
genPyType' :: OptionalType -> Q Type -> Q PyType
626
genPyType' opt typ = typ >>= pyOptionalType (opt /= NotOptional)
627

    
628
-- | Generates Python types from opcode parameters.
629
genPyType :: Field -> Q PyType
630
genPyType f = genPyType' (fieldIsOptional f) (fieldType f)
631

    
632
-- | Generates Python default values from opcode parameters.
633
genPyDefault :: Field -> Q Exp
634
genPyDefault f = maybeApp (fieldDefault f) (fieldType f)
635

    
636
pyField :: Field -> Q Exp
637
pyField f = genPyType f >>= \t ->
638
            [| OpCodeField $(stringE (fieldName f))
639
                           t
640
                           $(genPyDefault f)
641
                           $(stringE (fieldDoc f)) |]
642

    
643
-- | Generates a Haskell function call to "showPyClass" with the
644
-- necessary information on how to build the Python class string.
645
pyClass :: OpCodeConstructor -> Q Exp
646
pyClass (consName, consType, consDoc, consFields, consDscField) =
647
  do let pyClassVar = varNameE "showPyClass"
648
         consName' = stringE consName
649
     consType' <- genPyType' NotOptional consType
650
     let consDoc' = stringE consDoc
651
     [| OpCodeDescriptor $consName'
652
                         consType'
653
                         $consDoc'
654
                         $(listE $ map pyField consFields)
655
                         consDscField |]
656

    
657
-- | Generates a function called "pyClasses" that holds the list of
658
-- all the opcode descriptors necessary for generating the Python
659
-- opcodes.
660
pyClasses :: [OpCodeConstructor] -> Q [Dec]
661
pyClasses cons =
662
  do let name = mkName "pyClasses"
663
         sig = SigD name (AppT ListT (ConT ''OpCodeDescriptor))
664
     fn <- FunD name <$> (:[]) <$> declClause cons
665
     return [sig, fn]
666
  where declClause c =
667
          clause [] (normalB (ListE <$> mapM pyClass c)) []
668

    
669
-- | Converts from an opcode constructor to a Luxi constructor.
670
opcodeConsToLuxiCons :: (a, b, c, d, e) -> (a, d)
671
opcodeConsToLuxiCons (x, _, _, y, _) = (x, y)
672

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

    
697
-- | Generates the function pattern returning the list of fields for a
698
-- given constructor.
699
genOpConsFields :: OpCodeConstructor -> Clause
700
genOpConsFields (cname, _, _, fields, _) =
701
  let op_id = deCamelCase cname
702
      fvals = map (LitE . StringL) . sort . nub $
703
              concatMap (\f -> fieldName f:fieldExtraKeys f) fields
704
  in Clause [LitP (StringL op_id)] (NormalB $ ListE fvals) []
705

    
706
-- | Generates a list of all fields of an opcode constructor.
707
genAllOpFields  :: String              -- ^ Function name
708
                -> [OpCodeConstructor] -- ^ Object definition
709
                -> (Dec, Dec)
710
genAllOpFields sname opdefs =
711
  let cclauses = map genOpConsFields opdefs
712
      other = Clause [WildP] (NormalB (ListE [])) []
713
      fname = mkName sname
714
      sigt = AppT  (AppT ArrowT (ConT ''String)) (AppT ListT (ConT ''String))
715
  in (SigD fname sigt, FunD fname (cclauses++[other]))
716

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

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

    
765
-- | Generates load code for a single constructor of the opcode data type.
766
loadConstructor :: OpCodeConstructor -> Q Exp
767
loadConstructor (sname, _, _, fields, _) = do
768
  let name = mkName sname
769
  fbinds <- mapM (loadObjectField fields) fields
770
  let (fnames, fstmts) = unzip fbinds
771
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
772
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
773
  return $ DoE fstmts'
774

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

    
798
-- * Template code for luxi
799

    
800
-- | Constructor-to-string for LuxiOp.
801
genStrOfOp :: Name -> String -> Q [Dec]
802
genStrOfOp = genConstrToStr id
803

    
804
-- | Constructor-to-string for MsgKeys.
805
genStrOfKey :: Name -> String -> Q [Dec]
806
genStrOfKey = genConstrToStr ensureLower
807

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

    
838
-- | Generates the \"save\" clause for entire LuxiOp constructor.
839
saveLuxiConstructor :: LuxiConstructor -> Q Clause
840
saveLuxiConstructor (sname, fields) = do
841
  let cname = mkName sname
842
  fnames <- mapM (newName . fieldVariable) fields
843
  let pat = conP cname (map varP fnames)
844
  let felems = map (uncurry saveObjectField) (zip fnames fields)
845
      flist = [| concat $(listE felems) |]
846
  clause [pat] (normalB flist) []
847

    
848
-- * "Objects" functionality
849

    
850
-- | Extract the field's declaration from a Field structure.
851
fieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
852
fieldTypeInfo field_pfx fd = do
853
  t <- actualFieldType fd
854
  let n = mkName . (field_pfx ++) . fieldRecordName $ fd
855
  return (n, NotStrict, t)
856

    
857
-- | Build an object declaration.
858
buildObject :: String -> String -> [Field] -> Q [Dec]
859
buildObject sname field_pfx fields = do
860
  let name = mkName sname
861
  fields_d <- mapM (fieldTypeInfo field_pfx) fields
862
  let decl_d = RecC name fields_d
863
  let declD = DataD [] name [] [decl_d] [''Show, ''Eq]
864
  ser_decls <- buildObjectSerialisation sname fields
865
  return $ declD:ser_decls
866

    
867
-- | Generates an object definition: data type and its JSON instance.
868
buildObjectSerialisation :: String -> [Field] -> Q [Dec]
869
buildObjectSerialisation sname fields = do
870
  let name = mkName sname
871
  savedecls <- genSaveObject saveObjectField sname fields
872
  (loadsig, loadfn) <- genLoadObject (loadObjectField fields) sname fields
873
  shjson <- objectShowJSON sname
874
  rdjson <- objectReadJSON sname
875
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
876
                 [rdjson, shjson]
877
  return $ savedecls ++ [loadsig, loadfn, instdecl]
878

    
879
-- | The toDict function name for a given type.
880
toDictName :: String -> Name
881
toDictName sname = mkName ("toDict" ++ sname)
882

    
883
-- | Generates the save object functionality.
884
genSaveObject :: (Name -> Field -> Q Exp)
885
              -> String -> [Field] -> Q [Dec]
886
genSaveObject save_fn sname fields = do
887
  let name = mkName sname
888
  fnames <- mapM (newName . fieldVariable) fields
889
  let pat = conP name (map varP fnames)
890
  let tdname = toDictName sname
891
  tdsigt <- [t| $(conT name) -> [(String, JSON.JSValue)] |]
892

    
893
  let felems = map (uncurry save_fn) (zip fnames fields)
894
      flist = listE felems
895
      -- and finally convert all this to a json object
896
      tdlist = [| concat $flist |]
897
      iname = mkName "i"
898
  tclause <- clause [pat] (normalB tdlist) []
899
  cclause <- [| $makeObjE . $(varE tdname) |]
900
  let fname = mkName ("save" ++ sname)
901
  sigt <- [t| $(conT name) -> JSON.JSValue |]
902
  return [SigD tdname tdsigt, FunD tdname [tclause],
903
          SigD fname sigt, ValD (VarP fname) (NormalB cclause) []]
904

    
905
-- | Generates the code for saving an object's field, handling the
906
-- various types of fields that we have.
907
saveObjectField :: Name -> Field -> Q Exp
908
saveObjectField fvar field =
909
  case fieldIsOptional field of
910
    OptionalOmitNull -> [| case $(varE fvar) of
911
                             Nothing -> []
912
                             Just v  -> [( $nameE, JSON.showJSON v )]
913
                         |]
914
    OptionalSerializeNull -> [| case $(varE fvar) of
915
                                  Nothing -> [( $nameE, JSON.JSNull )]
916
                                  Just v  -> [( $nameE, JSON.showJSON v )]
917
                              |]
918
    NotOptional ->
919
      case fieldShow field of
920
        -- Note: the order of actual:extra is important, since for
921
        -- some serialisation types (e.g. Luxi), we use tuples
922
        -- (positional info) rather than object (name info)
923
        Nothing -> [| [( $nameE, JSON.showJSON $fvarE)] |]
924
        Just fn -> [| let (actual, extra) = $fn $fvarE
925
                      in ($nameE, JSON.showJSON actual):extra
926
                    |]
927
    AndRestArguments -> [| M.toList $(varE fvar) |]
928
  where nameE = stringE (fieldName field)
929
        fvarE = varE fvar
930

    
931
-- | Generates the showJSON clause for a given object name.
932
objectShowJSON :: String -> Q Dec
933
objectShowJSON name = do
934
  body <- [| JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
935
  return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
936

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

    
961
-- | Generates code for loading an object's field.
962
loadObjectField :: [Field] -> Field -> Q (Name, Stmt)
963
loadObjectField allFields field = do
964
  let name = fieldVariable field
965
      names = map fieldVariable allFields
966
      otherNames = listE . map stringE $ names \\ [name]
967
  fvar <- newName name
968
  -- these are used in all patterns below
969
  let objvar = varNameE "o"
970
      objfield = stringE (fieldName field)
971
      loadexp =
972
        case fieldIsOptional field of
973
          NotOptional ->
974
            case fieldDefault field of
975
                 Just defv ->
976
                   [| $(varE 'fromObjWithDefault) $objvar
977
                      $objfield $defv |]
978
                 Nothing -> [| $fromObjE $objvar $objfield |]
979
          AndRestArguments -> [| return . M.fromList
980
                                   $ filter (not . (`elem` $otherNames) . fst)
981
                                            $objvar |]
982
          _ -> [| $(varE 'maybeFromObj) $objvar $objfield |]
983
          -- we treat both optional types the same, since
984
          -- 'maybeFromObj' can deal with both missing and null values
985
          -- appropriately (the same)
986
  bexp <- loadFn field loadexp objvar
987

    
988
  return (fvar, BindS (VarP fvar) bexp)
989

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

    
1002
-- * Inheritable parameter tables implementation
1003

    
1004
-- | Compute parameter type names.
1005
paramTypeNames :: String -> (String, String)
1006
paramTypeNames root = ("Filled"  ++ root ++ "Params",
1007
                       "Partial" ++ root ++ "Params")
1008

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

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

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

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

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

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

    
1082
-- | Generates code to load an optional parameter field.
1083
loadPParamField :: Field -> Q (Name, Stmt)
1084
loadPParamField field = do
1085
  checkNonOptDef field
1086
  let name = fieldName field
1087
  fvar <- newName name
1088
  -- these are used in all patterns below
1089
  let objvar = varNameE "o"
1090
      objfield = stringE name
1091
      loadexp = [| $(varE 'maybeFromObj) $objvar $objfield |]
1092
      field' = field {fieldRead=fmap (appE (varE 'makeReadOptional))
1093
                                  $ fieldRead field}
1094
  bexp <- loadFn field' loadexp objvar
1095
  return (fvar, BindS (VarP fvar) bexp)
1096

    
1097
-- | Builds a simple declaration of type @n_x = fromMaybe f_x p_x@.
1098
buildFromMaybe :: String -> Q Dec
1099
buildFromMaybe fname =
1100
  valD (varP (mkName $ "n_" ++ fname))
1101
         (normalB [| $(varE 'fromMaybe)
1102
                        $(varNameE $ "f_" ++ fname)
1103
                        $(varNameE $ "p_" ++ fname) |]) []
1104

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

    
1130
-- * Template code for exceptions
1131

    
1132
-- | Exception simple error message field.
1133
excErrMsg :: (String, Q Type)
1134
excErrMsg = ("errMsg", [t| String |])
1135

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

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

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

    
1188
{-| Generates the loadException function.
1189

    
1190
This generates a quite complicated function, along the lines of:
1191

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