Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / THH.hs @ 7eda951b

History | View | Annotate | Download (16.8 kB)

1 e9aaa3c6 Iustin Pop
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
2 e9aaa3c6 Iustin Pop
3 e9aaa3c6 Iustin Pop
{-| TemplateHaskell helper for HTools.
4 e9aaa3c6 Iustin Pop
5 e9aaa3c6 Iustin Pop
As TemplateHaskell require that splices be defined in a separate
6 e9aaa3c6 Iustin Pop
module, we combine all the TemplateHaskell functionality that HTools
7 e9aaa3c6 Iustin Pop
needs in this module (except the one for unittests).
8 e9aaa3c6 Iustin Pop
9 e9aaa3c6 Iustin Pop
-}
10 e9aaa3c6 Iustin Pop
11 e9aaa3c6 Iustin Pop
{-
12 e9aaa3c6 Iustin Pop
13 e9aaa3c6 Iustin Pop
Copyright (C) 2011 Google Inc.
14 e9aaa3c6 Iustin Pop
15 e9aaa3c6 Iustin Pop
This program is free software; you can redistribute it and/or modify
16 e9aaa3c6 Iustin Pop
it under the terms of the GNU General Public License as published by
17 e9aaa3c6 Iustin Pop
the Free Software Foundation; either version 2 of the License, or
18 e9aaa3c6 Iustin Pop
(at your option) any later version.
19 e9aaa3c6 Iustin Pop
20 e9aaa3c6 Iustin Pop
This program is distributed in the hope that it will be useful, but
21 e9aaa3c6 Iustin Pop
WITHOUT ANY WARRANTY; without even the implied warranty of
22 e9aaa3c6 Iustin Pop
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23 e9aaa3c6 Iustin Pop
General Public License for more details.
24 e9aaa3c6 Iustin Pop
25 e9aaa3c6 Iustin Pop
You should have received a copy of the GNU General Public License
26 e9aaa3c6 Iustin Pop
along with this program; if not, write to the Free Software
27 e9aaa3c6 Iustin Pop
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28 e9aaa3c6 Iustin Pop
02110-1301, USA.
29 e9aaa3c6 Iustin Pop
30 e9aaa3c6 Iustin Pop
-}
31 e9aaa3c6 Iustin Pop
32 b20cbf06 Iustin Pop
module Ganeti.THH ( declareSADT
33 e9aaa3c6 Iustin Pop
                  , makeJSONInstance
34 6111e296 Iustin Pop
                  , genOpID
35 12c19659 Iustin Pop
                  , genOpCode
36 12c19659 Iustin Pop
                  , noDefault
37 a0090487 Agata Murawska
                  , genStrOfOp
38 a0090487 Agata Murawska
                  , genStrOfKey
39 a0090487 Agata Murawska
                  , genLuxiOp
40 e9aaa3c6 Iustin Pop
                  ) where
41 e9aaa3c6 Iustin Pop
42 60de49c3 Iustin Pop
import Control.Monad (liftM, liftM2)
43 e9aaa3c6 Iustin Pop
import Data.Char
44 6111e296 Iustin Pop
import Data.List
45 e9aaa3c6 Iustin Pop
import Language.Haskell.TH
46 e9aaa3c6 Iustin Pop
47 e9aaa3c6 Iustin Pop
import qualified Text.JSON as JSON
48 e9aaa3c6 Iustin Pop
49 53664e15 Iustin Pop
-- * Helper functions
50 53664e15 Iustin Pop
51 e9aaa3c6 Iustin Pop
-- | Ensure first letter is lowercase.
52 e9aaa3c6 Iustin Pop
--
53 e9aaa3c6 Iustin Pop
-- Used to convert type name to function prefix, e.g. in @data Aa ->
54 e9aaa3c6 Iustin Pop
-- aaToString@.
55 e9aaa3c6 Iustin Pop
ensureLower :: String -> String
56 e9aaa3c6 Iustin Pop
ensureLower [] = []
57 e9aaa3c6 Iustin Pop
ensureLower (x:xs) = toLower x:xs
58 e9aaa3c6 Iustin Pop
59 53664e15 Iustin Pop
-- | Helper for quoted expressions.
60 53664e15 Iustin Pop
varNameE :: String -> Q Exp
61 53664e15 Iustin Pop
varNameE = varE . mkName
62 53664e15 Iustin Pop
63 53664e15 Iustin Pop
-- | showJSON as an expression, for reuse.
64 53664e15 Iustin Pop
showJSONE :: Q Exp
65 53664e15 Iustin Pop
showJSONE = varNameE "showJSON"
66 53664e15 Iustin Pop
67 e9aaa3c6 Iustin Pop
-- | ToString function name.
68 e9aaa3c6 Iustin Pop
toStrName :: String -> Name
69 e9aaa3c6 Iustin Pop
toStrName = mkName . (++ "ToString") . ensureLower
70 e9aaa3c6 Iustin Pop
71 e9aaa3c6 Iustin Pop
-- | FromString function name.
72 e9aaa3c6 Iustin Pop
fromStrName :: String -> Name
73 e9aaa3c6 Iustin Pop
fromStrName = mkName . (++ "FromString") . ensureLower
74 e9aaa3c6 Iustin Pop
75 6111e296 Iustin Pop
-- | Converts a name to it's varE/litE representations.
76 6111e296 Iustin Pop
--
77 6111e296 Iustin Pop
reprE :: Either String Name -> Q Exp
78 53664e15 Iustin Pop
reprE = either stringE varE
79 53664e15 Iustin Pop
80 60de49c3 Iustin Pop
-- | Smarter function application.
81 60de49c3 Iustin Pop
--
82 60de49c3 Iustin Pop
-- This does simply f x, except that if is 'id', it will skip it, in
83 60de49c3 Iustin Pop
-- order to generate more readable code when using -ddump-splices.
84 60de49c3 Iustin Pop
appFn :: Exp -> Exp -> Exp
85 60de49c3 Iustin Pop
appFn f x | f == VarE 'id = x
86 60de49c3 Iustin Pop
          | otherwise = AppE f x
87 60de49c3 Iustin Pop
88 53664e15 Iustin Pop
-- * Template code for simple string-equivalent ADTs
89 6111e296 Iustin Pop
90 e9aaa3c6 Iustin Pop
-- | Generates a data type declaration.
91 e9aaa3c6 Iustin Pop
--
92 e9aaa3c6 Iustin Pop
-- The type will have a fixed list of instances.
93 e9aaa3c6 Iustin Pop
strADTDecl :: Name -> [String] -> Dec
94 e9aaa3c6 Iustin Pop
strADTDecl name constructors =
95 e9aaa3c6 Iustin Pop
    DataD [] name []
96 e9aaa3c6 Iustin Pop
              (map (flip NormalC [] . mkName) constructors)
97 e9aaa3c6 Iustin Pop
              [''Show, ''Read, ''Eq, ''Enum, ''Bounded, ''Ord]
98 e9aaa3c6 Iustin Pop
99 e9aaa3c6 Iustin Pop
-- | Generates a toString function.
100 e9aaa3c6 Iustin Pop
--
101 e9aaa3c6 Iustin Pop
-- This generates a simple function of the form:
102 e9aaa3c6 Iustin Pop
--
103 e9aaa3c6 Iustin Pop
-- @
104 e9aaa3c6 Iustin Pop
-- nameToString :: Name -> String
105 e9aaa3c6 Iustin Pop
-- nameToString Cons1 = var1
106 e9aaa3c6 Iustin Pop
-- nameToString Cons2 = \"value2\"
107 e9aaa3c6 Iustin Pop
-- @
108 6111e296 Iustin Pop
genToString :: Name -> Name -> [(String, Either String Name)] -> Q [Dec]
109 e9aaa3c6 Iustin Pop
genToString fname tname constructors = do
110 e9aaa3c6 Iustin Pop
  sigt <- [t| $(conT tname) -> String |]
111 e9aaa3c6 Iustin Pop
  -- the body clauses, matching on the constructor and returning the
112 e9aaa3c6 Iustin Pop
  -- string value
113 e9aaa3c6 Iustin Pop
  clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
114 6111e296 Iustin Pop
                             (normalB (reprE v)) []) constructors
115 e9aaa3c6 Iustin Pop
  return [SigD fname sigt, FunD fname clauses]
116 e9aaa3c6 Iustin Pop
117 e9aaa3c6 Iustin Pop
-- | Generates a fromString function.
118 e9aaa3c6 Iustin Pop
--
119 e9aaa3c6 Iustin Pop
-- The function generated is monadic and can fail parsing the
120 e9aaa3c6 Iustin Pop
-- string. It is of the form:
121 e9aaa3c6 Iustin Pop
--
122 e9aaa3c6 Iustin Pop
-- @
123 e9aaa3c6 Iustin Pop
-- nameFromString :: (Monad m) => String -> m Name
124 e9aaa3c6 Iustin Pop
-- nameFromString s | s == var1       = Cons1
125 e9aaa3c6 Iustin Pop
--                  | s == \"value2\" = Cons2
126 e9aaa3c6 Iustin Pop
--                  | otherwise = fail /.../
127 e9aaa3c6 Iustin Pop
-- @
128 e9aaa3c6 Iustin Pop
genFromString :: Name -> Name -> [(String, Name)] -> Q [Dec]
129 e9aaa3c6 Iustin Pop
genFromString fname tname constructors = do
130 e9aaa3c6 Iustin Pop
  -- signature of form (Monad m) => String -> m $name
131 e9aaa3c6 Iustin Pop
  sigt <- [t| (Monad m) => String -> m $(conT tname) |]
132 e9aaa3c6 Iustin Pop
  -- clauses for a guarded pattern
133 e9aaa3c6 Iustin Pop
  let varp = mkName "s"
134 e9aaa3c6 Iustin Pop
      varpe = varE varp
135 e9aaa3c6 Iustin Pop
  clauses <- mapM (\(c, v) -> do
136 e9aaa3c6 Iustin Pop
                     -- the clause match condition
137 e9aaa3c6 Iustin Pop
                     g <- normalG [| $varpe == $(varE v) |]
138 e9aaa3c6 Iustin Pop
                     -- the clause result
139 e9aaa3c6 Iustin Pop
                     r <- [| return $(conE (mkName c)) |]
140 e9aaa3c6 Iustin Pop
                     return (g, r)) constructors
141 e9aaa3c6 Iustin Pop
  -- the otherwise clause (fallback)
142 e9aaa3c6 Iustin Pop
  oth_clause <- do
143 e9aaa3c6 Iustin Pop
    g <- normalG [| otherwise |]
144 e9aaa3c6 Iustin Pop
    r <- [|fail ("Invalid string value for type " ++
145 e9aaa3c6 Iustin Pop
                 $(litE (stringL (nameBase tname))) ++ ": " ++ $varpe) |]
146 e9aaa3c6 Iustin Pop
    return (g, r)
147 e9aaa3c6 Iustin Pop
  let fun = FunD fname [Clause [VarP varp]
148 e9aaa3c6 Iustin Pop
                        (GuardedB (clauses++[oth_clause])) []]
149 e9aaa3c6 Iustin Pop
  return [SigD fname sigt, fun]
150 e9aaa3c6 Iustin Pop
151 e9aaa3c6 Iustin Pop
-- | Generates a data type from a given string format.
152 e9aaa3c6 Iustin Pop
--
153 e9aaa3c6 Iustin Pop
-- The format is expected to multiline. The first line contains the
154 e9aaa3c6 Iustin Pop
-- type name, and the rest of the lines must contain two words: the
155 e9aaa3c6 Iustin Pop
-- constructor name and then the string representation of the
156 e9aaa3c6 Iustin Pop
-- respective constructor.
157 e9aaa3c6 Iustin Pop
--
158 e9aaa3c6 Iustin Pop
-- The function will generate the data type declaration, and then two
159 e9aaa3c6 Iustin Pop
-- functions:
160 e9aaa3c6 Iustin Pop
--
161 e9aaa3c6 Iustin Pop
-- * /name/ToString, which converts the type to a string
162 e9aaa3c6 Iustin Pop
--
163 e9aaa3c6 Iustin Pop
-- * /name/FromString, which (monadically) converts from a string to the type
164 e9aaa3c6 Iustin Pop
--
165 e9aaa3c6 Iustin Pop
-- Note that this is basically just a custom show/read instance,
166 e9aaa3c6 Iustin Pop
-- nothing else.
167 e9aaa3c6 Iustin Pop
declareSADT :: String -> [(String, Name)] -> Q [Dec]
168 e9aaa3c6 Iustin Pop
declareSADT sname cons = do
169 e9aaa3c6 Iustin Pop
  let name = mkName sname
170 e9aaa3c6 Iustin Pop
      ddecl = strADTDecl name (map fst cons)
171 6111e296 Iustin Pop
      -- process cons in the format expected by genToString
172 6111e296 Iustin Pop
      cons' = map (\(a, b) -> (a, Right b)) cons
173 6111e296 Iustin Pop
  tostr <- genToString (toStrName sname) name cons'
174 e9aaa3c6 Iustin Pop
  fromstr <- genFromString (fromStrName sname) name cons
175 e9aaa3c6 Iustin Pop
  return $ ddecl:tostr ++ fromstr
176 e9aaa3c6 Iustin Pop
177 e9aaa3c6 Iustin Pop
178 e9aaa3c6 Iustin Pop
-- | Creates the showJSON member of a JSON instance declaration.
179 e9aaa3c6 Iustin Pop
--
180 e9aaa3c6 Iustin Pop
-- This will create what is the equivalent of:
181 e9aaa3c6 Iustin Pop
--
182 e9aaa3c6 Iustin Pop
-- @
183 e9aaa3c6 Iustin Pop
-- showJSON = showJSON . /name/ToString
184 e9aaa3c6 Iustin Pop
-- @
185 e9aaa3c6 Iustin Pop
--
186 e9aaa3c6 Iustin Pop
-- in an instance JSON /name/ declaration
187 e9aaa3c6 Iustin Pop
genShowJSON :: String -> Q [Dec]
188 e9aaa3c6 Iustin Pop
genShowJSON name = [d| showJSON = JSON.showJSON . $(varE (toStrName name)) |]
189 e9aaa3c6 Iustin Pop
190 e9aaa3c6 Iustin Pop
-- | Creates the readJSON member of a JSON instance declaration.
191 e9aaa3c6 Iustin Pop
--
192 e9aaa3c6 Iustin Pop
-- This will create what is the equivalent of:
193 e9aaa3c6 Iustin Pop
--
194 e9aaa3c6 Iustin Pop
-- @
195 e9aaa3c6 Iustin Pop
-- readJSON s = case readJSON s of
196 e9aaa3c6 Iustin Pop
--                Ok s' -> /name/FromString s'
197 e9aaa3c6 Iustin Pop
--                Error e -> Error /description/
198 e9aaa3c6 Iustin Pop
-- @
199 e9aaa3c6 Iustin Pop
--
200 e9aaa3c6 Iustin Pop
-- in an instance JSON /name/ declaration
201 e9aaa3c6 Iustin Pop
genReadJSON :: String -> Q Dec
202 e9aaa3c6 Iustin Pop
genReadJSON name = do
203 e9aaa3c6 Iustin Pop
  let s = mkName "s"
204 e9aaa3c6 Iustin Pop
  body <- [| case JSON.readJSON $(varE s) of
205 e9aaa3c6 Iustin Pop
               JSON.Ok s' -> $(varE (fromStrName name)) s'
206 e9aaa3c6 Iustin Pop
               JSON.Error e ->
207 e9aaa3c6 Iustin Pop
                   JSON.Error $ "Can't parse string value for type " ++
208 53664e15 Iustin Pop
                           $(stringE name) ++ ": " ++ e
209 e9aaa3c6 Iustin Pop
           |]
210 e9aaa3c6 Iustin Pop
  return $ FunD (mkName "readJSON") [Clause [VarP s] (NormalB body) []]
211 e9aaa3c6 Iustin Pop
212 e9aaa3c6 Iustin Pop
-- | Generates a JSON instance for a given type.
213 e9aaa3c6 Iustin Pop
--
214 e9aaa3c6 Iustin Pop
-- This assumes that the /name/ToString and /name/FromString functions
215 e9aaa3c6 Iustin Pop
-- have been defined as by the 'declareSADT' function.
216 e9aaa3c6 Iustin Pop
makeJSONInstance :: Name -> Q [Dec]
217 e9aaa3c6 Iustin Pop
makeJSONInstance name = do
218 e9aaa3c6 Iustin Pop
  let base = nameBase name
219 e9aaa3c6 Iustin Pop
  showJ <- genShowJSON base
220 e9aaa3c6 Iustin Pop
  readJ <- genReadJSON base
221 e9aaa3c6 Iustin Pop
  return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) (readJ:showJ)]
222 6111e296 Iustin Pop
223 53664e15 Iustin Pop
-- * Template code for opcodes
224 53664e15 Iustin Pop
225 6111e296 Iustin Pop
-- | Transforms a CamelCase string into an_underscore_based_one.
226 6111e296 Iustin Pop
deCamelCase :: String -> String
227 6111e296 Iustin Pop
deCamelCase =
228 6111e296 Iustin Pop
    intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
229 6111e296 Iustin Pop
230 05ff7a00 Agata Murawska
-- | Computes the name of a given constructor.
231 6111e296 Iustin Pop
constructorName :: Con -> Q Name
232 6111e296 Iustin Pop
constructorName (NormalC name _) = return name
233 6111e296 Iustin Pop
constructorName (RecC name _)    = return name
234 6111e296 Iustin Pop
constructorName x                = fail $ "Unhandled constructor " ++ show x
235 6111e296 Iustin Pop
236 a0090487 Agata Murawska
-- | Builds the generic constructor-to-string function.
237 6111e296 Iustin Pop
--
238 6111e296 Iustin Pop
-- This generates a simple function of the following form:
239 6111e296 Iustin Pop
--
240 6111e296 Iustin Pop
-- @
241 a0090487 Agata Murawska
-- fname (ConStructorOne {}) = trans_fun("ConStructorOne")
242 a0090487 Agata Murawska
-- fname (ConStructorTwo {}) = trans_fun("ConStructorTwo")
243 6111e296 Iustin Pop
-- @
244 6111e296 Iustin Pop
--
245 6111e296 Iustin Pop
-- This builds a custom list of name/string pairs and then uses
246 6111e296 Iustin Pop
-- 'genToString' to actually generate the function
247 a0090487 Agata Murawska
genConstrToStr :: (String -> String) -> Name -> String -> Q [Dec]
248 a0090487 Agata Murawska
genConstrToStr trans_fun name fname = do
249 6111e296 Iustin Pop
  TyConI (DataD _ _ _ cons _) <- reify name
250 6111e296 Iustin Pop
  cnames <- mapM (liftM nameBase . constructorName) cons
251 a0090487 Agata Murawska
  let svalues = map (Left . trans_fun) cnames
252 6111e296 Iustin Pop
  genToString (mkName fname) name $ zip cnames svalues
253 12c19659 Iustin Pop
254 a0090487 Agata Murawska
-- | Constructor-to-string for OpCode.
255 a0090487 Agata Murawska
genOpID :: Name -> String -> Q [Dec]
256 a0090487 Agata Murawska
genOpID = genConstrToStr deCamelCase
257 12c19659 Iustin Pop
258 05ff7a00 Agata Murawska
-- | OpCode parameter (field) type.
259 12c19659 Iustin Pop
type OpParam = (String, Q Type, Q Exp)
260 12c19659 Iustin Pop
261 12c19659 Iustin Pop
-- | Generates the OpCode data type.
262 12c19659 Iustin Pop
--
263 12c19659 Iustin Pop
-- This takes an opcode logical definition, and builds both the
264 12c19659 Iustin Pop
-- datatype and the JSON serialisation out of it. We can't use a
265 12c19659 Iustin Pop
-- generic serialisation since we need to be compatible with Ganeti's
266 12c19659 Iustin Pop
-- own, so we have a few quirks to work around.
267 12c19659 Iustin Pop
--
268 12c19659 Iustin Pop
-- There are three things to be defined for each parameter:
269 12c19659 Iustin Pop
--
270 12c19659 Iustin Pop
-- * name
271 12c19659 Iustin Pop
--
272 12c19659 Iustin Pop
-- * type; if this is 'Maybe', will only be serialised if it's a
273 12c19659 Iustin Pop
--   'Just' value
274 12c19659 Iustin Pop
--
275 12c19659 Iustin Pop
-- * default; if missing, won't raise an exception, but will instead
276 12c19659 Iustin Pop
--   use the default
277 12c19659 Iustin Pop
--
278 12c19659 Iustin Pop
genOpCode :: String                -- ^ Type name to use
279 12c19659 Iustin Pop
          -> [(String, [OpParam])] -- ^ Constructor name and parameters
280 12c19659 Iustin Pop
          -> Q [Dec]
281 12c19659 Iustin Pop
genOpCode name cons = do
282 12c19659 Iustin Pop
  decl_d <- mapM (\(cname, fields) -> do
283 12c19659 Iustin Pop
                    -- we only need the type of the field, without Q
284 12c19659 Iustin Pop
                    fields' <- mapM (\(_, qt, _) ->
285 12c19659 Iustin Pop
                                         qt >>= \t -> return (NotStrict, t))
286 12c19659 Iustin Pop
                               fields
287 12c19659 Iustin Pop
                    return $ NormalC (mkName cname) fields')
288 12c19659 Iustin Pop
            cons
289 12c19659 Iustin Pop
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read, ''Eq]
290 12c19659 Iustin Pop
291 12c19659 Iustin Pop
  (savesig, savefn) <- genSaveOpCode cons
292 12c19659 Iustin Pop
  (loadsig, loadfn) <- genLoadOpCode cons
293 12c19659 Iustin Pop
  return [declD, loadsig, loadfn, savesig, savefn]
294 12c19659 Iustin Pop
295 05ff7a00 Agata Murawska
-- | Checks whether a given parameter is options.
296 12c19659 Iustin Pop
--
297 12c19659 Iustin Pop
-- This requires that it's a 'Maybe'.
298 12c19659 Iustin Pop
isOptional :: Type -> Bool
299 12c19659 Iustin Pop
isOptional (AppT (ConT dt) _) | dt == ''Maybe = True
300 12c19659 Iustin Pop
isOptional _ = False
301 12c19659 Iustin Pop
302 12c19659 Iustin Pop
-- | Generates the \"save\" expression for a single opcode parameter.
303 12c19659 Iustin Pop
--
304 12c19659 Iustin Pop
-- There is only one special handling mode: if the parameter is of
305 12c19659 Iustin Pop
-- 'Maybe' type, then we only save it if it's a 'Just' value,
306 12c19659 Iustin Pop
-- otherwise we skip it.
307 12c19659 Iustin Pop
saveField :: Name    -- ^ The name of variable that contains the value
308 12c19659 Iustin Pop
          -> OpParam -- ^ Parameter definition
309 12c19659 Iustin Pop
          -> Q Exp
310 12c19659 Iustin Pop
saveField fvar (fname, qt, _) = do
311 12c19659 Iustin Pop
  t <- qt
312 53664e15 Iustin Pop
  let fnexp = stringE fname
313 12c19659 Iustin Pop
      fvare = varE fvar
314 12c19659 Iustin Pop
  (if isOptional t
315 12c19659 Iustin Pop
   then [| case $fvare of
316 53664e15 Iustin Pop
             Just v' -> [( $fnexp, $showJSONE v')]
317 12c19659 Iustin Pop
             Nothing -> []
318 12c19659 Iustin Pop
         |]
319 53664e15 Iustin Pop
   else [| [( $fnexp, $showJSONE $fvare )] |])
320 12c19659 Iustin Pop
321 12c19659 Iustin Pop
-- | Generates the \"save\" clause for an entire opcode constructor.
322 12c19659 Iustin Pop
--
323 12c19659 Iustin Pop
-- This matches the opcode with variables named the same as the
324 12c19659 Iustin Pop
-- constructor fields (just so that the spliced in code looks nicer),
325 12c19659 Iustin Pop
-- and passes those name plus the parameter definition to 'saveField'.
326 12c19659 Iustin Pop
saveConstructor :: String    -- ^ The constructor name
327 12c19659 Iustin Pop
                -> [OpParam] -- ^ The parameter definitions for this
328 12c19659 Iustin Pop
                             -- constructor
329 12c19659 Iustin Pop
                -> Q Clause  -- ^ Resulting clause
330 12c19659 Iustin Pop
saveConstructor sname fields = do
331 12c19659 Iustin Pop
  let cname = mkName sname
332 12c19659 Iustin Pop
  let fnames = map (\(n, _, _) -> mkName n) fields
333 12c19659 Iustin Pop
  let pat = conP cname (map varP fnames)
334 12c19659 Iustin Pop
  let felems = map (uncurry saveField) (zip fnames fields)
335 12c19659 Iustin Pop
      -- now build the OP_ID serialisation
336 53664e15 Iustin Pop
      opid = [| [( $(stringE "OP_ID"),
337 53664e15 Iustin Pop
                   $showJSONE $(stringE . deCamelCase $ sname) )] |]
338 12c19659 Iustin Pop
      flist = listE (opid:felems)
339 12c19659 Iustin Pop
      -- and finally convert all this to a json object
340 53664e15 Iustin Pop
      flist' = [| $(varNameE "makeObj") (concat $flist) |]
341 12c19659 Iustin Pop
  clause [pat] (normalB flist') []
342 12c19659 Iustin Pop
343 12c19659 Iustin Pop
-- | Generates the main save opcode function.
344 12c19659 Iustin Pop
--
345 12c19659 Iustin Pop
-- This builds a per-constructor match clause that contains the
346 12c19659 Iustin Pop
-- respective constructor-serialisation code.
347 12c19659 Iustin Pop
genSaveOpCode :: [(String, [OpParam])] -> Q (Dec, Dec)
348 12c19659 Iustin Pop
genSaveOpCode opdefs = do
349 12c19659 Iustin Pop
  cclauses <- mapM (uncurry saveConstructor) opdefs
350 12c19659 Iustin Pop
  let fname = mkName "saveOpCode"
351 12c19659 Iustin Pop
  sigt <- [t| $(conT (mkName "OpCode")) -> JSON.JSValue |]
352 12c19659 Iustin Pop
  return $ (SigD fname sigt, FunD fname cclauses)
353 12c19659 Iustin Pop
354 12c19659 Iustin Pop
-- | Generates the \"load\" field for a single parameter.
355 12c19659 Iustin Pop
--
356 12c19659 Iustin Pop
-- There is custom handling, depending on how the parameter is
357 12c19659 Iustin Pop
-- specified. For a 'Maybe' type parameter, we allow that it is not
358 12c19659 Iustin Pop
-- present (via 'Utils.maybeFromObj'). Otherwise, if there is a
359 12c19659 Iustin Pop
-- default value, we allow the parameter to be abset, and finally if
360 12c19659 Iustin Pop
-- there is no default value, we require its presence.
361 12c19659 Iustin Pop
loadField :: OpParam -> Q (Name, Stmt)
362 12c19659 Iustin Pop
loadField (fname, qt, qdefa) = do
363 12c19659 Iustin Pop
  let fvar = mkName fname
364 12c19659 Iustin Pop
  t <- qt
365 12c19659 Iustin Pop
  defa <- qdefa
366 12c19659 Iustin Pop
  -- these are used in all patterns below
367 53664e15 Iustin Pop
  let objvar = varNameE "o"
368 53664e15 Iustin Pop
      objfield = stringE fname
369 12c19659 Iustin Pop
  bexp <- if isOptional t
370 53664e15 Iustin Pop
          then [| $((varNameE "maybeFromObj")) $objvar $objfield |]
371 12c19659 Iustin Pop
          else case defa of
372 12c19659 Iustin Pop
                 AppE (ConE dt) defval | dt == 'Just ->
373 12c19659 Iustin Pop
                   -- but has a default value
374 53664e15 Iustin Pop
                   [| $(varNameE "fromObjWithDefault")
375 12c19659 Iustin Pop
                      $objvar $objfield $(return defval) |]
376 12c19659 Iustin Pop
                 ConE dt | dt == 'Nothing ->
377 53664e15 Iustin Pop
                     [| $(varNameE "fromObj") $objvar $objfield |]
378 12c19659 Iustin Pop
                 s -> fail $ "Invalid default value " ++ show s ++
379 12c19659 Iustin Pop
                      ", expecting either 'Nothing' or a 'Just defval'"
380 12c19659 Iustin Pop
  return (fvar, BindS (VarP fvar) bexp)
381 12c19659 Iustin Pop
382 12c19659 Iustin Pop
loadConstructor :: String -> [OpParam] -> Q Exp
383 12c19659 Iustin Pop
loadConstructor sname fields = do
384 12c19659 Iustin Pop
  let name = mkName sname
385 12c19659 Iustin Pop
  fbinds <- mapM loadField fields
386 12c19659 Iustin Pop
  let (fnames, fstmts) = unzip fbinds
387 12c19659 Iustin Pop
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
388 12c19659 Iustin Pop
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
389 12c19659 Iustin Pop
  return $ DoE fstmts'
390 12c19659 Iustin Pop
391 12c19659 Iustin Pop
genLoadOpCode :: [(String, [OpParam])] -> Q (Dec, Dec)
392 12c19659 Iustin Pop
genLoadOpCode opdefs = do
393 12c19659 Iustin Pop
  let fname = mkName "loadOpCode"
394 12c19659 Iustin Pop
      arg1 = mkName "v"
395 12c19659 Iustin Pop
      objname = mkName "o"
396 12c19659 Iustin Pop
      opid = mkName "op_id"
397 12c19659 Iustin Pop
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
398 12c19659 Iustin Pop
                                 (JSON.readJSON $(varE arg1)) |]
399 53664e15 Iustin Pop
  st2 <- bindS (varP opid) [| $(varNameE "fromObj")
400 53664e15 Iustin Pop
                              $(varE objname) $(stringE "OP_ID") |]
401 12c19659 Iustin Pop
  -- the match results (per-constructor blocks)
402 12c19659 Iustin Pop
  mexps <- mapM (uncurry loadConstructor) opdefs
403 12c19659 Iustin Pop
  fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
404 12c19659 Iustin Pop
  let mpats = map (\(me, c) ->
405 12c19659 Iustin Pop
                       let mp = LitP . StringL . deCamelCase . fst $ c
406 12c19659 Iustin Pop
                       in Match mp (NormalB me) []
407 12c19659 Iustin Pop
                  ) $ zip mexps opdefs
408 12c19659 Iustin Pop
      defmatch = Match WildP (NormalB fails) []
409 12c19659 Iustin Pop
      cst = NoBindS $ CaseE (VarE opid) $ mpats++[defmatch]
410 12c19659 Iustin Pop
      body = DoE [st1, st2, cst]
411 12c19659 Iustin Pop
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
412 12c19659 Iustin Pop
  return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
413 12c19659 Iustin Pop
414 12c19659 Iustin Pop
-- | No default type.
415 12c19659 Iustin Pop
noDefault :: Q Exp
416 12c19659 Iustin Pop
noDefault = conE 'Nothing
417 a0090487 Agata Murawska
418 a0090487 Agata Murawska
-- * Template code for luxi
419 a0090487 Agata Murawska
420 a0090487 Agata Murawska
-- | Constructor-to-string for LuxiOp.
421 a0090487 Agata Murawska
genStrOfOp :: Name -> String -> Q [Dec]
422 a0090487 Agata Murawska
genStrOfOp = genConstrToStr id
423 a0090487 Agata Murawska
424 a0090487 Agata Murawska
-- | Constructor-to-string for MsgKeys.
425 a0090487 Agata Murawska
genStrOfKey :: Name -> String -> Q [Dec]
426 a0090487 Agata Murawska
genStrOfKey = genConstrToStr ensureLower
427 a0090487 Agata Murawska
428 a0090487 Agata Murawska
-- | LuxiOp parameter type.
429 a0090487 Agata Murawska
type LuxiParam = (String, Q Type, Q Exp)
430 a0090487 Agata Murawska
431 a0090487 Agata Murawska
-- | Generates the LuxiOp data type.
432 a0090487 Agata Murawska
--
433 a0090487 Agata Murawska
-- This takes a Luxi operation definition and builds both the
434 a0090487 Agata Murawska
-- datatype and the function trnasforming the arguments to JSON.
435 a0090487 Agata Murawska
-- We can't use anything less generic, because the way different
436 a0090487 Agata Murawska
-- operations are serialized differs on both parameter- and top-level.
437 a0090487 Agata Murawska
--
438 a0090487 Agata Murawska
-- There are three things to be defined for each parameter:
439 a0090487 Agata Murawska
--
440 a0090487 Agata Murawska
-- * name
441 a0090487 Agata Murawska
--
442 a0090487 Agata Murawska
-- * type
443 a0090487 Agata Murawska
--
444 a0090487 Agata Murawska
-- * operation; this is the operation performed on the parameter before
445 a0090487 Agata Murawska
--   serialization
446 a0090487 Agata Murawska
--
447 b20cbf06 Iustin Pop
genLuxiOp :: String -> [(String, [LuxiParam])] -> Q [Dec]
448 a0090487 Agata Murawska
genLuxiOp name cons = do
449 b20cbf06 Iustin Pop
  decl_d <- mapM (\(cname, fields) -> do
450 a0090487 Agata Murawska
                    fields' <- mapM (\(_, qt, _) ->
451 a0090487 Agata Murawska
                                         qt >>= \t -> return (NotStrict, t))
452 a0090487 Agata Murawska
                               fields
453 a0090487 Agata Murawska
                    return $ NormalC (mkName cname) fields')
454 a0090487 Agata Murawska
            cons
455 a0090487 Agata Murawska
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read]
456 a0090487 Agata Murawska
  (savesig, savefn) <- genSaveLuxiOp cons
457 a0090487 Agata Murawska
  return [declD, savesig, savefn]
458 a0090487 Agata Murawska
459 92678b3c Iustin Pop
-- | Generates the \"save\" expression for a single luxi parameter.
460 b20cbf06 Iustin Pop
saveLuxiField :: Name -> LuxiParam -> Q Exp
461 b20cbf06 Iustin Pop
saveLuxiField fvar (_, qt, fn) =
462 b20cbf06 Iustin Pop
    [| JSON.showJSON ( $(liftM2 appFn fn $ varE fvar) ) |]
463 92678b3c Iustin Pop
464 a0090487 Agata Murawska
-- | Generates the \"save\" clause for entire LuxiOp constructor.
465 b20cbf06 Iustin Pop
saveLuxiConstructor :: (String, [LuxiParam]) -> Q Clause
466 b20cbf06 Iustin Pop
saveLuxiConstructor (sname, fields) = do
467 a0090487 Agata Murawska
  let cname = mkName sname
468 a0090487 Agata Murawska
      fnames = map (\(nm, _, _) -> mkName nm) fields
469 a0090487 Agata Murawska
      pat = conP cname (map varP fnames)
470 b20cbf06 Iustin Pop
      flist = map (uncurry saveLuxiField) (zip fnames fields)
471 b20cbf06 Iustin Pop
      finval = if null flist
472 b20cbf06 Iustin Pop
               then [| JSON.showJSON ()    |]
473 b20cbf06 Iustin Pop
               else [| JSON.showJSON $(listE flist) |]
474 92678b3c Iustin Pop
  clause [pat] (normalB finval) []
475 a0090487 Agata Murawska
476 a0090487 Agata Murawska
-- | Generates the main save LuxiOp function.
477 b20cbf06 Iustin Pop
genSaveLuxiOp :: [(String, [LuxiParam])]-> Q (Dec, Dec)
478 a0090487 Agata Murawska
genSaveLuxiOp opdefs = do
479 a0090487 Agata Murawska
  sigt <- [t| $(conT (mkName "LuxiOp")) -> JSON.JSValue |]
480 a0090487 Agata Murawska
  let fname = mkName "opToArgs"
481 a0090487 Agata Murawska
  cclauses <- mapM saveLuxiConstructor opdefs
482 a0090487 Agata Murawska
  return $ (SigD fname sigt, FunD fname cclauses)