Statistics
| Branch: | Tag: | Revision:

root / autotools / convert-constants @ 25d7b289

History | View | Annotate | Download (10.8 kB)

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

    
4
# Copyright (C) 2011, 2012, 2013 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, bool):
91
    return ("Bool", "%s" % value)
92
  elif isinstance(value, int):
93
    return ("Int", "%d" % value)
94
  elif isinstance(value, long):
95
    return ("Integer", "%d" % value)
96
  elif isinstance(value, float):
97
    return ("Double", "%f" % value)
98
  else:
99
    return None
100

    
101

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

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

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

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

    
121

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

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

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

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

    
141

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

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

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

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

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

    
190

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

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

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

    
284

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

    
288
  """
289
  lines = [""]
290

    
291
  all_items = dict((name, getattr(module, name)) for name in dir(module))
292

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

    
300
  return "\n".join(lines)
301

    
302

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

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

    
312

    
313
def main():
314
  print Convert(constants, "")
315
  print Convert(luxi, "luxi")
316
  print Convert(qlang, "qlang")
317
  print Convert(_autoconf, "autoconf")
318
  print Convert(errors, "errors")
319
  print Convert(jstore, "jstore")
320
  print ConvertMisc()
321

    
322

    
323
if __name__ == "__main__":
324
  main()