4 # Copyright (C) 2011, 2012 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Script for converting Python constants to Haskell code fragments.
28 from ganeti import _autoconf
29 from ganeti import compat
30 from ganeti import constants
31 from ganeti import errors
32 from ganeti import luxi
33 from ganeti import opcodes
34 from ganeti import qlang
35 from ganeti import jstore
38 #: Constant name regex
39 CONSTANT_RE = re.compile("^[A-Z][A-Z0-9_-]+$")
42 PRIVATE_RE = re.compile("^__.+__$")
44 #: The type of regex objects
45 RE_TYPE = type(CONSTANT_RE)
47 #: Keys which do not declare a value (manually maintained). By adding
48 # values here, we can make more lists use the actual names; otherwise
49 # we'll have (e.g.) both DEFAULT_ENABLED_HYPERVISOR and HT_XEN_PVM
50 # declare the same value, and thus the list of valid hypervisors will
51 # have strings instead of easily looked-up names.
52 IGNORED_DECL_NAMES = ["DEFAULT_ENABLED_HYPERVISOR"]
56 """Converts the upper-cased Python name to Haskell camelCase.
59 name = name.replace("-", "_")
60 elems = name.split("_")
61 return elems[0].lower() + "".join(e.capitalize() for e in elems[1:])
64 def StringValueRules(value):
65 """Converts a string value from Python to Haskell.
68 value = value.encode("string_escape") # escapes backslashes
69 value = value.replace("\"", "\\\"")
73 def DictKeyName(dict_name, key_name):
74 """Converts a dict plus key name to a full name.
77 return"%s_%s" % (dict_name, str(key_name).upper())
80 def HaskellTypeVal(value):
81 """Returns the Haskell type and value for a Python value.
83 Note that this only work for 'plain' Python types.
85 @returns: (string, string) or None, if we can't determine the type.
88 if isinstance(value, basestring):
89 return ("String", "\"%s\"" % StringValueRules(value))
90 elif isinstance(value, int):
91 return ("Int", "%d" % value)
92 elif isinstance(value, long):
93 return ("Integer", "%d" % value)
94 elif isinstance(value, float):
95 return ("Double", "%f" % value)
100 def IdentifyOrigin(all_items, value):
101 """Tries to identify a constant name from a constant's value.
103 This uses a simple algorithm: is there a constant (and only one)
104 with the same value? If so, then it returns that constants' name.
106 @note: it is recommended to use this only for tuples/lists/sets, and
107 not for individual (top-level) values
108 @param all_items: a dictionary of name/values for the current module
109 @param value: the value for which we try to find an origin
112 found = [name for (name, v) in all_items.items()
113 if v is value and name not in IGNORED_DECL_NAMES]
120 def FormatListElems(all_items, pfx_name, ovals, tvals):
121 """Formats a list's elements.
123 This formats the elements as either values or, if we find all
126 @param all_items: a dictionary of name/values for the current module
127 @param pfx_name: the prefix name currently used
128 @param ovals: the list of actual (Python) values
129 @param tvals: the list of values we want to format in the Haskell form
132 origins = [IdentifyOrigin(all_items, v) for v in ovals]
133 if compat.all(x is not None for x in origins):
134 values = [NameRules(pfx_name + origin) for origin in origins]
137 return ", ".join(values)
140 def FormatDict(all_items, pfx_name, py_name, hs_name, mydict):
141 """Converts a dictionary to a Haskell association list ([(k, v)]),
144 @param all_items: a dictionary of name/values for the current module
145 @param pfx_name: the prefix name currently used
146 @param py_name: the Python name
147 @param hs_name: the Haskell name
148 @param mydict: a dictonary, unknown yet if homogenous or not
151 # need this for ordering
152 orig_list = mydict.items()
153 list_form = [(HaskellTypeVal(k), HaskellTypeVal(v)) for k, v in orig_list]
154 if compat.any(v is None or k is None for k, v in list_form):
157 all_keys = [k for k, _ in list_form]
158 all_vals = [v for _, v in list_form]
159 key_types = set(k[0] for k in all_keys)
160 val_types = set(v[0] for v in all_vals)
161 if not(len(key_types) == 1 and len(val_types) == 1):
164 # record the key and value Haskell types
165 key_type = key_types.pop()
166 val_type = val_types.pop()
168 # now try to find names for the keys, instead of raw values
169 key_origins = [IdentifyOrigin(all_items, k) for k, _ in orig_list]
170 if compat.all(x is not None for x in key_origins):
171 key_v = [NameRules(pfx_name + origin) for origin in key_origins]
173 key_v = [k[1] for k in all_keys]
175 val_origins = [IdentifyOrigin(all_items, v) for _, v in orig_list]
176 if compat.all(x is not None for x in val_origins):
177 val_v = [NameRules(pfx_name + origin) for origin in val_origins]
179 val_v = [v[1] for v in all_vals]
181 # finally generate the output
182 kv_pairs = ["(%s, %s)" % (k, v) for k, v in zip(key_v, val_v)]
183 return ["-- | Converted from Python dictionary %s" % py_name,
184 "%s :: [(%s, %s)]" % (hs_name, key_type, val_type),
185 "%s = [%s]" % (hs_name, ", ".join(kv_pairs)),
189 def ConvertVariable(prefix, name, value, all_items):
190 """Converts a given variable to Haskell code.
192 @param prefix: a prefix for the Haskell name (useful for module
194 @param name: the Python name
195 @param value: the value
196 @param all_items: a dictionary of name/value for the module being
198 @return: a list of Haskell code lines
203 pfx_name = prefix + "_"
204 fqn = prefix + "." + name
208 hs_name = NameRules(pfx_name + name)
209 hs_typeval = HaskellTypeVal(value)
210 if (isinstance(value, types.ModuleType) or callable(value) or
211 PRIVATE_RE.match(name)):
212 # no sense in marking these, as we don't _want_ to convert them; the
213 # message in the next if block is for datatypes we don't _know_
214 # (yet) how to convert
216 elif not CONSTANT_RE.match(name):
217 lines.append("-- Skipped %s %s, not constant" % (fqn, type(value)))
218 elif hs_typeval is not None:
219 # this is a simple value
220 (hs_type, hs_val) = hs_typeval
221 lines.append("-- | Converted from Python constant %s" % fqn)
222 lines.append("%s :: %s" % (hs_name, hs_type))
223 lines.append("%s = %s" % (hs_name, hs_val))
224 elif isinstance(value, dict):
226 lines.append("-- Following lines come from dictionary %s" % fqn)
227 # try to build a real map here, if all keys have same type, and
228 # all values too (i.e. we have a homogeneous dictionary)
229 lines.extend(FormatDict(all_items, pfx_name, fqn, hs_name, value))
230 # and now create individual names
231 for k in sorted(value.keys()):
232 lines.extend(ConvertVariable(prefix, DictKeyName(name, k),
233 value[k], all_items))
234 elif isinstance(value, tuple):
235 tvs = [HaskellTypeVal(elem) for elem in value]
236 # Custom rule for special cluster verify error tuples
237 if name.startswith("CV_E") and len(value) == 3 and tvs[1][0] is not None:
238 cv_ename = hs_name + "Code"
239 lines.append("-- | Special cluster verify code %s" % name)
240 lines.append("%s :: %s" % (cv_ename, tvs[1][0]))
241 lines.append("%s = %s" % (cv_ename, tvs[1][1]))
243 if compat.all(e is not None for e in tvs):
244 ttypes = ", ".join(e[0] for e in tvs)
245 tvals = FormatListElems(all_items, pfx_name, value, [e[1] for e in tvs])
246 lines.append("-- | Converted from Python tuple %s" % fqn)
247 lines.append("%s :: (%s)" % (hs_name, ttypes))
248 lines.append("%s = (%s)" % (hs_name, tvals))
250 lines.append("-- Skipped tuple %s, cannot convert all elements" % fqn)
251 elif isinstance(value, (list, set, frozenset)):
252 # Lists and frozensets are handled the same in Haskell: as lists,
253 # since lists are immutable and we don't need for constants the
254 # high-speed of an actual Set type. However, we can only convert
255 # them if they have the same type for all elements (which is a
256 # normal expectation for constants, our code should be well
257 # behaved); note that this is different from the tuples case,
258 # where we always (for some values of always) can convert
259 tvs = [HaskellTypeVal(elem) for elem in value]
260 if compat.all(e is not None for e in tvs):
261 ttypes, tvals = zip(*tvs)
262 uniq_types = set(ttypes)
263 if len(uniq_types) == 1:
264 values = FormatListElems(all_items, pfx_name, value, tvals)
265 lines.append("-- | Converted from Python list or set %s" % fqn)
266 lines.append("%s :: [%s]" % (hs_name, uniq_types.pop()))
267 lines.append("%s = [%s]" % (hs_name, values))
269 lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
271 lines.append("-- | Skipped list/set %s, cannot convert all elems" % fqn)
272 elif isinstance(value, RE_TYPE):
273 tvs = HaskellTypeVal(value.pattern)
274 assert tvs is not None
275 lines.append("-- | Converted from Python RE object %s" % fqn)
276 lines.append("%s :: %s" % (hs_name, tvs[0]))
277 lines.append("%s = %s" % (hs_name, tvs[1]))
279 lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
283 def Convert(module, prefix):
284 """Converts the constants to Haskell.
289 all_items = dict((name, getattr(module, name)) for name in dir(module))
291 for name in sorted(all_items.keys()):
292 value = all_items[name]
293 new_lines = ConvertVariable(prefix, name, value, all_items)
295 lines.extend(new_lines)
298 return "\n".join(lines)
302 """Convert some extra computed-values to Haskell.
306 lines.extend(ConvertVariable("opcodes", "OP_IDS",
307 opcodes.OP_MAPPING.keys(), {}))
308 return "\n".join(lines)
312 print Convert(constants, "")
313 print Convert(luxi, "luxi")
314 print Convert(qlang, "qlang")
315 print Convert(_autoconf, "autoconf")
316 print Convert(errors, "errors")
317 print Convert(jstore, "jstore")
321 if __name__ == "__main__":