| # Copyright 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # TODO(vtl): "data" is a pretty vague name. Rename it? |
| |
| import copy |
| |
| import module as mojom |
| |
| # This module provides a mechanism to turn mojom Modules to dictionaries and |
| # back again. This can be used to persist a mojom Module created progromatically |
| # or to read a dictionary from code or a file. |
| # Example: |
| # test_dict = { |
| # 'name': 'test', |
| # 'namespace': 'testspace', |
| # 'structs': [{ |
| # 'name': 'teststruct', |
| # 'fields': [ |
| # {'name': 'testfield1', 'kind': 'i32'}, |
| # {'name': 'testfield2', 'kind': 'a:i32', 'ordinal': 42}]}], |
| # 'interfaces': [{ |
| # 'name': 'Server', |
| # 'methods': [{ |
| # 'name': 'Foo', |
| # 'parameters': [{ |
| # 'name': 'foo', 'kind': 'i32'}, |
| # {'name': 'bar', 'kind': 'a:x:teststruct'}], |
| # 'ordinal': 42}]}] |
| # } |
| # test_module = data.ModuleFromData(test_dict) |
| |
| # Used to create a subclass of str that supports sorting by index, to make |
| # pretty printing maintain the order. |
| def istr(index, string): |
| class IndexedString(str): |
| def __lt__(self, other): |
| return self.__index__ < other.__index__ |
| |
| rv = IndexedString(string) |
| rv.__index__ = index |
| return rv |
| |
| def AddOptional(dictionary, key, value): |
| if value is not None: |
| dictionary[key] = value; |
| |
| builtin_values = frozenset([ |
| "double.INFINITY", |
| "double.NEGATIVE_INFINITY", |
| "double.NAN", |
| "float.INFINITY", |
| "float.NEGATIVE_INFINITY", |
| "float.NAN"]) |
| |
| def IsBuiltinValue(value): |
| return value in builtin_values |
| |
| def LookupKind(kinds, spec, scope): |
| """Tries to find which Kind a spec refers to, given the scope in which its |
| referenced. Starts checking from the narrowest scope to most general. For |
| example, given a struct field like |
| Foo.Bar x; |
| Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner |
| type 'Bar' in the struct 'Foo' in the current namespace. |
| |
| |scope| is a tuple that looks like (namespace, struct/interface), referring |
| to the location where the type is referenced.""" |
| if spec.startswith('x:'): |
| name = spec[2:] |
| for i in xrange(len(scope), -1, -1): |
| test_spec = 'x:' |
| if i > 0: |
| test_spec += '.'.join(scope[:i]) + '.' |
| test_spec += name |
| kind = kinds.get(test_spec) |
| if kind: |
| return kind |
| |
| return kinds.get(spec) |
| |
| def LookupValue(values, name, scope, kind): |
| """Like LookupKind, but for constant values.""" |
| # If the type is an enum, the value can be specified as a qualified name, in |
| # which case the form EnumName.ENUM_VALUE must be used. We use the presence |
| # of a '.' in the requested name to identify this. Otherwise, we prepend the |
| # enum name. |
| if isinstance(kind, mojom.Enum) and '.' not in name: |
| name = '%s.%s' % (kind.spec.split(':', 1)[1], name) |
| for i in reversed(xrange(len(scope) + 1)): |
| test_spec = '.'.join(scope[:i]) |
| if test_spec: |
| test_spec += '.' |
| test_spec += name |
| value = values.get(test_spec) |
| if value: |
| return value |
| |
| return values.get(name) |
| |
| def FixupExpression(module, value, scope, kind): |
| """Translates an IDENTIFIER into a built-in value or structured NamedValue |
| object.""" |
| if isinstance(value, tuple) and value[0] == 'IDENTIFIER': |
| # Allow user defined values to shadow builtins. |
| result = LookupValue(module.values, value[1], scope, kind) |
| if result: |
| if isinstance(result, tuple): |
| raise Exception('Unable to resolve expression: %r' % value[1]) |
| return result |
| if IsBuiltinValue(value[1]): |
| return mojom.BuiltinValue(value[1]) |
| return value |
| |
| def KindToData(kind): |
| return kind.spec |
| |
| def MakeNullableKind(kind): |
| if isinstance(kind, mojom.ReferenceKind): |
| return kind.MakeNullableKind() |
| raise Exception('kind is not nullable: %s' % kind.spec) |
| |
| def KindFromData(kinds, data, scope): |
| kind = LookupKind(kinds, data, scope) |
| if kind: |
| return kind |
| |
| if data.startswith('?'): |
| kind = MakeNullableKind(KindFromData(kinds, data[1:], scope)) |
| elif data.startswith('a:'): |
| kind = mojom.Array(KindFromData(kinds, data[2:], scope)) |
| elif data.startswith('a'): |
| colon = data.find(':') |
| length = int(data[1:colon]) |
| kind = mojom.Array(KindFromData(kinds, data[colon+1:], scope), length) |
| elif data.startswith('r:'): |
| kind = mojom.InterfaceRequest(KindFromData(kinds, data[2:], scope)) |
| elif data.startswith('m['): |
| # Isolate the two types from their brackets. |
| |
| # It is not allowed to use map as key, so there shouldn't be nested ']'s |
| # inside the key type spec. |
| key_end = data.find(']') |
| assert key_end != -1 and key_end < len(data) - 1 |
| assert data[key_end+1] == '[' and data[-1] == ']' |
| |
| first_kind = data[2:key_end] |
| second_kind = data[key_end+2:-1] |
| |
| kind = mojom.Map(KindFromData(kinds, first_kind, scope), |
| KindFromData(kinds, second_kind, scope)) |
| else: |
| kind = mojom.Kind(data) |
| |
| kinds[data] = kind |
| return kind |
| |
| def KindFromImport(original_kind, imported_from): |
| """Used with 'import module' - clones the kind imported from the given |
| module's namespace. Only used with Structs, Unions, Interfaces and Enums.""" |
| kind = copy.copy(original_kind) |
| # |shared_definition| is used to store various properties (see |
| # |AddSharedProperty()| in module.py), including |imported_from|. We don't |
| # want the copy to share these with the original, so copy it if necessary. |
| if hasattr(original_kind, 'shared_definition'): |
| kind.shared_definition = copy.copy(original_kind.shared_definition) |
| kind.imported_from = imported_from |
| return kind |
| |
| def ComputeTransitiveImports(module): |
| """Compute a module's transitive imports.""" |
| to_process = {imp['module'].path: imp for imp in module.imports} |
| processed = set() |
| transitive_imports = [] |
| |
| while to_process: |
| _, imp = to_process.popitem() |
| transitive_imports.append(imp) |
| processed.add(imp['module'].path) |
| |
| for sub_imp in imp['module'].imports: |
| if sub_imp['module'].path not in processed: |
| to_process[sub_imp['module'].path] = sub_imp |
| |
| return transitive_imports |
| |
| def ImportFromData(module, data): |
| """Adds to the pool of available kinds in the current module, the list of |
| kinds provided by the imported module. Also creates a dict describing |
| the imported module.""" |
| import_module = data['module'] |
| |
| import_item = {} |
| import_item['module_name'] = import_module.name |
| import_item['namespace'] = import_module.namespace |
| |
| # Create a new module that contains only the needed values to prevent |
| # accidentally introducing dependencies on currently-unneeded data. |
| new_imported_module = mojom.Module( |
| import_module.name, import_module.namespace, import_module.attributes) |
| new_imported_module.path = import_module.path |
| import_item['module'] = new_imported_module |
| |
| # Copy the struct kinds from our imports into the current module. |
| importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface) |
| for kind in import_module.kinds.itervalues(): |
| if (isinstance(kind, importable_kinds) and |
| kind.imported_from is None): |
| kind = KindFromImport(kind, import_item) |
| module.kinds[kind.spec] = kind |
| # Ditto for values. |
| for value in import_module.values.itervalues(): |
| if value.imported_from is None: |
| # Values don't have shared definitions (since they're not nullable), so no |
| # need to do anything special. |
| value = copy.copy(value) |
| value.imported_from = import_item |
| module.values[value.GetSpec()] = value |
| |
| return import_item |
| |
| def StructToData(struct): |
| data = { |
| istr(0, 'name'): struct.name, |
| istr(1, 'fields'): map(FieldToData, struct.fields), |
| # TODO(yzshen): EnumToData() and ConstantToData() are missing. |
| istr(2, 'enums'): [], |
| istr(3, 'constants'): [] |
| } |
| AddOptional(data, istr(4, 'attributes'), struct.attributes) |
| return data |
| |
| def StructFromData(module, data): |
| struct = mojom.Struct(module=module) |
| struct.name = data['name'] |
| struct.spec = 'x:' + module.namespace + '.' + struct.name |
| module.kinds[struct.spec] = struct |
| struct.enums = map(lambda enum: |
| EnumFromData(module, enum, struct), data['enums']) |
| struct.constants = map(lambda constant: |
| ConstantFromData(module, constant, struct), data['constants']) |
| # Stash fields data here temporarily. |
| struct.fields_data = data['fields'] |
| struct.attributes = data.get('attributes') |
| return struct |
| |
| def UnionToData(union): |
| data = { |
| istr(0, 'name'): union.name, |
| istr(1, 'fields'): map(FieldToData, union.fields) |
| } |
| AddOptional(data, istr(2, 'attributes'), union.attributes) |
| return data |
| |
| def UnionFromData(module, data): |
| union = mojom.Union(module=module) |
| union.name = data['name'] |
| union.spec = 'x:' + module.namespace + '.' + union.name |
| module.kinds[union.spec] = union |
| # Stash fields data here temporarily. |
| union.fields_data = data['fields'] |
| union.attributes = data.get('attributes') |
| return union |
| |
| def FieldToData(field): |
| data = { |
| istr(0, 'name'): field.name, |
| istr(1, 'kind'): KindToData(field.kind) |
| } |
| AddOptional(data, istr(2, 'ordinal'), field.ordinal) |
| AddOptional(data, istr(3, 'default'), field.default) |
| AddOptional(data, istr(4, 'attributes'), field.attributes) |
| return data |
| |
| def StructFieldFromData(module, data, struct): |
| field = mojom.StructField() |
| PopulateField(field, module, data, struct) |
| return field |
| |
| def UnionFieldFromData(module, data, union): |
| field = mojom.UnionField() |
| PopulateField(field, module, data, union) |
| return field |
| |
| def PopulateField(field, module, data, parent): |
| field.name = data['name'] |
| field.kind = KindFromData( |
| module.kinds, data['kind'], (module.namespace, parent.name)) |
| field.ordinal = data.get('ordinal') |
| field.default = FixupExpression( |
| module, data.get('default'), (module.namespace, parent.name), field.kind) |
| field.attributes = data.get('attributes') |
| |
| def ParameterToData(parameter): |
| data = { |
| istr(0, 'name'): parameter.name, |
| istr(1, 'kind'): parameter.kind.spec |
| } |
| AddOptional(data, istr(2, 'ordinal'), parameter.ordinal) |
| AddOptional(data, istr(3, 'default'), parameter.default) |
| AddOptional(data, istr(4, 'attributes'), parameter.attributes) |
| return data |
| |
| def ParameterFromData(module, data, interface): |
| parameter = mojom.Parameter() |
| parameter.name = data['name'] |
| parameter.kind = KindFromData( |
| module.kinds, data['kind'], (module.namespace, interface.name)) |
| parameter.ordinal = data.get('ordinal') |
| parameter.default = data.get('default') |
| parameter.attributes = data.get('attributes') |
| return parameter |
| |
| def MethodToData(method): |
| data = { |
| istr(0, 'name'): method.name, |
| istr(1, 'parameters'): map(ParameterToData, method.parameters) |
| } |
| if method.response_parameters is not None: |
| data[istr(2, 'response_parameters')] = map( |
| ParameterToData, method.response_parameters) |
| AddOptional(data, istr(3, 'ordinal'), method.ordinal) |
| AddOptional(data, istr(4, 'attributes'), method.attributes) |
| return data |
| |
| def MethodFromData(module, data, interface): |
| method = mojom.Method(interface, data['name'], ordinal=data.get('ordinal')) |
| method.parameters = map(lambda parameter: |
| ParameterFromData(module, parameter, interface), data['parameters']) |
| if data.has_key('response_parameters'): |
| method.response_parameters = map( |
| lambda parameter: ParameterFromData(module, parameter, interface), |
| data['response_parameters']) |
| method.attributes = data.get('attributes') |
| return method |
| |
| def InterfaceToData(interface): |
| data = { |
| istr(0, 'name'): interface.name, |
| istr(1, 'methods'): map(MethodToData, interface.methods), |
| # TODO(yzshen): EnumToData() and ConstantToData() are missing. |
| istr(2, 'enums'): [], |
| istr(3, 'constants'): [] |
| } |
| AddOptional(data, istr(4, 'attributes'), interface.attributes) |
| return data |
| |
| def InterfaceFromData(module, data): |
| interface = mojom.Interface(module=module) |
| interface.name = data['name'] |
| interface.spec = 'x:' + module.namespace + '.' + interface.name |
| module.kinds[interface.spec] = interface |
| interface.enums = map(lambda enum: |
| EnumFromData(module, enum, interface), data['enums']) |
| interface.constants = map(lambda constant: |
| ConstantFromData(module, constant, interface), data['constants']) |
| # Stash methods data here temporarily. |
| interface.methods_data = data['methods'] |
| interface.attributes = data.get('attributes') |
| interface.service_name = None |
| if interface.attributes: |
| interface.service_name = interface.attributes.get('ServiceName') |
| return interface |
| |
| def EnumFieldFromData(module, enum, data, parent_kind): |
| field = mojom.EnumField() |
| field.name = data['name'] |
| # TODO(mpcomplete): FixupExpression should be done in the second pass, |
| # so constants and enums can refer to each other. |
| # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or |
| # vice versa? |
| if parent_kind: |
| field.value = FixupExpression( |
| module, data.get('value'), (module.namespace, parent_kind.name), enum) |
| else: |
| field.value = FixupExpression( |
| module, data.get('value'), (module.namespace, ), enum) |
| field.attributes = data.get('attributes') |
| value = mojom.EnumValue(module, enum, field) |
| module.values[value.GetSpec()] = value |
| return field |
| |
| def ResolveNumericEnumValues(enum_fields): |
| """ |
| Given a reference to a list of mojom.EnumField, resolves and assigns their |
| values to EnumField.numeric_value. |
| """ |
| |
| # map of <name> -> integral value |
| resolved_enum_values = {} |
| prev_value = -1 |
| for field in enum_fields: |
| # This enum value is +1 the previous enum value (e.g: BEGIN). |
| if field.value is None: |
| prev_value += 1 |
| |
| # Integral value (e.g: BEGIN = -0x1). |
| elif type(field.value) is str: |
| prev_value = int(field.value, 0) |
| |
| # Reference to a previous enum value (e.g: INIT = BEGIN). |
| elif type(field.value) is mojom.EnumValue: |
| prev_value = resolved_enum_values[field.value.name] |
| else: |
| raise Exception("Unresolved enum value.") |
| |
| resolved_enum_values[field.name] = prev_value |
| field.numeric_value = prev_value |
| |
| def EnumFromData(module, data, parent_kind): |
| enum = mojom.Enum(module=module) |
| enum.name = data['name'] |
| name = enum.name |
| if parent_kind: |
| name = parent_kind.name + '.' + name |
| enum.spec = 'x:%s.%s' % (module.namespace, name) |
| enum.parent_kind = parent_kind |
| enum.fields = map( |
| lambda field: EnumFieldFromData(module, enum, field, parent_kind), |
| data['fields']) |
| enum.attributes = data.get('attributes') |
| ResolveNumericEnumValues(enum.fields) |
| |
| module.kinds[enum.spec] = enum |
| return enum |
| |
| def ConstantFromData(module, data, parent_kind): |
| constant = mojom.Constant() |
| constant.name = data['name'] |
| if parent_kind: |
| scope = (module.namespace, parent_kind.name) |
| else: |
| scope = (module.namespace, ) |
| # TODO(mpcomplete): maybe we should only support POD kinds. |
| constant.kind = KindFromData(module.kinds, data['kind'], scope) |
| constant.parent_kind = parent_kind |
| constant.value = FixupExpression(module, data.get('value'), scope, None) |
| |
| value = mojom.ConstantValue(module, parent_kind, constant) |
| module.values[value.GetSpec()] = value |
| return constant |
| |
| def ModuleToData(module): |
| data = { |
| istr(0, 'name'): module.name, |
| istr(1, 'namespace'): module.namespace, |
| # TODO(yzshen): Imports information is missing. |
| istr(2, 'imports'): [], |
| istr(3, 'structs'): map(StructToData, module.structs), |
| istr(4, 'unions'): map(UnionToData, module.unions), |
| istr(5, 'interfaces'): map(InterfaceToData, module.interfaces), |
| # TODO(yzshen): EnumToData() and ConstantToData() are missing. |
| istr(6, 'enums'): [], |
| istr(7, 'constants'): [] |
| } |
| AddOptional(data, istr(8, 'attributes'), module.attributes) |
| return data |
| |
| def ModuleFromData(data): |
| module = mojom.Module() |
| module.kinds = {} |
| for kind in mojom.PRIMITIVES: |
| module.kinds[kind.spec] = kind |
| |
| module.values = {} |
| |
| module.name = data['name'] |
| module.namespace = data['namespace'] |
| # Imports must come first, because they add to module.kinds which is used |
| # by by the others. |
| module.imports = map( |
| lambda import_data: ImportFromData(module, import_data), |
| data['imports']) |
| module.transitive_imports = ComputeTransitiveImports(module) |
| module.attributes = data.get('attributes') |
| |
| # First pass collects kinds. |
| module.enums = map( |
| lambda enum: EnumFromData(module, enum, None), data['enums']) |
| module.structs = map( |
| lambda struct: StructFromData(module, struct), data['structs']) |
| module.unions = map( |
| lambda union: UnionFromData(module, union), data.get('unions', [])) |
| module.interfaces = map( |
| lambda interface: InterfaceFromData(module, interface), |
| data['interfaces']) |
| module.constants = map( |
| lambda constant: ConstantFromData(module, constant, None), |
| data['constants']) |
| |
| # Second pass expands fields and methods. This allows fields and parameters |
| # to refer to kinds defined anywhere in the mojom. |
| for struct in module.structs: |
| struct.fields = map(lambda field: |
| StructFieldFromData(module, field, struct), struct.fields_data) |
| del struct.fields_data |
| for union in module.unions: |
| union.fields = map(lambda field: |
| UnionFieldFromData(module, field, union), union.fields_data) |
| del union.fields_data |
| for interface in module.interfaces: |
| interface.methods = map(lambda method: |
| MethodFromData(module, method, interface), interface.methods_data) |
| del interface.methods_data |
| |
| return module |
| |
| def OrderedModuleFromData(data): |
| module = ModuleFromData(data) |
| for interface in module.interfaces: |
| next_ordinal = 0 |
| for method in interface.methods: |
| if method.ordinal is None: |
| method.ordinal = next_ordinal |
| next_ordinal = method.ordinal + 1 |
| return module |