Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / THH.hs @ 85bcb1de

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 PyValue a where
566
  showValue :: a -> String
567

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

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

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

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

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

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

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

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

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

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

    
665

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

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

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

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

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

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

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

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

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

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

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

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

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

    
845
-- * Template code for luxi
846

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

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

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

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

    
895
-- * "Objects" functionality
896

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

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

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

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

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

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

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

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

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

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

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

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

    
1042
-- * Inheritable parameter tables implementation
1043

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

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

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

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

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

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

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

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

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

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

    
1168
-- * Template code for exceptions
1169

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

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

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

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

    
1226
{-| Generates the loadException function.
1227

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

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