Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / THH.hs @ b775af80

History | View | Annotate | Download (51.1 kB)

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

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

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

    
9
-}
10

    
11
{-
12

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

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

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

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

    
30
-}
31

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

    
73
import Control.Arrow ((&&&))
74
import Control.Applicative
75
import Control.Monad
76
import Data.Attoparsec () -- Needed to prevent spurious GHC 7.4 linking errors.
77
  -- See issue #683 and https://ghc.haskell.org/trac/ghc/ticket/4899
78
import Data.Char
79
import Data.List
80
import Data.Maybe
81
import qualified Data.Map as M
82
import qualified Data.Set as Set
83
import Language.Haskell.TH
84
import System.Time (ClockTime(..))
85

    
86
import qualified Text.JSON as JSON
87
import Text.JSON.Pretty (pp_value)
88

    
89
import Ganeti.JSON
90
import Ganeti.PyValue
91
import Ganeti.THH.PyType
92

    
93

    
94
-- * Exported types
95

    
96
-- | Class of objects that can be converted to 'JSObject'
97
-- lists-format.
98
class DictObject a where
99
  toDict :: a -> [(String, JSON.JSValue)]
100

    
101
-- | Optional field information.
102
data OptionalType
103
  = NotOptional           -- ^ Field is not optional
104
  | OptionalOmitNull      -- ^ Field is optional, null is not serialised
105
  | OptionalSerializeNull -- ^ Field is optional, null is serialised
106
  | AndRestArguments      -- ^ Special field capturing all the remaining fields
107
                          -- as plain JSON values
108
  deriving (Show, Eq)
109

    
110
-- | Serialised field data type describing how to generate code for the field.
111
-- Each field has a type, which isn't captured in the type of the data type,
112
-- but is saved in the 'Q' monad in 'fieldType'.
113
--
114
-- Let @t@ be a type we want to parametrize the field with. There are the
115
-- following possible types of fields:
116
--
117
--   [Mandatory with no default.] Then @fieldType@ holds @t@,
118
--     @fieldDefault = Nothing@ and @fieldIsOptional = NotOptional@.
119
--
120
--   [Field with a default value.] Then @fieldType@ holds @t@ and
121
--     @fieldDefault = Just exp@ where @exp@ is an expression of type @t@ and
122
--    @fieldIsOptional = NotOptional@.
123
--
124
--   [Optional, no default value.] Then @fieldType@ holds @Maybe t@,
125
--     @fieldDefault = Nothing@ and @fieldIsOptional@ is either
126
--     'OptionalOmitNull' or 'OptionalSerializeNull'.
127
--
128
-- Optional fields with a default value are prohibited, as their main
129
-- intention is to represent the information that a request didn't contain
130
-- the field data.
131
--
132
-- /Custom (de)serialization:/
133
-- Field can have custom (de)serialization functions that are stored in
134
-- 'fieldRead' and 'fieldShow'. If they aren't provided, the default is to use
135
-- 'readJSON' and 'showJSON' for the field's type @t@. If they are provided,
136
-- the type of the contained deserializing expression must be
137
--
138
-- @
139
--   [(String, JSON.JSValue)] -> JSON.JSValue -> JSON.Result t
140
-- @
141
--
142
-- where the first argument carries the whole record in the case the
143
-- deserializing function needs to process additional information.
144
--
145
-- The type of the contained serializing experssion must be
146
--
147
-- @
148
--   t -> (JSON.JSValue, [(String, JSON.JSValue)])
149
-- @
150
--
151
-- where the result can provide extra JSON fields to include in the output
152
-- record (or just return @[]@ if they're not needed).
153
--
154
-- Note that for optional fields the type appearing in the custom functions
155
-- is still @t@. Therefore making a field optional doesn't change the
156
-- functions.
157
--
158
-- There is also a special type of optional field 'AndRestArguments' which
159
-- allows to parse any additional arguments not covered by other fields. There
160
-- can be at most one such special field and it's type must be
161
-- @Map String JSON.JSValue@. See also 'andRestArguments'.
162
data Field = Field { fieldName        :: String
163
                   , fieldType        :: Q Type
164
                     -- ^ the type of the field, @t@ for non-optional fields,
165
                     -- @Maybe t@ for optional ones.
166
                   , fieldRead        :: Maybe (Q Exp)
167
                     -- ^ an optional custom deserialization function of type
168
                     -- @[(String, JSON.JSValue)] -> JSON.JSValue ->
169
                     -- JSON.Result t@
170
                   , fieldShow        :: Maybe (Q Exp)
171
                     -- ^ an optional custom serialization function of type
172
                     -- @t -> (JSON.JSValue, [(String, JSON.JSValue)])@
173
                   , fieldExtraKeys   :: [String]
174
                   , fieldDefault     :: Maybe (Q Exp)
175
                     -- ^ an optional default value of type @t@
176
                   , fieldConstr      :: Maybe String
177
                   , fieldIsOptional  :: OptionalType
178
                     -- ^ determines if a field is optional, and if yes,
179
                     -- how
180
                   , fieldDoc         :: String
181
                   }
182

    
183
-- | Generates a simple field.
184
simpleField :: String -> Q Type -> Field
185
simpleField fname ftype =
186
  Field { fieldName        = fname
187
        , fieldType        = ftype
188
        , fieldRead        = Nothing
189
        , fieldShow        = Nothing
190
        , fieldExtraKeys   = []
191
        , fieldDefault     = Nothing
192
        , fieldConstr      = Nothing
193
        , fieldIsOptional  = NotOptional
194
        , fieldDoc         = ""
195
        }
196

    
197
-- | Generate an AndRestArguments catch-all field.
198
andRestArguments :: String -> Field
199
andRestArguments fname =
200
  Field { fieldName        = fname
201
        , fieldType        = [t| M.Map String JSON.JSValue |]
202
        , fieldRead        = Nothing
203
        , fieldShow        = Nothing
204
        , fieldExtraKeys   = []
205
        , fieldDefault     = Nothing
206
        , fieldConstr      = Nothing
207
        , fieldIsOptional  = AndRestArguments
208
        , fieldDoc         = ""
209
        }
210

    
211
withDoc :: String -> Field -> Field
212
withDoc doc field =
213
  field { fieldDoc = doc }
214

    
215
-- | Sets the renamed constructor field.
216
renameField :: String -> Field -> Field
217
renameField constrName field = field { fieldConstr = Just constrName }
218

    
219
-- | Sets the default value on a field (makes it optional with a
220
-- default value).
221
defaultField :: Q Exp -> Field -> Field
222
defaultField defval field = field { fieldDefault = Just defval }
223

    
224
-- | Marks a field optional (turning its base type into a Maybe).
225
optionalField :: Field -> Field
226
optionalField field = field { fieldIsOptional = OptionalOmitNull }
227

    
228
-- | Marks a field optional (turning its base type into a Maybe), but
229
-- with 'Nothing' serialised explicitly as /null/.
230
optionalNullSerField :: Field -> Field
231
optionalNullSerField field = field { fieldIsOptional = OptionalSerializeNull }
232

    
233
-- | Wrapper around a special parse function, suitable as field-parsing
234
-- function.
235
numericalReadFn :: JSON.JSON a => (String -> JSON.Result a)
236
                   -> [(String, JSON.JSValue)] -> JSON.JSValue -> JSON.Result a
237
numericalReadFn _ _ v@(JSON.JSRational _ _) = JSON.readJSON v
238
numericalReadFn f _ (JSON.JSString x) = f $ JSON.fromJSString x
239
numericalReadFn _ _ _ = JSON.Error "A numerical field has to be a number or\ 
240
                                   \ a string."
241

    
242
-- | Sets the read function to also accept string parsable by the given
243
-- function.
244
specialNumericalField :: Name -> Field -> Field
245
specialNumericalField f field =
246
     field { fieldRead = Just (appE (varE 'numericalReadFn) (varE f)) }
247

    
248
-- | Creates a new mandatory field that reads time as the (floating point)
249
-- number of seconds since the standard UNIX epoch, and represents it in
250
-- Haskell as 'ClockTime'.
251
timeAsDoubleField :: String -> Field
252
timeAsDoubleField fname =
253
  (simpleField fname [t| ClockTime |])
254
    { fieldRead = Just $ [| \_ -> liftM unTimeAsDoubleJSON . JSON.readJSON |]
255
    , fieldShow = Just $ [| \c -> (JSON.showJSON $ TimeAsDoubleJSON c, []) |]
256
    }
257

    
258
-- | Sets custom functions on a field.
259
customField :: Name      -- ^ The name of the read function
260
            -> Name      -- ^ The name of the show function
261
            -> [String]  -- ^ The name of extra field keys
262
            -> Field     -- ^ The original field
263
            -> Field     -- ^ Updated field
264
customField readfn showfn extra field =
265
  field { fieldRead = Just (varE readfn), fieldShow = Just (varE showfn)
266
        , fieldExtraKeys = extra }
267

    
268
-- | Computes the record name for a given field, based on either the
269
-- string value in the JSON serialisation or the custom named if any
270
-- exists.
271
fieldRecordName :: Field -> String
272
fieldRecordName (Field { fieldName = name, fieldConstr = alias }) =
273
  fromMaybe (camelCase name) alias
274

    
275
-- | Computes the preferred variable name to use for the value of this
276
-- field. If the field has a specific constructor name, then we use a
277
-- first-letter-lowercased version of that; otherwise, we simply use
278
-- the field name. See also 'fieldRecordName'.
279
fieldVariable :: Field -> String
280
fieldVariable f =
281
  case (fieldConstr f) of
282
    Just name -> ensureLower name
283
    _ -> map (\c -> if c == '-' then '_' else c) $ fieldName f
284

    
285
-- | Compute the actual field type (taking into account possible
286
-- optional status).
287
actualFieldType :: Field -> Q Type
288
actualFieldType f | fieldIsOptional f `elem` [NotOptional, AndRestArguments] = t
289
                  | otherwise =  [t| Maybe $t |]
290
                  where t = fieldType f
291

    
292
-- | Checks that a given field is not optional (for object types or
293
-- fields which should not allow this case).
294
checkNonOptDef :: (Monad m) => Field -> m ()
295
checkNonOptDef (Field { fieldIsOptional = OptionalOmitNull
296
                      , fieldName = name }) =
297
  fail $ "Optional field " ++ name ++ " used in parameter declaration"
298
checkNonOptDef (Field { fieldIsOptional = OptionalSerializeNull
299
                      , fieldName = name }) =
300
  fail $ "Optional field " ++ name ++ " used in parameter declaration"
301
checkNonOptDef (Field { fieldDefault = (Just _), fieldName = name }) =
302
  fail $ "Default field " ++ name ++ " used in parameter declaration"
303
checkNonOptDef _ = return ()
304

    
305
-- | Construct a function that parses a field value. If the field has
306
-- a custom 'fieldRead', it's applied to @o@ and used. Otherwise
307
-- @JSON.readJSON@ is used.
308
parseFn :: Field   -- ^ The field definition
309
        -> Q Exp   -- ^ The entire object in JSON object format
310
        -> Q Exp   -- ^ The resulting function that parses a JSON message
311
parseFn field o
312
  = maybe [| readJSONWithDesc $(stringE $ fieldName field) False |]
313
          (`appE` o) (fieldRead field)
314

    
315
-- | Produces the expression that will de-serialise a given
316
-- field. Since some custom parsing functions might need to use the
317
-- entire object, we do take and pass the object to any custom read
318
-- functions.
319
loadFn :: Field   -- ^ The field definition
320
       -> Q Exp   -- ^ The value of the field as existing in the JSON message
321
       -> Q Exp   -- ^ The entire object in JSON object format
322
       -> Q Exp   -- ^ Resulting expression
323
loadFn field expr o = [| $expr >>= $(parseFn field o) |]
324

    
325
-- | Just as 'loadFn', but for optional fields.
326
loadFnOpt :: Field   -- ^ The field definition
327
          -> Q Exp   -- ^ The value of the field as existing in the JSON message
328
                     -- as Maybe
329
          -> Q Exp   -- ^ The entire object in JSON object format
330
          -> Q Exp   -- ^ Resulting expression
331
loadFnOpt field@(Field { fieldDefault = Just def }) expr o
332
  = case fieldIsOptional field of
333
      NotOptional -> [| $expr >>= maybe (return $def) $(parseFn field o) |]
334
      _           -> fail $ "Field " ++ fieldName field ++ ":\
335
                            \ A field can't be optional and\
336
                            \ have a default value at the same time."
337
loadFnOpt field expr o
338
  = [| $expr >>= maybe (return Nothing) (liftM Just . $(parseFn field o)) |]
339

    
340
-- * Common field declarations
341

    
342
-- | Timestamp fields description.
343
timeStampFields :: [Field]
344
timeStampFields = map (defaultField [| TOD 0 0 |] . timeAsDoubleField)
345
                      ["ctime", "mtime"]
346

    
347

    
348
-- | Serial number fields description.
349
serialFields :: [Field]
350
serialFields =
351
    [ renameField  "Serial" $ simpleField "serial_no" [t| Int |] ]
352

    
353
-- | UUID fields description.
354
uuidFields :: [Field]
355
uuidFields = [ simpleField "uuid" [t| String |] ]
356

    
357
-- | Tag set type alias.
358
type TagSet = Set.Set String
359

    
360
-- | Tag field description.
361
tagsFields :: [Field]
362
tagsFields = [ defaultField [| Set.empty |] $
363
               simpleField "tags" [t| TagSet |] ]
364

    
365
-- * Internal types
366

    
367
-- | A simple field, in constrast to the customisable 'Field' type.
368
type SimpleField = (String, Q Type)
369

    
370
-- | A definition for a single constructor for a simple object.
371
type SimpleConstructor = (String, [SimpleField])
372

    
373
-- | A definition for ADTs with simple fields.
374
type SimpleObject = [SimpleConstructor]
375

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

    
379
-- | A type alias for a Luxi constructor of a regular object.
380
type LuxiConstructor = (String, [Field])
381

    
382
-- * Helper functions
383

    
384
-- | Ensure first letter is lowercase.
385
--
386
-- Used to convert type name to function prefix, e.g. in @data Aa ->
387
-- aaToRaw@.
388
ensureLower :: String -> String
389
ensureLower [] = []
390
ensureLower (x:xs) = toLower x:xs
391

    
392
-- | Ensure first letter is uppercase.
393
--
394
-- Used to convert constructor name to component
395
ensureUpper :: String -> String
396
ensureUpper [] = []
397
ensureUpper (x:xs) = toUpper x:xs
398

    
399
-- | Helper for quoted expressions.
400
varNameE :: String -> Q Exp
401
varNameE = varE . mkName
402

    
403
-- | showJSON as an expression, for reuse.
404
showJSONE :: Q Exp
405
showJSONE = varE 'JSON.showJSON
406

    
407
-- | makeObj as an expression, for reuse.
408
makeObjE :: Q Exp
409
makeObjE = varE 'JSON.makeObj
410

    
411
-- | fromObj (Ganeti specific) as an expression, for reuse.
412
fromObjE :: Q Exp
413
fromObjE = varE 'fromObj
414

    
415
-- | ToRaw function name.
416
toRawName :: String -> Name
417
toRawName = mkName . (++ "ToRaw") . ensureLower
418

    
419
-- | FromRaw function name.
420
fromRawName :: String -> Name
421
fromRawName = mkName . (++ "FromRaw") . ensureLower
422

    
423
-- | Converts a name to it's varE\/litE representations.
424
reprE :: Either String Name -> Q Exp
425
reprE = either stringE varE
426

    
427
-- | Smarter function application.
428
--
429
-- This does simply f x, except that if is 'id', it will skip it, in
430
-- order to generate more readable code when using -ddump-splices.
431
appFn :: Exp -> Exp -> Exp
432
appFn f x | f == VarE 'id = x
433
          | otherwise = AppE f x
434

    
435
-- | Builds a field for a normal constructor.
436
buildConsField :: Q Type -> StrictTypeQ
437
buildConsField ftype = do
438
  ftype' <- ftype
439
  return (NotStrict, ftype')
440

    
441
-- | Builds a constructor based on a simple definition (not field-based).
442
buildSimpleCons :: Name -> SimpleObject -> Q Dec
443
buildSimpleCons tname cons = do
444
  decl_d <- mapM (\(cname, fields) -> do
445
                    fields' <- mapM (buildConsField . snd) fields
446
                    return $ NormalC (mkName cname) fields') cons
447
  return $ DataD [] tname [] decl_d [''Show, ''Eq]
448

    
449
-- | Generate the save function for a given type.
450
genSaveSimpleObj :: Name                            -- ^ Object type
451
                 -> String                          -- ^ Function name
452
                 -> SimpleObject                    -- ^ Object definition
453
                 -> (SimpleConstructor -> Q Clause) -- ^ Constructor save fn
454
                 -> Q (Dec, Dec)
455
genSaveSimpleObj tname sname opdefs fn = do
456
  let sigt = AppT (AppT ArrowT (ConT tname)) (ConT ''JSON.JSValue)
457
      fname = mkName sname
458
  cclauses <- mapM fn opdefs
459
  return $ (SigD fname sigt, FunD fname cclauses)
460

    
461
-- * Template code for simple raw type-equivalent ADTs
462

    
463
-- | Generates a data type declaration.
464
--
465
-- The type will have a fixed list of instances.
466
strADTDecl :: Name -> [String] -> Dec
467
strADTDecl name constructors =
468
  DataD [] name []
469
          (map (flip NormalC [] . mkName) constructors)
470
          [''Show, ''Eq, ''Enum, ''Bounded, ''Ord]
471

    
472
-- | Generates a toRaw function.
473
--
474
-- This generates a simple function of the form:
475
--
476
-- @
477
-- nameToRaw :: Name -> /traw/
478
-- nameToRaw Cons1 = var1
479
-- nameToRaw Cons2 = \"value2\"
480
-- @
481
genToRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
482
genToRaw traw fname tname constructors = do
483
  let sigt = AppT (AppT ArrowT (ConT tname)) (ConT traw)
484
  -- the body clauses, matching on the constructor and returning the
485
  -- raw value
486
  clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
487
                             (normalB (reprE v)) []) constructors
488
  return [SigD fname sigt, FunD fname clauses]
489

    
490
-- | Generates a fromRaw function.
491
--
492
-- The function generated is monadic and can fail parsing the
493
-- raw value. It is of the form:
494
--
495
-- @
496
-- nameFromRaw :: (Monad m) => /traw/ -> m Name
497
-- nameFromRaw s | s == var1       = Cons1
498
--               | s == \"value2\" = Cons2
499
--               | otherwise = fail /.../
500
-- @
501
genFromRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
502
genFromRaw traw fname tname constructors = do
503
  -- signature of form (Monad m) => String -> m $name
504
  sigt <- [t| (Monad m) => $(conT traw) -> m $(conT tname) |]
505
  -- clauses for a guarded pattern
506
  let varp = mkName "s"
507
      varpe = varE varp
508
  clauses <- mapM (\(c, v) -> do
509
                     -- the clause match condition
510
                     g <- normalG [| $varpe == $(reprE v) |]
511
                     -- the clause result
512
                     r <- [| return $(conE (mkName c)) |]
513
                     return (g, r)) constructors
514
  -- the otherwise clause (fallback)
515
  oth_clause <- do
516
    g <- normalG [| otherwise |]
517
    r <- [|fail ("Invalid string value for type " ++
518
                 $(litE (stringL (nameBase tname))) ++ ": " ++ show $varpe) |]
519
    return (g, r)
520
  let fun = FunD fname [Clause [VarP varp]
521
                        (GuardedB (clauses++[oth_clause])) []]
522
  return [SigD fname sigt, fun]
523

    
524
-- | Generates a data type from a given raw format.
525
--
526
-- The format is expected to multiline. The first line contains the
527
-- type name, and the rest of the lines must contain two words: the
528
-- constructor name and then the string representation of the
529
-- respective constructor.
530
--
531
-- The function will generate the data type declaration, and then two
532
-- functions:
533
--
534
-- * /name/ToRaw, which converts the type to a raw type
535
--
536
-- * /name/FromRaw, which (monadically) converts from a raw type to the type
537
--
538
-- Note that this is basically just a custom show\/read instance,
539
-- nothing else.
540
declareADT
541
  :: (a -> Either String Name) -> Name -> String -> [(String, a)] -> Q [Dec]
542
declareADT fn traw sname cons = do
543
  let name = mkName sname
544
      ddecl = strADTDecl name (map fst cons)
545
      -- process cons in the format expected by genToRaw
546
      cons' = map (\(a, b) -> (a, fn b)) cons
547
  toraw <- genToRaw traw (toRawName sname) name cons'
548
  fromraw <- genFromRaw traw (fromRawName sname) name cons'
549
  return $ ddecl:toraw ++ fromraw
550

    
551
declareLADT :: Name -> String -> [(String, String)] -> Q [Dec]
552
declareLADT = declareADT Left
553

    
554
declareILADT :: String -> [(String, Int)] -> Q [Dec]
555
declareILADT sname cons = do
556
  consNames <- sequence [ newName ('_':n) | (n, _) <- cons ]
557
  consFns <- concat <$> sequence
558
             [ do sig <- sigD n [t| Int |]
559
                  let expr = litE (IntegerL (toInteger i))
560
                  fn <- funD n [clause [] (normalB expr) []]
561
                  return [sig, fn]
562
             | n <- consNames
563
             | (_, i) <- cons ]
564
  let cons' = [ (n, n') | (n, _) <- cons | n' <- consNames ]
565
  (consFns ++) <$> declareADT Right ''Int sname cons'
566

    
567
declareIADT :: String -> [(String, Name)] -> Q [Dec]
568
declareIADT = declareADT Right ''Int
569

    
570
declareSADT :: String -> [(String, Name)] -> Q [Dec]
571
declareSADT = declareADT Right ''String
572

    
573
-- | Creates the showJSON member of a JSON instance declaration.
574
--
575
-- This will create what is the equivalent of:
576
--
577
-- @
578
-- showJSON = showJSON . /name/ToRaw
579
-- @
580
--
581
-- in an instance JSON /name/ declaration
582
genShowJSON :: String -> Q Dec
583
genShowJSON name = do
584
  body <- [| JSON.showJSON . $(varE (toRawName name)) |]
585
  return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
586

    
587
-- | Creates the readJSON member of a JSON instance declaration.
588
--
589
-- This will create what is the equivalent of:
590
--
591
-- @
592
-- readJSON s = case readJSON s of
593
--                Ok s' -> /name/FromRaw s'
594
--                Error e -> Error /description/
595
-- @
596
--
597
-- in an instance JSON /name/ declaration
598
genReadJSON :: String -> Q Dec
599
genReadJSON name = do
600
  let s = mkName "s"
601
  body <- [| $(varE (fromRawName name)) =<<
602
             readJSONWithDesc $(stringE name) True $(varE s) |]
603
  return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
604

    
605
-- | Generates a JSON instance for a given type.
606
--
607
-- This assumes that the /name/ToRaw and /name/FromRaw functions
608
-- have been defined as by the 'declareSADT' function.
609
makeJSONInstance :: Name -> Q [Dec]
610
makeJSONInstance name = do
611
  let base = nameBase name
612
  showJ <- genShowJSON base
613
  readJ <- genReadJSON base
614
  return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) [readJ,showJ]]
615

    
616
-- * Template code for opcodes
617

    
618
-- | Transforms a CamelCase string into an_underscore_based_one.
619
deCamelCase :: String -> String
620
deCamelCase =
621
    intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
622

    
623
-- | Transform an underscore_name into a CamelCase one.
624
camelCase :: String -> String
625
camelCase = concatMap (ensureUpper . drop 1) .
626
            groupBy (\_ b -> b /= '_' && b /= '-') . ('_':)
627

    
628
-- | Computes the name of a given constructor.
629
constructorName :: Con -> Q Name
630
constructorName (NormalC name _) = return name
631
constructorName (RecC name _)    = return name
632
constructorName x                = fail $ "Unhandled constructor " ++ show x
633

    
634
-- | Extract all constructor names from a given type.
635
reifyConsNames :: Name -> Q [String]
636
reifyConsNames name = do
637
  reify_result <- reify name
638
  case reify_result of
639
    TyConI (DataD _ _ _ cons _) -> mapM (liftM nameBase . constructorName) cons
640
    o -> fail $ "Unhandled name passed to reifyConsNames, expected\
641
                \ type constructor but got '" ++ show o ++ "'"
642

    
643
-- | Builds the generic constructor-to-string function.
644
--
645
-- This generates a simple function of the following form:
646
--
647
-- @
648
-- fname (ConStructorOne {}) = trans_fun("ConStructorOne")
649
-- fname (ConStructorTwo {}) = trans_fun("ConStructorTwo")
650
-- @
651
--
652
-- This builds a custom list of name\/string pairs and then uses
653
-- 'genToRaw' to actually generate the function.
654
genConstrToStr :: (String -> String) -> Name -> String -> Q [Dec]
655
genConstrToStr trans_fun name fname = do
656
  cnames <- reifyConsNames name
657
  let svalues = map (Left . trans_fun) cnames
658
  genToRaw ''String (mkName fname) name $ zip cnames svalues
659

    
660
-- | Constructor-to-string for OpCode.
661
genOpID :: Name -> String -> Q [Dec]
662
genOpID = genConstrToStr deCamelCase
663

    
664
-- | Builds a list with all defined constructor names for a type.
665
--
666
-- @
667
-- vstr :: String
668
-- vstr = [...]
669
-- @
670
--
671
-- Where the actual values of the string are the constructor names
672
-- mapped via @trans_fun@.
673
genAllConstr :: (String -> String) -> Name -> String -> Q [Dec]
674
genAllConstr trans_fun name vstr = do
675
  cnames <- reifyConsNames name
676
  let svalues = sort $ map trans_fun cnames
677
      vname = mkName vstr
678
      sig = SigD vname (AppT ListT (ConT ''String))
679
      body = NormalB (ListE (map (LitE . StringL) svalues))
680
  return $ [sig, ValD (VarP vname) body []]
681

    
682
-- | Generates a list of all defined opcode IDs.
683
genAllOpIDs :: Name -> String -> Q [Dec]
684
genAllOpIDs = genAllConstr deCamelCase
685

    
686
-- | OpCode parameter (field) type.
687
type OpParam = (String, Q Type, Q Exp)
688

    
689
-- * Python code generation
690

    
691
data OpCodeField = OpCodeField { ocfName :: String
692
                               , ocfType :: PyType
693
                               , ocfDefl :: Maybe PyValueEx
694
                               , ocfDoc  :: String
695
                               }
696

    
697
-- | Transfers opcode data between the opcode description (through
698
-- @genOpCode@) and the Python code generation functions.
699
data OpCodeDescriptor = OpCodeDescriptor { ocdName   :: String
700
                                         , ocdType   :: PyType
701
                                         , ocdDoc    :: String
702
                                         , ocdFields :: [OpCodeField]
703
                                         , ocdDescr  :: String
704
                                         }
705

    
706
-- | Optionally encapsulates default values in @PyValueEx@.
707
--
708
-- @maybeApp exp typ@ returns a quoted expression that encapsulates
709
-- the default value @exp@ of an opcode parameter cast to @typ@ in a
710
-- @PyValueEx@, if @exp@ is @Just@.  Otherwise, it returns a quoted
711
-- expression with @Nothing@.
712
maybeApp :: Maybe (Q Exp) -> Q Type -> Q Exp
713
maybeApp Nothing _ =
714
  [| Nothing |]
715

    
716
maybeApp (Just expr) typ =
717
  [| Just ($(conE (mkName "PyValueEx")) ($expr :: $typ)) |]
718

    
719
-- | Generates a Python type according to whether the field is
720
-- optional.
721
--
722
-- The type of created expression is PyType.
723
genPyType' :: OptionalType -> Q Type -> Q PyType
724
genPyType' opt typ = typ >>= pyOptionalType (opt /= NotOptional)
725

    
726
-- | Generates Python types from opcode parameters.
727
genPyType :: Field -> Q PyType
728
genPyType f = genPyType' (fieldIsOptional f) (fieldType f)
729

    
730
-- | Generates Python default values from opcode parameters.
731
genPyDefault :: Field -> Q Exp
732
genPyDefault f = maybeApp (fieldDefault f) (fieldType f)
733

    
734
pyField :: Field -> Q Exp
735
pyField f = genPyType f >>= \t ->
736
            [| OpCodeField $(stringE (fieldName f))
737
                           t
738
                           $(genPyDefault f)
739
                           $(stringE (fieldDoc f)) |]
740

    
741
-- | Generates a Haskell function call to "showPyClass" with the
742
-- necessary information on how to build the Python class string.
743
pyClass :: OpCodeConstructor -> Q Exp
744
pyClass (consName, consType, consDoc, consFields, consDscField) =
745
  do let pyClassVar = varNameE "showPyClass"
746
         consName' = stringE consName
747
     consType' <- genPyType' NotOptional consType
748
     let consDoc' = stringE consDoc
749
     [| OpCodeDescriptor $consName'
750
                         consType'
751
                         $consDoc'
752
                         $(listE $ map pyField consFields)
753
                         consDscField |]
754

    
755
-- | Generates a function called "pyClasses" that holds the list of
756
-- all the opcode descriptors necessary for generating the Python
757
-- opcodes.
758
pyClasses :: [OpCodeConstructor] -> Q [Dec]
759
pyClasses cons =
760
  do let name = mkName "pyClasses"
761
         sig = SigD name (AppT ListT (ConT ''OpCodeDescriptor))
762
     fn <- FunD name <$> (:[]) <$> declClause cons
763
     return [sig, fn]
764
  where declClause c =
765
          clause [] (normalB (ListE <$> mapM pyClass c)) []
766

    
767
-- | Converts from an opcode constructor to a Luxi constructor.
768
opcodeConsToLuxiCons :: (a, b, c, d, e) -> (a, d)
769
opcodeConsToLuxiCons (x, _, _, y, _) = (x, y)
770

    
771
-- | Generates the OpCode data type.
772
--
773
-- This takes an opcode logical definition, and builds both the
774
-- datatype and the JSON serialisation out of it. We can't use a
775
-- generic serialisation since we need to be compatible with Ganeti's
776
-- own, so we have a few quirks to work around.
777
genOpCode :: String              -- ^ Type name to use
778
          -> [OpCodeConstructor] -- ^ Constructor name and parameters
779
          -> Q [Dec]
780
genOpCode name cons = do
781
  let tname = mkName name
782
  decl_d <- mapM (\(cname, _, _, fields, _) -> do
783
                    -- we only need the type of the field, without Q
784
                    fields' <- mapM (fieldTypeInfo "op") fields
785
                    return $ RecC (mkName cname) fields')
786
            cons
787
  let declD = DataD [] tname [] decl_d [''Show, ''Eq]
788
  let (allfsig, allffn) = genAllOpFields "allOpFields" cons
789
  save_decs <- genSaveOpCode tname "saveOpCode" "toDictOpCode"
790
               (map opcodeConsToLuxiCons cons) saveConstructor True
791
  (loadsig, loadfn) <- genLoadOpCode cons
792
  pyDecls <- pyClasses cons
793
  return $ [declD, allfsig, allffn, loadsig, loadfn] ++ save_decs ++ pyDecls
794

    
795
-- | Generates the function pattern returning the list of fields for a
796
-- given constructor.
797
genOpConsFields :: OpCodeConstructor -> Clause
798
genOpConsFields (cname, _, _, fields, _) =
799
  let op_id = deCamelCase cname
800
      fvals = map (LitE . StringL) . sort . nub $
801
              concatMap (\f -> fieldName f:fieldExtraKeys f) fields
802
  in Clause [LitP (StringL op_id)] (NormalB $ ListE fvals) []
803

    
804
-- | Generates a list of all fields of an opcode constructor.
805
genAllOpFields  :: String              -- ^ Function name
806
                -> [OpCodeConstructor] -- ^ Object definition
807
                -> (Dec, Dec)
808
genAllOpFields sname opdefs =
809
  let cclauses = map genOpConsFields opdefs
810
      other = Clause [WildP] (NormalB (ListE [])) []
811
      fname = mkName sname
812
      sigt = AppT  (AppT ArrowT (ConT ''String)) (AppT ListT (ConT ''String))
813
  in (SigD fname sigt, FunD fname (cclauses++[other]))
814

    
815
-- | Generates the \"save\" clause for an entire opcode constructor.
816
--
817
-- This matches the opcode with variables named the same as the
818
-- constructor fields (just so that the spliced in code looks nicer),
819
-- and passes those name plus the parameter definition to 'saveObjectField'.
820
saveConstructor :: LuxiConstructor -- ^ The constructor
821
                -> Q Clause        -- ^ Resulting clause
822
saveConstructor (sname, fields) = do
823
  let cname = mkName sname
824
  fnames <- mapM (newName . fieldVariable) fields
825
  let pat = conP cname (map varP fnames)
826
  let felems = map (uncurry saveObjectField) (zip fnames fields)
827
      -- now build the OP_ID serialisation
828
      opid = [| [( $(stringE "OP_ID"),
829
                   JSON.showJSON $(stringE . deCamelCase $ sname) )] |]
830
      flist = listE (opid:felems)
831
      -- and finally convert all this to a json object
832
      flist' = [| concat $flist |]
833
  clause [pat] (normalB flist') []
834

    
835
-- | Generates the main save opcode function.
836
--
837
-- This builds a per-constructor match clause that contains the
838
-- respective constructor-serialisation code.
839
genSaveOpCode :: Name                          -- ^ Object ype
840
              -> String                        -- ^ To 'JSValue' function name
841
              -> String                        -- ^ To 'JSObject' function name
842
              -> [LuxiConstructor]             -- ^ Object definition
843
              -> (LuxiConstructor -> Q Clause) -- ^ Constructor save fn
844
              -> Bool                          -- ^ Whether to generate
845
                                               -- obj or just a
846
                                               -- list\/tuple of values
847
              -> Q [Dec]
848
genSaveOpCode tname jvalstr tdstr opdefs fn gen_object = do
849
  tdclauses <- mapM fn opdefs
850
  let typecon = ConT tname
851
      jvalname = mkName jvalstr
852
      jvalsig = AppT  (AppT ArrowT typecon) (ConT ''JSON.JSValue)
853
      tdname = mkName tdstr
854
  tdsig <- [t| $(return typecon) -> [(String, JSON.JSValue)] |]
855
  jvalclause <- if gen_object
856
                  then [| $makeObjE . $(varE tdname) |]
857
                  else [| JSON.showJSON . map snd . $(varE tdname) |]
858
  return [ SigD tdname tdsig
859
         , FunD tdname tdclauses
860
         , SigD jvalname jvalsig
861
         , ValD (VarP jvalname) (NormalB jvalclause) []]
862

    
863
-- | Generates load code for a single constructor of the opcode data type.
864
loadConstructor :: OpCodeConstructor -> Q Exp
865
loadConstructor (sname, _, _, fields, _) = do
866
  let name = mkName sname
867
  fbinds <- mapM (loadObjectField fields) fields
868
  let (fnames, fstmts) = unzip fbinds
869
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
870
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
871
  return $ DoE fstmts'
872

    
873
-- | Generates the loadOpCode function.
874
genLoadOpCode :: [OpCodeConstructor] -> Q (Dec, Dec)
875
genLoadOpCode opdefs = do
876
  let fname = mkName "loadOpCode"
877
      arg1 = mkName "v"
878
      objname = mkName "o"
879
      opid = mkName "op_id"
880
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
881
                                 (JSON.readJSON $(varE arg1)) |]
882
  st2 <- bindS (varP opid) [| $fromObjE $(varE objname) $(stringE "OP_ID") |]
883
  -- the match results (per-constructor blocks)
884
  mexps <- mapM loadConstructor opdefs
885
  fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
886
  let mpats = map (\(me, (consName, _, _, _, _)) ->
887
                       let mp = LitP . StringL . deCamelCase $ consName
888
                       in Match mp (NormalB me) []
889
                  ) $ zip mexps opdefs
890
      defmatch = Match WildP (NormalB fails) []
891
      cst = NoBindS $ CaseE (VarE opid) $ mpats++[defmatch]
892
      body = DoE [st1, st2, cst]
893
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
894
  return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
895

    
896
-- * Template code for luxi
897

    
898
-- | Constructor-to-string for LuxiOp.
899
genStrOfOp :: Name -> String -> Q [Dec]
900
genStrOfOp = genConstrToStr id
901

    
902
-- | Constructor-to-string for MsgKeys.
903
genStrOfKey :: Name -> String -> Q [Dec]
904
genStrOfKey = genConstrToStr ensureLower
905

    
906
-- | Generates the LuxiOp data type.
907
--
908
-- This takes a Luxi operation definition and builds both the
909
-- datatype and the function transforming the arguments to JSON.
910
-- We can't use anything less generic, because the way different
911
-- operations are serialized differs on both parameter- and top-level.
912
--
913
-- There are two things to be defined for each parameter:
914
--
915
-- * name
916
--
917
-- * type
918
--
919
genLuxiOp :: String -> [LuxiConstructor] -> Q [Dec]
920
genLuxiOp name cons = do
921
  let tname = mkName name
922
  decl_d <- mapM (\(cname, fields) -> do
923
                    -- we only need the type of the field, without Q
924
                    fields' <- mapM actualFieldType fields
925
                    let fields'' = zip (repeat NotStrict) fields'
926
                    return $ NormalC (mkName cname) fields'')
927
            cons
928
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Eq]
929
  save_decs <- genSaveOpCode tname "opToArgs" "opToDict"
930
               cons saveLuxiConstructor False
931
  req_defs <- declareSADT "LuxiReq" .
932
              map (\(str, _) -> ("Req" ++ str, mkName ("luxiReq" ++ str))) $
933
                  cons
934
  return $ declD:save_decs ++ req_defs
935

    
936
-- | Generates the \"save\" clause for entire LuxiOp constructor.
937
saveLuxiConstructor :: LuxiConstructor -> Q Clause
938
saveLuxiConstructor (sname, fields) = do
939
  let cname = mkName sname
940
  fnames <- mapM (newName . fieldVariable) fields
941
  let pat = conP cname (map varP fnames)
942
  let felems = map (uncurry saveObjectField) (zip fnames fields)
943
      flist = [| concat $(listE felems) |]
944
  clause [pat] (normalB flist) []
945

    
946
-- * "Objects" functionality
947

    
948
-- | Extract the field's declaration from a Field structure.
949
fieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
950
fieldTypeInfo field_pfx fd = do
951
  t <- actualFieldType fd
952
  let n = mkName . (field_pfx ++) . fieldRecordName $ fd
953
  return (n, NotStrict, t)
954

    
955
-- | Build an object declaration.
956
buildObject :: String -> String -> [Field] -> Q [Dec]
957
buildObject sname field_pfx fields = do
958
  when (any ((==) AndRestArguments . fieldIsOptional)
959
         . drop 1 $ reverse fields)
960
    $ fail "Objects may have only one AndRestArguments field,\
961
           \ and it must be the last one."
962
  let name = mkName sname
963
  fields_d <- mapM (fieldTypeInfo field_pfx) fields
964
  let decl_d = RecC name fields_d
965
  let declD = DataD [] name [] [decl_d] [''Show, ''Eq]
966
  ser_decls <- buildObjectSerialisation sname fields
967
  return $ declD:ser_decls
968

    
969
-- | Generates an object definition: data type and its JSON instance.
970
buildObjectSerialisation :: String -> [Field] -> Q [Dec]
971
buildObjectSerialisation sname fields = do
972
  let name = mkName sname
973
  savedecls <- genSaveObject saveObjectField sname fields
974
  (loadsig, loadfn) <- genLoadObject (loadObjectField fields) sname fields
975
  shjson <- objectShowJSON sname
976
  rdjson <- objectReadJSON sname
977
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
978
                 [rdjson, shjson]
979
  return $ savedecls ++ [loadsig, loadfn, instdecl]
980

    
981
-- | The toDict function name for a given type.
982
toDictName :: String -> Name
983
toDictName sname = mkName ("toDict" ++ sname)
984

    
985
-- | Generates the save object functionality.
986
genSaveObject :: (Name -> Field -> Q Exp)
987
              -> String -> [Field] -> Q [Dec]
988
genSaveObject save_fn sname fields = do
989
  let name = mkName sname
990
  fnames <- mapM (newName . fieldVariable) fields
991
  let pat = conP name (map varP fnames)
992
  let tdname = toDictName sname
993
  tdsigt <- [t| $(conT name) -> [(String, JSON.JSValue)] |]
994

    
995
  let felems = map (uncurry save_fn) (zip fnames fields)
996
      flist = listE felems
997
      -- and finally convert all this to a json object
998
      tdlist = [| concat $flist |]
999
      iname = mkName "i"
1000
  tclause <- clause [pat] (normalB tdlist) []
1001
  cclause <- [| $makeObjE . $(varE tdname) |]
1002
  let fname = mkName ("save" ++ sname)
1003
  sigt <- [t| $(conT name) -> JSON.JSValue |]
1004
  return [SigD tdname tdsigt, FunD tdname [tclause],
1005
          SigD fname sigt, ValD (VarP fname) (NormalB cclause) []]
1006

    
1007
-- | Generates the code for saving an object's field, handling the
1008
-- various types of fields that we have.
1009
saveObjectField :: Name -> Field -> Q Exp
1010
saveObjectField fvar field =
1011
  let formatFn = fromMaybe [| JSON.showJSON &&& (const []) |] $
1012
                           fieldShow field
1013
      formatCode v = [| let (actual, extra) = $formatFn $(v)
1014
                         in ($nameE, actual) : extra |]
1015
  in case fieldIsOptional field of
1016
    OptionalOmitNull ->       [| case $(fvarE) of
1017
                                   Nothing -> []
1018
                                   Just v  -> $(formatCode [| v |])
1019
                              |]
1020
    OptionalSerializeNull ->  [| case $(fvarE) of
1021
                                   Nothing -> [( $nameE, JSON.JSNull )]
1022
                                   Just v  -> $(formatCode [| v |])
1023
                              |]
1024
    NotOptional ->            formatCode fvarE
1025
    AndRestArguments -> [| M.toList $(varE fvar) |]
1026
  where nameE = stringE (fieldName field)
1027
        fvarE = varE fvar
1028

    
1029
-- | Generates the showJSON clause for a given object name.
1030
objectShowJSON :: String -> Q Dec
1031
objectShowJSON name = do
1032
  body <- [| JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
1033
  return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []]
1034

    
1035
-- | Generates the load object functionality.
1036
genLoadObject :: (Field -> Q (Name, Stmt))
1037
              -> String -> [Field] -> Q (Dec, Dec)
1038
genLoadObject load_fn sname fields = do
1039
  let name = mkName sname
1040
      funname = mkName $ "load" ++ sname
1041
      arg1 = mkName $ if null fields then "_" else "v"
1042
      objname = mkName "o"
1043
      opid = mkName "op_id"
1044
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
1045
                                 (JSON.readJSON $(varE arg1)) |]
1046
  fbinds <- mapM load_fn fields
1047
  let (fnames, fstmts) = unzip fbinds
1048
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
1049
      retstmt = [NoBindS (AppE (VarE 'return) cval)]
1050
      -- FIXME: should we require an empty dict for an empty type?
1051
      -- this allows any JSValue right now
1052
      fstmts' = if null fields
1053
                  then retstmt
1054
                  else st1:fstmts ++ retstmt
1055
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT name) |]
1056
  return $ (SigD funname sigt,
1057
            FunD funname [Clause [VarP arg1] (NormalB (DoE fstmts')) []])
1058

    
1059
-- | Generates code for loading an object's field.
1060
loadObjectField :: [Field] -> Field -> Q (Name, Stmt)
1061
loadObjectField allFields field = do
1062
  let name = fieldVariable field
1063
      names = map fieldVariable allFields
1064
      otherNames = listE . map stringE $ names \\ [name]
1065
  fvar <- newName name
1066
  -- these are used in all patterns below
1067
  let objvar = varNameE "o"
1068
      objfield = stringE (fieldName field)
1069
  bexp <- case (fieldDefault field, fieldIsOptional field) of
1070
            -- Only non-optional fields without defaults must have a value;
1071
            -- we treat both optional types the same, since
1072
            -- 'maybeFromObj' can deal with both missing and null values
1073
            -- appropriately (the same)
1074
            (Nothing, NotOptional) ->
1075
                  loadFn field [| fromObj $objvar $objfield |] objvar
1076
            -- AndRestArguments need not to be parsed at all,
1077
            -- they're just extracted from the list of other fields.
1078
            (Nothing, AndRestArguments) ->
1079
                  [| return . M.fromList
1080
                     $ filter (not . (`elem` $otherNames) . fst) $objvar |]
1081
            _ ->  loadFnOpt field [| maybeFromObj $objvar $objfield |] objvar
1082

    
1083
  return (fvar, BindS (VarP fvar) bexp)
1084

    
1085
-- | Builds the readJSON instance for a given object name.
1086
objectReadJSON :: String -> Q Dec
1087
objectReadJSON name = do
1088
  let s = mkName "s"
1089
  body <- [| $(varE . mkName $ "load" ++ name) =<<
1090
             readJSONWithDesc $(stringE name) False $(varE s) |]
1091
  return $ FunD 'JSON.readJSON [Clause [VarP s] (NormalB body) []]
1092

    
1093
-- * Inheritable parameter tables implementation
1094

    
1095
-- | Compute parameter type names.
1096
paramTypeNames :: String -> (String, String)
1097
paramTypeNames root = ("Filled"  ++ root ++ "Params",
1098
                       "Partial" ++ root ++ "Params")
1099

    
1100
-- | Compute information about the type of a parameter field.
1101
paramFieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
1102
paramFieldTypeInfo field_pfx fd = do
1103
  t <- actualFieldType fd
1104
  let n = mkName . (++ "P") . (field_pfx ++) .
1105
          fieldRecordName $ fd
1106
  return (n, NotStrict, AppT (ConT ''Maybe) t)
1107

    
1108
-- | Build a parameter declaration.
1109
--
1110
-- This function builds two different data structures: a /filled/ one,
1111
-- in which all fields are required, and a /partial/ one, in which all
1112
-- fields are optional. Due to the current record syntax issues, the
1113
-- fields need to be named differrently for the two structures, so the
1114
-- partial ones get a /P/ suffix.
1115
buildParam :: String -> String -> [Field] -> Q [Dec]
1116
buildParam sname field_pfx fields = do
1117
  let (sname_f, sname_p) = paramTypeNames sname
1118
      name_f = mkName sname_f
1119
      name_p = mkName sname_p
1120
  fields_f <- mapM (fieldTypeInfo field_pfx) fields
1121
  fields_p <- mapM (paramFieldTypeInfo field_pfx) fields
1122
  let decl_f = RecC name_f fields_f
1123
      decl_p = RecC name_p fields_p
1124
  let declF = DataD [] name_f [] [decl_f] [''Show, ''Eq]
1125
      declP = DataD [] name_p [] [decl_p] [''Show, ''Eq]
1126
  ser_decls_f <- buildObjectSerialisation sname_f fields
1127
  ser_decls_p <- buildPParamSerialisation sname_p fields
1128
  fill_decls <- fillParam sname field_pfx fields
1129
  return $ [declF, declP] ++ ser_decls_f ++ ser_decls_p ++ fill_decls ++
1130
           buildParamAllFields sname fields ++
1131
           buildDictObjectInst name_f sname_f
1132

    
1133
-- | Builds a list of all fields of a parameter.
1134
buildParamAllFields :: String -> [Field] -> [Dec]
1135
buildParamAllFields sname fields =
1136
  let vname = mkName ("all" ++ sname ++ "ParamFields")
1137
      sig = SigD vname (AppT ListT (ConT ''String))
1138
      val = ListE $ map (LitE . StringL . fieldName) fields
1139
  in [sig, ValD (VarP vname) (NormalB val) []]
1140

    
1141
-- | Builds the 'DictObject' instance for a filled parameter.
1142
buildDictObjectInst :: Name -> String -> [Dec]
1143
buildDictObjectInst name sname =
1144
  [InstanceD [] (AppT (ConT ''DictObject) (ConT name))
1145
   [ValD (VarP 'toDict) (NormalB (VarE (toDictName sname))) []]]
1146

    
1147
-- | Generates the serialisation for a partial parameter.
1148
buildPParamSerialisation :: String -> [Field] -> Q [Dec]
1149
buildPParamSerialisation sname fields = do
1150
  let name = mkName sname
1151
  savedecls <- genSaveObject savePParamField sname fields
1152
  (loadsig, loadfn) <- genLoadObject loadPParamField sname fields
1153
  shjson <- objectShowJSON sname
1154
  rdjson <- objectReadJSON sname
1155
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
1156
                 [rdjson, shjson]
1157
  return $ savedecls ++ [loadsig, loadfn, instdecl]
1158

    
1159
-- | Generates code to save an optional parameter field.
1160
savePParamField :: Name -> Field -> Q Exp
1161
savePParamField fvar field = do
1162
  checkNonOptDef field
1163
  let actualVal = mkName "v"
1164
  normalexpr <- saveObjectField actualVal field
1165
  -- we have to construct the block here manually, because we can't
1166
  -- splice-in-splice
1167
  return $ CaseE (VarE fvar) [ Match (ConP 'Nothing [])
1168
                                       (NormalB (ConE '[])) []
1169
                             , Match (ConP 'Just [VarP actualVal])
1170
                                       (NormalB normalexpr) []
1171
                             ]
1172

    
1173
-- | Generates code to load an optional parameter field.
1174
loadPParamField :: Field -> Q (Name, Stmt)
1175
loadPParamField field = do
1176
  checkNonOptDef field
1177
  let name = fieldName field
1178
  fvar <- newName name
1179
  -- these are used in all patterns below
1180
  let objvar = varNameE "o"
1181
      objfield = stringE name
1182
      loadexp = [| $(varE 'maybeFromObj) $objvar $objfield |]
1183
  bexp <- loadFnOpt field loadexp objvar
1184
  return (fvar, BindS (VarP fvar) bexp)
1185

    
1186
-- | Builds a simple declaration of type @n_x = fromMaybe f_x p_x@.
1187
buildFromMaybe :: String -> Q Dec
1188
buildFromMaybe fname =
1189
  valD (varP (mkName $ "n_" ++ fname))
1190
         (normalB [| $(varE 'fromMaybe)
1191
                        $(varNameE $ "f_" ++ fname)
1192
                        $(varNameE $ "p_" ++ fname) |]) []
1193

    
1194
-- | Builds a function that executes the filling of partial parameter
1195
-- from a full copy (similar to Python's fillDict).
1196
fillParam :: String -> String -> [Field] -> Q [Dec]
1197
fillParam sname field_pfx fields = do
1198
  let fnames = map (\fd -> field_pfx ++ fieldRecordName fd) fields
1199
      (sname_f, sname_p) = paramTypeNames sname
1200
      oname_f = "fobj"
1201
      oname_p = "pobj"
1202
      name_f = mkName sname_f
1203
      name_p = mkName sname_p
1204
      fun_name = mkName $ "fill" ++ sname ++ "Params"
1205
      le_full = ValD (ConP name_f (map (VarP . mkName . ("f_" ++)) fnames))
1206
                (NormalB . VarE . mkName $ oname_f) []
1207
      le_part = ValD (ConP name_p (map (VarP . mkName . ("p_" ++)) fnames))
1208
                (NormalB . VarE . mkName $ oname_p) []
1209
      obj_new = foldl (\accu vname -> AppE accu (VarE vname)) (ConE name_f)
1210
                $ map (mkName . ("n_" ++)) fnames
1211
  le_new <- mapM buildFromMaybe fnames
1212
  funt <- [t| $(conT name_f) -> $(conT name_p) -> $(conT name_f) |]
1213
  let sig = SigD fun_name funt
1214
      fclause = Clause [VarP (mkName oname_f), VarP (mkName oname_p)]
1215
                (NormalB $ LetE (le_full:le_part:le_new) obj_new) []
1216
      fun = FunD fun_name [fclause]
1217
  return [sig, fun]
1218

    
1219
-- * Template code for exceptions
1220

    
1221
-- | Exception simple error message field.
1222
excErrMsg :: (String, Q Type)
1223
excErrMsg = ("errMsg", [t| String |])
1224

    
1225
-- | Builds an exception type definition.
1226
genException :: String                  -- ^ Name of new type
1227
             -> SimpleObject -- ^ Constructor name and parameters
1228
             -> Q [Dec]
1229
genException name cons = do
1230
  let tname = mkName name
1231
  declD <- buildSimpleCons tname cons
1232
  (savesig, savefn) <- genSaveSimpleObj tname ("save" ++ name) cons $
1233
                         uncurry saveExcCons
1234
  (loadsig, loadfn) <- genLoadExc tname ("load" ++ name) cons
1235
  return [declD, loadsig, loadfn, savesig, savefn]
1236

    
1237
-- | Generates the \"save\" clause for an entire exception constructor.
1238
--
1239
-- This matches the exception with variables named the same as the
1240
-- constructor fields (just so that the spliced in code looks nicer),
1241
-- and calls showJSON on it.
1242
saveExcCons :: String        -- ^ The constructor name
1243
            -> [SimpleField] -- ^ The parameter definitions for this
1244
                             -- constructor
1245
            -> Q Clause      -- ^ Resulting clause
1246
saveExcCons sname fields = do
1247
  let cname = mkName sname
1248
  fnames <- mapM (newName . fst) fields
1249
  let pat = conP cname (map varP fnames)
1250
      felems = if null fnames
1251
                 then conE '() -- otherwise, empty list has no type
1252
                 else listE $ map (\f -> [| JSON.showJSON $(varE f) |]) fnames
1253
  let tup = tupE [ litE (stringL sname), felems ]
1254
  clause [pat] (normalB [| JSON.showJSON $tup |]) []
1255

    
1256
-- | Generates load code for a single constructor of an exception.
1257
--
1258
-- Generates the code (if there's only one argument, we will use a
1259
-- list, not a tuple:
1260
--
1261
-- @
1262
-- do
1263
--  (x1, x2, ...) <- readJSON args
1264
--  return $ Cons x1 x2 ...
1265
-- @
1266
loadExcConstructor :: Name -> String -> [SimpleField] -> Q Exp
1267
loadExcConstructor inname sname fields = do
1268
  let name = mkName sname
1269
  f_names <- mapM (newName . fst) fields
1270
  let read_args = AppE (VarE 'JSON.readJSON) (VarE inname)
1271
  let binds = case f_names of
1272
                [x] -> BindS (ListP [VarP x])
1273
                _   -> BindS (TupP (map VarP f_names))
1274
      cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) f_names
1275
  return $ DoE [binds read_args, NoBindS (AppE (VarE 'return) cval)]
1276

    
1277
{-| Generates the loadException function.
1278

    
1279
This generates a quite complicated function, along the lines of:
1280

    
1281
@
1282
loadFn (JSArray [JSString name, args]) = case name of
1283
   "A1" -> do
1284
     (x1, x2, ...) <- readJSON args
1285
     return $ A1 x1 x2 ...
1286
   "a2" -> ...
1287
   s -> fail $ "Unknown exception" ++ s
1288
loadFn v = fail $ "Expected array but got " ++ show v
1289
@
1290
-}
1291
genLoadExc :: Name -> String -> SimpleObject -> Q (Dec, Dec)
1292
genLoadExc tname sname opdefs = do
1293
  let fname = mkName sname
1294
  exc_name <- newName "name"
1295
  exc_args <- newName "args"
1296
  exc_else <- newName "s"
1297
  arg_else <- newName "v"
1298
  fails <- [| fail $ "Unknown exception '" ++ $(varE exc_else) ++ "'" |]
1299
  -- default match for unknown exception name
1300
  let defmatch = Match (VarP exc_else) (NormalB fails) []
1301
  -- the match results (per-constructor blocks)
1302
  str_matches <-
1303
    mapM (\(s, params) -> do
1304
            body_exp <- loadExcConstructor exc_args s params
1305
            return $ Match (LitP (StringL s)) (NormalB body_exp) [])
1306
    opdefs
1307
  -- the first function clause; we can't use [| |] due to TH
1308
  -- limitations, so we have to build the AST by hand
1309
  let clause1 = Clause [ConP 'JSON.JSArray
1310
                               [ListP [ConP 'JSON.JSString [VarP exc_name],
1311
                                            VarP exc_args]]]
1312
                (NormalB (CaseE (AppE (VarE 'JSON.fromJSString)
1313
                                        (VarE exc_name))
1314
                          (str_matches ++ [defmatch]))) []
1315
  -- the fail expression for the second function clause
1316
  fail_type <- [| fail $ "Invalid exception: expected '(string, [args])' " ++
1317
                  "      but got " ++ show (pp_value $(varE arg_else)) ++ "'"
1318
                |]
1319
  -- the second function clause
1320
  let clause2 = Clause [VarP arg_else] (NormalB fail_type) []
1321
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT tname) |]
1322
  return $ (SigD fname sigt, FunD fname [clause1, clause2])