• 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  istr = IndexedString(string)
42  istr.__index__ = index
43  return istr
44
45def LookupKind(kinds, spec, scope):
46  """Tries to find which Kind a spec refers to, given the scope in which its
47  referenced. Starts checking from the narrowest scope to most general. For
48  example, given a struct field like
49    Foo.Bar x;
50  Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner
51  type 'Bar' in the struct 'Foo' in the current namespace.
52
53  |scope| is a tuple that looks like (namespace, struct/interface), referring
54  to the location where the type is referenced."""
55  if spec.startswith('x:'):
56    name = spec[2:]
57    for i in xrange(len(scope), -1, -1):
58      test_spec = 'x:'
59      if i > 0:
60        test_spec += '.'.join(scope[:i]) + '.'
61      test_spec += name
62      kind = kinds.get(test_spec)
63      if kind:
64        return kind
65
66  return kinds.get(spec)
67
68def LookupValue(values, name, scope):
69  """Like LookupKind, but for constant values."""
70  for i in xrange(len(scope), -1, -1):
71    if i > 0:
72      test_spec = '.'.join(scope[:i]) + '.'
73    test_spec += name
74    value = values.get(test_spec)
75    if value:
76      return value
77
78  return values.get(name)
79
80def FixupExpression(module, value, scope):
81  """Translates an IDENTIFIER into a structured Value object."""
82  if isinstance(value, tuple) and value[0] == 'IDENTIFIER':
83    result = LookupValue(module.values, value[1], scope)
84    if result:
85      return result
86  return value
87
88def KindToData(kind):
89  return kind.spec
90
91def KindFromData(kinds, data, scope):
92  kind = LookupKind(kinds, data, scope)
93  if kind:
94    return kind
95  if data.startswith('a:'):
96    kind = mojom.Array()
97    kind.kind = KindFromData(kinds, data[2:], scope)
98  elif data.startswith('r:'):
99    kind = mojom.InterfaceRequest()
100    kind.kind = KindFromData(kinds, data[2:], scope)
101  else:
102    kind = mojom.Kind()
103  kind.spec = data
104  kinds[data] = kind
105  return kind
106
107def KindFromImport(original_kind, imported_from):
108  """Used with 'import module' - clones the kind imported from the given
109  module's namespace. Only used with Structs, Interfaces and Enums."""
110  kind = copy.deepcopy(original_kind)
111  kind.imported_from = imported_from
112  return kind
113
114def ImportFromData(module, data):
115  import_module = data['module']
116
117  import_item = {}
118  import_item['module_name'] = import_module.name
119  import_item['namespace'] = import_module.namespace
120  import_item['module'] = import_module
121
122  # Copy the struct kinds from our imports into the current module.
123  for kind in import_module.kinds.itervalues():
124    if (isinstance(kind, (mojom.Struct, mojom.Enum, mojom.Interface)) and
125        kind.imported_from is None):
126      kind = KindFromImport(kind, import_item)
127      module.kinds[kind.spec] = kind
128  # Ditto for values.
129  for value in import_module.values.itervalues():
130    if value.imported_from is None:
131      value = copy.deepcopy(value)
132      value.imported_from = import_item
133      module.values[value.GetSpec()] = value
134
135  return import_item
136
137def StructToData(struct):
138  return {
139    istr(0, 'name'): struct.name,
140    istr(1, 'fields'): map(FieldToData, struct.fields)
141  }
142
143def StructFromData(module, data):
144  struct = mojom.Struct(module=module)
145  struct.name = data['name']
146  struct.attributes = data['attributes']
147  struct.spec = 'x:' + module.namespace + '.' + struct.name
148  module.kinds[struct.spec] = struct
149  struct.enums = map(lambda enum:
150      EnumFromData(module, enum, struct), data['enums'])
151  struct.constants = map(lambda constant:
152      ConstantFromData(module, constant, struct), data['constants'])
153  # Stash fields data here temporarily.
154  struct.fields_data = data['fields']
155  return struct
156
157def FieldToData(field):
158  data = {
159    istr(0, 'name'): field.name,
160    istr(1, 'kind'): KindToData(field.kind)
161  }
162  if field.ordinal != None:
163    data[istr(2, 'ordinal')] = field.ordinal
164  if field.default != None:
165    data[istr(3, 'default')] = field.default
166  return data
167
168def FieldFromData(module, data, struct):
169  field = mojom.Field()
170  field.name = data['name']
171  field.kind = KindFromData(
172      module.kinds, data['kind'], (module.namespace, struct.name))
173  field.ordinal = data.get('ordinal')
174  field.default = FixupExpression(
175      module, data.get('default'), (module.namespace, struct.name))
176  return field
177
178def ParameterToData(parameter):
179  data = {
180    istr(0, 'name'): parameter.name,
181    istr(1, 'kind'): parameter.kind.spec
182  }
183  if parameter.ordinal != None:
184    data[istr(2, 'ordinal')] = parameter.ordinal
185  if parameter.default != None:
186    data[istr(3, 'default')] = parameter.default
187  return data
188
189def ParameterFromData(module, data, interface):
190  parameter = mojom.Parameter()
191  parameter.name = data['name']
192  parameter.kind = KindFromData(
193      module.kinds, data['kind'], (module.namespace, interface.name))
194  parameter.ordinal = data.get('ordinal')
195  parameter.default = data.get('default')
196  return parameter
197
198def MethodToData(method):
199  data = {
200    istr(0, 'name'):       method.name,
201    istr(1, 'parameters'): map(ParameterToData, method.parameters)
202  }
203  if method.ordinal != None:
204    data[istr(2, 'ordinal')] = method.ordinal
205  if method.response_parameters != None:
206    data[istr(3, 'response_parameters')] = map(
207        ParameterToData, method.response_parameters)
208  return data
209
210def MethodFromData(module, data, interface):
211  method = mojom.Method(interface, data['name'], ordinal=data.get('ordinal'))
212  method.default = data.get('default')
213  method.parameters = map(lambda parameter:
214      ParameterFromData(module, parameter, interface), data['parameters'])
215  if data.has_key('response_parameters'):
216    method.response_parameters = map(
217        lambda parameter: ParameterFromData(module, parameter, interface),
218                          data['response_parameters'])
219  return method
220
221def InterfaceToData(interface):
222  return {
223    istr(0, 'name'):    interface.name,
224    istr(1, 'client'):  interface.client,
225    istr(2, 'methods'): map(MethodToData, interface.methods)
226  }
227
228def InterfaceFromData(module, data):
229  interface = mojom.Interface(module=module)
230  interface.name = data['name']
231  interface.spec = 'x:' + module.namespace + '.' + interface.name
232  interface.client = data['client'] if data.has_key('client') else None
233  module.kinds[interface.spec] = interface
234  interface.enums = map(lambda enum:
235      EnumFromData(module, enum, interface), data['enums'])
236  interface.constants = map(lambda constant:
237      ConstantFromData(module, constant, interface), data['constants'])
238  # Stash methods data here temporarily.
239  interface.methods_data = data['methods']
240  return interface
241
242def EnumFieldFromData(module, enum, data, parent_kind):
243  field = mojom.EnumField()
244  field.name = data['name']
245  # TODO(mpcomplete): FixupExpression should be done in the second pass,
246  # so constants and enums can refer to each other.
247  # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or
248  # vice versa?
249  if parent_kind:
250    field.value = FixupExpression(
251        module, data['value'], (module.namespace, parent_kind.name))
252  else:
253    field.value = FixupExpression(
254        module, data['value'], (module.namespace, ))
255  value = mojom.EnumValue(module, enum, field)
256  module.values[value.GetSpec()] = value
257  return field
258
259def EnumFromData(module, data, parent_kind):
260  enum = mojom.Enum(module=module)
261  enum.name = data['name']
262  name = enum.name
263  if parent_kind:
264    name = parent_kind.name + '.' + name
265  enum.spec = 'x:%s.%s' % (module.namespace, name)
266  enum.parent_kind = parent_kind
267
268  enum.fields = map(
269      lambda field: EnumFieldFromData(module, enum, field, parent_kind),
270      data['fields'])
271  module.kinds[enum.spec] = enum
272  return enum
273
274def ConstantFromData(module, data, parent_kind):
275  constant = mojom.Constant()
276  constant.name = data['name']
277  if parent_kind:
278    scope = (module.namespace, parent_kind.name)
279  else:
280    scope = (module.namespace, )
281  # TODO(mpcomplete): maybe we should only support POD kinds.
282  constant.kind = KindFromData(module.kinds, data['kind'], scope)
283  constant.value = FixupExpression(module, data.get('value'), scope)
284
285  value = mojom.NamedValue(module, parent_kind, constant.name)
286  module.values[value.GetSpec()] = value
287  return constant
288
289def ModuleToData(module):
290  return {
291    istr(0, 'name'):       module.name,
292    istr(1, 'namespace'):  module.namespace,
293    istr(2, 'structs'):    map(StructToData, module.structs),
294    istr(3, 'interfaces'): map(InterfaceToData, module.interfaces)
295  }
296
297def ModuleFromData(data):
298  module = mojom.Module()
299  module.kinds = {}
300  for kind in mojom.PRIMITIVES:
301    module.kinds[kind.spec] = kind
302
303  module.values = {}
304
305  module.name = data['name']
306  module.namespace = data['namespace']
307  module.attributes = data['attributes']
308  # Imports must come first, because they add to module.kinds which is used
309  # by by the others.
310  module.imports = map(
311      lambda import_data: ImportFromData(module, import_data),
312      data['imports'])
313
314  # First pass collects kinds.
315  module.enums = map(
316      lambda enum: EnumFromData(module, enum, None), data['enums'])
317  module.structs = map(
318      lambda struct: StructFromData(module, struct), data['structs'])
319  module.interfaces = map(
320      lambda interface: InterfaceFromData(module, interface),
321      data['interfaces'])
322  module.constants = map(
323      lambda constant: ConstantFromData(module, constant, None),
324      data['constants'])
325
326  # Second pass expands fields and methods. This allows fields and parameters
327  # to refer to kinds defined anywhere in the mojom.
328  for struct in module.structs:
329    struct.fields = map(lambda field:
330        FieldFromData(module, field, struct), struct.fields_data)
331    del struct.fields_data
332  for interface in module.interfaces:
333    interface.methods = map(lambda method:
334        MethodFromData(module, method, interface), interface.methods_data)
335    del interface.methods_data
336
337  return module
338
339def OrderedModuleFromData(data):
340  module = ModuleFromData(data)
341  next_interface_ordinal = 0
342  for interface in module.interfaces:
343    next_ordinal = 0
344    for method in interface.methods:
345      if method.ordinal is None:
346        method.ordinal = next_ordinal
347      next_ordinal = method.ordinal + 1
348  return module
349