Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / THH.hs @ d286d795

History | View | Annotate | Download (46.8 kB)

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

    
3
{-| TemplateHaskell helper for Ganeti Haskell code.
4

    
5
As TemplateHaskell require that splices be defined in a separate
6
module, we combine all the TemplateHaskell functionality that HTools
7
needs in this module (except the one for unittests).
8

    
9
-}
10

    
11
{-
12

    
13
Copyright (C) 2011, 2012 Google Inc.
14

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

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

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

    
30
-}
31

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

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

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

    
78
import Ganeti.JSON
79

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

    
83
-- * Exported types
84

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

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

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

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

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

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

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

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

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

    
145
-- | Sets custom functions on a field.
146
customField :: Name      -- ^ The name of the read function
147
            -> Name      -- ^ The name of the show function
148
            -> [String]  -- ^ The name of extra field keys
149
            -> Field     -- ^ The original field
150
            -> Field     -- ^ Updated field
151
customField readfn showfn extra field =
152
  field { fieldRead = Just (varE readfn), fieldShow = Just (varE showfn)
153
        , fieldExtraKeys = extra }
154

    
155
-- | Computes the record name for a given field, based on either the
156
-- string value in the JSON serialisation or the custom named if any
157
-- exists.
158
fieldRecordName :: Field -> String
159
fieldRecordName (Field { fieldName = name, fieldConstr = alias }) =
160
  fromMaybe (camelCase name) alias
161

    
162
-- | Computes the preferred variable name to use for the value of this
163
-- field. If the field has a specific constructor name, then we use a
164
-- first-letter-lowercased version of that; otherwise, we simply use
165
-- the field name. See also 'fieldRecordName'.
166
fieldVariable :: Field -> String
167
fieldVariable f =
168
  case (fieldConstr f) of
169
    Just name -> ensureLower name
170
    _ -> map (\c -> if c == '-' then '_' else c) $ fieldName f
171

    
172
-- | Compute the actual field type (taking into account possible
173
-- optional status).
174
actualFieldType :: Field -> Q Type
175
actualFieldType f | fieldIsOptional f /= NotOptional = [t| Maybe $t |]
176
                  | otherwise = t
177
                  where t = fieldType f
178

    
179
-- | Checks that a given field is not optional (for object types or
180
-- fields which should not allow this case).
181
checkNonOptDef :: (Monad m) => Field -> m ()
182
checkNonOptDef (Field { fieldIsOptional = OptionalOmitNull
183
                      , fieldName = name }) =
184
  fail $ "Optional field " ++ name ++ " used in parameter declaration"
185
checkNonOptDef (Field { fieldIsOptional = OptionalSerializeNull
186
                      , fieldName = name }) =
187
  fail $ "Optional field " ++ name ++ " used in parameter declaration"
188
checkNonOptDef (Field { fieldDefault = (Just _), fieldName = name }) =
189
  fail $ "Default field " ++ name ++ " used in parameter declaration"
190
checkNonOptDef _ = return ()
191

    
192
-- | Produces the expression that will de-serialise a given
193
-- field. Since some custom parsing functions might need to use the
194
-- entire object, we do take and pass the object to any custom read
195
-- functions.
196
loadFn :: Field   -- ^ The field definition
197
       -> Q Exp   -- ^ The value of the field as existing in the JSON message
198
       -> Q Exp   -- ^ The entire object in JSON object format
199
       -> Q Exp   -- ^ Resulting expression
200
loadFn (Field { fieldRead = Just readfn }) expr o = [| $expr >>= $readfn $o |]
201
loadFn _ expr _ = expr
202

    
203
-- * Common field declarations
204

    
205
-- | Timestamp fields description.
206
timeStampFields :: [Field]
207
timeStampFields =
208
    [ defaultField [| 0::Double |] $ simpleField "ctime" [t| Double |]
209
    , defaultField [| 0::Double |] $ simpleField "mtime" [t| Double |]
210
    ]
211

    
212
-- | Serial number fields description.
213
serialFields :: [Field]
214
serialFields =
215
    [ renameField  "Serial" $ simpleField "serial_no" [t| Int |] ]
216

    
217
-- | UUID fields description.
218
uuidFields :: [Field]
219
uuidFields = [ simpleField "uuid" [t| String |] ]
220

    
221
-- | Tag set type alias.
222
type TagSet = Set.Set String
223

    
224
-- | Tag field description.
225
tagsFields :: [Field]
226
tagsFields = [ defaultField [| Set.empty |] $
227
               simpleField "tags" [t| TagSet |] ]
228

    
229
-- * Internal types
230

    
231
-- | A simple field, in constrast to the customisable 'Field' type.
232
type SimpleField = (String, Q Type)
233

    
234
-- | A definition for a single constructor for a simple object.
235
type SimpleConstructor = (String, [SimpleField])
236

    
237
-- | A definition for ADTs with simple fields.
238
type SimpleObject = [SimpleConstructor]
239

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

    
243
-- | A type alias for a Luxi constructor of a regular object.
244
type LuxiConstructor = (String, [Field])
245

    
246
-- * Helper functions
247

    
248
-- | Ensure first letter is lowercase.
249
--
250
-- Used to convert type name to function prefix, e.g. in @data Aa ->
251
-- aaToRaw@.
252
ensureLower :: String -> String
253
ensureLower [] = []
254
ensureLower (x:xs) = toLower x:xs
255

    
256
-- | Ensure first letter is uppercase.
257
--
258
-- Used to convert constructor name to component
259
ensureUpper :: String -> String
260
ensureUpper [] = []
261
ensureUpper (x:xs) = toUpper x:xs
262

    
263
-- | Helper for quoted expressions.
264
varNameE :: String -> Q Exp
265
varNameE = varE . mkName
266

    
267
-- | showJSON as an expression, for reuse.
268
showJSONE :: Q Exp
269
showJSONE = varE 'JSON.showJSON
270

    
271
-- | makeObj as an expression, for reuse.
272
makeObjE :: Q Exp
273
makeObjE = varE 'JSON.makeObj
274

    
275
-- | fromObj (Ganeti specific) as an expression, for reuse.
276
fromObjE :: Q Exp
277
fromObjE = varE 'fromObj
278

    
279
-- | ToRaw function name.
280
toRawName :: String -> Name
281
toRawName = mkName . (++ "ToRaw") . ensureLower
282

    
283
-- | FromRaw function name.
284
fromRawName :: String -> Name
285
fromRawName = mkName . (++ "FromRaw") . ensureLower
286

    
287
-- | Converts a name to it's varE\/litE representations.
288
reprE :: Either String Name -> Q Exp
289
reprE = either stringE varE
290

    
291
-- | Smarter function application.
292
--
293
-- This does simply f x, except that if is 'id', it will skip it, in
294
-- order to generate more readable code when using -ddump-splices.
295
appFn :: Exp -> Exp -> Exp
296
appFn f x | f == VarE 'id = x
297
          | otherwise = AppE f x
298

    
299
-- | Builds a field for a normal constructor.
300
buildConsField :: Q Type -> StrictTypeQ
301
buildConsField ftype = do
302
  ftype' <- ftype
303
  return (NotStrict, ftype')
304

    
305
-- | Builds a constructor based on a simple definition (not field-based).
306
buildSimpleCons :: Name -> SimpleObject -> Q Dec
307
buildSimpleCons tname cons = do
308
  decl_d <- mapM (\(cname, fields) -> do
309
                    fields' <- mapM (buildConsField . snd) fields
310
                    return $ NormalC (mkName cname) fields') cons
311
  return $ DataD [] tname [] decl_d [''Show, ''Eq]
312

    
313
-- | Generate the save function for a given type.
314
genSaveSimpleObj :: Name                            -- ^ Object type
315
                 -> String                          -- ^ Function name
316
                 -> SimpleObject                    -- ^ Object definition
317
                 -> (SimpleConstructor -> Q Clause) -- ^ Constructor save fn
318
                 -> Q (Dec, Dec)
319
genSaveSimpleObj tname sname opdefs fn = do
320
  let sigt = AppT (AppT ArrowT (ConT tname)) (ConT ''JSON.JSValue)
321
      fname = mkName sname
322
  cclauses <- mapM fn opdefs
323
  return $ (SigD fname sigt, FunD fname cclauses)
324

    
325
-- * Template code for simple raw type-equivalent ADTs
326

    
327
-- | Generates a data type declaration.
328
--
329
-- The type will have a fixed list of instances.
330
strADTDecl :: Name -> [String] -> Dec
331
strADTDecl name constructors =
332
  DataD [] name []
333
          (map (flip NormalC [] . mkName) constructors)
334
          [''Show, ''Eq, ''Enum, ''Bounded, ''Ord]
335

    
336
-- | Generates a toRaw function.
337
--
338
-- This generates a simple function of the form:
339
--
340
-- @
341
-- nameToRaw :: Name -> /traw/
342
-- nameToRaw Cons1 = var1
343
-- nameToRaw Cons2 = \"value2\"
344
-- @
345
genToRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
346
genToRaw traw fname tname constructors = do
347
  let sigt = AppT (AppT ArrowT (ConT tname)) (ConT traw)
348
  -- the body clauses, matching on the constructor and returning the
349
  -- raw value
350
  clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
351
                             (normalB (reprE v)) []) constructors
352
  return [SigD fname sigt, FunD fname clauses]
353

    
354
-- | Generates a fromRaw function.
355
--
356
-- The function generated is monadic and can fail parsing the
357
-- raw value. It is of the form:
358
--
359
-- @
360
-- nameFromRaw :: (Monad m) => /traw/ -> m Name
361
-- nameFromRaw s | s == var1       = Cons1
362
--               | s == \"value2\" = Cons2
363
--               | otherwise = fail /.../
364
-- @
365
genFromRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
366
genFromRaw traw fname tname constructors = do
367
  -- signature of form (Monad m) => String -> m $name
368
  sigt <- [t| (Monad m) => $(conT traw) -> m $(conT tname) |]
369
  -- clauses for a guarded pattern
370
  let varp = mkName "s"
371
      varpe = varE varp
372
  clauses <- mapM (\(c, v) -> do
373
                     -- the clause match condition
374
                     g <- normalG [| $varpe == $(reprE v) |]
375
                     -- the clause result
376
                     r <- [| return $(conE (mkName c)) |]
377
                     return (g, r)) constructors
378
  -- the otherwise clause (fallback)
379
  oth_clause <- do
380
    g <- normalG [| otherwise |]
381
    r <- [|fail ("Invalid string value for type " ++
382
                 $(litE (stringL (nameBase tname))) ++ ": " ++ show $varpe) |]
383
    return (g, r)
384
  let fun = FunD fname [Clause [VarP varp]
385
                        (GuardedB (clauses++[oth_clause])) []]
386
  return [SigD fname sigt, fun]
387

    
388
-- | Generates a data type from a given raw format.
389
--
390
-- The format is expected to multiline. The first line contains the
391
-- type name, and the rest of the lines must contain two words: the
392
-- constructor name and then the string representation of the
393
-- respective constructor.
394
--
395
-- The function will generate the data type declaration, and then two
396
-- functions:
397
--
398
-- * /name/ToRaw, which converts the type to a raw type
399
--
400
-- * /name/FromRaw, which (monadically) converts from a raw type to the type
401
--
402
-- Note that this is basically just a custom show\/read instance,
403
-- nothing else.
404
declareADT
405
  :: (a -> Either String Name) -> Name -> String -> [(String, a)] -> Q [Dec]
406
declareADT fn traw sname cons = do
407
  let name = mkName sname
408
      ddecl = strADTDecl name (map fst cons)
409
      -- process cons in the format expected by genToRaw
410
      cons' = map (\(a, b) -> (a, fn b)) cons
411
  toraw <- genToRaw traw (toRawName sname) name cons'
412
  fromraw <- genFromRaw traw (fromRawName sname) name cons'
413
  return $ ddecl:toraw ++ fromraw
414

    
415
declareLADT :: Name -> String -> [(String, String)] -> Q [Dec]
416
declareLADT = declareADT Left
417

    
418
declareILADT :: String -> [(String, Int)] -> Q [Dec]
419
declareILADT sname cons = do
420
  consNames <- sequence [ newName ('_':n) | (n, _) <- cons ]
421
  consFns <- concat <$> sequence
422
             [ do sig <- sigD n [t| Int |]
423
                  let expr = litE (IntegerL (toInteger i))
424
                  fn <- funD n [clause [] (normalB expr) []]
425
                  return [sig, fn]
426
             | n <- consNames
427
             | (_, i) <- cons ]
428
  let cons' = [ (n, n') | (n, _) <- cons | n' <- consNames ]
429
  (consFns ++) <$> declareADT Right ''Int sname cons'
430

    
431
declareIADT :: String -> [(String, Name)] -> Q [Dec]
432
declareIADT = declareADT Right ''Int
433

    
434
declareSADT :: String -> [(String, Name)] -> Q [Dec]
435
declareSADT = declareADT Right ''String
436

    
437
-- | Creates the showJSON member of a JSON instance declaration.
438
--
439
-- This will create what is the equivalent of:
440
--
441
-- @
442
-- showJSON = showJSON . /name/ToRaw
443
-- @
444
--
445
-- in an instance JSON /name/ declaration
446
genShowJSON :: String -> Q Dec
447
genShowJSON name = do
448
  body <- [| JSON.showJSON . $(varE (toRawName name)) |]
449
  return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
450

    
451
-- | Creates the readJSON member of a JSON instance declaration.
452
--
453
-- This will create what is the equivalent of:
454
--
455
-- @
456
-- readJSON s = case readJSON s of
457
--                Ok s' -> /name/FromRaw s'
458
--                Error e -> Error /description/
459
-- @
460
--
461
-- in an instance JSON /name/ declaration
462
genReadJSON :: String -> Q Dec
463
genReadJSON name = do
464
  let s = mkName "s"
465
  body <- [| case JSON.readJSON $(varE s) of
466
               JSON.Ok s' -> $(varE (fromRawName name)) s'
467
               JSON.Error e ->
468
                   JSON.Error $ "Can't parse raw value for type " ++
469
                           $(stringE name) ++ ": " ++ e ++ " from " ++
470
                           show $(varE s)
471
           |]
472
  return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
473

    
474
-- | Generates a JSON instance for a given type.
475
--
476
-- This assumes that the /name/ToRaw and /name/FromRaw functions
477
-- have been defined as by the 'declareSADT' function.
478
makeJSONInstance :: Name -> Q [Dec]
479
makeJSONInstance name = do
480
  let base = nameBase name
481
  showJ <- genShowJSON base
482
  readJ <- genReadJSON base
483
  return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) [readJ,showJ]]
484

    
485
-- * Template code for opcodes
486

    
487
-- | Transforms a CamelCase string into an_underscore_based_one.
488
deCamelCase :: String -> String
489
deCamelCase =
490
    intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
491

    
492
-- | Transform an underscore_name into a CamelCase one.
493
camelCase :: String -> String
494
camelCase = concatMap (ensureUpper . drop 1) .
495
            groupBy (\_ b -> b /= '_' && b /= '-') . ('_':)
496

    
497
-- | Computes the name of a given constructor.
498
constructorName :: Con -> Q Name
499
constructorName (NormalC name _) = return name
500
constructorName (RecC name _)    = return name
501
constructorName x                = fail $ "Unhandled constructor " ++ show x
502

    
503
-- | Extract all constructor names from a given type.
504
reifyConsNames :: Name -> Q [String]
505
reifyConsNames name = do
506
  reify_result <- reify name
507
  case reify_result of
508
    TyConI (DataD _ _ _ cons _) -> mapM (liftM nameBase . constructorName) cons
509
    o -> fail $ "Unhandled name passed to reifyConsNames, expected\
510
                \ type constructor but got '" ++ show o ++ "'"
511

    
512
-- | Builds the generic constructor-to-string function.
513
--
514
-- This generates a simple function of the following form:
515
--
516
-- @
517
-- fname (ConStructorOne {}) = trans_fun("ConStructorOne")
518
-- fname (ConStructorTwo {}) = trans_fun("ConStructorTwo")
519
-- @
520
--
521
-- This builds a custom list of name\/string pairs and then uses
522
-- 'genToRaw' to actually generate the function.
523
genConstrToStr :: (String -> String) -> Name -> String -> Q [Dec]
524
genConstrToStr trans_fun name fname = do
525
  cnames <- reifyConsNames name
526
  let svalues = map (Left . trans_fun) cnames
527
  genToRaw ''String (mkName fname) name $ zip cnames svalues
528

    
529
-- | Constructor-to-string for OpCode.
530
genOpID :: Name -> String -> Q [Dec]
531
genOpID = genConstrToStr deCamelCase
532

    
533
-- | Builds a list with all defined constructor names for a type.
534
--
535
-- @
536
-- vstr :: String
537
-- vstr = [...]
538
-- @
539
--
540
-- Where the actual values of the string are the constructor names
541
-- mapped via @trans_fun@.
542
genAllConstr :: (String -> String) -> Name -> String -> Q [Dec]
543
genAllConstr trans_fun name vstr = do
544
  cnames <- reifyConsNames name
545
  let svalues = sort $ map trans_fun cnames
546
      vname = mkName vstr
547
      sig = SigD vname (AppT ListT (ConT ''String))
548
      body = NormalB (ListE (map (LitE . StringL) svalues))
549
  return $ [sig, ValD (VarP vname) body []]
550

    
551
-- | Generates a list of all defined opcode IDs.
552
genAllOpIDs :: Name -> String -> Q [Dec]
553
genAllOpIDs = genAllConstr deCamelCase
554

    
555
-- | OpCode parameter (field) type.
556
type OpParam = (String, Q Type, Q Exp)
557

    
558
-- * Python code generation
559

    
560
-- | Converts Haskell values into Python values
561
--
562
-- This is necessary for the default values of opcode parameters and
563
-- return values.  For example, if a default value or return type is a
564
-- Data.Map, then it must be shown as a Python dictioanry.
565
class Show a => PyValue a where
566
  showValue :: a -> String
567
  showValue = show
568

    
569
-- | Encapsulates Python default values
570
data PyValueEx = forall a. PyValue a => PyValueEx a
571

    
572
-- | Transfers opcode data between the opcode description (through
573
-- @genOpCode@) and the Python code generation functions.
574
type OpCodeDescriptor =
575
  (String, String, String, [String],
576
   [String], [Maybe PyValueEx], [String], String)
577

    
578
-- | Strips out the module name
579
--
580
-- @
581
-- pyBaseName "Data.Map" = "Map"
582
-- @
583
pyBaseName :: String -> String
584
pyBaseName str =
585
  case span (/= '.') str of
586
    (x, []) -> x
587
    (_, _:x) -> pyBaseName x
588

    
589
-- | Converts a Haskell type name into a Python type name.
590
--
591
-- @
592
-- pyTypename "Bool" = "ht.TBool"
593
-- @
594
pyTypeName :: Show a => a -> String
595
pyTypeName name =
596
  "ht.T" ++ (case pyBaseName (show name) of
597
                "()" -> "None"
598
                "Map" -> "DictOf"
599
                "Set" -> "SetOf"
600
                "ListSet" -> "SetOf"
601
                "Either" -> "Or"
602
                "GenericContainer" -> "DictOf"
603
                "JSValue" -> "Any"
604
                "JSObject" -> "Object"
605
                str -> str)
606

    
607
-- | Converts a Haskell type into a Python type.
608
--
609
-- @
610
-- pyType [Int] = "ht.TListOf(ht.TInt)"
611
-- @
612
pyType :: Type -> Q String
613
pyType (AppT typ1 typ2) =
614
  do t <- pyCall typ1 typ2
615
     return $ t ++ ")"
616

    
617
pyType (ConT name) = return (pyTypeName name)
618
pyType ListT = return "ht.TListOf"
619
pyType (TupleT 0) = return "ht.TNone"
620
pyType (TupleT _) = return "ht.TTupleOf"
621
pyType typ = error $ "unhandled case for type " ++ show typ
622
        
623
-- | Converts a Haskell type application into a Python type.
624
--
625
-- @
626
-- Maybe Int = "ht.TMaybe(ht.TInt)"
627
-- @
628
pyCall :: Type -> Type -> Q String
629
pyCall (AppT typ1 typ2) arg =
630
  do t <- pyCall typ1 typ2
631
     targ <- pyType arg
632
     return $ t ++ ", " ++ targ
633

    
634
pyCall typ1 typ2 =
635
  do t1 <- pyType typ1
636
     t2 <- pyType typ2
637
     return $ t1 ++ "(" ++ t2
638

    
639
-- | @pyType opt typ@ converts Haskell type @typ@ into a Python type,
640
-- where @opt@ determines if the converted type is optional (i.e.,
641
-- Maybe).
642
--
643
-- @
644
-- pyType False [Int] = "ht.TListOf(ht.TInt)" (mandatory)
645
-- pyType True [Int] = "ht.TMaybe(ht.TListOf(ht.TInt))" (optional)
646
-- @
647
pyOptionalType :: Bool -> Type -> Q String
648
pyOptionalType opt typ
649
  | opt = do t <- pyType typ
650
             return $ "ht.TMaybe(" ++ t ++ ")"
651
  | otherwise = pyType typ
652

    
653
-- | Optionally encapsulates default values in @PyValueEx@.
654
--
655
-- @maybeApp exp typ@ returns a quoted expression that encapsulates
656
-- the default value @exp@ of an opcode parameter cast to @typ@ in a
657
-- @PyValueEx@, if @exp@ is @Just@.  Otherwise, it returns a quoted
658
-- expression with @Nothing@.
659
maybeApp :: Maybe (Q Exp) -> Q Type -> Q Exp
660
maybeApp Nothing _ =
661
  [| Nothing |]
662

    
663
maybeApp (Just expr) typ =
664
  [| Just ($(conE (mkName "PyValueEx")) ($expr :: $typ)) |]
665

    
666

    
667
-- | Generates a Python type according to whether the field is
668
-- optional
669
genPyType :: OptionalType -> Q Type -> Q ExpQ
670
genPyType opt typ =
671
  do t <- typ
672
     stringE <$> pyOptionalType (opt /= NotOptional) t
673

    
674
-- | Generates Python types from opcode parameters.
675
genPyTypes :: [Field] -> Q ExpQ
676
genPyTypes fs =
677
  listE <$> mapM (\f -> genPyType (fieldIsOptional f) (fieldType f)) fs
678

    
679
-- | Generates Python default values from opcode parameters.
680
genPyDefaults :: [Field] -> ExpQ
681
genPyDefaults fs =
682
  listE $ map (\f -> maybeApp (fieldDefault f) (fieldType f)) fs
683

    
684
-- | Generates a Haskell function call to "showPyClass" with the
685
-- necessary information on how to build the Python class string.
686
pyClass :: OpCodeConstructor -> ExpQ
687
pyClass (consName, consType, consDoc, consFields, consDscField) =
688
  do let pyClassVar = varNameE "showPyClass"
689
         consName' = stringE consName
690
     consType' <- genPyType NotOptional consType
691
     let consDoc' = stringE consDoc
692
         consFieldNames = listE $ map (stringE . fieldName) consFields
693
         consFieldDocs = listE $ map (stringE . fieldDoc) consFields
694
     consFieldTypes <- genPyTypes consFields
695
     let consFieldDefaults = genPyDefaults consFields
696
     [| ($consName',
697
         $consType',
698
         $consDoc',
699
         $consFieldNames,
700
         $consFieldTypes,
701
         $consFieldDefaults,
702
         $consFieldDocs,
703
         consDscField) |]
704

    
705
-- | Generates a function called "pyClasses" that holds the list of
706
-- all the opcode descriptors necessary for generating the Python
707
-- opcodes.
708
pyClasses :: [OpCodeConstructor] -> Q [Dec]
709
pyClasses cons =
710
  do let name = mkName "pyClasses"
711
         sig = SigD name (AppT ListT (ConT ''OpCodeDescriptor))
712
     fn <- FunD name <$> (:[]) <$> declClause cons
713
     return [sig, fn]
714
  where declClause c =
715
          clause [] (normalB (ListE <$> mapM pyClass c)) []
716

    
717
-- | Converts from an opcode constructor to a Luxi constructor.
718
opcodeConsToLuxiCons :: (a, b, c, d, e) -> (a, d)
719
opcodeConsToLuxiCons (x, _, _, y, _) = (x, y)
720

    
721
-- | Generates the OpCode data type.
722
--
723
-- This takes an opcode logical definition, and builds both the
724
-- datatype and the JSON serialisation out of it. We can't use a
725
-- generic serialisation since we need to be compatible with Ganeti's
726
-- own, so we have a few quirks to work around.
727
genOpCode :: String              -- ^ Type name to use
728
          -> [OpCodeConstructor] -- ^ Constructor name and parameters
729
          -> Q [Dec]
730
genOpCode name cons = do
731
  let tname = mkName name
732
  decl_d <- mapM (\(cname, _, _, fields, _) -> do
733
                    -- we only need the type of the field, without Q
734
                    fields' <- mapM (fieldTypeInfo "op") fields
735
                    return $ RecC (mkName cname) fields')
736
            cons
737
  let declD = DataD [] tname [] decl_d [''Show, ''Eq]
738
  let (allfsig, allffn) = genAllOpFields "allOpFields" cons
739
  save_decs <- genSaveOpCode tname "saveOpCode" "toDictOpCode"
740
               (map opcodeConsToLuxiCons cons) saveConstructor True
741
  (loadsig, loadfn) <- genLoadOpCode cons
742
  pyDecls <- pyClasses cons
743
  return $ [declD, allfsig, allffn, loadsig, loadfn] ++ save_decs ++ pyDecls
744

    
745
-- | Generates the function pattern returning the list of fields for a
746
-- given constructor.
747
genOpConsFields :: OpCodeConstructor -> Clause
748
genOpConsFields (cname, _, _, fields, _) =
749
  let op_id = deCamelCase cname
750
      fvals = map (LitE . StringL) . sort . nub $
751
              concatMap (\f -> fieldName f:fieldExtraKeys f) fields
752
  in Clause [LitP (StringL op_id)] (NormalB $ ListE fvals) []
753

    
754
-- | Generates a list of all fields of an opcode constructor.
755
genAllOpFields  :: String              -- ^ Function name
756
                -> [OpCodeConstructor] -- ^ Object definition
757
                -> (Dec, Dec)
758
genAllOpFields sname opdefs =
759
  let cclauses = map genOpConsFields opdefs
760
      other = Clause [WildP] (NormalB (ListE [])) []
761
      fname = mkName sname
762
      sigt = AppT  (AppT ArrowT (ConT ''String)) (AppT ListT (ConT ''String))
763
  in (SigD fname sigt, FunD fname (cclauses++[other]))
764

    
765
-- | Generates the \"save\" clause for an entire opcode constructor.
766
--
767
-- This matches the opcode with variables named the same as the
768
-- constructor fields (just so that the spliced in code looks nicer),
769
-- and passes those name plus the parameter definition to 'saveObjectField'.
770
saveConstructor :: LuxiConstructor -- ^ The constructor
771
                -> Q Clause        -- ^ Resulting clause
772
saveConstructor (sname, fields) = do
773
  let cname = mkName sname
774
  fnames <- mapM (newName . fieldVariable) fields
775
  let pat = conP cname (map varP fnames)
776
  let felems = map (uncurry saveObjectField) (zip fnames fields)
777
      -- now build the OP_ID serialisation
778
      opid = [| [( $(stringE "OP_ID"),
779
                   JSON.showJSON $(stringE . deCamelCase $ sname) )] |]
780
      flist = listE (opid:felems)
781
      -- and finally convert all this to a json object
782
      flist' = [| concat $flist |]
783
  clause [pat] (normalB flist') []
784

    
785
-- | Generates the main save opcode function.
786
--
787
-- This builds a per-constructor match clause that contains the
788
-- respective constructor-serialisation code.
789
genSaveOpCode :: Name                          -- ^ Object ype
790
              -> String                        -- ^ To 'JSValue' function name
791
              -> String                        -- ^ To 'JSObject' function name
792
              -> [LuxiConstructor]             -- ^ Object definition
793
              -> (LuxiConstructor -> Q Clause) -- ^ Constructor save fn
794
              -> Bool                          -- ^ Whether to generate
795
                                               -- obj or just a
796
                                               -- list\/tuple of values
797
              -> Q [Dec]
798
genSaveOpCode tname jvalstr tdstr opdefs fn gen_object = do
799
  tdclauses <- mapM fn opdefs
800
  let typecon = ConT tname
801
      jvalname = mkName jvalstr
802
      jvalsig = AppT  (AppT ArrowT typecon) (ConT ''JSON.JSValue)
803
      tdname = mkName tdstr
804
  tdsig <- [t| $(return typecon) -> [(String, JSON.JSValue)] |]
805
  jvalclause <- if gen_object
806
                  then [| $makeObjE . $(varE tdname) |]
807
                  else [| JSON.showJSON . map snd . $(varE tdname) |]
808
  return [ SigD tdname tdsig
809
         , FunD tdname tdclauses
810
         , SigD jvalname jvalsig
811
         , ValD (VarP jvalname) (NormalB jvalclause) []]
812

    
813
-- | Generates load code for a single constructor of the opcode data type.
814
loadConstructor :: OpCodeConstructor -> Q Exp
815
loadConstructor (sname, _, _, fields, _) = do
816
  let name = mkName sname
817
  fbinds <- mapM loadObjectField fields
818
  let (fnames, fstmts) = unzip fbinds
819
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
820
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
821
  return $ DoE fstmts'
822

    
823
-- | Generates the loadOpCode function.
824
genLoadOpCode :: [OpCodeConstructor] -> Q (Dec, Dec)
825
genLoadOpCode opdefs = do
826
  let fname = mkName "loadOpCode"
827
      arg1 = mkName "v"
828
      objname = mkName "o"
829
      opid = mkName "op_id"
830
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
831
                                 (JSON.readJSON $(varE arg1)) |]
832
  st2 <- bindS (varP opid) [| $fromObjE $(varE objname) $(stringE "OP_ID") |]
833
  -- the match results (per-constructor blocks)
834
  mexps <- mapM loadConstructor opdefs
835
  fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
836
  let mpats = map (\(me, (consName, _, _, _, _)) ->
837
                       let mp = LitP . StringL . deCamelCase $ consName
838
                       in Match mp (NormalB me) []
839
                  ) $ zip mexps opdefs
840
      defmatch = Match WildP (NormalB fails) []
841
      cst = NoBindS $ CaseE (VarE opid) $ mpats++[defmatch]
842
      body = DoE [st1, st2, cst]
843
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
844
  return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
845

    
846
-- * Template code for luxi
847

    
848
-- | Constructor-to-string for LuxiOp.
849
genStrOfOp :: Name -> String -> Q [Dec]
850
genStrOfOp = genConstrToStr id
851

    
852
-- | Constructor-to-string for MsgKeys.
853
genStrOfKey :: Name -> String -> Q [Dec]
854
genStrOfKey = genConstrToStr ensureLower
855

    
856
-- | Generates the LuxiOp data type.
857
--
858
-- This takes a Luxi operation definition and builds both the
859
-- datatype and the function transforming the arguments to JSON.
860
-- We can't use anything less generic, because the way different
861
-- operations are serialized differs on both parameter- and top-level.
862
--
863
-- There are two things to be defined for each parameter:
864
--
865
-- * name
866
--
867
-- * type
868
--
869
genLuxiOp :: String -> [LuxiConstructor] -> Q [Dec]
870
genLuxiOp name cons = do
871
  let tname = mkName name
872
  decl_d <- mapM (\(cname, fields) -> do
873
                    -- we only need the type of the field, without Q
874
                    fields' <- mapM actualFieldType fields
875
                    let fields'' = zip (repeat NotStrict) fields'
876
                    return $ NormalC (mkName cname) fields'')
877
            cons
878
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Eq]
879
  save_decs <- genSaveOpCode tname "opToArgs" "opToDict"
880
               cons saveLuxiConstructor False
881
  req_defs <- declareSADT "LuxiReq" .
882
              map (\(str, _) -> ("Req" ++ str, mkName ("luxiReq" ++ str))) $
883
                  cons
884
  return $ declD:save_decs ++ req_defs
885

    
886
-- | Generates the \"save\" clause for entire LuxiOp constructor.
887
saveLuxiConstructor :: LuxiConstructor -> Q Clause
888
saveLuxiConstructor (sname, fields) = do
889
  let cname = mkName sname
890
  fnames <- mapM (newName . fieldVariable) fields
891
  let pat = conP cname (map varP fnames)
892
  let felems = map (uncurry saveObjectField) (zip fnames fields)
893
      flist = [| concat $(listE felems) |]
894
  clause [pat] (normalB flist) []
895

    
896
-- * "Objects" functionality
897

    
898
-- | Extract the field's declaration from a Field structure.
899
fieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
900
fieldTypeInfo field_pfx fd = do
901
  t <- actualFieldType fd
902
  let n = mkName . (field_pfx ++) . fieldRecordName $ fd
903
  return (n, NotStrict, t)
904

    
905
-- | Build an object declaration.
906
buildObject :: String -> String -> [Field] -> Q [Dec]
907
buildObject sname field_pfx fields = do
908
  let name = mkName sname
909
  fields_d <- mapM (fieldTypeInfo field_pfx) fields
910
  let decl_d = RecC name fields_d
911
  let declD = DataD [] name [] [decl_d] [''Show, ''Eq]
912
  ser_decls <- buildObjectSerialisation sname fields
913
  return $ declD:ser_decls
914

    
915
-- | Generates an object definition: data type and its JSON instance.
916
buildObjectSerialisation :: String -> [Field] -> Q [Dec]
917
buildObjectSerialisation sname fields = do
918
  let name = mkName sname
919
  savedecls <- genSaveObject saveObjectField sname fields
920
  (loadsig, loadfn) <- genLoadObject loadObjectField sname fields
921
  shjson <- objectShowJSON sname
922
  rdjson <- objectReadJSON sname
923
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
924
                 [rdjson, shjson]
925
  return $ savedecls ++ [loadsig, loadfn, instdecl]
926

    
927
-- | The toDict function name for a given type.
928
toDictName :: String -> Name
929
toDictName sname = mkName ("toDict" ++ sname)
930

    
931
-- | Generates the save object functionality.
932
genSaveObject :: (Name -> Field -> Q Exp)
933
              -> String -> [Field] -> Q [Dec]
934
genSaveObject save_fn sname fields = do
935
  let name = mkName sname
936
  fnames <- mapM (newName . fieldVariable) fields
937
  let pat = conP name (map varP fnames)
938
  let tdname = toDictName sname
939
  tdsigt <- [t| $(conT name) -> [(String, JSON.JSValue)] |]
940

    
941
  let felems = map (uncurry save_fn) (zip fnames fields)
942
      flist = listE felems
943
      -- and finally convert all this to a json object
944
      tdlist = [| concat $flist |]
945
      iname = mkName "i"
946
  tclause <- clause [pat] (normalB tdlist) []
947
  cclause <- [| $makeObjE . $(varE tdname) |]
948
  let fname = mkName ("save" ++ sname)
949
  sigt <- [t| $(conT name) -> JSON.JSValue |]
950
  return [SigD tdname tdsigt, FunD tdname [tclause],
951
          SigD fname sigt, ValD (VarP fname) (NormalB cclause) []]
952

    
953
-- | Generates the code for saving an object's field, handling the
954
-- various types of fields that we have.
955
saveObjectField :: Name -> Field -> Q Exp
956
saveObjectField fvar field =
957
  case fieldIsOptional field of
958
    OptionalOmitNull -> [| case $(varE fvar) of
959
                             Nothing -> []
960
                             Just v  -> [( $nameE, JSON.showJSON v )]
961
                         |]
962
    OptionalSerializeNull -> [| case $(varE fvar) of
963
                                  Nothing -> [( $nameE, JSON.JSNull )]
964
                                  Just v  -> [( $nameE, JSON.showJSON v )]
965
                              |]
966
    NotOptional ->
967
      case fieldShow field of
968
        -- Note: the order of actual:extra is important, since for
969
        -- some serialisation types (e.g. Luxi), we use tuples
970
        -- (positional info) rather than object (name info)
971
        Nothing -> [| [( $nameE, JSON.showJSON $fvarE)] |]
972
        Just fn -> [| let (actual, extra) = $fn $fvarE
973
                      in ($nameE, JSON.showJSON actual):extra
974
                    |]
975
  where nameE = stringE (fieldName field)
976
        fvarE = varE fvar
977

    
978
-- | Generates the showJSON clause for a given object name.
979
objectShowJSON :: String -> Q Dec
980
objectShowJSON name = do
981
  body <- [| JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
982
  return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
983

    
984
-- | Generates the load object functionality.
985
genLoadObject :: (Field -> Q (Name, Stmt))
986
              -> String -> [Field] -> Q (Dec, Dec)
987
genLoadObject load_fn sname fields = do
988
  let name = mkName sname
989
      funname = mkName $ "load" ++ sname
990
      arg1 = mkName $ if null fields then "_" else "v"
991
      objname = mkName "o"
992
      opid = mkName "op_id"
993
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
994
                                 (JSON.readJSON $(varE arg1)) |]
995
  fbinds <- mapM load_fn fields
996
  let (fnames, fstmts) = unzip fbinds
997
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
998
      retstmt = [NoBindS (AppE (VarE 'return) cval)]
999
      -- FIXME: should we require an empty dict for an empty type?
1000
      -- this allows any JSValue right now
1001
      fstmts' = if null fields
1002
                  then retstmt
1003
                  else st1:fstmts ++ retstmt
1004
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT name) |]
1005
  return $ (SigD funname sigt,
1006
            FunD funname [Clause [VarP arg1] (NormalB (DoE fstmts')) []])
1007

    
1008
-- | Generates code for loading an object's field.
1009
loadObjectField :: Field -> Q (Name, Stmt)
1010
loadObjectField field = do
1011
  let name = fieldVariable field
1012
  fvar <- newName name
1013
  -- these are used in all patterns below
1014
  let objvar = varNameE "o"
1015
      objfield = stringE (fieldName field)
1016
      loadexp =
1017
        if fieldIsOptional field /= NotOptional
1018
          -- we treat both optional types the same, since
1019
          -- 'maybeFromObj' can deal with both missing and null values
1020
          -- appropriately (the same)
1021
          then [| $(varE 'maybeFromObj) $objvar $objfield |]
1022
          else case fieldDefault field of
1023
                 Just defv ->
1024
                   [| $(varE 'fromObjWithDefault) $objvar
1025
                      $objfield $defv |]
1026
                 Nothing -> [| $fromObjE $objvar $objfield |]
1027
  bexp <- loadFn field loadexp objvar
1028

    
1029
  return (fvar, BindS (VarP fvar) bexp)
1030

    
1031
-- | Builds the readJSON instance for a given object name.
1032
objectReadJSON :: String -> Q Dec
1033
objectReadJSON name = do
1034
  let s = mkName "s"
1035
  body <- [| case JSON.readJSON $(varE s) of
1036
               JSON.Ok s' -> $(varE .mkName $ "load" ++ name) s'
1037
               JSON.Error e ->
1038
                 JSON.Error $ "Can't parse value for type " ++
1039
                       $(stringE name) ++ ": " ++ e
1040
           |]
1041
  return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
1042

    
1043
-- * Inheritable parameter tables implementation
1044

    
1045
-- | Compute parameter type names.
1046
paramTypeNames :: String -> (String, String)
1047
paramTypeNames root = ("Filled"  ++ root ++ "Params",
1048
                       "Partial" ++ root ++ "Params")
1049

    
1050
-- | Compute information about the type of a parameter field.
1051
paramFieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
1052
paramFieldTypeInfo field_pfx fd = do
1053
  t <- actualFieldType fd
1054
  let n = mkName . (++ "P") . (field_pfx ++) .
1055
          fieldRecordName $ fd
1056
  return (n, NotStrict, AppT (ConT ''Maybe) t)
1057

    
1058
-- | Build a parameter declaration.
1059
--
1060
-- This function builds two different data structures: a /filled/ one,
1061
-- in which all fields are required, and a /partial/ one, in which all
1062
-- fields are optional. Due to the current record syntax issues, the
1063
-- fields need to be named differrently for the two structures, so the
1064
-- partial ones get a /P/ suffix.
1065
buildParam :: String -> String -> [Field] -> Q [Dec]
1066
buildParam sname field_pfx fields = do
1067
  let (sname_f, sname_p) = paramTypeNames sname
1068
      name_f = mkName sname_f
1069
      name_p = mkName sname_p
1070
  fields_f <- mapM (fieldTypeInfo field_pfx) fields
1071
  fields_p <- mapM (paramFieldTypeInfo field_pfx) fields
1072
  let decl_f = RecC name_f fields_f
1073
      decl_p = RecC name_p fields_p
1074
  let declF = DataD [] name_f [] [decl_f] [''Show, ''Eq]
1075
      declP = DataD [] name_p [] [decl_p] [''Show, ''Eq]
1076
  ser_decls_f <- buildObjectSerialisation sname_f fields
1077
  ser_decls_p <- buildPParamSerialisation sname_p fields
1078
  fill_decls <- fillParam sname field_pfx fields
1079
  return $ [declF, declP] ++ ser_decls_f ++ ser_decls_p ++ fill_decls ++
1080
           buildParamAllFields sname fields ++
1081
           buildDictObjectInst name_f sname_f
1082

    
1083
-- | Builds a list of all fields of a parameter.
1084
buildParamAllFields :: String -> [Field] -> [Dec]
1085
buildParamAllFields sname fields =
1086
  let vname = mkName ("all" ++ sname ++ "ParamFields")
1087
      sig = SigD vname (AppT ListT (ConT ''String))
1088
      val = ListE $ map (LitE . StringL . fieldName) fields
1089
  in [sig, ValD (VarP vname) (NormalB val) []]
1090

    
1091
-- | Builds the 'DictObject' instance for a filled parameter.
1092
buildDictObjectInst :: Name -> String -> [Dec]
1093
buildDictObjectInst name sname =
1094
  [InstanceD [] (AppT (ConT ''DictObject) (ConT name))
1095
   [ValD (VarP 'toDict) (NormalB (VarE (toDictName sname))) []]]
1096

    
1097
-- | Generates the serialisation for a partial parameter.
1098
buildPParamSerialisation :: String -> [Field] -> Q [Dec]
1099
buildPParamSerialisation sname fields = do
1100
  let name = mkName sname
1101
  savedecls <- genSaveObject savePParamField sname fields
1102
  (loadsig, loadfn) <- genLoadObject loadPParamField sname fields
1103
  shjson <- objectShowJSON sname
1104
  rdjson <- objectReadJSON sname
1105
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
1106
                 [rdjson, shjson]
1107
  return $ savedecls ++ [loadsig, loadfn, instdecl]
1108

    
1109
-- | Generates code to save an optional parameter field.
1110
savePParamField :: Name -> Field -> Q Exp
1111
savePParamField fvar field = do
1112
  checkNonOptDef field
1113
  let actualVal = mkName "v"
1114
  normalexpr <- saveObjectField actualVal field
1115
  -- we have to construct the block here manually, because we can't
1116
  -- splice-in-splice
1117
  return $ CaseE (VarE fvar) [ Match (ConP 'Nothing [])
1118
                                       (NormalB (ConE '[])) []
1119
                             , Match (ConP 'Just [VarP actualVal])
1120
                                       (NormalB normalexpr) []
1121
                             ]
1122

    
1123
-- | Generates code to load an optional parameter field.
1124
loadPParamField :: Field -> Q (Name, Stmt)
1125
loadPParamField field = do
1126
  checkNonOptDef field
1127
  let name = fieldName field
1128
  fvar <- newName name
1129
  -- these are used in all patterns below
1130
  let objvar = varNameE "o"
1131
      objfield = stringE name
1132
      loadexp = [| $(varE 'maybeFromObj) $objvar $objfield |]
1133
  bexp <- loadFn field loadexp objvar
1134
  return (fvar, BindS (VarP fvar) bexp)
1135

    
1136
-- | Builds a simple declaration of type @n_x = fromMaybe f_x p_x@.
1137
buildFromMaybe :: String -> Q Dec
1138
buildFromMaybe fname =
1139
  valD (varP (mkName $ "n_" ++ fname))
1140
         (normalB [| $(varE 'fromMaybe)
1141
                        $(varNameE $ "f_" ++ fname)
1142
                        $(varNameE $ "p_" ++ fname) |]) []
1143

    
1144
-- | Builds a function that executes the filling of partial parameter
1145
-- from a full copy (similar to Python's fillDict).
1146
fillParam :: String -> String -> [Field] -> Q [Dec]
1147
fillParam sname field_pfx fields = do
1148
  let fnames = map (\fd -> field_pfx ++ fieldRecordName fd) fields
1149
      (sname_f, sname_p) = paramTypeNames sname
1150
      oname_f = "fobj"
1151
      oname_p = "pobj"
1152
      name_f = mkName sname_f
1153
      name_p = mkName sname_p
1154
      fun_name = mkName $ "fill" ++ sname ++ "Params"
1155
      le_full = ValD (ConP name_f (map (VarP . mkName . ("f_" ++)) fnames))
1156
                (NormalB . VarE . mkName $ oname_f) []
1157
      le_part = ValD (ConP name_p (map (VarP . mkName . ("p_" ++)) fnames))
1158
                (NormalB . VarE . mkName $ oname_p) []
1159
      obj_new = foldl (\accu vname -> AppE accu (VarE vname)) (ConE name_f)
1160
                $ map (mkName . ("n_" ++)) fnames
1161
  le_new <- mapM buildFromMaybe fnames
1162
  funt <- [t| $(conT name_f) -> $(conT name_p) -> $(conT name_f) |]
1163
  let sig = SigD fun_name funt
1164
      fclause = Clause [VarP (mkName oname_f), VarP (mkName oname_p)]
1165
                (NormalB $ LetE (le_full:le_part:le_new) obj_new) []
1166
      fun = FunD fun_name [fclause]
1167
  return [sig, fun]
1168

    
1169
-- * Template code for exceptions
1170

    
1171
-- | Exception simple error message field.
1172
excErrMsg :: (String, Q Type)
1173
excErrMsg = ("errMsg", [t| String |])
1174

    
1175
-- | Builds an exception type definition.
1176
genException :: String                  -- ^ Name of new type
1177
             -> SimpleObject -- ^ Constructor name and parameters
1178
             -> Q [Dec]
1179
genException name cons = do
1180
  let tname = mkName name
1181
  declD <- buildSimpleCons tname cons
1182
  (savesig, savefn) <- genSaveSimpleObj tname ("save" ++ name) cons $
1183
                         uncurry saveExcCons
1184
  (loadsig, loadfn) <- genLoadExc tname ("load" ++ name) cons
1185
  return [declD, loadsig, loadfn, savesig, savefn]
1186

    
1187
-- | Generates the \"save\" clause for an entire exception constructor.
1188
--
1189
-- This matches the exception with variables named the same as the
1190
-- constructor fields (just so that the spliced in code looks nicer),
1191
-- and calls showJSON on it.
1192
saveExcCons :: String        -- ^ The constructor name
1193
            -> [SimpleField] -- ^ The parameter definitions for this
1194
                             -- constructor
1195
            -> Q Clause      -- ^ Resulting clause
1196
saveExcCons sname fields = do
1197
  let cname = mkName sname
1198
  fnames <- mapM (newName . fst) fields
1199
  let pat = conP cname (map varP fnames)
1200
      felems = if null fnames
1201
                 then conE '() -- otherwise, empty list has no type
1202
                 else listE $ map (\f -> [| JSON.showJSON $(varE f) |]) fnames
1203
  let tup = tupE [ litE (stringL sname), felems ]
1204
  clause [pat] (normalB [| JSON.showJSON $tup |]) []
1205

    
1206
-- | Generates load code for a single constructor of an exception.
1207
--
1208
-- Generates the code (if there's only one argument, we will use a
1209
-- list, not a tuple:
1210
--
1211
-- @
1212
-- do
1213
--  (x1, x2, ...) <- readJSON args
1214
--  return $ Cons x1 x2 ...
1215
-- @
1216
loadExcConstructor :: Name -> String -> [SimpleField] -> Q Exp
1217
loadExcConstructor inname sname fields = do
1218
  let name = mkName sname
1219
  f_names <- mapM (newName . fst) fields
1220
  let read_args = AppE (VarE 'JSON.readJSON) (VarE inname)
1221
  let binds = case f_names of
1222
                [x] -> BindS (ListP [VarP x])
1223
                _   -> BindS (TupP (map VarP f_names))
1224
      cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) f_names
1225
  return $ DoE [binds read_args, NoBindS (AppE (VarE 'return) cval)]
1226

    
1227
{-| Generates the loadException function.
1228

    
1229
This generates a quite complicated function, along the lines of:
1230

    
1231
@
1232
loadFn (JSArray [JSString name, args]) = case name of
1233
   "A1" -> do
1234
     (x1, x2, ...) <- readJSON args
1235
     return $ A1 x1 x2 ...
1236
   "a2" -> ...
1237
   s -> fail $ "Unknown exception" ++ s
1238
loadFn v = fail $ "Expected array but got " ++ show v
1239
@
1240
-}
1241
genLoadExc :: Name -> String -> SimpleObject -> Q (Dec, Dec)
1242
genLoadExc tname sname opdefs = do
1243
  let fname = mkName sname
1244
  exc_name <- newName "name"
1245
  exc_args <- newName "args"
1246
  exc_else <- newName "s"
1247
  arg_else <- newName "v"
1248
  fails <- [| fail $ "Unknown exception '" ++ $(varE exc_else) ++ "'" |]
1249
  -- default match for unknown exception name
1250
  let defmatch = Match (VarP exc_else) (NormalB fails) []
1251
  -- the match results (per-constructor blocks)
1252
  str_matches <-
1253
    mapM (\(s, params) -> do
1254
            body_exp <- loadExcConstructor exc_args s params
1255
            return $ Match (LitP (StringL s)) (NormalB body_exp) [])
1256
    opdefs
1257
  -- the first function clause; we can't use [| |] due to TH
1258
  -- limitations, so we have to build the AST by hand
1259
  let clause1 = Clause [ConP 'JSON.JSArray
1260
                               [ListP [ConP 'JSON.JSString [VarP exc_name],
1261
                                            VarP exc_args]]]
1262
                (NormalB (CaseE (AppE (VarE 'JSON.fromJSString)
1263
                                        (VarE exc_name))
1264
                          (str_matches ++ [defmatch]))) []
1265
  -- the fail expression for the second function clause
1266
  fail_type <- [| fail $ "Invalid exception: expected '(string, [args])' " ++
1267
                  "      but got " ++ show (pp_value $(varE arg_else)) ++ "'"
1268
                |]
1269
  -- the second function clause
1270
  let clause2 = Clause [VarP arg_else] (NormalB fail_type) []
1271
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT tname) |]
1272
  return $ (SigD fname sigt, FunD fname [clause1, clause2])