Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / THH.hs @ d5a93a80

History | View | Annotate | Download (28.5 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2

    
3
{-| TemplateHaskell helper for HTools.
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
                  , declareIADT
34
                  , makeJSONInstance
35
                  , genOpID
36
                  , genOpCode
37
                  , genStrOfOp
38
                  , genStrOfKey
39
                  , genLuxiOp
40
                  , Field
41
                  , simpleField
42
                  , defaultField
43
                  , optionalField
44
                  , renameField
45
                  , customField
46
                  , timeStampFields
47
                  , uuidFields
48
                  , serialFields
49
                  , tagsFields
50
                  , buildObject
51
                  , buildObjectSerialisation
52
                  , buildParam
53
                  ) where
54

    
55
import Control.Monad (liftM, liftM2)
56
import Data.Char
57
import Data.List
58
import qualified Data.Set as Set
59
import Language.Haskell.TH
60

    
61
import qualified Text.JSON as JSON
62

    
63
-- * Exported types
64

    
65
-- | Serialised field data type.
66
data Field = Field { fieldName        :: String
67
                   , fieldType        :: Q Type
68
                   , fieldRead        :: Maybe (Q Exp)
69
                   , fieldShow        :: Maybe (Q Exp)
70
                   , fieldDefault     :: Maybe (Q Exp)
71
                   , fieldConstr      :: Maybe String
72
                   , fieldIsOptional  :: Bool
73
                   }
74

    
75
-- | Generates a simple field.
76
simpleField :: String -> Q Type -> Field
77
simpleField fname ftype =
78
  Field { fieldName        = fname
79
        , fieldType        = ftype
80
        , fieldRead        = Nothing
81
        , fieldShow        = Nothing
82
        , fieldDefault     = Nothing
83
        , fieldConstr      = Nothing
84
        , fieldIsOptional  = False
85
        }
86

    
87
-- | Sets the renamed constructor field.
88
renameField :: String -> Field -> Field
89
renameField constrName field = field { fieldConstr = Just constrName }
90

    
91
-- | Sets the default value on a field (makes it optional with a
92
-- default value).
93
defaultField :: Q Exp -> Field -> Field
94
defaultField defval field = field { fieldDefault = Just defval }
95

    
96
-- | Marks a field optional (turning its base type into a Maybe).
97
optionalField :: Field -> Field
98
optionalField field = field { fieldIsOptional = True }
99

    
100
-- | Sets custom functions on a field.
101
customField :: Name    -- ^ The name of the read function
102
            -> Name    -- ^ The name of the show function
103
            -> Field   -- ^ The original field
104
            -> Field   -- ^ Updated field
105
customField readfn showfn field =
106
  field { fieldRead = Just (varE readfn), fieldShow = Just (varE showfn) }
107

    
108
fieldRecordName :: Field -> String
109
fieldRecordName (Field { fieldName = name, fieldConstr = alias }) =
110
  maybe (camelCase name) id alias
111

    
112
-- | Computes the preferred variable name to use for the value of this
113
-- field. If the field has a specific constructor name, then we use a
114
-- first-letter-lowercased version of that; otherwise, we simply use
115
-- the field name. See also 'fieldRecordName'.
116
fieldVariable :: Field -> String
117
fieldVariable f =
118
  case (fieldConstr f) of
119
    Just name -> ensureLower name
120
    _ -> map (\c -> if c == '-' then '_' else c) $ fieldName f
121

    
122
actualFieldType :: Field -> Q Type
123
actualFieldType f | fieldIsOptional f  = [t| Maybe $t     |]
124
                  | otherwise = t
125
                  where t = fieldType f
126

    
127
checkNonOptDef :: (Monad m) => Field -> m ()
128
checkNonOptDef (Field { fieldIsOptional = True, fieldName = name }) =
129
  fail $ "Optional field " ++ name ++ " used in parameter declaration"
130
checkNonOptDef (Field { fieldDefault = (Just _), fieldName = name }) =
131
  fail $ "Default field " ++ name ++ " used in parameter declaration"
132
checkNonOptDef _ = return ()
133

    
134
-- | Produces the expression that will de-serialise a given
135
-- field. Since some custom parsing functions might need to use the
136
-- entire object, we do take and pass the object to any custom read
137
-- functions.
138
loadFn :: Field   -- ^ The field definition
139
       -> Q Exp   -- ^ The value of the field as existing in the JSON message
140
       -> Q Exp   -- ^ The entire object in JSON object format
141
       -> Q Exp   -- ^ Resulting expression
142
loadFn (Field { fieldRead = Just readfn }) expr o = [| $expr >>= $readfn $o |]
143
loadFn _ expr _ = expr
144

    
145
-- * Common field declarations
146

    
147
timeStampFields :: [Field]
148
timeStampFields =
149
    [ defaultField [| 0::Double |] $ simpleField "ctime" [t| Double |]
150
    , defaultField [| 0::Double |] $ simpleField "mtime" [t| Double |]
151
    ]
152

    
153
serialFields :: [Field]
154
serialFields =
155
    [ renameField  "Serial" $ simpleField "serial_no" [t| Int |] ]
156

    
157
uuidFields :: [Field]
158
uuidFields = [ simpleField "uuid" [t| String |] ]
159

    
160
-- | Tag field description.
161
tagsFields :: [Field]
162
tagsFields = [ defaultField [| Set.empty |] $
163
               simpleField "tags" [t| Set.Set String |] ]
164

    
165
-- * Helper functions
166

    
167
-- | Ensure first letter is lowercase.
168
--
169
-- Used to convert type name to function prefix, e.g. in @data Aa ->
170
-- aaToRaw@.
171
ensureLower :: String -> String
172
ensureLower [] = []
173
ensureLower (x:xs) = toLower x:xs
174

    
175
-- | Ensure first letter is uppercase.
176
--
177
-- Used to convert constructor name to component
178
ensureUpper :: String -> String
179
ensureUpper [] = []
180
ensureUpper (x:xs) = toUpper x:xs
181

    
182
-- | Helper for quoted expressions.
183
varNameE :: String -> Q Exp
184
varNameE = varE . mkName
185

    
186
-- | showJSON as an expression, for reuse.
187
showJSONE :: Q Exp
188
showJSONE = varNameE "showJSON"
189

    
190
-- | ToRaw function name.
191
toRawName :: String -> Name
192
toRawName = mkName . (++ "ToRaw") . ensureLower
193

    
194
-- | FromRaw function name.
195
fromRawName :: String -> Name
196
fromRawName = mkName . (++ "FromRaw") . ensureLower
197

    
198
-- | Converts a name to it's varE/litE representations.
199
--
200
reprE :: Either String Name -> Q Exp
201
reprE = either stringE varE
202

    
203
-- | Smarter function application.
204
--
205
-- This does simply f x, except that if is 'id', it will skip it, in
206
-- order to generate more readable code when using -ddump-splices.
207
appFn :: Exp -> Exp -> Exp
208
appFn f x | f == VarE 'id = x
209
          | otherwise = AppE f x
210

    
211
-- * Template code for simple raw type-equivalent ADTs
212

    
213
-- | Generates a data type declaration.
214
--
215
-- The type will have a fixed list of instances.
216
strADTDecl :: Name -> [String] -> Dec
217
strADTDecl name constructors =
218
  DataD [] name []
219
          (map (flip NormalC [] . mkName) constructors)
220
          [''Show, ''Read, ''Eq, ''Enum, ''Bounded, ''Ord]
221

    
222
-- | Generates a toRaw function.
223
--
224
-- This generates a simple function of the form:
225
--
226
-- @
227
-- nameToRaw :: Name -> /traw/
228
-- nameToRaw Cons1 = var1
229
-- nameToRaw Cons2 = \"value2\"
230
-- @
231
genToRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
232
genToRaw traw fname tname constructors = do
233
  let sigt = AppT (AppT ArrowT (ConT tname)) (ConT traw)
234
  -- the body clauses, matching on the constructor and returning the
235
  -- raw value
236
  clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
237
                             (normalB (reprE v)) []) constructors
238
  return [SigD fname sigt, FunD fname clauses]
239

    
240
-- | Generates a fromRaw function.
241
--
242
-- The function generated is monadic and can fail parsing the
243
-- raw value. It is of the form:
244
--
245
-- @
246
-- nameFromRaw :: (Monad m) => /traw/ -> m Name
247
-- nameFromRaw s | s == var1       = Cons1
248
--               | s == \"value2\" = Cons2
249
--               | otherwise = fail /.../
250
-- @
251
genFromRaw :: Name -> Name -> Name -> [(String, Name)] -> Q [Dec]
252
genFromRaw traw fname tname constructors = do
253
  -- signature of form (Monad m) => String -> m $name
254
  sigt <- [t| (Monad m) => $(conT traw) -> m $(conT tname) |]
255
  -- clauses for a guarded pattern
256
  let varp = mkName "s"
257
      varpe = varE varp
258
  clauses <- mapM (\(c, v) -> do
259
                     -- the clause match condition
260
                     g <- normalG [| $varpe == $(varE v) |]
261
                     -- the clause result
262
                     r <- [| return $(conE (mkName c)) |]
263
                     return (g, r)) constructors
264
  -- the otherwise clause (fallback)
265
  oth_clause <- do
266
    g <- normalG [| otherwise |]
267
    r <- [|fail ("Invalid string value for type " ++
268
                 $(litE (stringL (nameBase tname))) ++ ": " ++ show $varpe) |]
269
    return (g, r)
270
  let fun = FunD fname [Clause [VarP varp]
271
                        (GuardedB (clauses++[oth_clause])) []]
272
  return [SigD fname sigt, fun]
273

    
274
-- | Generates a data type from a given raw format.
275
--
276
-- The format is expected to multiline. The first line contains the
277
-- type name, and the rest of the lines must contain two words: the
278
-- constructor name and then the string representation of the
279
-- respective constructor.
280
--
281
-- The function will generate the data type declaration, and then two
282
-- functions:
283
--
284
-- * /name/ToRaw, which converts the type to a raw type
285
--
286
-- * /name/FromRaw, which (monadically) converts from a raw type to the type
287
--
288
-- Note that this is basically just a custom show/read instance,
289
-- nothing else.
290
declareADT :: Name -> String -> [(String, Name)] -> Q [Dec]
291
declareADT traw sname cons = do
292
  let name = mkName sname
293
      ddecl = strADTDecl name (map fst cons)
294
      -- process cons in the format expected by genToRaw
295
      cons' = map (\(a, b) -> (a, Right b)) cons
296
  toraw <- genToRaw traw (toRawName sname) name cons'
297
  fromraw <- genFromRaw traw (fromRawName sname) name cons
298
  return $ ddecl:toraw ++ fromraw
299

    
300
declareIADT :: String -> [(String, Name)] -> Q [Dec]
301
declareIADT = declareADT ''Int
302

    
303
declareSADT :: String -> [(String, Name)] -> Q [Dec]
304
declareSADT = declareADT ''String
305

    
306
-- | Creates the showJSON member of a JSON instance declaration.
307
--
308
-- This will create what is the equivalent of:
309
--
310
-- @
311
-- showJSON = showJSON . /name/ToRaw
312
-- @
313
--
314
-- in an instance JSON /name/ declaration
315
genShowJSON :: String -> Q Dec
316
genShowJSON name = do
317
  body <- [| JSON.showJSON . $(varE (toRawName name)) |]
318
  return $ FunD (mkName "showJSON") [Clause [] (NormalB body) []]
319

    
320
-- | Creates the readJSON member of a JSON instance declaration.
321
--
322
-- This will create what is the equivalent of:
323
--
324
-- @
325
-- readJSON s = case readJSON s of
326
--                Ok s' -> /name/FromRaw s'
327
--                Error e -> Error /description/
328
-- @
329
--
330
-- in an instance JSON /name/ declaration
331
genReadJSON :: String -> Q Dec
332
genReadJSON name = do
333
  let s = mkName "s"
334
  body <- [| case JSON.readJSON $(varE s) of
335
               JSON.Ok s' -> $(varE (fromRawName name)) s'
336
               JSON.Error e ->
337
                   JSON.Error $ "Can't parse raw value for type " ++
338
                           $(stringE name) ++ ": " ++ e ++ " from " ++
339
                           show $(varE s)
340
           |]
341
  return $ FunD (mkName "readJSON") [Clause [VarP s] (NormalB body) []]
342

    
343
-- | Generates a JSON instance for a given type.
344
--
345
-- This assumes that the /name/ToRaw and /name/FromRaw functions
346
-- have been defined as by the 'declareSADT' function.
347
makeJSONInstance :: Name -> Q [Dec]
348
makeJSONInstance name = do
349
  let base = nameBase name
350
  showJ <- genShowJSON base
351
  readJ <- genReadJSON base
352
  return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) [readJ,showJ]]
353

    
354
-- * Template code for opcodes
355

    
356
-- | Transforms a CamelCase string into an_underscore_based_one.
357
deCamelCase :: String -> String
358
deCamelCase =
359
    intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
360

    
361
-- | Transform an underscore_name into a CamelCase one.
362
camelCase :: String -> String
363
camelCase = concatMap (ensureUpper . drop 1) .
364
            groupBy (\_ b -> b /= '_' && b /= '-') . ('_':)
365

    
366
-- | Computes the name of a given constructor.
367
constructorName :: Con -> Q Name
368
constructorName (NormalC name _) = return name
369
constructorName (RecC name _)    = return name
370
constructorName x                = fail $ "Unhandled constructor " ++ show x
371

    
372
-- | Builds the generic constructor-to-string function.
373
--
374
-- This generates a simple function of the following form:
375
--
376
-- @
377
-- fname (ConStructorOne {}) = trans_fun("ConStructorOne")
378
-- fname (ConStructorTwo {}) = trans_fun("ConStructorTwo")
379
-- @
380
--
381
-- This builds a custom list of name/string pairs and then uses
382
-- 'genToRaw' to actually generate the function
383
genConstrToStr :: (String -> String) -> Name -> String -> Q [Dec]
384
genConstrToStr trans_fun name fname = do
385
  TyConI (DataD _ _ _ cons _) <- reify name
386
  cnames <- mapM (liftM nameBase . constructorName) cons
387
  let svalues = map (Left . trans_fun) cnames
388
  genToRaw ''String (mkName fname) name $ zip cnames svalues
389

    
390
-- | Constructor-to-string for OpCode.
391
genOpID :: Name -> String -> Q [Dec]
392
genOpID = genConstrToStr deCamelCase
393

    
394
-- | OpCode parameter (field) type.
395
type OpParam = (String, Q Type, Q Exp)
396

    
397
-- | Generates the OpCode data type.
398
--
399
-- This takes an opcode logical definition, and builds both the
400
-- datatype and the JSON serialisation out of it. We can't use a
401
-- generic serialisation since we need to be compatible with Ganeti's
402
-- own, so we have a few quirks to work around.
403
genOpCode :: String                -- ^ Type name to use
404
          -> [(String, [Field])]   -- ^ Constructor name and parameters
405
          -> Q [Dec]
406
genOpCode name cons = do
407
  decl_d <- mapM (\(cname, fields) -> do
408
                    -- we only need the type of the field, without Q
409
                    fields' <- mapM actualFieldType fields
410
                    let fields'' = zip (repeat NotStrict) fields'
411
                    return $ NormalC (mkName cname) fields'')
412
            cons
413
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read, ''Eq]
414

    
415
  (savesig, savefn) <- genSaveOpCode cons
416
  (loadsig, loadfn) <- genLoadOpCode cons
417
  return [declD, loadsig, loadfn, savesig, savefn]
418

    
419
-- | Checks whether a given parameter is options.
420
--
421
-- This requires that it's a 'Maybe'.
422
isOptional :: Type -> Bool
423
isOptional (AppT (ConT dt) _) | dt == ''Maybe = True
424
isOptional _ = False
425

    
426
-- | Generates the \"save\" clause for an entire opcode constructor.
427
--
428
-- This matches the opcode with variables named the same as the
429
-- constructor fields (just so that the spliced in code looks nicer),
430
-- and passes those name plus the parameter definition to 'saveObjectField'.
431
saveConstructor :: String    -- ^ The constructor name
432
                -> [Field]   -- ^ The parameter definitions for this
433
                             -- constructor
434
                -> Q Clause  -- ^ Resulting clause
435
saveConstructor sname fields = do
436
  let cname = mkName sname
437
  fnames <- mapM (newName . fieldVariable) fields
438
  let pat = conP cname (map varP fnames)
439
  let felems = map (uncurry saveObjectField) (zip fnames fields)
440
      -- now build the OP_ID serialisation
441
      opid = [| [( $(stringE "OP_ID"),
442
                   JSON.showJSON $(stringE . deCamelCase $ sname) )] |]
443
      flist = listE (opid:felems)
444
      -- and finally convert all this to a json object
445
      flist' = [| $(varNameE "makeObj") (concat $flist) |]
446
  clause [pat] (normalB flist') []
447

    
448
-- | Generates the main save opcode function.
449
--
450
-- This builds a per-constructor match clause that contains the
451
-- respective constructor-serialisation code.
452
genSaveOpCode :: [(String, [Field])] -> Q (Dec, Dec)
453
genSaveOpCode opdefs = do
454
  cclauses <- mapM (uncurry saveConstructor) opdefs
455
  let fname = mkName "saveOpCode"
456
  sigt <- [t| $(conT (mkName "OpCode")) -> JSON.JSValue |]
457
  return $ (SigD fname sigt, FunD fname cclauses)
458

    
459
loadConstructor :: String -> [Field] -> Q Exp
460
loadConstructor sname fields = do
461
  let name = mkName sname
462
  fbinds <- mapM loadObjectField fields
463
  let (fnames, fstmts) = unzip fbinds
464
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
465
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
466
  return $ DoE fstmts'
467

    
468
genLoadOpCode :: [(String, [Field])] -> Q (Dec, Dec)
469
genLoadOpCode opdefs = do
470
  let fname = mkName "loadOpCode"
471
      arg1 = mkName "v"
472
      objname = mkName "o"
473
      opid = mkName "op_id"
474
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
475
                                 (JSON.readJSON $(varE arg1)) |]
476
  st2 <- bindS (varP opid) [| $(varNameE "fromObj")
477
                              $(varE objname) $(stringE "OP_ID") |]
478
  -- the match results (per-constructor blocks)
479
  mexps <- mapM (uncurry loadConstructor) opdefs
480
  fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
481
  let mpats = map (\(me, c) ->
482
                       let mp = LitP . StringL . deCamelCase . fst $ c
483
                       in Match mp (NormalB me) []
484
                  ) $ zip mexps opdefs
485
      defmatch = Match WildP (NormalB fails) []
486
      cst = NoBindS $ CaseE (VarE opid) $ mpats++[defmatch]
487
      body = DoE [st1, st2, cst]
488
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
489
  return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
490

    
491
-- * Template code for luxi
492

    
493
-- | Constructor-to-string for LuxiOp.
494
genStrOfOp :: Name -> String -> Q [Dec]
495
genStrOfOp = genConstrToStr id
496

    
497
-- | Constructor-to-string for MsgKeys.
498
genStrOfKey :: Name -> String -> Q [Dec]
499
genStrOfKey = genConstrToStr ensureLower
500

    
501
-- | LuxiOp parameter type.
502
type LuxiParam = (String, Q Type, Q Exp)
503

    
504
-- | Generates the LuxiOp data type.
505
--
506
-- This takes a Luxi operation definition and builds both the
507
-- datatype and the function trnasforming the arguments to JSON.
508
-- We can't use anything less generic, because the way different
509
-- operations are serialized differs on both parameter- and top-level.
510
--
511
-- There are three things to be defined for each parameter:
512
--
513
-- * name
514
--
515
-- * type
516
--
517
-- * operation; this is the operation performed on the parameter before
518
--   serialization
519
--
520
genLuxiOp :: String -> [(String, [LuxiParam])] -> Q [Dec]
521
genLuxiOp name cons = do
522
  decl_d <- mapM (\(cname, fields) -> do
523
                    fields' <- mapM (\(_, qt, _) ->
524
                                         qt >>= \t -> return (NotStrict, t))
525
                               fields
526
                    return $ NormalC (mkName cname) fields')
527
            cons
528
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read, ''Eq]
529
  (savesig, savefn) <- genSaveLuxiOp cons
530
  req_defs <- declareSADT "LuxiReq" .
531
              map (\(str, _) -> ("Req" ++ str, mkName ("luxiReq" ++ str))) $
532
                  cons
533
  return $ [declD, savesig, savefn] ++ req_defs
534

    
535
-- | Generates the \"save\" expression for a single luxi parameter.
536
saveLuxiField :: Name -> LuxiParam -> Q Exp
537
saveLuxiField fvar (_, qt, fn) =
538
    [| JSON.showJSON ( $(liftM2 appFn fn $ varE fvar) ) |]
539

    
540
-- | Generates the \"save\" clause for entire LuxiOp constructor.
541
saveLuxiConstructor :: (String, [LuxiParam]) -> Q Clause
542
saveLuxiConstructor (sname, fields) = do
543
  let cname = mkName sname
544
      fnames = map (\(nm, _, _) -> mkName nm) fields
545
      pat = conP cname (map varP fnames)
546
      flist = map (uncurry saveLuxiField) (zip fnames fields)
547
      finval = if null flist
548
               then [| JSON.showJSON ()    |]
549
               else [| JSON.showJSON $(listE flist) |]
550
  clause [pat] (normalB finval) []
551

    
552
-- | Generates the main save LuxiOp function.
553
genSaveLuxiOp :: [(String, [LuxiParam])]-> Q (Dec, Dec)
554
genSaveLuxiOp opdefs = do
555
  sigt <- [t| $(conT (mkName "LuxiOp")) -> JSON.JSValue |]
556
  let fname = mkName "opToArgs"
557
  cclauses <- mapM saveLuxiConstructor opdefs
558
  return $ (SigD fname sigt, FunD fname cclauses)
559

    
560
-- * "Objects" functionality
561

    
562
-- | Extract the field's declaration from a Field structure.
563
fieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
564
fieldTypeInfo field_pfx fd = do
565
  t <- actualFieldType fd
566
  let n = mkName . (field_pfx ++) . fieldRecordName $ fd
567
  return (n, NotStrict, t)
568

    
569
-- | Build an object declaration.
570
buildObject :: String -> String -> [Field] -> Q [Dec]
571
buildObject sname field_pfx fields = do
572
  let name = mkName sname
573
  fields_d <- mapM (fieldTypeInfo field_pfx) fields
574
  let decl_d = RecC name fields_d
575
  let declD = DataD [] name [] [decl_d] [''Show, ''Read, ''Eq]
576
  ser_decls <- buildObjectSerialisation sname fields
577
  return $ declD:ser_decls
578

    
579
buildObjectSerialisation :: String -> [Field] -> Q [Dec]
580
buildObjectSerialisation sname fields = do
581
  let name = mkName sname
582
  savedecls <- genSaveObject saveObjectField sname fields
583
  (loadsig, loadfn) <- genLoadObject loadObjectField sname fields
584
  shjson <- objectShowJSON sname
585
  rdjson <- objectReadJSON sname
586
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
587
                 [rdjson, shjson]
588
  return $ savedecls ++ [loadsig, loadfn, instdecl]
589

    
590
genSaveObject :: (Name -> Field -> Q Exp)
591
              -> String -> [Field] -> Q [Dec]
592
genSaveObject save_fn sname fields = do
593
  let name = mkName sname
594
  fnames <- mapM (newName . fieldVariable) fields
595
  let pat = conP name (map varP fnames)
596
  let tdname = mkName ("toDict" ++ sname)
597
  tdsigt <- [t| $(conT name) -> [(String, JSON.JSValue)] |]
598

    
599
  let felems = map (uncurry save_fn) (zip fnames fields)
600
      flist = listE felems
601
      -- and finally convert all this to a json object
602
      tdlist = [| concat $flist |]
603
      iname = mkName "i"
604
  tclause <- clause [pat] (normalB tdlist) []
605
  cclause <- [| $(varNameE "makeObj") . $(varE tdname) |]
606
  let fname = mkName ("save" ++ sname)
607
  sigt <- [t| $(conT name) -> JSON.JSValue |]
608
  return [SigD tdname tdsigt, FunD tdname [tclause],
609
          SigD fname sigt, ValD (VarP fname) (NormalB cclause) []]
610

    
611
saveObjectField :: Name -> Field -> Q Exp
612
saveObjectField fvar field
613
  | fisOptional = [| case $(varE fvar) of
614
                      Nothing -> []
615
                      Just v -> [( $nameE, JSON.showJSON v)]
616
                  |]
617
  | otherwise = case fieldShow field of
618
      Nothing -> [| [( $nameE, JSON.showJSON $fvarE)] |]
619
      Just fn -> [| let (actual, extra) = $fn $fvarE
620
                    in extra ++ [( $nameE, JSON.showJSON actual)]
621
                  |]
622
  where fisOptional  = fieldIsOptional field
623
        nameE = stringE (fieldName field)
624
        fvarE = varE fvar
625

    
626
objectShowJSON :: String -> Q Dec
627
objectShowJSON name = do
628
  body <- [| JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
629
  return $ FunD (mkName "showJSON") [Clause [] (NormalB body) []]
630

    
631
genLoadObject :: (Field -> Q (Name, Stmt))
632
              -> String -> [Field] -> Q (Dec, Dec)
633
genLoadObject load_fn sname fields = do
634
  let name = mkName sname
635
      funname = mkName $ "load" ++ sname
636
      arg1 = mkName "v"
637
      objname = mkName "o"
638
      opid = mkName "op_id"
639
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
640
                                 (JSON.readJSON $(varE arg1)) |]
641
  fbinds <- mapM load_fn fields
642
  let (fnames, fstmts) = unzip fbinds
643
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
644
      fstmts' = st1:fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
645
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT name) |]
646
  return $ (SigD funname sigt,
647
            FunD funname [Clause [VarP arg1] (NormalB (DoE fstmts')) []])
648

    
649
loadObjectField :: Field -> Q (Name, Stmt)
650
loadObjectField field = do
651
  let name = fieldVariable field
652
  fvar <- newName name
653
  -- these are used in all patterns below
654
  let objvar = varNameE "o"
655
      objfield = stringE (fieldName field)
656
      loadexp =
657
        if fieldIsOptional field
658
          then [| $(varNameE "maybeFromObj") $objvar $objfield |]
659
          else case fieldDefault field of
660
                 Just defv ->
661
                   [| $(varNameE "fromObjWithDefault") $objvar
662
                      $objfield $defv |]
663
                 Nothing -> [| $(varNameE "fromObj") $objvar $objfield |]
664
  bexp <- loadFn field loadexp objvar
665

    
666
  return (fvar, BindS (VarP fvar) bexp)
667

    
668
objectReadJSON :: String -> Q Dec
669
objectReadJSON name = do
670
  let s = mkName "s"
671
  body <- [| case JSON.readJSON $(varE s) of
672
               JSON.Ok s' -> $(varE .mkName $ "load" ++ name) s'
673
               JSON.Error e ->
674
                 JSON.Error $ "Can't parse value for type " ++
675
                       $(stringE name) ++ ": " ++ e
676
           |]
677
  return $ FunD (mkName "readJSON") [Clause [VarP s] (NormalB body) []]
678

    
679
-- * Inheritable parameter tables implementation
680

    
681
-- | Compute parameter type names.
682
paramTypeNames :: String -> (String, String)
683
paramTypeNames root = ("Filled"  ++ root ++ "Params",
684
                       "Partial" ++ root ++ "Params")
685

    
686
-- | Compute information about the type of a parameter field.
687
paramFieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
688
paramFieldTypeInfo field_pfx fd = do
689
  t <- actualFieldType fd
690
  let n = mkName . (++ "P") . (field_pfx ++) .
691
          fieldRecordName $ fd
692
  return (n, NotStrict, AppT (ConT ''Maybe) t)
693

    
694
-- | Build a parameter declaration.
695
--
696
-- This function builds two different data structures: a /filled/ one,
697
-- in which all fields are required, and a /partial/ one, in which all
698
-- fields are optional. Due to the current record syntax issues, the
699
-- fields need to be named differrently for the two structures, so the
700
-- partial ones get a /P/ suffix.
701
buildParam :: String -> String -> [Field] -> Q [Dec]
702
buildParam sname field_pfx fields = do
703
  let (sname_f, sname_p) = paramTypeNames sname
704
      name_f = mkName sname_f
705
      name_p = mkName sname_p
706
  fields_f <- mapM (fieldTypeInfo field_pfx) fields
707
  fields_p <- mapM (paramFieldTypeInfo field_pfx) fields
708
  let decl_f = RecC name_f fields_f
709
      decl_p = RecC name_p fields_p
710
  let declF = DataD [] name_f [] [decl_f] [''Show, ''Read, ''Eq]
711
      declP = DataD [] name_p [] [decl_p] [''Show, ''Read, ''Eq]
712
  ser_decls_f <- buildObjectSerialisation sname_f fields
713
  ser_decls_p <- buildPParamSerialisation sname_p fields
714
  fill_decls <- fillParam sname field_pfx fields
715
  return $ [declF, declP] ++ ser_decls_f ++ ser_decls_p ++ fill_decls
716

    
717
buildPParamSerialisation :: String -> [Field] -> Q [Dec]
718
buildPParamSerialisation sname fields = do
719
  let name = mkName sname
720
  savedecls <- genSaveObject savePParamField sname fields
721
  (loadsig, loadfn) <- genLoadObject loadPParamField sname fields
722
  shjson <- objectShowJSON sname
723
  rdjson <- objectReadJSON sname
724
  let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
725
                 [rdjson, shjson]
726
  return $ savedecls ++ [loadsig, loadfn, instdecl]
727

    
728
savePParamField :: Name -> Field -> Q Exp
729
savePParamField fvar field = do
730
  checkNonOptDef field
731
  let actualVal = mkName "v"
732
  normalexpr <- saveObjectField actualVal field
733
  -- we have to construct the block here manually, because we can't
734
  -- splice-in-splice
735
  return $ CaseE (VarE fvar) [ Match (ConP 'Nothing [])
736
                                       (NormalB (ConE '[])) []
737
                             , Match (ConP 'Just [VarP actualVal])
738
                                       (NormalB normalexpr) []
739
                             ]
740
loadPParamField :: Field -> Q (Name, Stmt)
741
loadPParamField field = do
742
  checkNonOptDef field
743
  let name = fieldName field
744
  fvar <- newName name
745
  -- these are used in all patterns below
746
  let objvar = varNameE "o"
747
      objfield = stringE name
748
      loadexp = [| $(varNameE "maybeFromObj") $objvar $objfield |]
749
  bexp <- loadFn field loadexp objvar
750
  return (fvar, BindS (VarP fvar) bexp)
751

    
752
-- | Builds a simple declaration of type @n_x = fromMaybe f_x p_x@.
753
buildFromMaybe :: String -> Q Dec
754
buildFromMaybe fname =
755
  valD (varP (mkName $ "n_" ++ fname))
756
         (normalB [| $(varNameE "fromMaybe")
757
                        $(varNameE $ "f_" ++ fname)
758
                        $(varNameE $ "p_" ++ fname) |]) []
759

    
760
fillParam :: String -> String -> [Field] -> Q [Dec]
761
fillParam sname field_pfx fields = do
762
  let fnames = map (\fd -> field_pfx ++ fieldRecordName fd) fields
763
      (sname_f, sname_p) = paramTypeNames sname
764
      oname_f = "fobj"
765
      oname_p = "pobj"
766
      name_f = mkName sname_f
767
      name_p = mkName sname_p
768
      fun_name = mkName $ "fill" ++ sname ++ "Params"
769
      le_full = ValD (ConP name_f (map (VarP . mkName . ("f_" ++)) fnames))
770
                (NormalB . VarE . mkName $ oname_f) []
771
      le_part = ValD (ConP name_p (map (VarP . mkName . ("p_" ++)) fnames))
772
                (NormalB . VarE . mkName $ oname_p) []
773
      obj_new = foldl (\accu vname -> AppE accu (VarE vname)) (ConE name_f)
774
                $ map (mkName . ("n_" ++)) fnames
775
  le_new <- mapM buildFromMaybe fnames
776
  funt <- [t| $(conT name_f) -> $(conT name_p) -> $(conT name_f) |]
777
  let sig = SigD fun_name funt
778
      fclause = Clause [VarP (mkName oname_f), VarP (mkName oname_p)]
779
                (NormalB $ LetE (le_full:le_part:le_new) obj_new) []
780
      fun = FunD fun_name [fclause]
781
  return [sig, fun]