1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5# TODO(vtl): "data" is a pretty vague name. Rename it? 6 7import copy 8 9import module as mojom 10 11# This module provides a mechanism to turn mojom Modules to dictionaries and 12# back again. This can be used to persist a mojom Module created progromatically 13# or to read a dictionary from code or a file. 14# Example: 15# test_dict = { 16# 'name': 'test', 17# 'namespace': 'testspace', 18# 'structs': [{ 19# 'name': 'teststruct', 20# 'fields': [ 21# {'name': 'testfield1', 'kind': 'i32'}, 22# {'name': 'testfield2', 'kind': 'a:i32', 'ordinal': 42}]}], 23# 'interfaces': [{ 24# 'name': 'Server', 25# 'methods': [{ 26# 'name': 'Foo', 27# 'parameters': [{ 28# 'name': 'foo', 'kind': 'i32'}, 29# {'name': 'bar', 'kind': 'a:x:teststruct'}], 30# 'ordinal': 42}]}] 31# } 32# test_module = data.ModuleFromData(test_dict) 33 34# Used to create a subclass of str that supports sorting by index, to make 35# pretty printing maintain the order. 36def istr(index, string): 37 class IndexedString(str): 38 def __lt__(self, other): 39 return self.__index__ < other.__index__ 40 41 rv = IndexedString(string) 42 rv.__index__ = index 43 return rv 44 45def AddOptional(dictionary, key, value): 46 if value is not None: 47 dictionary[key] = value; 48 49builtin_values = frozenset([ 50 "double.INFINITY", 51 "double.NEGATIVE_INFINITY", 52 "double.NAN", 53 "float.INFINITY", 54 "float.NEGATIVE_INFINITY", 55 "float.NAN"]) 56 57def IsBuiltinValue(value): 58 return value in builtin_values 59 60def LookupKind(kinds, spec, scope): 61 """Tries to find which Kind a spec refers to, given the scope in which its 62 referenced. Starts checking from the narrowest scope to most general. For 63 example, given a struct field like 64 Foo.Bar x; 65 Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner 66 type 'Bar' in the struct 'Foo' in the current namespace. 67 68 |scope| is a tuple that looks like (namespace, struct/interface), referring 69 to the location where the type is referenced.""" 70 if spec.startswith('x:'): 71 name = spec[2:] 72 for i in xrange(len(scope), -1, -1): 73 test_spec = 'x:' 74 if i > 0: 75 test_spec += '.'.join(scope[:i]) + '.' 76 test_spec += name 77 kind = kinds.get(test_spec) 78 if kind: 79 return kind 80 81 return kinds.get(spec) 82 83def LookupValue(values, name, scope, kind): 84 """Like LookupKind, but for constant values.""" 85 # If the type is an enum, the value can be specified as a qualified name, in 86 # which case the form EnumName.ENUM_VALUE must be used. We use the presence 87 # of a '.' in the requested name to identify this. Otherwise, we prepend the 88 # enum name. 89 if isinstance(kind, mojom.Enum) and '.' not in name: 90 name = '%s.%s' % (kind.spec.split(':', 1)[1], name) 91 for i in reversed(xrange(len(scope) + 1)): 92 test_spec = '.'.join(scope[:i]) 93 if test_spec: 94 test_spec += '.' 95 test_spec += name 96 value = values.get(test_spec) 97 if value: 98 return value 99 100 return values.get(name) 101 102def FixupExpression(module, value, scope, kind): 103 """Translates an IDENTIFIER into a built-in value or structured NamedValue 104 object.""" 105 if isinstance(value, tuple) and value[0] == 'IDENTIFIER': 106 # Allow user defined values to shadow builtins. 107 result = LookupValue(module.values, value[1], scope, kind) 108 if result: 109 if isinstance(result, tuple): 110 raise Exception('Unable to resolve expression: %r' % value[1]) 111 return result 112 if IsBuiltinValue(value[1]): 113 return mojom.BuiltinValue(value[1]) 114 return value 115 116def KindToData(kind): 117 return kind.spec 118 119def KindFromData(kinds, data, scope): 120 kind = LookupKind(kinds, data, scope) 121 if kind: 122 return kind 123 124 if data.startswith('?'): 125 kind = KindFromData(kinds, data[1:], scope).MakeNullableKind() 126 elif data.startswith('a:'): 127 kind = mojom.Array(KindFromData(kinds, data[2:], scope)) 128 elif data.startswith('asso:'): 129 inner_kind = KindFromData(kinds, data[5:], scope) 130 if isinstance(inner_kind, mojom.InterfaceRequest): 131 kind = mojom.AssociatedInterfaceRequest(inner_kind) 132 else: 133 kind = mojom.AssociatedInterface(inner_kind) 134 elif data.startswith('a'): 135 colon = data.find(':') 136 length = int(data[1:colon]) 137 kind = mojom.Array(KindFromData(kinds, data[colon+1:], scope), length) 138 elif data.startswith('r:'): 139 kind = mojom.InterfaceRequest(KindFromData(kinds, data[2:], scope)) 140 elif data.startswith('m['): 141 # Isolate the two types from their brackets. 142 143 # It is not allowed to use map as key, so there shouldn't be nested ']'s 144 # inside the key type spec. 145 key_end = data.find(']') 146 assert key_end != -1 and key_end < len(data) - 1 147 assert data[key_end+1] == '[' and data[-1] == ']' 148 149 first_kind = data[2:key_end] 150 second_kind = data[key_end+2:-1] 151 152 kind = mojom.Map(KindFromData(kinds, first_kind, scope), 153 KindFromData(kinds, second_kind, scope)) 154 else: 155 kind = mojom.Kind(data) 156 157 kinds[data] = kind 158 return kind 159 160def KindFromImport(original_kind, imported_from): 161 """Used with 'import module' - clones the kind imported from the given 162 module's namespace. Only used with Structs, Unions, Interfaces and Enums.""" 163 kind = copy.copy(original_kind) 164 # |shared_definition| is used to store various properties (see 165 # |AddSharedProperty()| in module.py), including |imported_from|. We don't 166 # want the copy to share these with the original, so copy it if necessary. 167 if hasattr(original_kind, 'shared_definition'): 168 kind.shared_definition = copy.copy(original_kind.shared_definition) 169 kind.imported_from = imported_from 170 return kind 171 172def ImportFromData(module, data): 173 import_module = data['module'] 174 175 import_item = {} 176 import_item['module_name'] = import_module.name 177 import_item['namespace'] = import_module.namespace 178 import_item['module'] = import_module 179 180 # Copy the struct kinds from our imports into the current module. 181 importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface) 182 for kind in import_module.kinds.itervalues(): 183 if (isinstance(kind, importable_kinds) and 184 kind.imported_from is None): 185 kind = KindFromImport(kind, import_item) 186 module.kinds[kind.spec] = kind 187 # Ditto for values. 188 for value in import_module.values.itervalues(): 189 if value.imported_from is None: 190 # Values don't have shared definitions (since they're not nullable), so no 191 # need to do anything special. 192 value = copy.copy(value) 193 value.imported_from = import_item 194 module.values[value.GetSpec()] = value 195 196 return import_item 197 198def StructToData(struct): 199 data = { 200 istr(0, 'name'): struct.name, 201 istr(1, 'fields'): map(FieldToData, struct.fields), 202 # TODO(yzshen): EnumToData() and ConstantToData() are missing. 203 istr(2, 'enums'): [], 204 istr(3, 'constants'): [] 205 } 206 AddOptional(data, istr(4, 'attributes'), struct.attributes) 207 return data 208 209def StructFromData(module, data): 210 struct = mojom.Struct(module=module) 211 struct.name = data['name'] 212 struct.native_only = data['native_only'] 213 struct.spec = 'x:' + module.namespace + '.' + struct.name 214 module.kinds[struct.spec] = struct 215 if struct.native_only: 216 struct.enums = [] 217 struct.constants = [] 218 struct.fields_data = [] 219 else: 220 struct.enums = map(lambda enum: 221 EnumFromData(module, enum, struct), data['enums']) 222 struct.constants = map(lambda constant: 223 ConstantFromData(module, constant, struct), data['constants']) 224 # Stash fields data here temporarily. 225 struct.fields_data = data['fields'] 226 struct.attributes = data.get('attributes') 227 228 # Enforce that a [Native] attribute is set to make native-only struct 229 # declarations more explicit. 230 if struct.native_only: 231 if not struct.attributes or not struct.attributes.get('Native', False): 232 raise Exception("Native-only struct declarations must include a " + 233 "Native attribute.") 234 235 return struct 236 237def UnionToData(union): 238 data = { 239 istr(0, 'name'): union.name, 240 istr(1, 'fields'): map(FieldToData, union.fields) 241 } 242 AddOptional(data, istr(2, 'attributes'), union.attributes) 243 return data 244 245def UnionFromData(module, data): 246 union = mojom.Union(module=module) 247 union.name = data['name'] 248 union.spec = 'x:' + module.namespace + '.' + union.name 249 module.kinds[union.spec] = union 250 # Stash fields data here temporarily. 251 union.fields_data = data['fields'] 252 union.attributes = data.get('attributes') 253 return union 254 255def FieldToData(field): 256 data = { 257 istr(0, 'name'): field.name, 258 istr(1, 'kind'): KindToData(field.kind) 259 } 260 AddOptional(data, istr(2, 'ordinal'), field.ordinal) 261 AddOptional(data, istr(3, 'default'), field.default) 262 AddOptional(data, istr(4, 'attributes'), field.attributes) 263 return data 264 265def StructFieldFromData(module, data, struct): 266 field = mojom.StructField() 267 PopulateField(field, module, data, struct) 268 return field 269 270def UnionFieldFromData(module, data, union): 271 field = mojom.UnionField() 272 PopulateField(field, module, data, union) 273 return field 274 275def PopulateField(field, module, data, parent): 276 field.name = data['name'] 277 field.kind = KindFromData( 278 module.kinds, data['kind'], (module.namespace, parent.name)) 279 field.ordinal = data.get('ordinal') 280 field.default = FixupExpression( 281 module, data.get('default'), (module.namespace, parent.name), field.kind) 282 field.attributes = data.get('attributes') 283 284def ParameterToData(parameter): 285 data = { 286 istr(0, 'name'): parameter.name, 287 istr(1, 'kind'): parameter.kind.spec 288 } 289 AddOptional(data, istr(2, 'ordinal'), parameter.ordinal) 290 AddOptional(data, istr(3, 'default'), parameter.default) 291 AddOptional(data, istr(4, 'attributes'), parameter.attributes) 292 return data 293 294def ParameterFromData(module, data, interface): 295 parameter = mojom.Parameter() 296 parameter.name = data['name'] 297 parameter.kind = KindFromData( 298 module.kinds, data['kind'], (module.namespace, interface.name)) 299 parameter.ordinal = data.get('ordinal') 300 parameter.default = data.get('default') 301 parameter.attributes = data.get('attributes') 302 return parameter 303 304def MethodToData(method): 305 data = { 306 istr(0, 'name'): method.name, 307 istr(1, 'parameters'): map(ParameterToData, method.parameters) 308 } 309 if method.response_parameters is not None: 310 data[istr(2, 'response_parameters')] = map( 311 ParameterToData, method.response_parameters) 312 AddOptional(data, istr(3, 'ordinal'), method.ordinal) 313 AddOptional(data, istr(4, 'attributes'), method.attributes) 314 return data 315 316def MethodFromData(module, data, interface): 317 method = mojom.Method(interface, data['name'], ordinal=data.get('ordinal')) 318 method.parameters = map(lambda parameter: 319 ParameterFromData(module, parameter, interface), data['parameters']) 320 if data.has_key('response_parameters'): 321 method.response_parameters = map( 322 lambda parameter: ParameterFromData(module, parameter, interface), 323 data['response_parameters']) 324 method.attributes = data.get('attributes') 325 326 # Enforce that only methods with response can have a [Sync] attribute. 327 if method.sync and method.response_parameters is None: 328 raise Exception("Only methods with response can include a [Sync] " 329 "attribute. If no response parameters are needed, you " 330 "could use an empty response parameter list, i.e., " 331 "\"=> ()\".") 332 333 return method 334 335def InterfaceToData(interface): 336 data = { 337 istr(0, 'name'): interface.name, 338 istr(1, 'methods'): map(MethodToData, interface.methods), 339 # TODO(yzshen): EnumToData() and ConstantToData() are missing. 340 istr(2, 'enums'): [], 341 istr(3, 'constants'): [] 342 } 343 AddOptional(data, istr(4, 'attributes'), interface.attributes) 344 return data 345 346def InterfaceFromData(module, data): 347 interface = mojom.Interface(module=module) 348 interface.name = data['name'] 349 interface.spec = 'x:' + module.namespace + '.' + interface.name 350 module.kinds[interface.spec] = interface 351 interface.enums = map(lambda enum: 352 EnumFromData(module, enum, interface), data['enums']) 353 interface.constants = map(lambda constant: 354 ConstantFromData(module, constant, interface), data['constants']) 355 # Stash methods data here temporarily. 356 interface.methods_data = data['methods'] 357 interface.attributes = data.get('attributes') 358 return interface 359 360def EnumFieldFromData(module, enum, data, parent_kind): 361 field = mojom.EnumField() 362 field.name = data['name'] 363 # TODO(mpcomplete): FixupExpression should be done in the second pass, 364 # so constants and enums can refer to each other. 365 # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or 366 # vice versa? 367 if parent_kind: 368 field.value = FixupExpression( 369 module, data.get('value'), (module.namespace, parent_kind.name), enum) 370 else: 371 field.value = FixupExpression( 372 module, data.get('value'), (module.namespace, ), enum) 373 field.attributes = data.get('attributes') 374 value = mojom.EnumValue(module, enum, field) 375 module.values[value.GetSpec()] = value 376 return field 377 378def ResolveNumericEnumValues(enum_fields): 379 """ 380 Given a reference to a list of mojom.EnumField, resolves and assigns their 381 values to EnumField.numeric_value. 382 """ 383 384 # map of <name> -> integral value 385 resolved_enum_values = {} 386 prev_value = -1 387 for field in enum_fields: 388 # This enum value is +1 the previous enum value (e.g: BEGIN). 389 if field.value is None: 390 prev_value += 1 391 392 # Integral value (e.g: BEGIN = -0x1). 393 elif type(field.value) is str: 394 prev_value = int(field.value, 0) 395 396 # Reference to a previous enum value (e.g: INIT = BEGIN). 397 elif type(field.value) is mojom.EnumValue: 398 prev_value = resolved_enum_values[field.value.name] 399 else: 400 raise Exception("Unresolved enum value.") 401 402 resolved_enum_values[field.name] = prev_value 403 field.numeric_value = prev_value 404 405def EnumFromData(module, data, parent_kind): 406 enum = mojom.Enum(module=module) 407 enum.name = data['name'] 408 enum.native_only = data['native_only'] 409 name = enum.name 410 if parent_kind: 411 name = parent_kind.name + '.' + name 412 enum.spec = 'x:%s.%s' % (module.namespace, name) 413 enum.parent_kind = parent_kind 414 enum.attributes = data.get('attributes') 415 if enum.native_only: 416 enum.fields = [] 417 else: 418 enum.fields = map( 419 lambda field: EnumFieldFromData(module, enum, field, parent_kind), 420 data['fields']) 421 ResolveNumericEnumValues(enum.fields) 422 423 module.kinds[enum.spec] = enum 424 425 # Enforce that a [Native] attribute is set to make native-only enum 426 # declarations more explicit. 427 if enum.native_only: 428 if not enum.attributes or not enum.attributes.get('Native', False): 429 raise Exception("Native-only enum declarations must include a " + 430 "Native attribute.") 431 432 return enum 433 434def ConstantFromData(module, data, parent_kind): 435 constant = mojom.Constant() 436 constant.name = data['name'] 437 if parent_kind: 438 scope = (module.namespace, parent_kind.name) 439 else: 440 scope = (module.namespace, ) 441 # TODO(mpcomplete): maybe we should only support POD kinds. 442 constant.kind = KindFromData(module.kinds, data['kind'], scope) 443 constant.parent_kind = parent_kind 444 constant.value = FixupExpression(module, data.get('value'), scope, None) 445 446 value = mojom.ConstantValue(module, parent_kind, constant) 447 module.values[value.GetSpec()] = value 448 return constant 449 450def ModuleToData(module): 451 data = { 452 istr(0, 'name'): module.name, 453 istr(1, 'namespace'): module.namespace, 454 # TODO(yzshen): Imports information is missing. 455 istr(2, 'imports'): [], 456 istr(3, 'structs'): map(StructToData, module.structs), 457 istr(4, 'unions'): map(UnionToData, module.unions), 458 istr(5, 'interfaces'): map(InterfaceToData, module.interfaces), 459 # TODO(yzshen): EnumToData() and ConstantToData() are missing. 460 istr(6, 'enums'): [], 461 istr(7, 'constants'): [] 462 } 463 AddOptional(data, istr(8, 'attributes'), module.attributes) 464 return data 465 466def ModuleFromData(data): 467 module = mojom.Module() 468 module.kinds = {} 469 for kind in mojom.PRIMITIVES: 470 module.kinds[kind.spec] = kind 471 472 module.values = {} 473 474 module.name = data['name'] 475 module.namespace = data['namespace'] 476 # Imports must come first, because they add to module.kinds which is used 477 # by by the others. 478 module.imports = map( 479 lambda import_data: ImportFromData(module, import_data), 480 data['imports']) 481 module.attributes = data.get('attributes') 482 483 # First pass collects kinds. 484 module.enums = map( 485 lambda enum: EnumFromData(module, enum, None), data['enums']) 486 module.structs = map( 487 lambda struct: StructFromData(module, struct), data['structs']) 488 module.unions = map( 489 lambda union: UnionFromData(module, union), data.get('unions', [])) 490 module.interfaces = map( 491 lambda interface: InterfaceFromData(module, interface), 492 data['interfaces']) 493 module.constants = map( 494 lambda constant: ConstantFromData(module, constant, None), 495 data['constants']) 496 497 # Second pass expands fields and methods. This allows fields and parameters 498 # to refer to kinds defined anywhere in the mojom. 499 for struct in module.structs: 500 struct.fields = map(lambda field: 501 StructFieldFromData(module, field, struct), struct.fields_data) 502 del struct.fields_data 503 for union in module.unions: 504 union.fields = map(lambda field: 505 UnionFieldFromData(module, field, union), union.fields_data) 506 del union.fields_data 507 for interface in module.interfaces: 508 interface.methods = map(lambda method: 509 MethodFromData(module, method, interface), interface.methods_data) 510 del interface.methods_data 511 512 return module 513 514def OrderedModuleFromData(data): 515 """Convert Mojom IR to a module. 516 517 Args: 518 data: The Mojom IR as a dict. 519 520 Returns: 521 A mojom.generate.module.Module object. 522 """ 523 module = ModuleFromData(data) 524 for interface in module.interfaces: 525 next_ordinal = 0 526 for method in interface.methods: 527 if method.ordinal is None: 528 method.ordinal = next_ordinal 529 next_ordinal = method.ordinal + 1 530 return module 531