Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / THH.hs @ 5f828ce4

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