htools: small change in error message in THH.hs
[ganeti-local] / htools / Ganeti / THH.hs
1 {-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
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 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                   , noDefault
38                   , genStrOfOp
39                   , genStrOfKey
40                   , genLuxiOp
41                   , Field
42                   , simpleField
43                   , defaultField
44                   , optionalField
45                   , renameField
46                   , containerField
47                   , customField
48                   , timeStampFields
49                   , uuidFields
50                   , serialFields
51                   , buildObject
52                   , buildObjectSerialisation
53                   , buildParam
54                   , Container
55                   ) where
56
57 import Control.Arrow
58 import Control.Monad (liftM, liftM2)
59 import Data.Char
60 import Data.List
61 import qualified Data.Map as M
62 import Language.Haskell.TH
63
64 import qualified Text.JSON as JSON
65
66 import Ganeti.HTools.JSON
67
68 -- * Exported types
69
70 type Container = M.Map String
71
72 -- | Serialised field data type.
73 data Field = Field { fieldName        :: String
74                    , fieldType        :: Q Type
75                    , fieldRead        :: Maybe (Q Exp)
76                    , fieldShow        :: Maybe (Q Exp)
77                    , fieldDefault     :: Maybe (Q Exp)
78                    , fieldConstr      :: Maybe String
79                    , fieldIsContainer :: Bool
80                    , fieldIsOptional  :: Bool
81                    }
82
83 -- | Generates a simple field.
84 simpleField :: String -> Q Type -> Field
85 simpleField fname ftype =
86   Field { fieldName        = fname
87         , fieldType        = ftype
88         , fieldRead        = Nothing
89         , fieldShow        = Nothing
90         , fieldDefault     = Nothing
91         , fieldConstr      = Nothing
92         , fieldIsContainer = False
93         , fieldIsOptional  = False
94         }
95
96 -- | Sets the renamed constructor field.
97 renameField :: String -> Field -> Field
98 renameField constrName field = field { fieldConstr = Just constrName }
99
100 -- | Sets the default value on a field (makes it optional with a
101 -- default value).
102 defaultField :: Q Exp -> Field -> Field
103 defaultField defval field = field { fieldDefault = Just defval }
104
105 -- | Marks a field optional (turning its base type into a Maybe).
106 optionalField :: Field -> Field
107 optionalField field = field { fieldIsOptional = True }
108
109 -- | Marks a field as a container.
110 containerField :: Field -> Field
111 containerField field = field { fieldIsContainer = True }
112
113 -- | Sets custom functions on a field.
114 customField :: Q Exp -> Q Exp -> Field -> Field
115 customField readfn showfn field =
116   field { fieldRead = Just readfn, fieldShow = Just showfn }
117
118 fieldRecordName :: Field -> String
119 fieldRecordName (Field { fieldName = name, fieldConstr = alias }) =
120   maybe (camelCase name) id alias
121
122 fieldVariable :: Field -> String
123 fieldVariable = map toLower . fieldRecordName
124
125 actualFieldType :: Field -> Q Type
126 actualFieldType f | fieldIsContainer f = [t| Container $t |]
127                   | fieldIsOptional f  = [t| Maybe $t     |]
128                   | otherwise = t
129                   where t = fieldType f
130
131 checkNonOptDef :: (Monad m) => Field -> m ()
132 checkNonOptDef (Field { fieldIsOptional = True, fieldName = name }) =
133   fail $ "Optional field " ++ name ++ " used in parameter declaration"
134 checkNonOptDef (Field { fieldDefault = (Just _), fieldName = name }) =
135   fail $ "Default field " ++ name ++ " used in parameter declaration"
136 checkNonOptDef _ = return ()
137
138 loadFn :: Field -> Q Exp -> Q Exp
139 loadFn (Field { fieldIsContainer = True }) expr = [| $expr >>= readContainer |]
140 loadFn (Field { fieldRead = Just readfn }) expr = [| $expr >>= $readfn |]
141 loadFn _ expr = expr
142
143 saveFn :: Field -> Q Exp -> Q Exp
144 saveFn (Field { fieldIsContainer = True }) expr = [| showContainer $expr |]
145 saveFn (Field { fieldRead = Just readfn }) expr = [| $readfn $expr |]
146 saveFn _ expr = expr
147
148 -- * Common field declarations
149
150 timeStampFields :: [Field]
151 timeStampFields =
152     [ defaultField [| 0::Double |] $ simpleField "ctime" [t| Double |]
153     , defaultField [| 0::Double |] $ simpleField "mtime" [t| Double |]
154     ]
155
156 serialFields :: [Field]
157 serialFields =
158     [ renameField  "Serial" $ simpleField "serial_no" [t| Int |] ]
159
160 uuidFields :: [Field]
161 uuidFields = [ simpleField "uuid" [t| String |] ]
162
163 -- * Helper functions
164
165 -- | Ensure first letter is lowercase.
166 --
167 -- Used to convert type name to function prefix, e.g. in @data Aa ->
168 -- aaToRaw@.
169 ensureLower :: String -> String
170 ensureLower [] = []
171 ensureLower (x:xs) = toLower x:xs
172
173 -- | Ensure first letter is uppercase.
174 --
175 -- Used to convert constructor name to component
176 ensureUpper :: String -> String
177 ensureUpper [] = []
178 ensureUpper (x:xs) = toUpper x:xs
179
180 -- | Helper for quoted expressions.
181 varNameE :: String -> Q Exp
182 varNameE = varE . mkName
183
184 -- | showJSON as an expression, for reuse.
185 showJSONE :: Q Exp
186 showJSONE = varNameE "showJSON"
187
188 -- | ToRaw function name.
189 toRawName :: String -> Name
190 toRawName = mkName . (++ "ToRaw") . ensureLower
191
192 -- | FromRaw function name.
193 fromRawName :: String -> Name
194 fromRawName = mkName . (++ "FromRaw") . ensureLower
195
196 -- | Converts a name to it's varE/litE representations.
197 --
198 reprE :: Either String Name -> Q Exp
199 reprE = either stringE varE
200
201 -- | Smarter function application.
202 --
203 -- This does simply f x, except that if is 'id', it will skip it, in
204 -- order to generate more readable code when using -ddump-splices.
205 appFn :: Exp -> Exp -> Exp
206 appFn f x | f == VarE 'id = x
207           | otherwise = AppE f x
208
209 -- | Container loader
210 readContainer :: (Monad m, JSON.JSON a) =>
211                  JSON.JSObject JSON.JSValue -> m (Container a)
212 readContainer obj = do
213   let kjvlist = JSON.fromJSObject obj
214   kalist <- mapM (\(k, v) -> fromKeyValue k v >>= \a -> return (k, a)) kjvlist
215   return $ M.fromList kalist
216
217 -- | Container dumper
218 showContainer :: (JSON.JSON a) => Container a -> JSON.JSValue
219 showContainer = JSON.makeObj . map (second JSON.showJSON) . M.toList
220
221 -- * Template code for simple raw type-equivalent ADTs
222
223 -- | Generates a data type declaration.
224 --
225 -- The type will have a fixed list of instances.
226 strADTDecl :: Name -> [String] -> Dec
227 strADTDecl name constructors =
228   DataD [] name []
229           (map (flip NormalC [] . mkName) constructors)
230           [''Show, ''Read, ''Eq, ''Enum, ''Bounded, ''Ord]
231
232 -- | Generates a toRaw function.
233 --
234 -- This generates a simple function of the form:
235 --
236 -- @
237 -- nameToRaw :: Name -> /traw/
238 -- nameToRaw Cons1 = var1
239 -- nameToRaw Cons2 = \"value2\"
240 -- @
241 genToRaw :: Name -> Name -> Name -> [(String, Either String Name)] -> Q [Dec]
242 genToRaw traw fname tname constructors = do
243   sigt <- [t| $(conT tname) -> $(conT traw) |]
244   -- the body clauses, matching on the constructor and returning the
245   -- raw value
246   clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
247                              (normalB (reprE v)) []) constructors
248   return [SigD fname sigt, FunD fname clauses]
249
250 -- | Generates a fromRaw function.
251 --
252 -- The function generated is monadic and can fail parsing the
253 -- raw value. It is of the form:
254 --
255 -- @
256 -- nameFromRaw :: (Monad m) => /traw/ -> m Name
257 -- nameFromRaw s | s == var1       = Cons1
258 --               | s == \"value2\" = Cons2
259 --               | otherwise = fail /.../
260 -- @
261 genFromRaw :: Name -> Name -> Name -> [(String, Name)] -> Q [Dec]
262 genFromRaw traw fname tname constructors = do
263   -- signature of form (Monad m) => String -> m $name
264   sigt <- [t| (Monad m) => $(conT traw) -> m $(conT tname) |]
265   -- clauses for a guarded pattern
266   let varp = mkName "s"
267       varpe = varE varp
268   clauses <- mapM (\(c, v) -> do
269                      -- the clause match condition
270                      g <- normalG [| $varpe == $(varE v) |]
271                      -- the clause result
272                      r <- [| return $(conE (mkName c)) |]
273                      return (g, r)) constructors
274   -- the otherwise clause (fallback)
275   oth_clause <- do
276     g <- normalG [| otherwise |]
277     r <- [|fail ("Invalid string value for type " ++
278                  $(litE (stringL (nameBase tname))) ++ ": " ++ show $varpe) |]
279     return (g, r)
280   let fun = FunD fname [Clause [VarP varp]
281                         (GuardedB (clauses++[oth_clause])) []]
282   return [SigD fname sigt, fun]
283
284 -- | Generates a data type from a given raw format.
285 --
286 -- The format is expected to multiline. The first line contains the
287 -- type name, and the rest of the lines must contain two words: the
288 -- constructor name and then the string representation of the
289 -- respective constructor.
290 --
291 -- The function will generate the data type declaration, and then two
292 -- functions:
293 --
294 -- * /name/ToRaw, which converts the type to a raw type
295 --
296 -- * /name/FromRaw, which (monadically) converts from a raw type to the type
297 --
298 -- Note that this is basically just a custom show/read instance,
299 -- nothing else.
300 declareADT :: Name -> String -> [(String, Name)] -> Q [Dec]
301 declareADT traw sname cons = do
302   let name = mkName sname
303       ddecl = strADTDecl name (map fst cons)
304       -- process cons in the format expected by genToRaw
305       cons' = map (\(a, b) -> (a, Right b)) cons
306   toraw <- genToRaw traw (toRawName sname) name cons'
307   fromraw <- genFromRaw traw (fromRawName sname) name cons
308   return $ ddecl:toraw ++ fromraw
309
310 declareIADT :: String -> [(String, Name)] -> Q [Dec]
311 declareIADT = declareADT ''Int
312
313 declareSADT :: String -> [(String, Name)] -> Q [Dec]
314 declareSADT = declareADT ''String
315
316 -- | Creates the showJSON member of a JSON instance declaration.
317 --
318 -- This will create what is the equivalent of:
319 --
320 -- @
321 -- showJSON = showJSON . /name/ToRaw
322 -- @
323 --
324 -- in an instance JSON /name/ declaration
325 genShowJSON :: String -> Q [Dec]
326 genShowJSON name = [d| showJSON = JSON.showJSON . $(varE (toRawName name)) |]
327
328 -- | Creates the readJSON member of a JSON instance declaration.
329 --
330 -- This will create what is the equivalent of:
331 --
332 -- @
333 -- readJSON s = case readJSON s of
334 --                Ok s' -> /name/FromRaw s'
335 --                Error e -> Error /description/
336 -- @
337 --
338 -- in an instance JSON /name/ declaration
339 genReadJSON :: String -> Q Dec
340 genReadJSON name = do
341   let s = mkName "s"
342   body <- [| case JSON.readJSON $(varE s) of
343                JSON.Ok s' -> $(varE (fromRawName name)) s'
344                JSON.Error e ->
345                    JSON.Error $ "Can't parse raw value for type " ++
346                            $(stringE name) ++ ": " ++ e ++ " from " ++
347                            show $(varE s)
348            |]
349   return $ FunD (mkName "readJSON") [Clause [VarP s] (NormalB body) []]
350
351 -- | Generates a JSON instance for a given type.
352 --
353 -- This assumes that the /name/ToRaw and /name/FromRaw functions
354 -- have been defined as by the 'declareSADT' function.
355 makeJSONInstance :: Name -> Q [Dec]
356 makeJSONInstance name = do
357   let base = nameBase name
358   showJ <- genShowJSON base
359   readJ <- genReadJSON base
360   return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) (readJ:showJ)]
361
362 -- * Template code for opcodes
363
364 -- | Transforms a CamelCase string into an_underscore_based_one.
365 deCamelCase :: String -> String
366 deCamelCase =
367     intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
368
369 -- | Transform an underscore_name into a CamelCase one.
370 camelCase :: String -> String
371 camelCase = concatMap (ensureUpper . drop 1) .
372             groupBy (\_ b -> b /= '_') . ('_':)
373
374 -- | Computes the name of a given constructor.
375 constructorName :: Con -> Q Name
376 constructorName (NormalC name _) = return name
377 constructorName (RecC name _)    = return name
378 constructorName x                = fail $ "Unhandled constructor " ++ show x
379
380 -- | Builds the generic constructor-to-string function.
381 --
382 -- This generates a simple function of the following form:
383 --
384 -- @
385 -- fname (ConStructorOne {}) = trans_fun("ConStructorOne")
386 -- fname (ConStructorTwo {}) = trans_fun("ConStructorTwo")
387 -- @
388 --
389 -- This builds a custom list of name/string pairs and then uses
390 -- 'genToRaw' to actually generate the function
391 genConstrToStr :: (String -> String) -> Name -> String -> Q [Dec]
392 genConstrToStr trans_fun name fname = do
393   TyConI (DataD _ _ _ cons _) <- reify name
394   cnames <- mapM (liftM nameBase . constructorName) cons
395   let svalues = map (Left . trans_fun) cnames
396   genToRaw ''String (mkName fname) name $ zip cnames svalues
397
398 -- | Constructor-to-string for OpCode.
399 genOpID :: Name -> String -> Q [Dec]
400 genOpID = genConstrToStr deCamelCase
401
402 -- | OpCode parameter (field) type.
403 type OpParam = (String, Q Type, Q Exp)
404
405 -- | Generates the OpCode data type.
406 --
407 -- This takes an opcode logical definition, and builds both the
408 -- datatype and the JSON serialisation out of it. We can't use a
409 -- generic serialisation since we need to be compatible with Ganeti's
410 -- own, so we have a few quirks to work around.
411 --
412 -- There are three things to be defined for each parameter:
413 --
414 -- * name
415 --
416 -- * type; if this is 'Maybe', will only be serialised if it's a
417 --   'Just' value
418 --
419 -- * default; if missing, won't raise an exception, but will instead
420 --   use the default
421 --
422 genOpCode :: String                -- ^ Type name to use
423           -> [(String, [OpParam])] -- ^ Constructor name and parameters
424           -> Q [Dec]
425 genOpCode name cons = do
426   decl_d <- mapM (\(cname, fields) -> do
427                     -- we only need the type of the field, without Q
428                     fields' <- mapM (\(_, qt, _) ->
429                                          qt >>= \t -> return (NotStrict, t))
430                                fields
431                     return $ NormalC (mkName cname) fields')
432             cons
433   let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read, ''Eq]
434
435   (savesig, savefn) <- genSaveOpCode cons
436   (loadsig, loadfn) <- genLoadOpCode cons
437   return [declD, loadsig, loadfn, savesig, savefn]
438
439 -- | Checks whether a given parameter is options.
440 --
441 -- This requires that it's a 'Maybe'.
442 isOptional :: Type -> Bool
443 isOptional (AppT (ConT dt) _) | dt == ''Maybe = True
444 isOptional _ = False
445
446 -- | Generates the \"save\" expression for a single opcode parameter.
447 --
448 -- There is only one special handling mode: if the parameter is of
449 -- 'Maybe' type, then we only save it if it's a 'Just' value,
450 -- otherwise we skip it.
451 saveField :: Name    -- ^ The name of variable that contains the value
452           -> OpParam -- ^ Parameter definition
453           -> Q Exp
454 saveField fvar (fname, qt, _) = do
455   t <- qt
456   let fnexp = stringE fname
457       fvare = varE fvar
458   (if isOptional t
459    then [| case $fvare of
460              Just v' -> [( $fnexp, $showJSONE v')]
461              Nothing -> []
462          |]
463    else [| [( $fnexp, $showJSONE $fvare )] |])
464
465 -- | Generates the \"save\" clause for an entire opcode constructor.
466 --
467 -- This matches the opcode with variables named the same as the
468 -- constructor fields (just so that the spliced in code looks nicer),
469 -- and passes those name plus the parameter definition to 'saveField'.
470 saveConstructor :: String    -- ^ The constructor name
471                 -> [OpParam] -- ^ The parameter definitions for this
472                              -- constructor
473                 -> Q Clause  -- ^ Resulting clause
474 saveConstructor sname fields = do
475   let cname = mkName sname
476   let fnames = map (\(n, _, _) -> mkName n) fields
477   let pat = conP cname (map varP fnames)
478   let felems = map (uncurry saveField) (zip fnames fields)
479       -- now build the OP_ID serialisation
480       opid = [| [( $(stringE "OP_ID"),
481                    $showJSONE $(stringE . deCamelCase $ sname) )] |]
482       flist = listE (opid:felems)
483       -- and finally convert all this to a json object
484       flist' = [| $(varNameE "makeObj") (concat $flist) |]
485   clause [pat] (normalB flist') []
486
487 -- | Generates the main save opcode function.
488 --
489 -- This builds a per-constructor match clause that contains the
490 -- respective constructor-serialisation code.
491 genSaveOpCode :: [(String, [OpParam])] -> Q (Dec, Dec)
492 genSaveOpCode opdefs = do
493   cclauses <- mapM (uncurry saveConstructor) opdefs
494   let fname = mkName "saveOpCode"
495   sigt <- [t| $(conT (mkName "OpCode")) -> JSON.JSValue |]
496   return $ (SigD fname sigt, FunD fname cclauses)
497
498 -- | Generates the \"load\" field for a single parameter.
499 --
500 -- There is custom handling, depending on how the parameter is
501 -- specified. For a 'Maybe' type parameter, we allow that it is not
502 -- present (via 'Utils.maybeFromObj'). Otherwise, if there is a
503 -- default value, we allow the parameter to be abset, and finally if
504 -- there is no default value, we require its presence.
505 loadField :: OpParam -> Q (Name, Stmt)
506 loadField (fname, qt, qdefa) = do
507   let fvar = mkName fname
508   t <- qt
509   defa <- qdefa
510   -- these are used in all patterns below
511   let objvar = varNameE "o"
512       objfield = stringE fname
513   bexp <- if isOptional t
514           then [| $((varNameE "maybeFromObj")) $objvar $objfield |]
515           else case defa of
516                  AppE (ConE dt) defval | dt == 'Just ->
517                    -- but has a default value
518                    [| $(varNameE "fromObjWithDefault")
519                       $objvar $objfield $(return defval) |]
520                  ConE dt | dt == 'Nothing ->
521                      [| $(varNameE "fromObj") $objvar $objfield |]
522                  s -> fail $ "Invalid default value " ++ show s ++
523                       ", expecting either 'Nothing' or a 'Just defval'"
524   return (fvar, BindS (VarP fvar) bexp)
525
526 loadConstructor :: String -> [OpParam] -> Q Exp
527 loadConstructor sname fields = do
528   let name = mkName sname
529   fbinds <- mapM loadField fields
530   let (fnames, fstmts) = unzip fbinds
531   let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
532       fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
533   return $ DoE fstmts'
534
535 genLoadOpCode :: [(String, [OpParam])] -> Q (Dec, Dec)
536 genLoadOpCode opdefs = do
537   let fname = mkName "loadOpCode"
538       arg1 = mkName "v"
539       objname = mkName "o"
540       opid = mkName "op_id"
541   st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
542                                  (JSON.readJSON $(varE arg1)) |]
543   st2 <- bindS (varP opid) [| $(varNameE "fromObj")
544                               $(varE objname) $(stringE "OP_ID") |]
545   -- the match results (per-constructor blocks)
546   mexps <- mapM (uncurry loadConstructor) opdefs
547   fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
548   let mpats = map (\(me, c) ->
549                        let mp = LitP . StringL . deCamelCase . fst $ c
550                        in Match mp (NormalB me) []
551                   ) $ zip mexps opdefs
552       defmatch = Match WildP (NormalB fails) []
553       cst = NoBindS $ CaseE (VarE opid) $ mpats++[defmatch]
554       body = DoE [st1, st2, cst]
555   sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
556   return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
557
558 -- | No default type.
559 noDefault :: Q Exp
560 noDefault = conE 'Nothing
561
562 -- * Template code for luxi
563
564 -- | Constructor-to-string for LuxiOp.
565 genStrOfOp :: Name -> String -> Q [Dec]
566 genStrOfOp = genConstrToStr id
567
568 -- | Constructor-to-string for MsgKeys.
569 genStrOfKey :: Name -> String -> Q [Dec]
570 genStrOfKey = genConstrToStr ensureLower
571
572 -- | LuxiOp parameter type.
573 type LuxiParam = (String, Q Type, Q Exp)
574
575 -- | Generates the LuxiOp data type.
576 --
577 -- This takes a Luxi operation definition and builds both the
578 -- datatype and the function trnasforming the arguments to JSON.
579 -- We can't use anything less generic, because the way different
580 -- operations are serialized differs on both parameter- and top-level.
581 --
582 -- There are three things to be defined for each parameter:
583 --
584 -- * name
585 --
586 -- * type
587 --
588 -- * operation; this is the operation performed on the parameter before
589 --   serialization
590 --
591 genLuxiOp :: String -> [(String, [LuxiParam])] -> Q [Dec]
592 genLuxiOp name cons = do
593   decl_d <- mapM (\(cname, fields) -> do
594                     fields' <- mapM (\(_, qt, _) ->
595                                          qt >>= \t -> return (NotStrict, t))
596                                fields
597                     return $ NormalC (mkName cname) fields')
598             cons
599   let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read]
600   (savesig, savefn) <- genSaveLuxiOp cons
601   return [declD, savesig, savefn]
602
603 -- | Generates the \"save\" expression for a single luxi parameter.
604 saveLuxiField :: Name -> LuxiParam -> Q Exp
605 saveLuxiField fvar (_, qt, fn) =
606     [| JSON.showJSON ( $(liftM2 appFn fn $ varE fvar) ) |]
607
608 -- | Generates the \"save\" clause for entire LuxiOp constructor.
609 saveLuxiConstructor :: (String, [LuxiParam]) -> Q Clause
610 saveLuxiConstructor (sname, fields) = do
611   let cname = mkName sname
612       fnames = map (\(nm, _, _) -> mkName nm) fields
613       pat = conP cname (map varP fnames)
614       flist = map (uncurry saveLuxiField) (zip fnames fields)
615       finval = if null flist
616                then [| JSON.showJSON ()    |]
617                else [| JSON.showJSON $(listE flist) |]
618   clause [pat] (normalB finval) []
619
620 -- | Generates the main save LuxiOp function.
621 genSaveLuxiOp :: [(String, [LuxiParam])]-> Q (Dec, Dec)
622 genSaveLuxiOp opdefs = do
623   sigt <- [t| $(conT (mkName "LuxiOp")) -> JSON.JSValue |]
624   let fname = mkName "opToArgs"
625   cclauses <- mapM saveLuxiConstructor opdefs
626   return $ (SigD fname sigt, FunD fname cclauses)
627
628 -- * "Objects" functionality
629
630 -- | Extract the field's declaration from a Field structure.
631 fieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
632 fieldTypeInfo field_pfx fd = do
633   t <- actualFieldType fd
634   let n = mkName . (field_pfx ++) . fieldRecordName $ fd
635   return (n, NotStrict, t)
636
637 -- | Build an object declaration.
638 buildObject :: String -> String -> [Field] -> Q [Dec]
639 buildObject sname field_pfx fields = do
640   let name = mkName sname
641   fields_d <- mapM (fieldTypeInfo field_pfx) fields
642   let decl_d = RecC name fields_d
643   let declD = DataD [] name [] [decl_d] [''Show, ''Read]
644   ser_decls <- buildObjectSerialisation sname fields
645   return $ declD:ser_decls
646
647 buildObjectSerialisation :: String -> [Field] -> Q [Dec]
648 buildObjectSerialisation sname fields = do
649   let name = mkName sname
650   savedecls <- genSaveObject saveObjectField sname fields
651   (loadsig, loadfn) <- genLoadObject loadObjectField sname fields
652   shjson <- objectShowJSON sname
653   rdjson <- objectReadJSON sname
654   let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
655                  (rdjson:shjson)
656   return $ savedecls ++ [loadsig, loadfn, instdecl]
657
658 genSaveObject :: (Name -> Field -> Q Exp)
659               -> String -> [Field] -> Q [Dec]
660 genSaveObject save_fn sname fields = do
661   let name = mkName sname
662   let fnames = map (mkName . fieldVariable) fields
663   let pat = conP name (map varP fnames)
664   let tdname = mkName ("toDict" ++ sname)
665   tdsigt <- [t| $(conT name) -> [(String, JSON.JSValue)] |]
666
667   let felems = map (uncurry save_fn) (zip fnames fields)
668       flist = listE felems
669       -- and finally convert all this to a json object
670       tdlist = [| concat $flist |]
671       iname = mkName "i"
672   tclause <- clause [pat] (normalB tdlist) []
673   cclause <- [| $(varNameE "makeObj") . $(varE tdname) |]
674   let fname = mkName ("save" ++ sname)
675   sigt <- [t| $(conT name) -> JSON.JSValue |]
676   return [SigD tdname tdsigt, FunD tdname [tclause],
677           SigD fname sigt, ValD (VarP fname) (NormalB cclause) []]
678
679 saveObjectField :: Name -> Field -> Q Exp
680 saveObjectField fvar field
681   | isContainer = [| [( $nameE , $showJSONE . showContainer $ $fvarE)] |]
682   | fisOptional = [| case $(varE fvar) of
683                       Nothing -> []
684                       Just v -> [( $nameE, $showJSONE v)]
685                   |]
686   | otherwise = case fieldShow field of
687       Nothing -> [| [( $nameE, $showJSONE $fvarE)] |]
688       Just fn -> [| [( $nameE, $showJSONE . $fn $ $fvarE)] |]
689   where isContainer = fieldIsContainer field
690         fisOptional  = fieldIsOptional field
691         nameE = stringE (fieldName field)
692         fvarE = varE fvar
693
694 objectShowJSON :: String -> Q [Dec]
695 objectShowJSON name =
696   [d| showJSON = JSON.showJSON . $(varE . mkName $ "save" ++ name) |]
697
698 genLoadObject :: (Field -> Q (Name, Stmt))
699               -> String -> [Field] -> Q (Dec, Dec)
700 genLoadObject load_fn sname fields = do
701   let name = mkName sname
702       funname = mkName $ "load" ++ sname
703       arg1 = mkName "v"
704       objname = mkName "o"
705       opid = mkName "op_id"
706   st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
707                                  (JSON.readJSON $(varE arg1)) |]
708   fbinds <- mapM load_fn fields
709   let (fnames, fstmts) = unzip fbinds
710   let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
711       fstmts' = st1:fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
712   sigt <- [t| JSON.JSValue -> JSON.Result $(conT name) |]
713   return $ (SigD funname sigt,
714             FunD funname [Clause [VarP arg1] (NormalB (DoE fstmts')) []])
715
716 loadObjectField :: Field -> Q (Name, Stmt)
717 loadObjectField field = do
718   let name = fieldVariable field
719       fvar = mkName name
720   -- these are used in all patterns below
721   let objvar = varNameE "o"
722       objfield = stringE (fieldName field)
723       loadexp =
724         if fieldIsOptional field
725           then [| $(varNameE "maybeFromObj") $objvar $objfield |]
726           else case fieldDefault field of
727                  Just defv ->
728                    [| $(varNameE "fromObjWithDefault") $objvar
729                       $objfield $defv |]
730                  Nothing -> [| $(varNameE "fromObj") $objvar $objfield |]
731   bexp <- loadFn field loadexp
732
733   return (fvar, BindS (VarP fvar) bexp)
734
735 objectReadJSON :: String -> Q Dec
736 objectReadJSON name = do
737   let s = mkName "s"
738   body <- [| case JSON.readJSON $(varE s) of
739                JSON.Ok s' -> $(varE .mkName $ "load" ++ name) s'
740                JSON.Error e ->
741                  JSON.Error $ "Can't parse value for type " ++
742                        $(stringE name) ++ ": " ++ e
743            |]
744   return $ FunD (mkName "readJSON") [Clause [VarP s] (NormalB body) []]
745
746 -- * Inheritable parameter tables implementation
747
748 -- | Compute parameter type names.
749 paramTypeNames :: String -> (String, String)
750 paramTypeNames root = ("Filled"  ++ root ++ "Params",
751                        "Partial" ++ root ++ "Params")
752
753 -- | Compute information about the type of a parameter field.
754 paramFieldTypeInfo :: String -> Field -> Q (Name, Strict, Type)
755 paramFieldTypeInfo field_pfx fd = do
756   t <- actualFieldType fd
757   let n = mkName . (++ "P") . (field_pfx ++) .
758           fieldRecordName $ fd
759   return (n, NotStrict, AppT (ConT ''Maybe) t)
760
761 -- | Build a parameter declaration.
762 --
763 -- This function builds two different data structures: a /filled/ one,
764 -- in which all fields are required, and a /partial/ one, in which all
765 -- fields are optional. Due to the current record syntax issues, the
766 -- fields need to be named differrently for the two structures, so the
767 -- partial ones get a /P/ suffix.
768 buildParam :: String -> String -> [Field] -> Q [Dec]
769 buildParam sname field_pfx fields = do
770   let (sname_f, sname_p) = paramTypeNames sname
771       name_f = mkName sname_f
772       name_p = mkName sname_p
773   fields_f <- mapM (fieldTypeInfo field_pfx) fields
774   fields_p <- mapM (paramFieldTypeInfo field_pfx) fields
775   let decl_f = RecC name_f fields_f
776       decl_p = RecC name_p fields_p
777   let declF = DataD [] name_f [] [decl_f] [''Show, ''Read]
778       declP = DataD [] name_p [] [decl_p] [''Show, ''Read]
779   ser_decls_f <- buildObjectSerialisation sname_f fields
780   ser_decls_p <- buildPParamSerialisation sname_p fields
781   fill_decls <- fillParam sname field_pfx fields
782   return $ [declF, declP] ++ ser_decls_f ++ ser_decls_p ++ fill_decls
783
784 buildPParamSerialisation :: String -> [Field] -> Q [Dec]
785 buildPParamSerialisation sname fields = do
786   let name = mkName sname
787   savedecls <- genSaveObject savePParamField sname fields
788   (loadsig, loadfn) <- genLoadObject loadPParamField sname fields
789   shjson <- objectShowJSON sname
790   rdjson <- objectReadJSON sname
791   let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name))
792                  (rdjson:shjson)
793   return $ savedecls ++ [loadsig, loadfn, instdecl]
794
795 savePParamField :: Name -> Field -> Q Exp
796 savePParamField fvar field = do
797   checkNonOptDef field
798   let actualVal = mkName "v"
799   normalexpr <- saveObjectField actualVal field
800   -- we have to construct the block here manually, because we can't
801   -- splice-in-splice
802   return $ CaseE (VarE fvar) [ Match (ConP 'Nothing [])
803                                        (NormalB (ConE '[])) []
804                              , Match (ConP 'Just [VarP actualVal])
805                                        (NormalB normalexpr) []
806                              ]
807 loadPParamField :: Field -> Q (Name, Stmt)
808 loadPParamField field = do
809   checkNonOptDef field
810   let name = fieldName field
811       fvar = mkName name
812   -- these are used in all patterns below
813   let objvar = varNameE "o"
814       objfield = stringE name
815       loadexp = [| $(varNameE "maybeFromObj") $objvar $objfield |]
816   bexp <- loadFn field loadexp
817   return (fvar, BindS (VarP fvar) bexp)
818
819 -- | Builds a simple declaration of type @n_x = fromMaybe f_x p_x@.
820 buildFromMaybe :: String -> Q Dec
821 buildFromMaybe fname =
822   valD (varP (mkName $ "n_" ++ fname))
823          (normalB [| $(varNameE "fromMaybe")
824                         $(varNameE $ "f_" ++ fname)
825                         $(varNameE $ "p_" ++ fname) |]) []
826
827 fillParam :: String -> String -> [Field] -> Q [Dec]
828 fillParam sname field_pfx fields = do
829   let fnames = map (\fd -> field_pfx ++ fieldRecordName fd) fields
830       (sname_f, sname_p) = paramTypeNames sname
831       oname_f = "fobj"
832       oname_p = "pobj"
833       name_f = mkName sname_f
834       name_p = mkName sname_p
835       fun_name = mkName $ "fill" ++ sname ++ "Params"
836       le_full = ValD (ConP name_f (map (VarP . mkName . ("f_" ++)) fnames))
837                 (NormalB . VarE . mkName $ oname_f) []
838       le_part = ValD (ConP name_p (map (VarP . mkName . ("p_" ++)) fnames))
839                 (NormalB . VarE . mkName $ oname_p) []
840       obj_new = foldl (\accu vname -> AppE accu (VarE vname)) (ConE name_f)
841                 $ map (mkName . ("n_" ++)) fnames
842   le_new <- mapM buildFromMaybe fnames
843   funt <- [t| $(conT name_f) -> $(conT name_p) -> $(conT name_f) |]
844   let sig = SigD fun_name funt
845       fclause = Clause [VarP (mkName oname_f), VarP (mkName oname_p)]
846                 (NormalB $ LetE (le_full:le_part:le_new) obj_new) []
847       fun = FunD fun_name [fclause]
848   return [sig, fun]