Statistics
| Branch: | Tag: | Revision:

root / autotools / convert-constants @ 24476fa0

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
from ganeti import jstore
36

    
37

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

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

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

    
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"]
53

    
54

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

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

    
63

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

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

    
72

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

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

    
79

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

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

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

    
87
  """
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)
96
  else:
97
    return None
98

    
99

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

    
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.
105

    
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
110

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

    
119

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

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

    
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
130

    
131
  """
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]
135
  else:
136
    values = tvals
137
  return ", ".join(values)
138

    
139

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

    
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
149

    
150
  """
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):
155
    # type not known
156
    return []
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):
162
    # multiple types
163
    return []
164
  # record the key and value Haskell types
165
  key_type = key_types.pop()
166
  val_type = val_types.pop()
167

    
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]
172
  else:
173
    key_v = [k[1] for k in all_keys]
174
  # ... and for values
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]
178
  else:
179
    val_v = [v[1] for v in all_vals]
180

    
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)),
186
          ]
187

    
188

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

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

    
200
  """
201
  lines = []
202
  if prefix:
203
    pfx_name = prefix + "_"
204
    fqn = prefix + "." + name
205
  else:
206
    pfx_name = ""
207
    fqn = 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
215
    pass
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):
225
    if value:
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]))
242
      lines.append("")
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))
249
    else:
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))
268
      else:
269
        lines.append("-- | Skipped list/set %s, is not homogeneous" % fqn)
270
    else:
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]))
278
  else:
279
    lines.append("-- Skipped %s, %s not handled" % (fqn, type(value)))
280
  return lines
281

    
282

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

    
286
  """
287
  lines = [""]
288

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

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

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

    
300

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

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

    
310

    
311
def main():
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")
318
  print ConvertMisc()
319

    
320

    
321
if __name__ == "__main__":
322
  main()