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