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