Statistics
| Branch: | Tag: | Revision:

root / autotools / convert-constants @ cefd4a4a

History | View | Annotate | Download (10.7 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2011, 2012 Google Inc.
5
#
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.
10
#
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.
15
#
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
19
# 02110-1301, USA.
20

    
21
"""Script for converting Python constants to Haskell code fragments.
22

    
23
"""
24

    
25
import re
26
import types
27

    
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

    
36

    
37
#: Constant name regex
38
CONSTANT_RE = re.compile("^[A-Z][A-Z0-9_-]+$")
39

    
40
#: Private name regex
41
PRIVATE_RE = re.compile("^__.+__$")
42

    
43
#: The type of regex objects
44
RE_TYPE = type(CONSTANT_RE)
45

    
46
#: Keys which do not declare a value (manually maintained). By adding
47
# values here, we can make more lists use the actual names; otherwise
48
# we'll have (e.g.) both DEFAULT_ENABLED_HYPERVISOR and HT_XEN_PVM
49
# declare the same value, and thus the list of valid hypervisors will
50
# have strings instead of easily looked-up names.
51
IGNORED_DECL_NAMES = ["DEFAULT_ENABLED_HYPERVISOR"]
52

    
53

    
54
def NameRules(name):
55
  """Converts the upper-cased Python name to Haskell camelCase.
56

    
57
  """
58
  name = name.replace("-", "_")
59
  elems = name.split("_")
60
  return elems[0].lower() + "".join(e.capitalize() for e in elems[1:])
61

    
62

    
63
def StringValueRules(value):
64
  """Converts a string value from Python to Haskell.
65

    
66
  """
67
  value = value.encode("string_escape") # escapes backslashes
68
  value = value.replace("\"", "\\\"")
69
  return value
70

    
71

    
72
def DictKeyName(dict_name, key_name):
73
  """Converts a dict plus key name to a full name.
74

    
75
  """
76
  return"%s_%s" % (dict_name, str(key_name).upper())
77

    
78

    
79
def HaskellTypeVal(value):
80
  """Returns the Haskell type and value for a Python value.
81

    
82
  Note that this only work for 'plain' Python types.
83

    
84
  @returns: (string, string) or None, if we can't determine the type.
85

    
86
  """
87
  if isinstance(value, basestring):
88
    return ("String", "\"%s\"" % StringValueRules(value))
89
  elif isinstance(value, int):
90
    return ("Int", "%d" % value)
91
  elif isinstance(value, long):
92
    return ("Integer", "%d" % value)
93
  elif isinstance(value, float):
94
    return ("Double", "%f" % value)
95
  else:
96
    return None
97

    
98

    
99
def IdentifyOrigin(all_items, value):
100
  """Tries to identify a constant name from a constant's value.
101

    
102
  This uses a simple algorithm: is there a constant (and only one)
103
  with the same value? If so, then it returns that constants' name.
104

    
105
  @note: it is recommended to use this only for tuples/lists/sets, and
106
      not for individual (top-level) values
107
  @param all_items: a dictionary of name/values for the current module
108
  @param value: the value for which we try to find an origin
109

    
110
  """
111
  found = [name for (name, v) in all_items.items()
112
           if v is value and name not in IGNORED_DECL_NAMES]
113
  if len(found) == 1:
114
    return found[0]
115
  else:
116
    return None
117

    
118

    
119
def FormatListElems(all_items, pfx_name, ovals, tvals):
120
  """Formats a list's elements.
121

    
122
  This formats the elements as either values or, if we find all
123
  origins, as names.
124

    
125
  @param all_items: a dictionary of name/values for the current module
126
  @param pfx_name: the prefix name currently used
127
  @param ovals: the list of actual (Python) values
128
  @param tvals: the list of values we want to format in the Haskell form
129

    
130
  """
131
  origins = [IdentifyOrigin(all_items, v) for v in ovals]
132
  if compat.all(x is not None for x in origins):
133
    values = [NameRules(pfx_name + origin) for origin in origins]
134
  else:
135
    values = tvals
136
  return ", ".join(values)
137

    
138

    
139
def FormatDict(all_items, pfx_name, py_name, hs_name, mydict):
140
  """Converts a dictionary to a Haskell association list ([(k, v)]),
141
  if possible.
142

    
143
  @param all_items: a dictionary of name/values for the current module
144
  @param pfx_name: the prefix name currently used
145
  @param py_name: the Python name
146
  @param hs_name: the Haskell name
147
  @param mydict: a dictonary, unknown yet if homogenous or not
148

    
149
  """
150
  # need this for ordering
151
  orig_list = mydict.items()
152
  list_form = [(HaskellTypeVal(k), HaskellTypeVal(v)) for k, v in orig_list]
153
  if compat.any(v is None or k is None for k, v in list_form):
154
    # type not known
155
    return []
156
  all_keys = [k for k, _ in list_form]
157
  all_vals = [v for _, v in list_form]
158
  key_types = set(k[0] for k in all_keys)
159
  val_types = set(v[0] for v in all_vals)
160
  if not(len(key_types) == 1 and len(val_types) == 1):
161
    # multiple types
162
    return []
163
  # record the key and value Haskell types
164
  key_type = key_types.pop()
165
  val_type = val_types.pop()
166

    
167
  # now try to find names for the keys, instead of raw values
168
  key_origins = [IdentifyOrigin(all_items, k) for k, _ in orig_list]
169
  if compat.all(x is not None for x in key_origins):
170
    key_v = [NameRules(pfx_name + origin) for origin in key_origins]
171
  else:
172
    key_v = [k[1] for k in all_keys]
173
  # ... and for values
174
  val_origins = [IdentifyOrigin(all_items, v) for _, v in orig_list]
175
  if compat.all(x is not None for x in val_origins):
176
    val_v = [NameRules(pfx_name + origin) for origin in val_origins]
177
  else:
178
    val_v = [v[1] for v in all_vals]
179

    
180
  # finally generate the output
181
  kv_pairs = ["(%s, %s)" % (k, v) for k, v in zip(key_v, val_v)]
182
  return ["-- | Converted from Python dictionary %s" % py_name,
183
          "%s :: [(%s, %s)]" % (hs_name, key_type, val_type),
184
          "%s = [%s]" % (hs_name, ", ".join(kv_pairs)),
185
          ]
186

    
187

    
188
def ConvertVariable(prefix, name, value, all_items):
189
  """Converts a given variable to Haskell code.
190

    
191
  @param prefix: a prefix for the Haskell name (useful for module
192
      identification)
193
  @param name: the Python name
194
  @param value: the value
195
  @param all_items: a dictionary of name/value for the module being
196
      processed
197
  @return: a list of Haskell code lines
198

    
199
  """
200
  lines = []
201
  if prefix:
202
    pfx_name = prefix + "_"
203
    fqn = prefix + "." + name
204
  else:
205
    pfx_name = ""
206
    fqn = name
207
  hs_name = NameRules(pfx_name + name)
208
  hs_typeval = HaskellTypeVal(value)
209
  if (isinstance(value, types.ModuleType) or callable(value) or
210
      PRIVATE_RE.match(name)):
211
    # no sense in marking these, as we don't _want_ to convert them; the
212
    # message in the next if block is for datatypes we don't _know_
213
    # (yet) how to convert
214
    pass
215
  elif not CONSTANT_RE.match(name):
216
    lines.append("-- Skipped %s %s, not constant" % (fqn, type(value)))
217
  elif hs_typeval is not None:
218
    # this is a simple value
219
    (hs_type, hs_val) = hs_typeval
220
    lines.append("-- | Converted from Python constant %s" % fqn)
221
    lines.append("%s :: %s" % (hs_name, hs_type))
222
    lines.append("%s = %s" % (hs_name, hs_val))
223
  elif isinstance(value, dict):
224
    if value:
225
      lines.append("-- Following lines come from dictionary %s" % fqn)
226
      # try to build a real map here, if all keys have same type, and
227
      # all values too (i.e. we have a homogeneous dictionary)
228
      lines.extend(FormatDict(all_items, pfx_name, fqn, hs_name, value))
229
      # and now create individual names
230
      for k in sorted(value.keys()):
231
        lines.extend(ConvertVariable(prefix, DictKeyName(name, k),
232
                                     value[k], all_items))
233
  elif isinstance(value, tuple):
234
    tvs = [HaskellTypeVal(elem) for elem in value]
235
    # Custom rule for special cluster verify error tuples
236
    if name.startswith("CV_E") and len(value) == 3 and tvs[1][0] is not None:
237
      cv_ename = hs_name + "Code"
238
      lines.append("-- | Special cluster verify code %s" % name)
239
      lines.append("%s :: %s" % (cv_ename, tvs[1][0]))
240
      lines.append("%s = %s" % (cv_ename, tvs[1][1]))
241
      lines.append("")
242
    if compat.all(e is not None for e in tvs):
243
      ttypes = ", ".join(e[0] for e in tvs)
244
      tvals = FormatListElems(all_items, pfx_name, value, [e[1] for e in tvs])
245
      lines.append("-- | Converted from Python tuple %s" % fqn)
246
      lines.append("%s :: (%s)" % (hs_name, ttypes))
247
      lines.append("%s = (%s)" % (hs_name, tvals))
248
    else:
249
      lines.append("-- Skipped tuple %s, cannot convert all elements" % fqn)
250
  elif isinstance(value, (list, set, frozenset)):
251
    # Lists and frozensets are handled the same in Haskell: as lists,
252
    # since lists are immutable and we don't need for constants the
253
    # high-speed of an actual Set type. However, we can only convert
254
    # them if they have the same type for all elements (which is a
255
    # normal expectation for constants, our code should be well
256
    # behaved); note that this is different from the tuples case,
257
    # where we always (for some values of always) can convert
258
    tvs = [HaskellTypeVal(elem) for elem in value]
259
    if compat.all(e is not None for e in tvs):
260
      ttypes, tvals = zip(*tvs)
261
      uniq_types = set(ttypes)
262
      if len(uniq_types) == 1:
263
        values = FormatListElems(all_items, pfx_name, value, tvals)
264
        lines.append("-- | Converted from Python list or set %s" % fqn)
265
        lines.append("%s :: [%s]" % (hs_name, uniq_types.pop()))
266
        lines.append("%s = [%s]" % (hs_name, values))
267
      else:
268
        lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
269
    else:
270
      lines.append("-- | Skipped list/set %s, cannot convert all elems" % fqn)
271
  elif isinstance(value, RE_TYPE):
272
    tvs = HaskellTypeVal(value.pattern)
273
    assert tvs is not None
274
    lines.append("-- | Converted from Python RE object %s" % fqn)
275
    lines.append("%s :: %s" % (hs_name, tvs[0]))
276
    lines.append("%s = %s" % (hs_name, tvs[1]))
277
  else:
278
    lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
279
  return lines
280

    
281

    
282
def Convert(module, prefix):
283
  """Converts the constants to Haskell.
284

    
285
  """
286
  lines = [""]
287

    
288
  all_items = dict((name, getattr(module, name)) for name in dir(module))
289

    
290
  for name in sorted(all_items.keys()):
291
    value = all_items[name]
292
    new_lines = ConvertVariable(prefix, name, value, all_items)
293
    if new_lines:
294
      lines.extend(new_lines)
295
      lines.append("")
296

    
297
  return "\n".join(lines)
298

    
299

    
300
def ConvertMisc():
301
  """Convert some extra computed-values to Haskell.
302

    
303
  """
304
  lines = [""]
305
  lines.extend(ConvertVariable("opcodes", "OP_IDS",
306
                               opcodes.OP_MAPPING.keys(), {}))
307
  return "\n".join(lines)
308

    
309

    
310
def main():
311
  print Convert(constants, "")
312
  print Convert(luxi, "luxi")
313
  print Convert(qlang, "qlang")
314
  print Convert(_autoconf, "autoconf")
315
  print Convert(errors, "errors")
316
  print ConvertMisc()
317

    
318

    
319
if __name__ == "__main__":
320
  main()