• 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 mojom.generate.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 range(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(range(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.values():
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.values():
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 = [
259        _Enum(module, enum, struct) for enum in
260        _ElemsOfType(parsed_struct.body, ast.Enum, parsed_struct.mojom_name)
261    ]
262    struct.constants = [
263        _Constant(module, constant, struct) for constant in
264        _ElemsOfType(parsed_struct.body, ast.Const, parsed_struct.mojom_name)
265    ]
266    # Stash fields parsed_struct here temporarily.
267    struct.fields_data = _ElemsOfType(
268        parsed_struct.body, ast.StructField, parsed_struct.mojom_name)
269  struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)
270
271  # Enforce that a [Native] attribute is set to make native-only struct
272  # declarations more explicit.
273  if struct.native_only:
274    if not struct.attributes or not struct.attributes.get('Native', False):
275      raise Exception("Native-only struct declarations must include a " +
276                      "Native attribute.")
277
278  if struct.attributes and struct.attributes.get('CustomSerializer', False):
279    struct.custom_serializer = True
280
281  return struct
282
283def _Union(module, parsed_union):
284  """
285  Args:
286    module: {mojom.Module} Module currently being constructed.
287    parsed_union: {ast.Union} Parsed union.
288
289  Returns:
290    {mojom.Union} AST union.
291  """
292  union = mojom.Union(module=module)
293  union.mojom_name = parsed_union.mojom_name
294  union.spec = 'x:' + module.mojom_namespace + '.' + union.mojom_name
295  module.kinds[union.spec] = union
296  # Stash fields parsed_union here temporarily.
297  union.fields_data = _ElemsOfType(
298      parsed_union.body, ast.UnionField, parsed_union.mojom_name)
299  union.attributes = _AttributeListToDict(parsed_union.attribute_list)
300  return union
301
302def _StructField(module, parsed_field, struct):
303  """
304  Args:
305    module: {mojom.Module} Module currently being constructed.
306    parsed_field: {ast.StructField} Parsed struct field.
307    struct: {mojom.Struct} Struct this field belongs to.
308
309  Returns:
310    {mojom.StructField} AST struct field.
311  """
312  field = mojom.StructField()
313  field.mojom_name = parsed_field.mojom_name
314  field.kind = _Kind(
315      module.kinds, _MapKind(parsed_field.typename),
316      (module.mojom_namespace, struct.mojom_name))
317  field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
318  field.default = _FixupExpression(
319      module, parsed_field.default_value,
320      (module.mojom_namespace, struct.mojom_name), field.kind)
321  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
322  return field
323
324def _UnionField(module, parsed_field, union):
325  """
326  Args:
327    module: {mojom.Module} Module currently being constructed.
328    parsed_field: {ast.UnionField} Parsed union field.
329    union: {mojom.Union} Union this fields belong to.
330
331  Returns:
332    {mojom.UnionField} AST union.
333  """
334  field = mojom.UnionField()
335  field.mojom_name = parsed_field.mojom_name
336  field.kind = _Kind(
337      module.kinds, _MapKind(parsed_field.typename),
338      (module.mojom_namespace, union.mojom_name))
339  field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
340  field.default = _FixupExpression(
341      module, None, (module.mojom_namespace, union.mojom_name), field.kind)
342  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
343  return field
344
345def _Parameter(module, parsed_param, interface):
346  """
347  Args:
348    module: {mojom.Module} Module currently being constructed.
349    parsed_param: {ast.Parameter} Parsed parameter.
350    union: {mojom.Interface} Interface this parameter belongs to.
351
352  Returns:
353    {mojom.Parameter} AST parameter.
354  """
355  parameter = mojom.Parameter()
356  parameter.mojom_name = parsed_param.mojom_name
357  parameter.kind = _Kind(
358      module.kinds, _MapKind(parsed_param.typename),
359      (module.mojom_namespace, interface.mojom_name))
360  parameter.ordinal = (
361      parsed_param.ordinal.value if parsed_param.ordinal else None)
362  parameter.default = None  # TODO(tibell): We never have these. Remove field?
363  parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)
364  return parameter
365
366def _Method(module, parsed_method, interface):
367  """
368  Args:
369    module: {mojom.Module} Module currently being constructed.
370    parsed_method: {ast.Method} Parsed method.
371    interface: {mojom.Interface} Interface this method belongs to.
372
373  Returns:
374    {mojom.Method} AST method.
375  """
376  method = mojom.Method(
377      interface, parsed_method.mojom_name,
378      ordinal=parsed_method.ordinal.value if parsed_method.ordinal else None)
379  method.parameters = [
380      _Parameter(module, parameter, interface) for parameter in
381      parsed_method.parameter_list
382  ]
383  if parsed_method.response_parameter_list is not None:
384    method.response_parameters = [
385        _Parameter(module, parameter, interface) for parameter in
386        parsed_method.response_parameter_list
387    ]
388  method.attributes = _AttributeListToDict(parsed_method.attribute_list)
389
390  # Enforce that only methods with response can have a [Sync] attribute.
391  if method.sync and method.response_parameters is None:
392    raise Exception("Only methods with response can include a [Sync] "
393                    "attribute. If no response parameters are needed, you "
394                    "could use an empty response parameter list, i.e., "
395                    "\"=> ()\".")
396
397  return method
398
399def _Interface(module, parsed_iface):
400  """
401  Args:
402    module: {mojom.Module} Module currently being constructed.
403    parsed_iface: {ast.Interface} Parsed interface.
404
405  Returns:
406    {mojom.Interface} AST interface.
407  """
408  interface = mojom.Interface(module=module)
409  interface.mojom_name = parsed_iface.mojom_name
410  interface.spec = 'x:' + module.mojom_namespace + '.' + interface.mojom_name
411  module.kinds[interface.spec] = interface
412  interface.enums = [
413    _Enum(module, enum, interface) for enum in
414    _ElemsOfType(parsed_iface.body, ast.Enum, parsed_iface.mojom_name)
415  ]
416  interface.constants = [
417      _Constant(module, constant, interface) for constant in
418      _ElemsOfType(parsed_iface.body, ast.Const, parsed_iface.mojom_name)
419  ]
420  # Stash methods parsed_iface here temporarily.
421  interface.methods_data = _ElemsOfType(
422      parsed_iface.body, ast.Method, parsed_iface.mojom_name)
423  interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)
424  return interface
425
426def _EnumField(module, enum, parsed_field, parent_kind):
427  """
428  Args:
429    module: {mojom.Module} Module currently being constructed.
430    enum: {mojom.Enum} Enum this field belongs to.
431    parsed_field: {ast.EnumValue} Parsed enum value.
432    parent_kind: {mojom.Kind} The enclosing type.
433
434  Returns:
435    {mojom.EnumField} AST enum field.
436  """
437  field = mojom.EnumField()
438  field.mojom_name = parsed_field.mojom_name
439  # TODO(mpcomplete): FixupExpression should be done in the second pass,
440  # so constants and enums can refer to each other.
441  # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or
442  # vice versa?
443  if parent_kind:
444    field.value = _FixupExpression(
445        module, parsed_field.value,
446        (module.mojom_namespace, parent_kind.mojom_name), enum)
447  else:
448    field.value = _FixupExpression(
449        module, parsed_field.value, (module.mojom_namespace, ), enum)
450  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
451  value = mojom.EnumValue(module, enum, field)
452  module.values[value.GetSpec()] = value
453  return field
454
455def _ResolveNumericEnumValues(enum_fields):
456  """
457  Given a reference to a list of mojom.EnumField, resolves and assigns their
458  values to EnumField.numeric_value.
459
460  Returns:
461    A tuple of the lowest and highest assigned enumerator value or None, None
462    if no enumerator values were assigned.
463  """
464
465  # map of <mojom_name> -> integral value
466  resolved_enum_values = {}
467  prev_value = -1
468  min_value = None
469  max_value = None
470  for field in enum_fields:
471    # This enum value is +1 the previous enum value (e.g: BEGIN).
472    if field.value is None:
473      prev_value += 1
474
475    # Integral value (e.g: BEGIN = -0x1).
476    elif type(field.value) is str:
477      prev_value = int(field.value, 0)
478
479    # Reference to a previous enum value (e.g: INIT = BEGIN).
480    elif type(field.value) is mojom.EnumValue:
481      prev_value = resolved_enum_values[field.value.mojom_name]
482    else:
483      raise Exception("Unresolved enum value.")
484
485    resolved_enum_values[field.mojom_name] = prev_value
486    field.numeric_value = prev_value
487    if min_value is None or prev_value < min_value:
488      min_value = prev_value
489    if max_value is None or prev_value > max_value:
490      max_value = prev_value
491
492  return min_value, max_value
493
494def _Enum(module, parsed_enum, parent_kind):
495  """
496  Args:
497    module: {mojom.Module} Module currently being constructed.
498    parsed_enum: {ast.Enum} Parsed enum.
499
500  Returns:
501    {mojom.Enum} AST enum.
502  """
503  enum = mojom.Enum(module=module)
504  enum.mojom_name = parsed_enum.mojom_name
505  enum.native_only = parsed_enum.enum_value_list is None
506  mojom_name = enum.mojom_name
507  if parent_kind:
508    mojom_name = parent_kind.mojom_name + '.' + mojom_name
509  enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
510  enum.parent_kind = parent_kind
511  enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
512  if not enum.native_only:
513    enum.fields = [
514        _EnumField(module, enum, field, parent_kind) for field in
515        parsed_enum.enum_value_list
516    ]
517    enum.min_value, enum.max_value = _ResolveNumericEnumValues(enum.fields)
518
519  module.kinds[enum.spec] = enum
520
521  # Enforce that a [Native] attribute is set to make native-only enum
522  # declarations more explicit.
523  if enum.native_only:
524    if not enum.attributes or not enum.attributes.get('Native', False):
525      raise Exception("Native-only enum declarations must include a " +
526                      "Native attribute.")
527
528  return enum
529
530def _Constant(module, parsed_const, parent_kind):
531  """
532  Args:
533    module: {mojom.Module} Module currently being constructed.
534    parsed_const: {ast.Const} Parsed constant.
535
536  Returns:
537    {mojom.Constant} AST constant.
538  """
539  constant = mojom.Constant()
540  constant.mojom_name = parsed_const.mojom_name
541  if parent_kind:
542    scope = (module.mojom_namespace, parent_kind.mojom_name)
543  else:
544    scope = (module.mojom_namespace, )
545  # TODO(mpcomplete): maybe we should only support POD kinds.
546  constant.kind = _Kind(module.kinds, _MapKind(parsed_const.typename), scope)
547  constant.parent_kind = parent_kind
548  constant.value = _FixupExpression(module, parsed_const.value, scope, None)
549
550  value = mojom.ConstantValue(module, parent_kind, constant)
551  module.values[value.GetSpec()] = value
552  return constant
553
554def _Module(tree, path, imports):
555  """
556  Args:
557    tree: {ast.Mojom} The parse tree.
558    path: {str} The path to the mojom file.
559    imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in
560        the import list, to already processed modules. Used to process imports.
561
562  Returns:
563    {mojom.Module} An AST for the mojom.
564  """
565  module = mojom.Module(path=path)
566  module.kinds = {}
567  for kind in mojom.PRIMITIVES:
568    module.kinds[kind.spec] = kind
569
570  module.values = {}
571
572  module.mojom_namespace = tree.module.mojom_namespace[1] if tree.module else ''
573  # Imports must come first, because they add to module.kinds which is used
574  # by by the others.
575  module.imports = [
576      _Import(module, imports[imp.import_filename])
577      for imp in tree.import_list]
578  if tree.module and tree.module.attribute_list:
579    assert isinstance(tree.module.attribute_list, ast.AttributeList)
580    # TODO(vtl): Check for duplicate keys here.
581    module.attributes = dict((attribute.key, attribute.value)
582                             for attribute in tree.module.attribute_list)
583
584  filename = os.path.basename(path)
585  # First pass collects kinds.
586  module.enums = [
587      _Enum(module, enum, None) for enum in
588      _ElemsOfType(tree.definition_list, ast.Enum, filename)
589  ]
590  module.structs = [
591      _Struct(module, struct) for struct in
592      _ElemsOfType(tree.definition_list, ast.Struct, filename)
593  ]
594  module.unions = [
595      _Union(module, union) for union in
596      _ElemsOfType(tree.definition_list, ast.Union, filename)
597  ]
598  module.interfaces = [
599      _Interface(module, interface) for interface in
600      _ElemsOfType(tree.definition_list, ast.Interface, filename)
601  ]
602  module.constants = [
603      _Constant(module, constant, None) for constant in
604      _ElemsOfType(tree.definition_list, ast.Const, filename)
605  ]
606
607  # Second pass expands fields and methods. This allows fields and parameters
608  # to refer to kinds defined anywhere in the mojom.
609  for struct in module.structs:
610    struct.fields = [_StructField(module, field, struct) for field in struct.fields_data]
611    del struct.fields_data
612  for union in module.unions:
613    union.fields = [_UnionField(module, field, union) for field in union.fields_data]
614    del union.fields_data
615  for interface in module.interfaces:
616    interface.methods = [_Method(module, method, interface) for method in interface.methods_data]
617    del interface.methods_data
618
619  return module
620
621def OrderedModule(tree, path, imports):
622  """Convert parse tree to AST module.
623
624  Args:
625    tree: {ast.Mojom} The parse tree.
626    path: {str} The path to the mojom file.
627    imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in
628        the import list, to already processed modules. Used to process imports.
629
630  Returns:
631    {mojom.Module} An AST for the mojom.
632  """
633  module = _Module(tree, path, imports)
634  return module
635