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