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 45builtin_values = frozenset([ 46 "double.INFINITY", 47 "double.NEGATIVE_INFINITY", 48 "double.NAN", 49 "float.INFINITY", 50 "float.NEGATIVE_INFINITY", 51 "float.NAN"]) 52 53def IsBuiltinValue(value): 54 return value in builtin_values 55 56def LookupKind(kinds, spec, scope): 57 """Tries to find which Kind a spec refers to, given the scope in which its 58 referenced. Starts checking from the narrowest scope to most general. For 59 example, given a struct field like 60 Foo.Bar x; 61 Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner 62 type 'Bar' in the struct 'Foo' in the current namespace. 63 64 |scope| is a tuple that looks like (namespace, struct/interface), referring 65 to the location where the type is referenced.""" 66 if spec.startswith('x:'): 67 name = spec[2:] 68 for i in xrange(len(scope), -1, -1): 69 test_spec = 'x:' 70 if i > 0: 71 test_spec += '.'.join(scope[:i]) + '.' 72 test_spec += name 73 kind = kinds.get(test_spec) 74 if kind: 75 return kind 76 77 return kinds.get(spec) 78 79def LookupValue(values, name, scope, kind): 80 """Like LookupKind, but for constant values.""" 81 # If the type is an enum, the value can be specified as a qualified name, in 82 # which case the form EnumName.ENUM_VALUE must be used. We use the presence 83 # of a '.' in the requested name to identify this. Otherwise, we prepend the 84 # enum name. 85 if isinstance(kind, mojom.Enum) and '.' not in name: 86 name = '%s.%s' % (kind.spec.split(':', 1)[1], name) 87 for i in reversed(xrange(len(scope) + 1)): 88 test_spec = '.'.join(scope[:i]) 89 if test_spec: 90 test_spec += '.' 91 test_spec += name 92 value = values.get(test_spec) 93 if value: 94 return value 95 96 return values.get(name) 97 98def FixupExpression(module, value, scope, kind): 99 """Translates an IDENTIFIER into a built-in value or structured NamedValue 100 object.""" 101 if isinstance(value, tuple) and value[0] == 'IDENTIFIER': 102 # Allow user defined values to shadow builtins. 103 result = LookupValue(module.values, value[1], scope, kind) 104 if result: 105 if isinstance(result, tuple): 106 raise Exception('Unable to resolve expression: %r' % value[1]) 107 return result 108 if IsBuiltinValue(value[1]): 109 return mojom.BuiltinValue(value[1]) 110 return value 111 112def KindToData(kind): 113 return kind.spec 114 115def KindFromData(kinds, data, scope): 116 kind = LookupKind(kinds, data, scope) 117 if kind: 118 return kind 119 120 if data.startswith('?'): 121 kind = KindFromData(kinds, data[1:], scope).MakeNullableKind() 122 elif data.startswith('a:'): 123 kind = mojom.Array(KindFromData(kinds, data[2:], scope)) 124 elif data.startswith('r:'): 125 kind = mojom.InterfaceRequest(KindFromData(kinds, data[2:], scope)) 126 elif data.startswith('a'): 127 colon = data.find(':') 128 length = int(data[1:colon]) 129 kind = mojom.FixedArray(length, KindFromData(kinds, data[colon+1:], scope)) 130 else: 131 kind = mojom.Kind(data) 132 133 kinds[data] = kind 134 return kind 135 136def KindFromImport(original_kind, imported_from): 137 """Used with 'import module' - clones the kind imported from the given 138 module's namespace. Only used with Structs, Interfaces and Enums.""" 139 kind = copy.copy(original_kind) 140 # |shared_definition| is used to store various properties (see 141 # |AddSharedProperty()| in module.py), including |imported_from|. We don't 142 # want the copy to share these with the original, so copy it if necessary. 143 if hasattr(original_kind, 'shared_definition'): 144 kind.shared_definition = copy.copy(original_kind.shared_definition) 145 kind.imported_from = imported_from 146 return kind 147 148def ImportFromData(module, data): 149 import_module = data['module'] 150 151 import_item = {} 152 import_item['module_name'] = import_module.name 153 import_item['namespace'] = import_module.namespace 154 import_item['module'] = import_module 155 156 # Copy the struct kinds from our imports into the current module. 157 for kind in import_module.kinds.itervalues(): 158 if (isinstance(kind, (mojom.Struct, mojom.Enum, mojom.Interface)) and 159 kind.imported_from is None): 160 kind = KindFromImport(kind, import_item) 161 module.kinds[kind.spec] = kind 162 # Ditto for values. 163 for value in import_module.values.itervalues(): 164 if value.imported_from is None: 165 # Values don't have shared definitions (since they're not nullable), so no 166 # need to do anything special. 167 value = copy.copy(value) 168 value.imported_from = import_item 169 module.values[value.GetSpec()] = value 170 171 return import_item 172 173def StructToData(struct): 174 return { 175 istr(0, 'name'): struct.name, 176 istr(1, 'fields'): map(FieldToData, struct.fields) 177 } 178 179def StructFromData(module, data): 180 struct = mojom.Struct(module=module) 181 struct.name = data['name'] 182 struct.attributes = data['attributes'] 183 struct.spec = 'x:' + module.namespace + '.' + struct.name 184 module.kinds[struct.spec] = struct 185 struct.enums = map(lambda enum: 186 EnumFromData(module, enum, struct), data['enums']) 187 struct.constants = map(lambda constant: 188 ConstantFromData(module, constant, struct), data['constants']) 189 # Stash fields data here temporarily. 190 struct.fields_data = data['fields'] 191 return struct 192 193def FieldToData(field): 194 data = { 195 istr(0, 'name'): field.name, 196 istr(1, 'kind'): KindToData(field.kind) 197 } 198 if field.ordinal != None: 199 data[istr(2, 'ordinal')] = field.ordinal 200 if field.default != None: 201 data[istr(3, 'default')] = field.default 202 return data 203 204def FieldFromData(module, data, struct): 205 field = mojom.Field() 206 field.name = data['name'] 207 field.kind = KindFromData( 208 module.kinds, data['kind'], (module.namespace, struct.name)) 209 field.ordinal = data.get('ordinal') 210 field.default = FixupExpression( 211 module, data.get('default'), (module.namespace, struct.name), field.kind) 212 return field 213 214def ParameterToData(parameter): 215 data = { 216 istr(0, 'name'): parameter.name, 217 istr(1, 'kind'): parameter.kind.spec 218 } 219 if parameter.ordinal != None: 220 data[istr(2, 'ordinal')] = parameter.ordinal 221 if parameter.default != None: 222 data[istr(3, 'default')] = parameter.default 223 return data 224 225def ParameterFromData(module, data, interface): 226 parameter = mojom.Parameter() 227 parameter.name = data['name'] 228 parameter.kind = KindFromData( 229 module.kinds, data['kind'], (module.namespace, interface.name)) 230 parameter.ordinal = data.get('ordinal') 231 parameter.default = data.get('default') 232 return parameter 233 234def MethodToData(method): 235 data = { 236 istr(0, 'name'): method.name, 237 istr(1, 'parameters'): map(ParameterToData, method.parameters) 238 } 239 if method.ordinal != None: 240 data[istr(2, 'ordinal')] = method.ordinal 241 if method.response_parameters != None: 242 data[istr(3, 'response_parameters')] = map( 243 ParameterToData, method.response_parameters) 244 return data 245 246def MethodFromData(module, data, interface): 247 method = mojom.Method(interface, data['name'], ordinal=data.get('ordinal')) 248 method.default = data.get('default') 249 method.parameters = map(lambda parameter: 250 ParameterFromData(module, parameter, interface), data['parameters']) 251 if data.has_key('response_parameters'): 252 method.response_parameters = map( 253 lambda parameter: ParameterFromData(module, parameter, interface), 254 data['response_parameters']) 255 return method 256 257def InterfaceToData(interface): 258 return { 259 istr(0, 'name'): interface.name, 260 istr(1, 'client'): interface.client, 261 istr(2, 'methods'): map(MethodToData, interface.methods) 262 } 263 264def InterfaceFromData(module, data): 265 interface = mojom.Interface(module=module) 266 interface.name = data['name'] 267 interface.spec = 'x:' + module.namespace + '.' + interface.name 268 interface.client = data['client'] if data.has_key('client') else None 269 module.kinds[interface.spec] = interface 270 interface.enums = map(lambda enum: 271 EnumFromData(module, enum, interface), data['enums']) 272 interface.constants = map(lambda constant: 273 ConstantFromData(module, constant, interface), data['constants']) 274 # Stash methods data here temporarily. 275 interface.methods_data = data['methods'] 276 return interface 277 278def EnumFieldFromData(module, enum, data, parent_kind): 279 field = mojom.EnumField() 280 field.name = data['name'] 281 # TODO(mpcomplete): FixupExpression should be done in the second pass, 282 # so constants and enums can refer to each other. 283 # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or 284 # vice versa? 285 if parent_kind: 286 field.value = FixupExpression( 287 module, data['value'], (module.namespace, parent_kind.name), enum) 288 else: 289 field.value = FixupExpression( 290 module, data['value'], (module.namespace, ), enum) 291 value = mojom.EnumValue(module, enum, field) 292 module.values[value.GetSpec()] = value 293 return field 294 295def EnumFromData(module, data, parent_kind): 296 enum = mojom.Enum(module=module) 297 enum.name = data['name'] 298 name = enum.name 299 if parent_kind: 300 name = parent_kind.name + '.' + name 301 enum.spec = 'x:%s.%s' % (module.namespace, name) 302 enum.parent_kind = parent_kind 303 304 enum.fields = map( 305 lambda field: EnumFieldFromData(module, enum, field, parent_kind), 306 data['fields']) 307 module.kinds[enum.spec] = enum 308 return enum 309 310def ConstantFromData(module, data, parent_kind): 311 constant = mojom.Constant() 312 constant.name = data['name'] 313 if parent_kind: 314 scope = (module.namespace, parent_kind.name) 315 else: 316 scope = (module.namespace, ) 317 # TODO(mpcomplete): maybe we should only support POD kinds. 318 constant.kind = KindFromData(module.kinds, data['kind'], scope) 319 constant.value = FixupExpression(module, data.get('value'), scope, None) 320 321 value = mojom.ConstantValue(module, parent_kind, constant) 322 module.values[value.GetSpec()] = value 323 return constant 324 325def ModuleToData(module): 326 return { 327 istr(0, 'name'): module.name, 328 istr(1, 'namespace'): module.namespace, 329 istr(2, 'structs'): map(StructToData, module.structs), 330 istr(3, 'interfaces'): map(InterfaceToData, module.interfaces) 331 } 332 333def ModuleFromData(data): 334 module = mojom.Module() 335 module.kinds = {} 336 for kind in mojom.PRIMITIVES: 337 module.kinds[kind.spec] = kind 338 339 module.values = {} 340 341 module.name = data['name'] 342 module.namespace = data['namespace'] 343 module.attributes = data['attributes'] 344 # Imports must come first, because they add to module.kinds which is used 345 # by by the others. 346 module.imports = map( 347 lambda import_data: ImportFromData(module, import_data), 348 data['imports']) 349 350 # First pass collects kinds. 351 module.enums = map( 352 lambda enum: EnumFromData(module, enum, None), data['enums']) 353 module.structs = map( 354 lambda struct: StructFromData(module, struct), data['structs']) 355 module.interfaces = map( 356 lambda interface: InterfaceFromData(module, interface), 357 data['interfaces']) 358 module.constants = map( 359 lambda constant: ConstantFromData(module, constant, None), 360 data['constants']) 361 362 # Second pass expands fields and methods. This allows fields and parameters 363 # to refer to kinds defined anywhere in the mojom. 364 for struct in module.structs: 365 struct.fields = map(lambda field: 366 FieldFromData(module, field, struct), struct.fields_data) 367 del struct.fields_data 368 for interface in module.interfaces: 369 interface.methods = map(lambda method: 370 MethodFromData(module, method, interface), interface.methods_data) 371 del interface.methods_data 372 373 return module 374 375def OrderedModuleFromData(data): 376 module = ModuleFromData(data) 377 for interface in module.interfaces: 378 next_ordinal = 0 379 for method in interface.methods: 380 if method.ordinal is None: 381 method.ordinal = next_ordinal 382 next_ordinal = method.ordinal + 1 383 return module 384