Revision c2e136e2 src/Ganeti/THH.hs
b/src/Ganeti/THH.hs | ||
---|---|---|
95 | 95 |
|
96 | 96 |
-- * Exported types |
97 | 97 |
|
98 |
-- | Class of objects that can be converted to 'JSObject' |
|
98 |
-- | Class of objects that can be converted from and to 'JSObject'
|
|
99 | 99 |
-- lists-format. |
100 | 100 |
class DictObject a where |
101 | 101 |
toDict :: a -> [(String, JSON.JSValue)] |
102 |
fromDict :: [(String, JSON.JSValue)] -> JSON.Result a |
|
102 | 103 |
|
103 | 104 |
-- | Optional field information. |
104 | 105 |
data OptionalType |
... | ... | |
837 | 838 |
let cname = mkName sname |
838 | 839 |
fnames <- mapM (newName . fieldVariable) fields |
839 | 840 |
let pat = conP cname (map varP fnames) |
840 |
let felems = map (uncurry saveObjectField) (zip fnames fields)
|
|
841 |
let felems = zipWith saveObjectField fnames fields
|
|
841 | 842 |
-- now build the OP_ID serialisation |
842 | 843 |
opid = [| [( $(stringE "OP_ID"), |
843 | 844 |
JSON.showJSON $(stringE . deCamelCase $ sname) )] |] |
... | ... | |
875 | 876 |
, ValD (VarP jvalname) (NormalB jvalclause) []] |
876 | 877 |
|
877 | 878 |
-- | Generates load code for a single constructor of the opcode data type. |
878 |
loadConstructor :: OpCodeConstructor -> Q Exp |
|
879 |
loadConstructor (sname, _, _, fields, _) = do |
|
880 |
let name = mkName sname |
|
881 |
fbinds <- mapM (loadObjectField fields) fields |
|
882 |
let (fnames, fstmts) = unzip fbinds |
|
883 |
let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames |
|
884 |
fstmts' = fstmts ++ [NoBindS (AppE (VarE 'return) cval)] |
|
885 |
return $ DoE fstmts' |
|
879 |
loadConstructor :: Name -> (Field -> Q Exp) -> [Field] -> Q Exp |
|
880 |
loadConstructor name loadfn fields = do |
|
881 |
fnames <- mapM (newName . ("r_" ++) . fieldName) fields |
|
882 |
fexps <- mapM loadfn fields |
|
883 |
let fstmts = zipWith (BindS . VarP) fnames fexps |
|
884 |
cexp = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames |
|
885 |
retstmt = [NoBindS (AppE (VarE 'return) cexp)] |
|
886 |
-- FIXME: should we require an empty dict for an empty type? |
|
887 |
-- this allows any JSValue right now |
|
888 |
return $ DoE (fstmts ++ retstmt) |
|
889 |
|
|
890 |
-- | Generates load code for a single constructor of the opcode data type. |
|
891 |
loadOpConstructor :: OpCodeConstructor -> Q Exp |
|
892 |
loadOpConstructor (sname, _, _, fields, _) = |
|
893 |
loadConstructor (mkName sname) (loadObjectField fields) fields |
|
886 | 894 |
|
887 | 895 |
-- | Generates the loadOpCode function. |
888 | 896 |
genLoadOpCode :: [OpCodeConstructor] -> Q (Dec, Dec) |
889 | 897 |
genLoadOpCode opdefs = do |
890 | 898 |
let fname = mkName "loadOpCode" |
891 | 899 |
arg1 = mkName "v" |
892 |
objname = mkName "o"
|
|
900 |
objname = objVarName
|
|
893 | 901 |
opid = mkName "op_id" |
894 | 902 |
st1 <- bindS (varP objname) [| liftM JSON.fromJSObject |
895 | 903 |
(JSON.readJSON $(varE arg1)) |] |
896 | 904 |
st2 <- bindS (varP opid) [| $fromObjE $(varE objname) $(stringE "OP_ID") |] |
897 | 905 |
-- the match results (per-constructor blocks) |
898 |
mexps <- mapM loadConstructor opdefs |
|
906 |
mexps <- mapM loadOpConstructor opdefs
|
|
899 | 907 |
fails <- [| fail $ "Unknown opcode " ++ $(varE opid) |] |
900 | 908 |
let mpats = map (\(me, (consName, _, _, _, _)) -> |
901 | 909 |
let mp = LitP . StringL . deCamelCase $ consName |
... | ... | |
953 | 961 |
let cname = mkName sname |
954 | 962 |
fnames <- mapM (newName . fieldVariable) fields |
955 | 963 |
let pat = conP cname (map varP fnames) |
956 |
let felems = map (uncurry saveObjectField) (zip fnames fields)
|
|
964 |
let felems = zipWith saveObjectField fnames fields
|
|
957 | 965 |
flist = [| concat $(listE felems) |] |
958 | 966 |
clause [pat] (normalB flist) [] |
959 | 967 |
|
... | ... | |
984 | 992 |
buildObjectSerialisation :: String -> [Field] -> Q [Dec] |
985 | 993 |
buildObjectSerialisation sname fields = do |
986 | 994 |
let name = mkName sname |
987 |
savedecls <- genSaveObject saveObjectField sname fields |
|
988 |
(loadsig, loadfn) <- genLoadObject (loadObjectField fields) sname fields |
|
995 |
dictdecls <- genDictObject saveObjectField |
|
996 |
(loadObjectField fields) sname fields |
|
997 |
savedecls <- genSaveObject sname |
|
998 |
(loadsig, loadfn) <- genLoadObject sname |
|
989 | 999 |
shjson <- objectShowJSON sname |
990 | 1000 |
rdjson <- objectReadJSON sname |
991 | 1001 |
let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) |
992 | 1002 |
[rdjson, shjson] |
993 |
return $ savedecls ++ [loadsig, loadfn, instdecl] |
|
1003 |
return $ dictdecls ++ savedecls ++ [loadsig, loadfn, instdecl] |
|
1004 |
|
|
1005 |
-- | An internal name used for naming variables that hold the entire |
|
1006 |
-- object of type @[(String,JSValue)]@. |
|
1007 |
objVarName :: Name |
|
1008 |
objVarName = mkName "_o" |
|
994 | 1009 |
|
995 | 1010 |
-- | The toDict function name for a given type. |
996 | 1011 |
toDictName :: String -> Name |
997 | 1012 |
toDictName sname = mkName ("toDict" ++ sname) |
998 | 1013 |
|
999 |
-- | Generates the save object functionality. |
|
1000 |
genSaveObject :: (Name -> Field -> Q Exp) |
|
1001 |
-> String -> [Field] -> Q [Dec] |
|
1002 |
genSaveObject save_fn sname fields = do |
|
1014 |
-- | The fromDict function name for a given type. |
|
1015 |
fromDictName :: String -> Name |
|
1016 |
fromDictName sname = mkName ("fromDict" ++ sname) |
|
1017 |
|
|
1018 |
-- | Generates 'DictObject' instance. |
|
1019 |
genDictObject :: (Name -> Field -> Q Exp) -- ^ a saving function |
|
1020 |
-> (Field -> Q Exp) -- ^ a loading function |
|
1021 |
-> String -- ^ an object name |
|
1022 |
-> [Field] -- ^ a list of fields |
|
1023 |
-> Q [Dec] |
|
1024 |
genDictObject save_fn load_fn sname fields = do |
|
1003 | 1025 |
let name = mkName sname |
1026 |
-- toDict |
|
1004 | 1027 |
fnames <- mapM (newName . fieldVariable) fields |
1005 | 1028 |
let pat = conP name (map varP fnames) |
1029 |
tdexp = [| concat $(listE $ zipWith save_fn fnames fields) |] |
|
1030 |
tdclause <- clause [pat] (normalB tdexp) [] |
|
1031 |
-- fromDict |
|
1032 |
fdexp <- loadConstructor name load_fn fields |
|
1033 |
let fdclause = Clause [VarP objVarName] (NormalB fdexp) [] |
|
1034 |
-- the toDict... function |
|
1006 | 1035 |
let tdname = toDictName sname |
1007 | 1036 |
tdsigt <- [t| $(conT name) -> [(String, JSON.JSValue)] |] |
1037 |
-- the final instance |
|
1038 |
return $ [InstanceD [] (AppT (ConT ''DictObject) (ConT name)) |
|
1039 |
[ ValD (VarP 'toDict) (NormalB (VarE tdname)) [] |
|
1040 |
, FunD 'fromDict [fdclause] |
|
1041 |
]] ++ |
|
1042 |
[ SigD tdname tdsigt |
|
1043 |
, ValD (VarP tdname) (NormalB (VarE 'toDict)) [] ] |
|
1008 | 1044 |
|
1009 |
let felems = map (uncurry save_fn) (zip fnames fields) |
|
1010 |
flist = listE felems |
|
1011 |
-- and finally convert all this to a json object |
|
1012 |
tdlist = [| concat $flist |] |
|
1013 |
iname = mkName "i" |
|
1014 |
tclause <- clause [pat] (normalB tdlist) [] |
|
1015 |
cclause <- [| $makeObjE . $(varE tdname) |] |
|
1045 |
-- | Generates the save object functionality. |
|
1046 |
genSaveObject :: String -> Q [Dec] |
|
1047 |
genSaveObject sname = do |
|
1016 | 1048 |
let fname = mkName ("save" ++ sname) |
1017 |
sigt <- [t| $(conT name) -> JSON.JSValue |] |
|
1018 |
return [SigD tdname tdsigt, FunD tdname [tclause],
|
|
1019 |
SigD fname sigt, ValD (VarP fname) (NormalB cclause) []]
|
|
1049 |
sigt <- [t| $(conT $ mkName sname) -> JSON.JSValue |]
|
|
1050 |
cclause <- [| $makeObjE . $(varE $ 'toDict) |]
|
|
1051 |
return [SigD fname sigt, ValD (VarP fname) (NormalB cclause) []]
|
|
1020 | 1052 |
|
1021 | 1053 |
-- | Generates the code for saving an object's field, handling the |
1022 | 1054 |
-- various types of fields that we have. |
... | ... | |
1047 | 1079 |
return $ FunD 'JSON.showJSON [Clause [] (NormalB body) []] |
1048 | 1080 |
|
1049 | 1081 |
-- | Generates the load object functionality. |
1050 |
genLoadObject :: (Field -> Q (Name, Stmt)) |
|
1051 |
-> String -> [Field] -> Q (Dec, Dec) |
|
1052 |
genLoadObject load_fn sname fields = do |
|
1053 |
let name = mkName sname |
|
1054 |
funname = mkName $ "load" ++ sname |
|
1055 |
arg1 = mkName $ if null fields then "_" else "v" |
|
1056 |
objname = mkName "o" |
|
1057 |
opid = mkName "op_id" |
|
1058 |
st1 <- bindS (varP objname) [| liftM JSON.fromJSObject |
|
1059 |
(JSON.readJSON $(varE arg1)) |] |
|
1060 |
fbinds <- mapM load_fn fields |
|
1061 |
let (fnames, fstmts) = unzip fbinds |
|
1062 |
let cval = foldl (\accu fn -> AppE accu (VarE fn)) (ConE name) fnames |
|
1063 |
retstmt = [NoBindS (AppE (VarE 'return) cval)] |
|
1064 |
-- FIXME: should we require an empty dict for an empty type? |
|
1065 |
-- this allows any JSValue right now |
|
1066 |
fstmts' = if null fields |
|
1067 |
then retstmt |
|
1068 |
else st1:fstmts ++ retstmt |
|
1069 |
sigt <- [t| JSON.JSValue -> JSON.Result $(conT name) |] |
|
1070 |
return $ (SigD funname sigt, |
|
1071 |
FunD funname [Clause [VarP arg1] (NormalB (DoE fstmts')) []]) |
|
1082 |
genLoadObject :: String -> Q (Dec, Dec) |
|
1083 |
genLoadObject sname = do |
|
1084 |
let fname = mkName $ "load" ++ sname |
|
1085 |
sigt <- [t| JSON.JSValue -> JSON.Result $(conT $ mkName sname) |] |
|
1086 |
cclause <- [| fromDict <=< liftM JSON.fromJSObject . JSON.readJSON |] |
|
1087 |
return $ (SigD fname sigt, |
|
1088 |
FunD fname [Clause [] (NormalB cclause) []]) |
|
1072 | 1089 |
|
1073 | 1090 |
-- | Generates code for loading an object's field. |
1074 |
loadObjectField :: [Field] -> Field -> Q (Name, Stmt)
|
|
1091 |
loadObjectField :: [Field] -> Field -> Q Exp
|
|
1075 | 1092 |
loadObjectField allFields field = do |
1076 | 1093 |
let name = fieldVariable field |
1077 | 1094 |
names = map fieldVariable allFields |
1078 | 1095 |
otherNames = listE . map stringE $ names \\ [name] |
1079 |
fvar <- newName name |
|
1080 | 1096 |
-- these are used in all patterns below |
1081 |
let objvar = varNameE "o"
|
|
1097 |
let objvar = varE objVarName
|
|
1082 | 1098 |
objfield = stringE (fieldName field) |
1083 |
bexp <- case (fieldDefault field, fieldIsOptional field) of
|
|
1099 |
case (fieldDefault field, fieldIsOptional field) of |
|
1084 | 1100 |
-- Only non-optional fields without defaults must have a value; |
1085 | 1101 |
-- we treat both optional types the same, since |
1086 | 1102 |
-- 'maybeFromObj' can deal with both missing and null values |
... | ... | |
1094 | 1110 |
$ filter (not . (`elem` $otherNames) . fst) $objvar |] |
1095 | 1111 |
_ -> loadFnOpt field [| maybeFromObj $objvar $objfield |] objvar |
1096 | 1112 |
|
1097 |
return (fvar, BindS (VarP fvar) bexp) |
|
1098 |
|
|
1099 | 1113 |
-- | Builds the readJSON instance for a given object name. |
1100 | 1114 |
objectReadJSON :: String -> Q Dec |
1101 | 1115 |
objectReadJSON name = do |
... | ... | |
1141 | 1155 |
ser_decls_p <- buildPParamSerialisation sname_p fields |
1142 | 1156 |
fill_decls <- fillParam sname field_pfx fields |
1143 | 1157 |
return $ [declF, declP] ++ ser_decls_f ++ ser_decls_p ++ fill_decls ++ |
1144 |
buildParamAllFields sname fields ++ |
|
1145 |
buildDictObjectInst name_f sname_f |
|
1158 |
buildParamAllFields sname fields |
|
1146 | 1159 |
|
1147 | 1160 |
-- | Builds a list of all fields of a parameter. |
1148 | 1161 |
buildParamAllFields :: String -> [Field] -> [Dec] |
... | ... | |
1156 | 1169 |
buildDictObjectInst :: Name -> String -> [Dec] |
1157 | 1170 |
buildDictObjectInst name sname = |
1158 | 1171 |
[InstanceD [] (AppT (ConT ''DictObject) (ConT name)) |
1159 |
[ValD (VarP 'toDict) (NormalB (VarE (toDictName sname))) []]] |
|
1172 |
[ ValD (VarP 'toDict) (NormalB (VarE (toDictName sname))) [] |
|
1173 |
, ValD (VarP 'fromDict) (NormalB (VarE (fromDictName sname))) [] |
|
1174 |
]] |
|
1160 | 1175 |
|
1161 | 1176 |
-- | Generates the serialisation for a partial parameter. |
1162 | 1177 |
buildPParamSerialisation :: String -> [Field] -> Q [Dec] |
1163 | 1178 |
buildPParamSerialisation sname fields = do |
1164 | 1179 |
let name = mkName sname |
1165 |
savedecls <- genSaveObject savePParamField sname fields |
|
1166 |
(loadsig, loadfn) <- genLoadObject loadPParamField sname fields |
|
1180 |
dictdecls <- genDictObject savePParamField loadPParamField sname fields |
|
1181 |
savedecls <- genSaveObject sname |
|
1182 |
(loadsig, loadfn) <- genLoadObject sname |
|
1167 | 1183 |
shjson <- objectShowJSON sname |
1168 | 1184 |
rdjson <- objectReadJSON sname |
1169 | 1185 |
let instdecl = InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) |
1170 | 1186 |
[rdjson, shjson] |
1171 |
return $ savedecls ++ [loadsig, loadfn, instdecl] |
|
1187 |
return $ dictdecls ++ savedecls ++ [loadsig, loadfn, instdecl]
|
|
1172 | 1188 |
|
1173 | 1189 |
-- | Generates code to save an optional parameter field. |
1174 | 1190 |
savePParamField :: Name -> Field -> Q Exp |
... | ... | |
1185 | 1201 |
] |
1186 | 1202 |
|
1187 | 1203 |
-- | Generates code to load an optional parameter field. |
1188 |
loadPParamField :: Field -> Q (Name, Stmt)
|
|
1204 |
loadPParamField :: Field -> Q Exp
|
|
1189 | 1205 |
loadPParamField field = do |
1190 | 1206 |
checkNonOptDef field |
1191 | 1207 |
let name = fieldName field |
1192 |
fvar <- newName name |
|
1193 | 1208 |
-- these are used in all patterns below |
1194 |
let objvar = varNameE "o"
|
|
1209 |
let objvar = varE objVarName
|
|
1195 | 1210 |
objfield = stringE name |
1196 | 1211 |
loadexp = [| $(varE 'maybeFromObj) $objvar $objfield |] |
1197 |
bexp <- loadFnOpt field loadexp objvar |
|
1198 |
return (fvar, BindS (VarP fvar) bexp) |
|
1212 |
loadFnOpt field loadexp objvar |
|
1199 | 1213 |
|
1200 | 1214 |
-- | Builds a simple declaration of type @n_x = fromMaybe f_x p_x@. |
1201 | 1215 |
buildFromMaybe :: String -> Q Dec |
Also available in: Unified diff