• 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"""Convert parse tree to AST.
6
7This module converts the parse tree to the AST we use for code generation. The
8main entry point is OrderedModule, which gets passed the parser
9representation of a mojom file. When called it's assumed that all imports have
10already been parsed and converted to ASTs before.
11"""
12
13import os
14import re
15
16import module as mojom
17from mojom.parse import ast
18
19def _DuplicateName(values):
20  """Returns the 'mojom_name' of the first entry in |values| whose 'mojom_name'
21  has already been encountered. If there are no duplicates, returns None."""
22  names = set()
23  for value in values:
24    if value.mojom_name in names:
25      return value.mojom_name
26    names.add(value.mojom_name)
27  return None
28
29def _ElemsOfType(elems, elem_type, scope):
30  """Find all elements of the given type.
31
32  Args:
33    elems: {Sequence[Any]} Sequence of elems.
34    elem_type: {Type[C]} Extract all elems of this type.
35    scope: {str} The name of the surrounding scope (e.g. struct
36        definition). Used in error messages.
37
38  Returns:
39    {List[C]} All elems of matching type.
40  """
41  assert isinstance(elem_type, type)
42  result = [elem for elem in elems if isinstance(elem, elem_type)]
43  duplicate_name = _DuplicateName(result)
44  if duplicate_name:
45    raise Exception('Names in mojom must be unique within a scope. The name '
46                    '"%s" is used more than once within the scope "%s".' %
47                    (duplicate_name, scope))
48  return result
49
50def _MapKind(kind):
51  map_to_kind = {'bool': 'b',
52                 'int8': 'i8',
53                 'int16': 'i16',
54                 'int32': 'i32',
55                 'int64': 'i64',
56                 'uint8': 'u8',
57                 'uint16': 'u16',
58                 'uint32': 'u32',
59                 'uint64': 'u64',
60                 'float': 'f',
61                 'double': 'd',
62                 'string': 's',
63                 'handle': 'h',
64                 'handle<data_pipe_consumer>': 'h:d:c',
65                 'handle<data_pipe_producer>': 'h:d:p',
66                 'handle<message_pipe>': 'h:m',
67                 'handle<shared_buffer>': 'h:s'}
68  if kind.endswith('?'):
69    base_kind = _MapKind(kind[0:-1])
70    # NOTE: This doesn't rule out enum types. Those will be detected later, when
71    # cross-reference is established.
72    reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso')
73    if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:
74      raise Exception(
75          'A type (spec "%s") cannot be made nullable' % base_kind)
76    return '?' + base_kind
77  if kind.endswith('}'):
78    lbracket = kind.rfind('{')
79    value = kind[0:lbracket]
80    return 'm[' + _MapKind(kind[lbracket+1:-1]) + '][' + _MapKind(value) + ']'
81  if kind.endswith(']'):
82    lbracket = kind.rfind('[')
83    typename = kind[0:lbracket]
84    return 'a' + kind[lbracket+1:-1] + ':' + _MapKind(typename)
85  if kind.endswith('&'):
86    return 'r:' + _MapKind(kind[0:-1])
87  if kind.startswith('asso<'):
88    assert kind.endswith('>')
89    return 'asso:' + _MapKind(kind[5:-1])
90  if kind in map_to_kind:
91    return map_to_kind[kind]
92  return 'x:' + kind
93
94def _AttributeListToDict(attribute_list):
95  if attribute_list is None:
96    return None
97  assert isinstance(attribute_list, ast.AttributeList)
98  # TODO(vtl): Check for duplicate keys here.
99  return dict([(attribute.key, attribute.value)
100                   for attribute in attribute_list])
101
102builtin_values = frozenset([
103    "double.INFINITY",
104    "double.NEGATIVE_INFINITY",
105    "double.NAN",
106    "float.INFINITY",
107    "float.NEGATIVE_INFINITY",
108    "float.NAN"])
109
110def _IsBuiltinValue(value):
111  return value in builtin_values
112
113def _LookupKind(kinds, spec, scope):
114  """Tries to find which Kind a spec refers to, given the scope in which its
115  referenced. Starts checking from the narrowest scope to most general. For
116  example, given a struct field like
117    Foo.Bar x;
118  Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner
119  type 'Bar' in the struct 'Foo' in the current namespace.
120
121  |scope| is a tuple that looks like (namespace, struct/interface), referring
122  to the location where the type is referenced."""
123  if spec.startswith('x:'):
124    mojom_name = spec[2:]
125    for i in xrange(len(scope), -1, -1):
126      test_spec = 'x:'
127      if i > 0:
128        test_spec += '.'.join(scope[:i]) + '.'
129      test_spec += mojom_name
130      kind = kinds.get(test_spec)
131      if kind:
132        return kind
133
134  return kinds.get(spec)
135
136def _LookupValue(values, mojom_name, scope, kind):
137  """Like LookupKind, but for constant values."""
138  # If the type is an enum, the value can be specified as a qualified name, in
139  # which case the form EnumName.ENUM_VALUE must be used. We use the presence
140  # of a '.' in the requested name to identify this. Otherwise, we prepend the
141  # enum name.
142  if isinstance(kind, mojom.Enum) and '.' not in mojom_name:
143    mojom_name = '%s.%s' % (kind.spec.split(':', 1)[1], mojom_name)
144  for i in reversed(xrange(len(scope) + 1)):
145    test_spec = '.'.join(scope[:i])
146    if test_spec:
147      test_spec += '.'
148    test_spec += mojom_name
149    value = values.get(test_spec)
150    if value:
151      return value
152
153  return values.get(mojom_name)
154
155def _FixupExpression(module, value, scope, kind):
156  """Translates an IDENTIFIER into a built-in value or structured NamedValue
157     object."""
158  if isinstance(value, tuple) and value[0] == 'IDENTIFIER':
159    # Allow user defined values to shadow builtins.
160    result = _LookupValue(module.values, value[1], scope, kind)
161    if result:
162      if isinstance(result, tuple):
163        raise Exception('Unable to resolve expression: %r' % value[1])
164      return result
165    if _IsBuiltinValue(value[1]):
166      return mojom.BuiltinValue(value[1])
167  return value
168
169def _Kind(kinds, spec, scope):
170  """Convert a type name into a mojom.Kind object.
171
172  As a side-effect this function adds the result to 'kinds'.
173
174  Args:
175    kinds: {Dict[str, mojom.Kind]} All known kinds up to this point, indexed by
176        their names.
177    spec: {str} A name uniquely identifying a type.
178    scope: {Tuple[str, str]} A tuple that looks like (namespace,
179        struct/interface), referring to the location where the type is
180        referenced.
181
182  Returns:
183    {mojom.Kind} The type corresponding to 'spec'.
184  """
185  kind = _LookupKind(kinds, spec, scope)
186  if kind:
187    return kind
188
189  if spec.startswith('?'):
190    kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()
191  elif spec.startswith('a:'):
192    kind = mojom.Array(_Kind(kinds, spec[2:], scope))
193  elif spec.startswith('asso:'):
194    inner_kind = _Kind(kinds, spec[5:], scope)
195    if isinstance(inner_kind, mojom.InterfaceRequest):
196      kind = mojom.AssociatedInterfaceRequest(inner_kind)
197    else:
198      kind = mojom.AssociatedInterface(inner_kind)
199  elif spec.startswith('a'):
200    colon = spec.find(':')
201    length = int(spec[1:colon])
202    kind = mojom.Array(_Kind(kinds, spec[colon+1:], scope), length)
203  elif spec.startswith('r:'):
204    kind = mojom.InterfaceRequest(_Kind(kinds, spec[2:], scope))
205  elif spec.startswith('m['):
206    # Isolate the two types from their brackets.
207
208    # It is not allowed to use map as key, so there shouldn't be nested ']'s
209    # inside the key type spec.
210    key_end = spec.find(']')
211    assert key_end != -1 and key_end < len(spec) - 1
212    assert spec[key_end+1] == '[' and spec[-1] == ']'
213
214    first_kind = spec[2:key_end]
215    second_kind = spec[key_end+2:-1]
216
217    kind = mojom.Map(_Kind(kinds, first_kind, scope),
218                     _Kind(kinds, second_kind, scope))
219  else:
220    kind = mojom.Kind(spec)
221
222  kinds[spec] = kind
223  return kind
224
225def _Import(module, import_module):
226  # Copy the struct kinds from our imports into the current module.
227  importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)
228  for kind in import_module.kinds.itervalues():
229    if (isinstance(kind, importable_kinds) and
230        kind.module.path == import_module.path):
231      module.kinds[kind.spec] = kind
232  # Ditto for values.
233  for value in import_module.values.itervalues():
234    if value.module.path == import_module.path:
235      module.values[value.GetSpec()] = value
236
237  return import_module
238
239def _Struct(module, parsed_struct):
240  """
241  Args:
242    module: {mojom.Module} Module currently being constructed.
243    parsed_struct: {ast.Struct} Parsed struct.
244
245  Returns:
246    {mojom.Struct} AST struct.
247  """
248  struct = mojom.Struct(module=module)
249  struct.mojom_name = parsed_struct.mojom_name
250  struct.native_only = parsed_struct.body is None
251  struct.spec = 'x:' + module.mojom_namespace + '.' + struct.mojom_name
252  module.kinds[struct.spec] = struct
253  if struct.native_only:
254    struct.enums = []
255    struct.constants = []
256    struct.fields_data = []
257  else:
258    struct.enums = map(
259        lambda enum: _Enum(module, enum, struct),
260        _ElemsOfType(parsed_struct.body, ast.Enum, parsed_struct.mojom_name))
261    struct.constants = map(
262        lambda constant: _Constant(module, constant, struct),
263        _ElemsOfType(parsed_struct.body, ast.Const, parsed_struct.mojom_name))
264    # Stash fields parsed_struct here temporarily.
265    struct.fields_data = _ElemsOfType(
266        parsed_struct.body, ast.StructField, parsed_struct.mojom_name)
267  struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)
268
269  # Enforce that a [Native] attribute is set to make native-only struct
270  # declarations more explicit.
271  if struct.native_only:
272    if not struct.attributes or not struct.attributes.get('Native', False):
273      raise Exception("Native-only struct declarations must include a " +
274                      "Native attribute.")
275
276  if struct.attributes and struct.attributes.get('CustomSerializer', False):
277    struct.custom_serializer = True
278
279  return struct
280
281def _Union(module, parsed_union):
282  """
283  Args:
284    module: {mojom.Module} Module currently being constructed.
285    parsed_union: {ast.Union} Parsed union.
286
287  Returns:
288    {mojom.Union} AST union.
289  """
290  union = mojom.Union(module=module)
291  union.mojom_name = parsed_union.mojom_name
292  union.spec = 'x:' + module.mojom_namespace + '.' + union.mojom_name
293  module.kinds[union.spec] = union
294  # Stash fields parsed_union here temporarily.
295  union.fields_data = _ElemsOfType(
296      parsed_union.body, ast.UnionField, parsed_union.mojom_name)
297  union.attributes = _AttributeListToDict(parsed_union.attribute_list)
298  return union
299
300def _StructField(module, parsed_field, struct):
301  """
302  Args:
303    module: {mojom.Module} Module currently being constructed.
304    parsed_field: {ast.StructField} Parsed struct field.
305    struct: {mojom.Struct} Struct this field belongs to.
306
307  Returns:
308    {mojom.StructField} AST struct field.
309  """
310  field = mojom.StructField()
311  field.mojom_name = parsed_field.mojom_name
312  field.kind = _Kind(
313      module.kinds, _MapKind(parsed_field.typename),
314      (module.mojom_namespace, struct.mojom_name))
315  field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
316  field.default = _FixupExpression(
317      module, parsed_field.default_value,
318      (module.mojom_namespace, struct.mojom_name), field.kind)
319  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
320  return field
321
322def _UnionField(module, parsed_field, union):
323  """
324  Args:
325    module: {mojom.Module} Module currently being constructed.
326    parsed_field: {ast.UnionField} Parsed union field.
327    union: {mojom.Union} Union this fields belong to.
328
329  Returns:
330    {mojom.UnionField} AST union.
331  """
332  field = mojom.UnionField()
333  field.mojom_name = parsed_field.mojom_name
334  field.kind = _Kind(
335      module.kinds, _MapKind(parsed_field.typename),
336      (module.mojom_namespace, union.mojom_name))
337  field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
338  field.default = _FixupExpression(
339      module, None, (module.mojom_namespace, union.mojom_name), field.kind)
340  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
341  return field
342
343def _Parameter(module, parsed_param, interface):
344  """
345  Args:
346    module: {mojom.Module} Module currently being constructed.
347    parsed_param: {ast.Parameter} Parsed parameter.
348    union: {mojom.Interface} Interface this parameter belongs to.
349
350  Returns:
351    {mojom.Parameter} AST parameter.
352  """
353  parameter = mojom.Parameter()
354  parameter.mojom_name = parsed_param.mojom_name
355  parameter.kind = _Kind(
356      module.kinds, _MapKind(parsed_param.typename),
357      (module.mojom_namespace, interface.mojom_name))
358  parameter.ordinal = (
359      parsed_param.ordinal.value if parsed_param.ordinal else None)
360  parameter.default = None  # TODO(tibell): We never have these. Remove field?
361  parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)
362  return parameter
363
364def _Method(module, parsed_method, interface):
365  """
366  Args:
367    module: {mojom.Module} Module currently being constructed.
368    parsed_method: {ast.Method} Parsed method.
369    interface: {mojom.Interface} Interface this method belongs to.
370
371  Returns:
372    {mojom.Method} AST method.
373  """
374  method = mojom.Method(
375      interface, parsed_method.mojom_name,
376      ordinal=parsed_method.ordinal.value if parsed_method.ordinal else None)
377  method.parameters = map(
378      lambda parameter: _Parameter(module, parameter, interface),
379      parsed_method.parameter_list)
380  if parsed_method.response_parameter_list is not None:
381    method.response_parameters = map(
382        lambda parameter: _Parameter(module, parameter, interface),
383                          parsed_method.response_parameter_list)
384  method.attributes = _AttributeListToDict(parsed_method.attribute_list)
385
386  # Enforce that only methods with response can have a [Sync] attribute.
387  if method.sync and method.response_parameters is None:
388    raise Exception("Only methods with response can include a [Sync] "
389                    "attribute. If no response parameters are needed, you "
390                    "could use an empty response parameter list, i.e., "
391                    "\"=> ()\".")
392
393  return method
394
395def _Interface(module, parsed_iface):
396  """
397  Args:
398    module: {mojom.Module} Module currently being constructed.
399    parsed_iface: {ast.Interface} Parsed interface.
400
401  Returns:
402    {mojom.Interface} AST interface.
403  """
404  interface = mojom.Interface(module=module)
405  interface.mojom_name = parsed_iface.mojom_name
406  interface.spec = 'x:' + module.mojom_namespace + '.' + interface.mojom_name
407  module.kinds[interface.spec] = interface
408  interface.enums = map(
409      lambda enum: _Enum(module, enum, interface),
410      _ElemsOfType(parsed_iface.body, ast.Enum, parsed_iface.mojom_name))
411  interface.constants = map(
412      lambda constant: _Constant(module, constant, interface),
413      _ElemsOfType(parsed_iface.body, ast.Const, parsed_iface.mojom_name))
414  # Stash methods parsed_iface here temporarily.
415  interface.methods_data = _ElemsOfType(
416      parsed_iface.body, ast.Method, parsed_iface.mojom_name)
417  interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)
418  return interface
419
420def _EnumField(module, enum, parsed_field, parent_kind):
421  """
422  Args:
423    module: {mojom.Module} Module currently being constructed.
424    enum: {mojom.Enum} Enum this field belongs to.
425    parsed_field: {ast.EnumValue} Parsed enum value.
426    parent_kind: {mojom.Kind} The enclosing type.
427
428  Returns:
429    {mojom.EnumField} AST enum field.
430  """
431  field = mojom.EnumField()
432  field.mojom_name = parsed_field.mojom_name
433  # TODO(mpcomplete): FixupExpression should be done in the second pass,
434  # so constants and enums can refer to each other.
435  # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or
436  # vice versa?
437  if parent_kind:
438    field.value = _FixupExpression(
439        module, parsed_field.value,
440        (module.mojom_namespace, parent_kind.mojom_name), enum)
441  else:
442    field.value = _FixupExpression(
443        module, parsed_field.value, (module.mojom_namespace, ), enum)
444  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
445  value = mojom.EnumValue(module, enum, field)
446  module.values[value.GetSpec()] = value
447  return field
448
449def _ResolveNumericEnumValues(enum_fields):
450  """
451  Given a reference to a list of mojom.EnumField, resolves and assigns their
452  values to EnumField.numeric_value.
453
454  Returns:
455    A tuple of the lowest and highest assigned enumerator value or None, None
456    if no enumerator values were assigned.
457  """
458
459  # map of <mojom_name> -> integral value
460  resolved_enum_values = {}
461  prev_value = -1
462  min_value = None
463  max_value = None
464  for field in enum_fields:
465    # This enum value is +1 the previous enum value (e.g: BEGIN).
466    if field.value is None:
467      prev_value += 1
468
469    # Integral value (e.g: BEGIN = -0x1).
470    elif type(field.value) is str:
471      prev_value = int(field.value, 0)
472
473    # Reference to a previous enum value (e.g: INIT = BEGIN).
474    elif type(field.value) is mojom.EnumValue:
475      prev_value = resolved_enum_values[field.value.mojom_name]
476    else:
477      raise Exception("Unresolved enum value.")
478
479    resolved_enum_values[field.mojom_name] = prev_value
480    field.numeric_value = prev_value
481    if min_value is None or prev_value < min_value:
482      min_value = prev_value
483    if max_value is None or prev_value > max_value:
484      max_value = prev_value
485
486  return min_value, max_value
487
488def _Enum(module, parsed_enum, parent_kind):
489  """
490  Args:
491    module: {mojom.Module} Module currently being constructed.
492    parsed_enum: {ast.Enum} Parsed enum.
493
494  Returns:
495    {mojom.Enum} AST enum.
496  """
497  enum = mojom.Enum(module=module)
498  enum.mojom_name = parsed_enum.mojom_name
499  enum.native_only = parsed_enum.enum_value_list is None
500  mojom_name = enum.mojom_name
501  if parent_kind:
502    mojom_name = parent_kind.mojom_name + '.' + mojom_name
503  enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
504  enum.parent_kind = parent_kind
505  enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
506  if not enum.native_only:
507    enum.fields = map(
508        lambda field: _EnumField(module, enum, field, parent_kind),
509        parsed_enum.enum_value_list)
510    enum.min_value, enum.max_value = _ResolveNumericEnumValues(enum.fields)
511
512  module.kinds[enum.spec] = enum
513
514  # Enforce that a [Native] attribute is set to make native-only enum
515  # declarations more explicit.
516  if enum.native_only:
517    if not enum.attributes or not enum.attributes.get('Native', False):
518      raise Exception("Native-only enum declarations must include a " +
519                      "Native attribute.")
520
521  return enum
522
523def _Constant(module, parsed_const, parent_kind):
524  """
525  Args:
526    module: {mojom.Module} Module currently being constructed.
527    parsed_const: {ast.Const} Parsed constant.
528
529  Returns:
530    {mojom.Constant} AST constant.
531  """
532  constant = mojom.Constant()
533  constant.mojom_name = parsed_const.mojom_name
534  if parent_kind:
535    scope = (module.mojom_namespace, parent_kind.mojom_name)
536  else:
537    scope = (module.mojom_namespace, )
538  # TODO(mpcomplete): maybe we should only support POD kinds.
539  constant.kind = _Kind(module.kinds, _MapKind(parsed_const.typename), scope)
540  constant.parent_kind = parent_kind
541  constant.value = _FixupExpression(module, parsed_const.value, scope, None)
542
543  value = mojom.ConstantValue(module, parent_kind, constant)
544  module.values[value.GetSpec()] = value
545  return constant
546
547def _Module(tree, path, imports):
548  """
549  Args:
550    tree: {ast.Mojom} The parse tree.
551    path: {str} The path to the mojom file.
552    imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in
553        the import list, to already processed modules. Used to process imports.
554
555  Returns:
556    {mojom.Module} An AST for the mojom.
557  """
558  module = mojom.Module(path=path)
559  module.kinds = {}
560  for kind in mojom.PRIMITIVES:
561    module.kinds[kind.spec] = kind
562
563  module.values = {}
564
565  module.mojom_namespace = tree.module.mojom_namespace[1] if tree.module else ''
566  # Imports must come first, because they add to module.kinds which is used
567  # by by the others.
568  module.imports = [
569      _Import(module, imports[imp.import_filename])
570      for imp in tree.import_list]
571  if tree.module and tree.module.attribute_list:
572    assert isinstance(tree.module.attribute_list, ast.AttributeList)
573    # TODO(vtl): Check for duplicate keys here.
574    module.attributes = dict((attribute.key, attribute.value)
575                             for attribute in tree.module.attribute_list)
576
577  filename = os.path.basename(path)
578  # First pass collects kinds.
579  module.enums = map(
580      lambda enum: _Enum(module, enum, None),
581      _ElemsOfType(tree.definition_list, ast.Enum, filename))
582  module.structs = map(
583      lambda struct: _Struct(module, struct),
584      _ElemsOfType(tree.definition_list, ast.Struct, filename))
585  module.unions = map(
586      lambda union: _Union(module, union),
587      _ElemsOfType(tree.definition_list, ast.Union, filename))
588  module.interfaces = map(
589      lambda interface: _Interface(module, interface),
590      _ElemsOfType(tree.definition_list, ast.Interface, filename))
591  module.constants = map(
592      lambda constant: _Constant(module, constant, None),
593      _ElemsOfType(tree.definition_list, ast.Const, filename))
594
595  # Second pass expands fields and methods. This allows fields and parameters
596  # to refer to kinds defined anywhere in the mojom.
597  for struct in module.structs:
598    struct.fields = map(lambda field:
599        _StructField(module, field, struct), struct.fields_data)
600    del struct.fields_data
601  for union in module.unions:
602    union.fields = map(lambda field:
603        _UnionField(module, field, union), union.fields_data)
604    del union.fields_data
605  for interface in module.interfaces:
606    interface.methods = map(lambda method:
607        _Method(module, method, interface), interface.methods_data)
608    del interface.methods_data
609
610  return module
611
612def OrderedModule(tree, path, imports):
613  """Convert parse tree to AST module.
614
615  Args:
616    tree: {ast.Mojom} The parse tree.
617    path: {str} The path to the mojom file.
618    imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in
619        the import list, to already processed modules. Used to process imports.
620
621  Returns:
622    {mojom.Module} An AST for the mojom.
623  """
624  module = _Module(tree, path, imports)
625  return module
626