• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5# TODO(vtl): "data" is a pretty vague name. Rename it?
6
7import copy
8
9import module as mojom
10
11# This module provides a mechanism to turn mojom Modules to dictionaries and
12# back again. This can be used to persist a mojom Module created progromatically
13# or to read a dictionary from code or a file.
14# Example:
15# test_dict = {
16#   'name': 'test',
17#   'namespace': 'testspace',
18#   'structs': [{
19#     'name': 'teststruct',
20#     'fields': [
21#       {'name': 'testfield1', 'kind': 'i32'},
22#       {'name': 'testfield2', 'kind': 'a:i32', 'ordinal': 42}]}],
23#   'interfaces': [{
24#     'name': 'Server',
25#     'methods': [{
26#       'name': 'Foo',
27#       'parameters': [{
28#         'name': 'foo', 'kind': 'i32'},
29#         {'name': 'bar', 'kind': 'a:x:teststruct'}],
30#     'ordinal': 42}]}]
31# }
32# test_module = data.ModuleFromData(test_dict)
33
34# Used to create a subclass of str that supports sorting by index, to make
35# pretty printing maintain the order.
36def istr(index, string):
37  class IndexedString(str):
38    def __lt__(self, other):
39      return self.__index__ < other.__index__
40
41  rv = IndexedString(string)
42  rv.__index__ = index
43  return rv
44
45def AddOptional(dictionary, key, value):
46  if value is not None:
47    dictionary[key] = value;
48
49builtin_values = frozenset([
50    "double.INFINITY",
51    "double.NEGATIVE_INFINITY",
52    "double.NAN",
53    "float.INFINITY",
54    "float.NEGATIVE_INFINITY",
55    "float.NAN"])
56
57def IsBuiltinValue(value):
58  return value in builtin_values
59
60def LookupKind(kinds, spec, scope):
61  """Tries to find which Kind a spec refers to, given the scope in which its
62  referenced. Starts checking from the narrowest scope to most general. For
63  example, given a struct field like
64    Foo.Bar x;
65  Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner
66  type 'Bar' in the struct 'Foo' in the current namespace.
67
68  |scope| is a tuple that looks like (namespace, struct/interface), referring
69  to the location where the type is referenced."""
70  if spec.startswith('x:'):
71    name = spec[2:]
72    for i in xrange(len(scope), -1, -1):
73      test_spec = 'x:'
74      if i > 0:
75        test_spec += '.'.join(scope[:i]) + '.'
76      test_spec += name
77      kind = kinds.get(test_spec)
78      if kind:
79        return kind
80
81  return kinds.get(spec)
82
83def LookupValue(values, name, scope, kind):
84  """Like LookupKind, but for constant values."""
85  # If the type is an enum, the value can be specified as a qualified name, in
86  # which case the form EnumName.ENUM_VALUE must be used. We use the presence
87  # of a '.' in the requested name to identify this. Otherwise, we prepend the
88  # enum name.
89  if isinstance(kind, mojom.Enum) and '.' not in name:
90    name = '%s.%s' % (kind.spec.split(':', 1)[1], name)
91  for i in reversed(xrange(len(scope) + 1)):
92    test_spec = '.'.join(scope[:i])
93    if test_spec:
94      test_spec += '.'
95    test_spec += name
96    value = values.get(test_spec)
97    if value:
98      return value
99
100  return values.get(name)
101
102def FixupExpression(module, value, scope, kind):
103  """Translates an IDENTIFIER into a built-in value or structured NamedValue
104     object."""
105  if isinstance(value, tuple) and value[0] == 'IDENTIFIER':
106    # Allow user defined values to shadow builtins.
107    result = LookupValue(module.values, value[1], scope, kind)
108    if result:
109      if isinstance(result, tuple):
110        raise Exception('Unable to resolve expression: %r' % value[1])
111      return result
112    if IsBuiltinValue(value[1]):
113      return mojom.BuiltinValue(value[1])
114  return value
115
116def KindToData(kind):
117  return kind.spec
118
119def KindFromData(kinds, data, scope):
120  kind = LookupKind(kinds, data, scope)
121  if kind:
122    return kind
123
124  if data.startswith('?'):
125    kind = KindFromData(kinds, data[1:], scope).MakeNullableKind()
126  elif data.startswith('a:'):
127    kind = mojom.Array(KindFromData(kinds, data[2:], scope))
128  elif data.startswith('asso:'):
129    inner_kind = KindFromData(kinds, data[5:], scope)
130    if isinstance(inner_kind, mojom.InterfaceRequest):
131      kind = mojom.AssociatedInterfaceRequest(inner_kind)
132    else:
133      kind = mojom.AssociatedInterface(inner_kind)
134  elif data.startswith('a'):
135    colon = data.find(':')
136    length = int(data[1:colon])
137    kind = mojom.Array(KindFromData(kinds, data[colon+1:], scope), length)
138  elif data.startswith('r:'):
139    kind = mojom.InterfaceRequest(KindFromData(kinds, data[2:], scope))
140  elif data.startswith('m['):
141    # Isolate the two types from their brackets.
142
143    # It is not allowed to use map as key, so there shouldn't be nested ']'s
144    # inside the key type spec.
145    key_end = data.find(']')
146    assert key_end != -1 and key_end < len(data) - 1
147    assert data[key_end+1] == '[' and data[-1] == ']'
148
149    first_kind = data[2:key_end]
150    second_kind = data[key_end+2:-1]
151
152    kind = mojom.Map(KindFromData(kinds, first_kind, scope),
153                     KindFromData(kinds, second_kind, scope))
154  else:
155    kind = mojom.Kind(data)
156
157  kinds[data] = kind
158  return kind
159
160def KindFromImport(original_kind, imported_from):
161  """Used with 'import module' - clones the kind imported from the given
162  module's namespace. Only used with Structs, Unions, Interfaces and Enums."""
163  kind = copy.copy(original_kind)
164  # |shared_definition| is used to store various properties (see
165  # |AddSharedProperty()| in module.py), including |imported_from|. We don't
166  # want the copy to share these with the original, so copy it if necessary.
167  if hasattr(original_kind, 'shared_definition'):
168    kind.shared_definition = copy.copy(original_kind.shared_definition)
169  kind.imported_from = imported_from
170  return kind
171
172def ImportFromData(module, data):
173  import_module = data['module']
174
175  import_item = {}
176  import_item['module_name'] = import_module.name
177  import_item['namespace'] = import_module.namespace
178  import_item['module'] = import_module
179
180  # Copy the struct kinds from our imports into the current module.
181  importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)
182  for kind in import_module.kinds.itervalues():
183    if (isinstance(kind, importable_kinds) and
184        kind.imported_from is None):
185      kind = KindFromImport(kind, import_item)
186      module.kinds[kind.spec] = kind
187  # Ditto for values.
188  for value in import_module.values.itervalues():
189    if value.imported_from is None:
190      # Values don't have shared definitions (since they're not nullable), so no
191      # need to do anything special.
192      value = copy.copy(value)
193      value.imported_from = import_item
194      module.values[value.GetSpec()] = value
195
196  return import_item
197
198def StructToData(struct):
199  data = {
200    istr(0, 'name'): struct.name,
201    istr(1, 'fields'): map(FieldToData, struct.fields),
202    # TODO(yzshen): EnumToData() and ConstantToData() are missing.
203    istr(2, 'enums'): [],
204    istr(3, 'constants'): []
205  }
206  AddOptional(data, istr(4, 'attributes'), struct.attributes)
207  return data
208
209def StructFromData(module, data):
210  struct = mojom.Struct(module=module)
211  struct.name = data['name']
212  struct.native_only = data['native_only']
213  struct.spec = 'x:' + module.namespace + '.' + struct.name
214  module.kinds[struct.spec] = struct
215  if struct.native_only:
216    struct.enums = []
217    struct.constants = []
218    struct.fields_data = []
219  else:
220    struct.enums = map(lambda enum:
221        EnumFromData(module, enum, struct), data['enums'])
222    struct.constants = map(lambda constant:
223        ConstantFromData(module, constant, struct), data['constants'])
224    # Stash fields data here temporarily.
225    struct.fields_data = data['fields']
226  struct.attributes = data.get('attributes')
227
228  # Enforce that a [Native] attribute is set to make native-only struct
229  # declarations more explicit.
230  if struct.native_only:
231    if not struct.attributes or not struct.attributes.get('Native', False):
232      raise Exception("Native-only struct declarations must include a " +
233                      "Native attribute.")
234
235  return struct
236
237def UnionToData(union):
238  data = {
239    istr(0, 'name'): union.name,
240    istr(1, 'fields'): map(FieldToData, union.fields)
241  }
242  AddOptional(data, istr(2, 'attributes'), union.attributes)
243  return data
244
245def UnionFromData(module, data):
246  union = mojom.Union(module=module)
247  union.name = data['name']
248  union.spec = 'x:' + module.namespace + '.' + union.name
249  module.kinds[union.spec] = union
250  # Stash fields data here temporarily.
251  union.fields_data = data['fields']
252  union.attributes = data.get('attributes')
253  return union
254
255def FieldToData(field):
256  data = {
257    istr(0, 'name'): field.name,
258    istr(1, 'kind'): KindToData(field.kind)
259  }
260  AddOptional(data, istr(2, 'ordinal'), field.ordinal)
261  AddOptional(data, istr(3, 'default'), field.default)
262  AddOptional(data, istr(4, 'attributes'), field.attributes)
263  return data
264
265def StructFieldFromData(module, data, struct):
266  field = mojom.StructField()
267  PopulateField(field, module, data, struct)
268  return field
269
270def UnionFieldFromData(module, data, union):
271  field = mojom.UnionField()
272  PopulateField(field, module, data, union)
273  return field
274
275def PopulateField(field, module, data, parent):
276  field.name = data['name']
277  field.kind = KindFromData(
278      module.kinds, data['kind'], (module.namespace, parent.name))
279  field.ordinal = data.get('ordinal')
280  field.default = FixupExpression(
281      module, data.get('default'), (module.namespace, parent.name), field.kind)
282  field.attributes = data.get('attributes')
283
284def ParameterToData(parameter):
285  data = {
286    istr(0, 'name'): parameter.name,
287    istr(1, 'kind'): parameter.kind.spec
288  }
289  AddOptional(data, istr(2, 'ordinal'), parameter.ordinal)
290  AddOptional(data, istr(3, 'default'), parameter.default)
291  AddOptional(data, istr(4, 'attributes'), parameter.attributes)
292  return data
293
294def ParameterFromData(module, data, interface):
295  parameter = mojom.Parameter()
296  parameter.name = data['name']
297  parameter.kind = KindFromData(
298      module.kinds, data['kind'], (module.namespace, interface.name))
299  parameter.ordinal = data.get('ordinal')
300  parameter.default = data.get('default')
301  parameter.attributes = data.get('attributes')
302  return parameter
303
304def MethodToData(method):
305  data = {
306    istr(0, 'name'):       method.name,
307    istr(1, 'parameters'): map(ParameterToData, method.parameters)
308  }
309  if method.response_parameters is not None:
310    data[istr(2, 'response_parameters')] = map(
311        ParameterToData, method.response_parameters)
312  AddOptional(data, istr(3, 'ordinal'), method.ordinal)
313  AddOptional(data, istr(4, 'attributes'), method.attributes)
314  return data
315
316def MethodFromData(module, data, interface):
317  method = mojom.Method(interface, data['name'], ordinal=data.get('ordinal'))
318  method.parameters = map(lambda parameter:
319      ParameterFromData(module, parameter, interface), data['parameters'])
320  if data.has_key('response_parameters'):
321    method.response_parameters = map(
322        lambda parameter: ParameterFromData(module, parameter, interface),
323                          data['response_parameters'])
324  method.attributes = data.get('attributes')
325
326  # Enforce that only methods with response can have a [Sync] attribute.
327  if method.sync and method.response_parameters is None:
328    raise Exception("Only methods with response can include a [Sync] "
329                    "attribute. If no response parameters are needed, you "
330                    "could use an empty response parameter list, i.e., "
331                    "\"=> ()\".")
332
333  return method
334
335def InterfaceToData(interface):
336  data = {
337    istr(0, 'name'):    interface.name,
338    istr(1, 'methods'): map(MethodToData, interface.methods),
339    # TODO(yzshen): EnumToData() and ConstantToData() are missing.
340    istr(2, 'enums'): [],
341    istr(3, 'constants'): []
342  }
343  AddOptional(data, istr(4, 'attributes'), interface.attributes)
344  return data
345
346def InterfaceFromData(module, data):
347  interface = mojom.Interface(module=module)
348  interface.name = data['name']
349  interface.spec = 'x:' + module.namespace + '.' + interface.name
350  module.kinds[interface.spec] = interface
351  interface.enums = map(lambda enum:
352      EnumFromData(module, enum, interface), data['enums'])
353  interface.constants = map(lambda constant:
354      ConstantFromData(module, constant, interface), data['constants'])
355  # Stash methods data here temporarily.
356  interface.methods_data = data['methods']
357  interface.attributes = data.get('attributes')
358  return interface
359
360def EnumFieldFromData(module, enum, data, parent_kind):
361  field = mojom.EnumField()
362  field.name = data['name']
363  # TODO(mpcomplete): FixupExpression should be done in the second pass,
364  # so constants and enums can refer to each other.
365  # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or
366  # vice versa?
367  if parent_kind:
368    field.value = FixupExpression(
369        module, data.get('value'), (module.namespace, parent_kind.name), enum)
370  else:
371    field.value = FixupExpression(
372        module, data.get('value'), (module.namespace, ), enum)
373  field.attributes = data.get('attributes')
374  value = mojom.EnumValue(module, enum, field)
375  module.values[value.GetSpec()] = value
376  return field
377
378def ResolveNumericEnumValues(enum_fields):
379  """
380  Given a reference to a list of mojom.EnumField, resolves and assigns their
381  values to EnumField.numeric_value.
382  """
383
384  # map of <name> -> integral value
385  resolved_enum_values = {}
386  prev_value = -1
387  for field in enum_fields:
388    # This enum value is +1 the previous enum value (e.g: BEGIN).
389    if field.value is None:
390      prev_value += 1
391
392    # Integral value (e.g: BEGIN = -0x1).
393    elif type(field.value) is str:
394      prev_value = int(field.value, 0)
395
396    # Reference to a previous enum value (e.g: INIT = BEGIN).
397    elif type(field.value) is mojom.EnumValue:
398      prev_value = resolved_enum_values[field.value.name]
399    else:
400      raise Exception("Unresolved enum value.")
401
402    resolved_enum_values[field.name] = prev_value
403    field.numeric_value = prev_value
404
405def EnumFromData(module, data, parent_kind):
406  enum = mojom.Enum(module=module)
407  enum.name = data['name']
408  enum.native_only = data['native_only']
409  name = enum.name
410  if parent_kind:
411    name = parent_kind.name + '.' + name
412  enum.spec = 'x:%s.%s' % (module.namespace, name)
413  enum.parent_kind = parent_kind
414  enum.attributes = data.get('attributes')
415  if enum.native_only:
416    enum.fields = []
417  else:
418    enum.fields = map(
419        lambda field: EnumFieldFromData(module, enum, field, parent_kind),
420        data['fields'])
421    ResolveNumericEnumValues(enum.fields)
422
423  module.kinds[enum.spec] = enum
424
425  # Enforce that a [Native] attribute is set to make native-only enum
426  # declarations more explicit.
427  if enum.native_only:
428    if not enum.attributes or not enum.attributes.get('Native', False):
429      raise Exception("Native-only enum declarations must include a " +
430                      "Native attribute.")
431
432  return enum
433
434def ConstantFromData(module, data, parent_kind):
435  constant = mojom.Constant()
436  constant.name = data['name']
437  if parent_kind:
438    scope = (module.namespace, parent_kind.name)
439  else:
440    scope = (module.namespace, )
441  # TODO(mpcomplete): maybe we should only support POD kinds.
442  constant.kind = KindFromData(module.kinds, data['kind'], scope)
443  constant.parent_kind = parent_kind
444  constant.value = FixupExpression(module, data.get('value'), scope, None)
445
446  value = mojom.ConstantValue(module, parent_kind, constant)
447  module.values[value.GetSpec()] = value
448  return constant
449
450def ModuleToData(module):
451  data = {
452    istr(0, 'name'):       module.name,
453    istr(1, 'namespace'):  module.namespace,
454    # TODO(yzshen): Imports information is missing.
455    istr(2, 'imports'): [],
456    istr(3, 'structs'):    map(StructToData, module.structs),
457    istr(4, 'unions'):     map(UnionToData, module.unions),
458    istr(5, 'interfaces'): map(InterfaceToData, module.interfaces),
459    # TODO(yzshen): EnumToData() and ConstantToData() are missing.
460    istr(6, 'enums'): [],
461    istr(7, 'constants'): []
462  }
463  AddOptional(data, istr(8, 'attributes'), module.attributes)
464  return data
465
466def ModuleFromData(data):
467  module = mojom.Module()
468  module.kinds = {}
469  for kind in mojom.PRIMITIVES:
470    module.kinds[kind.spec] = kind
471
472  module.values = {}
473
474  module.name = data['name']
475  module.namespace = data['namespace']
476  # Imports must come first, because they add to module.kinds which is used
477  # by by the others.
478  module.imports = map(
479      lambda import_data: ImportFromData(module, import_data),
480      data['imports'])
481  module.attributes = data.get('attributes')
482
483  # First pass collects kinds.
484  module.enums = map(
485      lambda enum: EnumFromData(module, enum, None), data['enums'])
486  module.structs = map(
487      lambda struct: StructFromData(module, struct), data['structs'])
488  module.unions = map(
489      lambda union: UnionFromData(module, union), data.get('unions', []))
490  module.interfaces = map(
491      lambda interface: InterfaceFromData(module, interface),
492      data['interfaces'])
493  module.constants = map(
494      lambda constant: ConstantFromData(module, constant, None),
495      data['constants'])
496
497  # Second pass expands fields and methods. This allows fields and parameters
498  # to refer to kinds defined anywhere in the mojom.
499  for struct in module.structs:
500    struct.fields = map(lambda field:
501        StructFieldFromData(module, field, struct), struct.fields_data)
502    del struct.fields_data
503  for union in module.unions:
504    union.fields = map(lambda field:
505        UnionFieldFromData(module, field, union), union.fields_data)
506    del union.fields_data
507  for interface in module.interfaces:
508    interface.methods = map(lambda method:
509        MethodFromData(module, method, interface), interface.methods_data)
510    del interface.methods_data
511
512  return module
513
514def OrderedModuleFromData(data):
515  """Convert Mojom IR to a module.
516
517  Args:
518    data: The Mojom IR as a dict.
519
520  Returns:
521    A mojom.generate.module.Module object.
522  """
523  module = ModuleFromData(data)
524  for interface in module.interfaces:
525    next_ordinal = 0
526    for method in interface.methods:
527      if method.ordinal is None:
528        method.ordinal = next_ordinal
529      next_ordinal = method.ordinal + 1
530  return module
531