Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / THH.hs @ 72bb6b4e

History | View | Annotate | Download (13.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 e9aaa3c6 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 e9aaa3c6 Iustin Pop
                  ) where
38 e9aaa3c6 Iustin Pop
39 6111e296 Iustin Pop
import Control.Monad (liftM)
40 e9aaa3c6 Iustin Pop
import Data.Char
41 6111e296 Iustin Pop
import Data.List
42 e9aaa3c6 Iustin Pop
import Language.Haskell.TH
43 e9aaa3c6 Iustin Pop
44 e9aaa3c6 Iustin Pop
import qualified Text.JSON as JSON
45 e9aaa3c6 Iustin Pop
46 e9aaa3c6 Iustin Pop
-- | Ensure first letter is lowercase.
47 e9aaa3c6 Iustin Pop
--
48 e9aaa3c6 Iustin Pop
-- Used to convert type name to function prefix, e.g. in @data Aa ->
49 e9aaa3c6 Iustin Pop
-- aaToString@.
50 e9aaa3c6 Iustin Pop
ensureLower :: String -> String
51 e9aaa3c6 Iustin Pop
ensureLower [] = []
52 e9aaa3c6 Iustin Pop
ensureLower (x:xs) = toLower x:xs
53 e9aaa3c6 Iustin Pop
54 e9aaa3c6 Iustin Pop
-- | ToString function name.
55 e9aaa3c6 Iustin Pop
toStrName :: String -> Name
56 e9aaa3c6 Iustin Pop
toStrName = mkName . (++ "ToString") . ensureLower
57 e9aaa3c6 Iustin Pop
58 e9aaa3c6 Iustin Pop
-- | FromString function name.
59 e9aaa3c6 Iustin Pop
fromStrName :: String -> Name
60 e9aaa3c6 Iustin Pop
fromStrName = mkName . (++ "FromString") . ensureLower
61 e9aaa3c6 Iustin Pop
62 6111e296 Iustin Pop
-- | Converts a name to it's varE/litE representations.
63 6111e296 Iustin Pop
--
64 6111e296 Iustin Pop
reprE :: Either String Name -> Q Exp
65 6111e296 Iustin Pop
reprE (Left name) = litE (StringL name)
66 6111e296 Iustin Pop
reprE (Right name) = varE name
67 6111e296 Iustin Pop
68 e9aaa3c6 Iustin Pop
-- | Generates a data type declaration.
69 e9aaa3c6 Iustin Pop
--
70 e9aaa3c6 Iustin Pop
-- The type will have a fixed list of instances.
71 e9aaa3c6 Iustin Pop
strADTDecl :: Name -> [String] -> Dec
72 e9aaa3c6 Iustin Pop
strADTDecl name constructors =
73 e9aaa3c6 Iustin Pop
    DataD [] name []
74 e9aaa3c6 Iustin Pop
              (map (flip NormalC [] . mkName) constructors)
75 e9aaa3c6 Iustin Pop
              [''Show, ''Read, ''Eq, ''Enum, ''Bounded, ''Ord]
76 e9aaa3c6 Iustin Pop
77 e9aaa3c6 Iustin Pop
-- | Generates a toString function.
78 e9aaa3c6 Iustin Pop
--
79 e9aaa3c6 Iustin Pop
-- This generates a simple function of the form:
80 e9aaa3c6 Iustin Pop
--
81 e9aaa3c6 Iustin Pop
-- @
82 e9aaa3c6 Iustin Pop
-- nameToString :: Name -> String
83 e9aaa3c6 Iustin Pop
-- nameToString Cons1 = var1
84 e9aaa3c6 Iustin Pop
-- nameToString Cons2 = \"value2\"
85 e9aaa3c6 Iustin Pop
-- @
86 6111e296 Iustin Pop
genToString :: Name -> Name -> [(String, Either String Name)] -> Q [Dec]
87 e9aaa3c6 Iustin Pop
genToString fname tname constructors = do
88 e9aaa3c6 Iustin Pop
  sigt <- [t| $(conT tname) -> String |]
89 e9aaa3c6 Iustin Pop
  -- the body clauses, matching on the constructor and returning the
90 e9aaa3c6 Iustin Pop
  -- string value
91 e9aaa3c6 Iustin Pop
  clauses <- mapM  (\(c, v) -> clause [recP (mkName c) []]
92 6111e296 Iustin Pop
                             (normalB (reprE v)) []) constructors
93 e9aaa3c6 Iustin Pop
  return [SigD fname sigt, FunD fname clauses]
94 e9aaa3c6 Iustin Pop
95 e9aaa3c6 Iustin Pop
-- | Generates a fromString function.
96 e9aaa3c6 Iustin Pop
--
97 e9aaa3c6 Iustin Pop
-- The function generated is monadic and can fail parsing the
98 e9aaa3c6 Iustin Pop
-- string. It is of the form:
99 e9aaa3c6 Iustin Pop
--
100 e9aaa3c6 Iustin Pop
-- @
101 e9aaa3c6 Iustin Pop
-- nameFromString :: (Monad m) => String -> m Name
102 e9aaa3c6 Iustin Pop
-- nameFromString s | s == var1       = Cons1
103 e9aaa3c6 Iustin Pop
--                  | s == \"value2\" = Cons2
104 e9aaa3c6 Iustin Pop
--                  | otherwise = fail /.../
105 e9aaa3c6 Iustin Pop
-- @
106 e9aaa3c6 Iustin Pop
genFromString :: Name -> Name -> [(String, Name)] -> Q [Dec]
107 e9aaa3c6 Iustin Pop
genFromString fname tname constructors = do
108 e9aaa3c6 Iustin Pop
  -- signature of form (Monad m) => String -> m $name
109 e9aaa3c6 Iustin Pop
  sigt <- [t| (Monad m) => String -> m $(conT tname) |]
110 e9aaa3c6 Iustin Pop
  -- clauses for a guarded pattern
111 e9aaa3c6 Iustin Pop
  let varp = mkName "s"
112 e9aaa3c6 Iustin Pop
      varpe = varE varp
113 e9aaa3c6 Iustin Pop
  clauses <- mapM (\(c, v) -> do
114 e9aaa3c6 Iustin Pop
                     -- the clause match condition
115 e9aaa3c6 Iustin Pop
                     g <- normalG [| $varpe == $(varE v) |]
116 e9aaa3c6 Iustin Pop
                     -- the clause result
117 e9aaa3c6 Iustin Pop
                     r <- [| return $(conE (mkName c)) |]
118 e9aaa3c6 Iustin Pop
                     return (g, r)) constructors
119 e9aaa3c6 Iustin Pop
  -- the otherwise clause (fallback)
120 e9aaa3c6 Iustin Pop
  oth_clause <- do
121 e9aaa3c6 Iustin Pop
    g <- normalG [| otherwise |]
122 e9aaa3c6 Iustin Pop
    r <- [|fail ("Invalid string value for type " ++
123 e9aaa3c6 Iustin Pop
                 $(litE (stringL (nameBase tname))) ++ ": " ++ $varpe) |]
124 e9aaa3c6 Iustin Pop
    return (g, r)
125 e9aaa3c6 Iustin Pop
  let fun = FunD fname [Clause [VarP varp]
126 e9aaa3c6 Iustin Pop
                        (GuardedB (clauses++[oth_clause])) []]
127 e9aaa3c6 Iustin Pop
  return [SigD fname sigt, fun]
128 e9aaa3c6 Iustin Pop
129 e9aaa3c6 Iustin Pop
-- | Generates a data type from a given string format.
130 e9aaa3c6 Iustin Pop
--
131 e9aaa3c6 Iustin Pop
-- The format is expected to multiline. The first line contains the
132 e9aaa3c6 Iustin Pop
-- type name, and the rest of the lines must contain two words: the
133 e9aaa3c6 Iustin Pop
-- constructor name and then the string representation of the
134 e9aaa3c6 Iustin Pop
-- respective constructor.
135 e9aaa3c6 Iustin Pop
--
136 e9aaa3c6 Iustin Pop
-- The function will generate the data type declaration, and then two
137 e9aaa3c6 Iustin Pop
-- functions:
138 e9aaa3c6 Iustin Pop
--
139 e9aaa3c6 Iustin Pop
-- * /name/ToString, which converts the type to a string
140 e9aaa3c6 Iustin Pop
--
141 e9aaa3c6 Iustin Pop
-- * /name/FromString, which (monadically) converts from a string to the type
142 e9aaa3c6 Iustin Pop
--
143 e9aaa3c6 Iustin Pop
-- Note that this is basically just a custom show/read instance,
144 e9aaa3c6 Iustin Pop
-- nothing else.
145 e9aaa3c6 Iustin Pop
declareSADT :: String -> [(String, Name)] -> Q [Dec]
146 e9aaa3c6 Iustin Pop
declareSADT sname cons = do
147 e9aaa3c6 Iustin Pop
  let name = mkName sname
148 e9aaa3c6 Iustin Pop
      ddecl = strADTDecl name (map fst cons)
149 6111e296 Iustin Pop
      -- process cons in the format expected by genToString
150 6111e296 Iustin Pop
      cons' = map (\(a, b) -> (a, Right b)) cons
151 6111e296 Iustin Pop
  tostr <- genToString (toStrName sname) name cons'
152 e9aaa3c6 Iustin Pop
  fromstr <- genFromString (fromStrName sname) name cons
153 e9aaa3c6 Iustin Pop
  return $ ddecl:tostr ++ fromstr
154 e9aaa3c6 Iustin Pop
155 e9aaa3c6 Iustin Pop
156 e9aaa3c6 Iustin Pop
-- | Creates the showJSON member of a JSON instance declaration.
157 e9aaa3c6 Iustin Pop
--
158 e9aaa3c6 Iustin Pop
-- This will create what is the equivalent of:
159 e9aaa3c6 Iustin Pop
--
160 e9aaa3c6 Iustin Pop
-- @
161 e9aaa3c6 Iustin Pop
-- showJSON = showJSON . /name/ToString
162 e9aaa3c6 Iustin Pop
-- @
163 e9aaa3c6 Iustin Pop
--
164 e9aaa3c6 Iustin Pop
-- in an instance JSON /name/ declaration
165 e9aaa3c6 Iustin Pop
genShowJSON :: String -> Q [Dec]
166 e9aaa3c6 Iustin Pop
genShowJSON name = [d| showJSON = JSON.showJSON . $(varE (toStrName name)) |]
167 e9aaa3c6 Iustin Pop
168 e9aaa3c6 Iustin Pop
-- | Creates the readJSON member of a JSON instance declaration.
169 e9aaa3c6 Iustin Pop
--
170 e9aaa3c6 Iustin Pop
-- This will create what is the equivalent of:
171 e9aaa3c6 Iustin Pop
--
172 e9aaa3c6 Iustin Pop
-- @
173 e9aaa3c6 Iustin Pop
-- readJSON s = case readJSON s of
174 e9aaa3c6 Iustin Pop
--                Ok s' -> /name/FromString s'
175 e9aaa3c6 Iustin Pop
--                Error e -> Error /description/
176 e9aaa3c6 Iustin Pop
-- @
177 e9aaa3c6 Iustin Pop
--
178 e9aaa3c6 Iustin Pop
-- in an instance JSON /name/ declaration
179 e9aaa3c6 Iustin Pop
genReadJSON :: String -> Q Dec
180 e9aaa3c6 Iustin Pop
genReadJSON name = do
181 e9aaa3c6 Iustin Pop
  let s = mkName "s"
182 e9aaa3c6 Iustin Pop
  body <- [| case JSON.readJSON $(varE s) of
183 e9aaa3c6 Iustin Pop
               JSON.Ok s' -> $(varE (fromStrName name)) s'
184 e9aaa3c6 Iustin Pop
               JSON.Error e ->
185 e9aaa3c6 Iustin Pop
                   JSON.Error $ "Can't parse string value for type " ++
186 e9aaa3c6 Iustin Pop
                           $(litE (StringL name)) ++ ": " ++ e
187 e9aaa3c6 Iustin Pop
           |]
188 e9aaa3c6 Iustin Pop
  return $ FunD (mkName "readJSON") [Clause [VarP s] (NormalB body) []]
189 e9aaa3c6 Iustin Pop
190 e9aaa3c6 Iustin Pop
-- | Generates a JSON instance for a given type.
191 e9aaa3c6 Iustin Pop
--
192 e9aaa3c6 Iustin Pop
-- This assumes that the /name/ToString and /name/FromString functions
193 e9aaa3c6 Iustin Pop
-- have been defined as by the 'declareSADT' function.
194 e9aaa3c6 Iustin Pop
makeJSONInstance :: Name -> Q [Dec]
195 e9aaa3c6 Iustin Pop
makeJSONInstance name = do
196 e9aaa3c6 Iustin Pop
  let base = nameBase name
197 e9aaa3c6 Iustin Pop
  showJ <- genShowJSON base
198 e9aaa3c6 Iustin Pop
  readJ <- genReadJSON base
199 e9aaa3c6 Iustin Pop
  return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) (readJ:showJ)]
200 6111e296 Iustin Pop
201 6111e296 Iustin Pop
-- | Transforms a CamelCase string into an_underscore_based_one.
202 6111e296 Iustin Pop
deCamelCase :: String -> String
203 6111e296 Iustin Pop
deCamelCase =
204 6111e296 Iustin Pop
    intercalate "_" . map (map toUpper) . groupBy (\_ b -> not $ isUpper b)
205 6111e296 Iustin Pop
206 6111e296 Iustin Pop
-- | Computes the name of a given constructor
207 6111e296 Iustin Pop
constructorName :: Con -> Q Name
208 6111e296 Iustin Pop
constructorName (NormalC name _) = return name
209 6111e296 Iustin Pop
constructorName (RecC name _)    = return name
210 6111e296 Iustin Pop
constructorName x                = fail $ "Unhandled constructor " ++ show x
211 6111e296 Iustin Pop
212 6111e296 Iustin Pop
-- | Builds the constructor-to-string function.
213 6111e296 Iustin Pop
--
214 6111e296 Iustin Pop
-- This generates a simple function of the following form:
215 6111e296 Iustin Pop
--
216 6111e296 Iustin Pop
-- @
217 6111e296 Iustin Pop
-- fname (ConStructorOne {}) = "CON_STRUCTOR_ONE"
218 6111e296 Iustin Pop
-- fname (ConStructorTwo {}) = "CON_STRUCTOR_TWO"
219 6111e296 Iustin Pop
-- @
220 6111e296 Iustin Pop
--
221 6111e296 Iustin Pop
-- This builds a custom list of name/string pairs and then uses
222 6111e296 Iustin Pop
-- 'genToString' to actually generate the function
223 6111e296 Iustin Pop
genOpID :: Name -> String -> Q [Dec]
224 6111e296 Iustin Pop
genOpID name fname = do
225 6111e296 Iustin Pop
  TyConI (DataD _ _ _ cons _) <- reify name
226 6111e296 Iustin Pop
  cnames <- mapM (liftM nameBase . constructorName) cons
227 6111e296 Iustin Pop
  let svalues = map (Left . deCamelCase) cnames
228 6111e296 Iustin Pop
  genToString (mkName fname) name $ zip cnames svalues
229 12c19659 Iustin Pop
230 12c19659 Iustin Pop
231 12c19659 Iustin Pop
-- | OpCode parameter (field) type
232 12c19659 Iustin Pop
type OpParam = (String, Q Type, Q Exp)
233 12c19659 Iustin Pop
234 12c19659 Iustin Pop
-- | Generates the OpCode data type.
235 12c19659 Iustin Pop
--
236 12c19659 Iustin Pop
-- This takes an opcode logical definition, and builds both the
237 12c19659 Iustin Pop
-- datatype and the JSON serialisation out of it. We can't use a
238 12c19659 Iustin Pop
-- generic serialisation since we need to be compatible with Ganeti's
239 12c19659 Iustin Pop
-- own, so we have a few quirks to work around.
240 12c19659 Iustin Pop
--
241 12c19659 Iustin Pop
-- There are three things to be defined for each parameter:
242 12c19659 Iustin Pop
--
243 12c19659 Iustin Pop
-- * name
244 12c19659 Iustin Pop
--
245 12c19659 Iustin Pop
-- * type; if this is 'Maybe', will only be serialised if it's a
246 12c19659 Iustin Pop
--   'Just' value
247 12c19659 Iustin Pop
--
248 12c19659 Iustin Pop
-- * default; if missing, won't raise an exception, but will instead
249 12c19659 Iustin Pop
--   use the default
250 12c19659 Iustin Pop
--
251 12c19659 Iustin Pop
genOpCode :: String                -- ^ Type name to use
252 12c19659 Iustin Pop
          -> [(String, [OpParam])] -- ^ Constructor name and parameters
253 12c19659 Iustin Pop
          -> Q [Dec]
254 12c19659 Iustin Pop
genOpCode name cons = do
255 12c19659 Iustin Pop
  decl_d <- mapM (\(cname, fields) -> do
256 12c19659 Iustin Pop
                    -- we only need the type of the field, without Q
257 12c19659 Iustin Pop
                    fields' <- mapM (\(_, qt, _) ->
258 12c19659 Iustin Pop
                                         qt >>= \t -> return (NotStrict, t))
259 12c19659 Iustin Pop
                               fields
260 12c19659 Iustin Pop
                    return $ NormalC (mkName cname) fields')
261 12c19659 Iustin Pop
            cons
262 12c19659 Iustin Pop
  let declD = DataD [] (mkName name) [] decl_d [''Show, ''Read, ''Eq]
263 12c19659 Iustin Pop
264 12c19659 Iustin Pop
  (savesig, savefn) <- genSaveOpCode cons
265 12c19659 Iustin Pop
  (loadsig, loadfn) <- genLoadOpCode cons
266 12c19659 Iustin Pop
  return [declD, loadsig, loadfn, savesig, savefn]
267 12c19659 Iustin Pop
268 12c19659 Iustin Pop
-- | Checks whether a given parameter is options
269 12c19659 Iustin Pop
--
270 12c19659 Iustin Pop
-- This requires that it's a 'Maybe'.
271 12c19659 Iustin Pop
isOptional :: Type -> Bool
272 12c19659 Iustin Pop
isOptional (AppT (ConT dt) _) | dt == ''Maybe = True
273 12c19659 Iustin Pop
isOptional _ = False
274 12c19659 Iustin Pop
275 12c19659 Iustin Pop
-- | Generates the \"save\" expression for a single opcode parameter.
276 12c19659 Iustin Pop
--
277 12c19659 Iustin Pop
-- There is only one special handling mode: if the parameter is of
278 12c19659 Iustin Pop
-- 'Maybe' type, then we only save it if it's a 'Just' value,
279 12c19659 Iustin Pop
-- otherwise we skip it.
280 12c19659 Iustin Pop
saveField :: Name    -- ^ The name of variable that contains the value
281 12c19659 Iustin Pop
          -> OpParam -- ^ Parameter definition
282 12c19659 Iustin Pop
          -> Q Exp
283 12c19659 Iustin Pop
saveField fvar (fname, qt, _) = do
284 12c19659 Iustin Pop
  t <- qt
285 12c19659 Iustin Pop
  let showJ = varE (mkName "showJSON")
286 12c19659 Iustin Pop
      fnexp = litE (stringL fname)
287 12c19659 Iustin Pop
      fvare = varE fvar
288 12c19659 Iustin Pop
  (if isOptional t
289 12c19659 Iustin Pop
   then [| case $fvare of
290 12c19659 Iustin Pop
             Just v' -> [( $fnexp, $showJ v')]
291 12c19659 Iustin Pop
             Nothing -> []
292 12c19659 Iustin Pop
         |]
293 12c19659 Iustin Pop
   else [| [( $fnexp, $showJ $fvare )] |])
294 12c19659 Iustin Pop
295 12c19659 Iustin Pop
-- | Generates the \"save\" clause for an entire opcode constructor.
296 12c19659 Iustin Pop
--
297 12c19659 Iustin Pop
-- This matches the opcode with variables named the same as the
298 12c19659 Iustin Pop
-- constructor fields (just so that the spliced in code looks nicer),
299 12c19659 Iustin Pop
-- and passes those name plus the parameter definition to 'saveField'.
300 12c19659 Iustin Pop
saveConstructor :: String    -- ^ The constructor name
301 12c19659 Iustin Pop
                -> [OpParam] -- ^ The parameter definitions for this
302 12c19659 Iustin Pop
                             -- constructor
303 12c19659 Iustin Pop
                -> Q Clause  -- ^ Resulting clause
304 12c19659 Iustin Pop
saveConstructor sname fields = do
305 12c19659 Iustin Pop
  let cname = mkName sname
306 12c19659 Iustin Pop
  let fnames = map (\(n, _, _) -> mkName n) fields
307 12c19659 Iustin Pop
  let pat = conP cname (map varP fnames)
308 12c19659 Iustin Pop
  let felems = map (uncurry saveField) (zip fnames fields)
309 12c19659 Iustin Pop
      -- now build the OP_ID serialisation
310 12c19659 Iustin Pop
      opid = [| [( $(litE (stringL "OP_ID")),
311 12c19659 Iustin Pop
                   $(varE (mkName "showJSON"))
312 12c19659 Iustin Pop
                        $(litE . stringL . deCamelCase $ sname) )] |]
313 12c19659 Iustin Pop
      flist = listE (opid:felems)
314 12c19659 Iustin Pop
      -- and finally convert all this to a json object
315 12c19659 Iustin Pop
      flist' = [| $(varE (mkName "makeObj")) (concat $flist) |]
316 12c19659 Iustin Pop
  clause [pat] (normalB flist') []
317 12c19659 Iustin Pop
318 12c19659 Iustin Pop
-- | Generates the main save opcode function.
319 12c19659 Iustin Pop
--
320 12c19659 Iustin Pop
-- This builds a per-constructor match clause that contains the
321 12c19659 Iustin Pop
-- respective constructor-serialisation code.
322 12c19659 Iustin Pop
genSaveOpCode :: [(String, [OpParam])] -> Q (Dec, Dec)
323 12c19659 Iustin Pop
genSaveOpCode opdefs = do
324 12c19659 Iustin Pop
  cclauses <- mapM (uncurry saveConstructor) opdefs
325 12c19659 Iustin Pop
  let fname = mkName "saveOpCode"
326 12c19659 Iustin Pop
  sigt <- [t| $(conT (mkName "OpCode")) -> JSON.JSValue |]
327 12c19659 Iustin Pop
  return $ (SigD fname sigt, FunD fname cclauses)
328 12c19659 Iustin Pop
329 12c19659 Iustin Pop
-- | Generates the \"load\" field for a single parameter.
330 12c19659 Iustin Pop
--
331 12c19659 Iustin Pop
-- There is custom handling, depending on how the parameter is
332 12c19659 Iustin Pop
-- specified. For a 'Maybe' type parameter, we allow that it is not
333 12c19659 Iustin Pop
-- present (via 'Utils.maybeFromObj'). Otherwise, if there is a
334 12c19659 Iustin Pop
-- default value, we allow the parameter to be abset, and finally if
335 12c19659 Iustin Pop
-- there is no default value, we require its presence.
336 12c19659 Iustin Pop
loadField :: OpParam -> Q (Name, Stmt)
337 12c19659 Iustin Pop
loadField (fname, qt, qdefa) = do
338 12c19659 Iustin Pop
  let fvar = mkName fname
339 12c19659 Iustin Pop
  t <- qt
340 12c19659 Iustin Pop
  defa <- qdefa
341 12c19659 Iustin Pop
  -- these are used in all patterns below
342 12c19659 Iustin Pop
  let objvar = varE (mkName "o")
343 12c19659 Iustin Pop
      objfield = litE (StringL fname)
344 12c19659 Iustin Pop
  bexp <- if isOptional t
345 12c19659 Iustin Pop
          then [| $((varE (mkName "maybeFromObj"))) $objvar $objfield |]
346 12c19659 Iustin Pop
          else case defa of
347 12c19659 Iustin Pop
                 AppE (ConE dt) defval | dt == 'Just ->
348 12c19659 Iustin Pop
                   -- but has a default value
349 12c19659 Iustin Pop
                   [| $(varE (mkName "fromObjWithDefault"))
350 12c19659 Iustin Pop
                      $objvar $objfield $(return defval) |]
351 12c19659 Iustin Pop
                 ConE dt | dt == 'Nothing ->
352 12c19659 Iustin Pop
                     [| $(varE (mkName "fromObj")) $objvar $objfield |]
353 12c19659 Iustin Pop
                 s -> fail $ "Invalid default value " ++ show s ++
354 12c19659 Iustin Pop
                      ", expecting either 'Nothing' or a 'Just defval'"
355 12c19659 Iustin Pop
  return (fvar, BindS (VarP fvar) bexp)
356 12c19659 Iustin Pop
357 12c19659 Iustin Pop
loadConstructor :: String -> [OpParam] -> Q Exp
358 12c19659 Iustin Pop
loadConstructor sname fields = do
359 12c19659 Iustin Pop
  let name = mkName sname
360 12c19659 Iustin Pop
  fbinds <- mapM loadField fields
361 12c19659 Iustin Pop
  let (fnames, fstmts) = unzip fbinds
362 12c19659 Iustin Pop
  let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames
363 12c19659 Iustin Pop
      fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)]
364 12c19659 Iustin Pop
  return $ DoE fstmts'
365 12c19659 Iustin Pop
366 12c19659 Iustin Pop
genLoadOpCode :: [(String, [OpParam])] -> Q (Dec, Dec)
367 12c19659 Iustin Pop
genLoadOpCode opdefs = do
368 12c19659 Iustin Pop
  let fname = mkName "loadOpCode"
369 12c19659 Iustin Pop
      arg1 = mkName "v"
370 12c19659 Iustin Pop
      objname = mkName "o"
371 12c19659 Iustin Pop
      opid = mkName "op_id"
372 12c19659 Iustin Pop
  st1 <- bindS (varP objname) [| liftM JSON.fromJSObject
373 12c19659 Iustin Pop
                                 (JSON.readJSON $(varE arg1)) |]
374 12c19659 Iustin Pop
  st2 <- bindS (varP opid) [| $(varE (mkName "fromObj"))
375 12c19659 Iustin Pop
                              $(varE objname) $(litE (stringL "OP_ID")) |]
376 12c19659 Iustin Pop
  -- the match results (per-constructor blocks)
377 12c19659 Iustin Pop
  mexps <- mapM (uncurry loadConstructor) opdefs
378 12c19659 Iustin Pop
  fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |]
379 12c19659 Iustin Pop
  let mpats = map (\(me, c) ->
380 12c19659 Iustin Pop
                       let mp = LitP . StringL . deCamelCase . fst $ c
381 12c19659 Iustin Pop
                       in Match mp (NormalB me) []
382 12c19659 Iustin Pop
                  ) $ zip mexps opdefs
383 12c19659 Iustin Pop
      defmatch = Match WildP (NormalB fails) []
384 12c19659 Iustin Pop
      cst = NoBindS $ CaseE (VarE opid) $ mpats++[defmatch]
385 12c19659 Iustin Pop
      body = DoE [st1, st2, cst]
386 12c19659 Iustin Pop
  sigt <- [t| JSON.JSValue -> JSON.Result $(conT (mkName "OpCode")) |]
387 12c19659 Iustin Pop
  return $ (SigD fname sigt, FunD fname [Clause [VarP arg1] (NormalB body) []])
388 12c19659 Iustin Pop
389 12c19659 Iustin Pop
-- | No default type.
390 12c19659 Iustin Pop
noDefault :: Q Exp
391 12c19659 Iustin Pop
noDefault = conE 'Nothing