OLD | NEW |
| (Empty) |
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 rv = IndexedString(string) | |
42 rv.__index__ = index | |
43 return rv | |
44 | |
45 builtin_values = frozenset([ | |
46 "double.INFINITY", | |
47 "double.NEGATIVE_INFINITY", | |
48 "double.NAN", | |
49 "float.INFINITY", | |
50 "float.NEGATIVE_INFINITY", | |
51 "float.NAN"]) | |
52 | |
53 def IsBuiltinValue(value): | |
54 return value in builtin_values | |
55 | |
56 def 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 | |
79 def 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 | |
98 def 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 | |
112 def KindToData(kind): | |
113 return kind.spec | |
114 | |
115 def 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('a'): | |
125 colon = data.find(':') | |
126 length = int(data[1:colon]) | |
127 kind = mojom.Array(KindFromData(kinds, data[colon+1:], scope), length) | |
128 elif data.startswith('r:'): | |
129 kind = mojom.InterfaceRequest(KindFromData(kinds, data[2:], scope)) | |
130 elif data.startswith('m['): | |
131 # Isolate the two types from their brackets. | |
132 | |
133 # It is not allowed to use map as key, so there shouldn't be nested ']'s | |
134 # inside the key type spec. | |
135 key_end = data.find(']') | |
136 assert key_end != -1 and key_end < len(data) - 1 | |
137 assert data[key_end+1] == '[' and data[-1] == ']' | |
138 | |
139 first_kind = data[2:key_end] | |
140 second_kind = data[key_end+2:-1] | |
141 | |
142 kind = mojom.Map(KindFromData(kinds, first_kind, scope), | |
143 KindFromData(kinds, second_kind, scope)) | |
144 else: | |
145 kind = mojom.Kind(data) | |
146 | |
147 kinds[data] = kind | |
148 return kind | |
149 | |
150 def KindFromImport(original_kind, imported_from): | |
151 """Used with 'import module' - clones the kind imported from the given | |
152 module's namespace. Only used with Structs, Unions, Interfaces and Enums.""" | |
153 kind = copy.copy(original_kind) | |
154 # |shared_definition| is used to store various properties (see | |
155 # |AddSharedProperty()| in module.py), including |imported_from|. We don't | |
156 # want the copy to share these with the original, so copy it if necessary. | |
157 if hasattr(original_kind, 'shared_definition'): | |
158 kind.shared_definition = copy.copy(original_kind.shared_definition) | |
159 kind.imported_from = imported_from | |
160 return kind | |
161 | |
162 def ImportFromData(module, data): | |
163 import_module = data['module'] | |
164 | |
165 import_item = {} | |
166 import_item['module_name'] = import_module.name | |
167 import_item['namespace'] = import_module.namespace | |
168 import_item['module'] = import_module | |
169 | |
170 # Copy the struct kinds from our imports into the current module. | |
171 importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface) | |
172 for kind in import_module.kinds.itervalues(): | |
173 if (isinstance(kind, importable_kinds) and | |
174 kind.imported_from is None): | |
175 kind = KindFromImport(kind, import_item) | |
176 module.kinds[kind.spec] = kind | |
177 # Ditto for values. | |
178 for value in import_module.values.itervalues(): | |
179 if value.imported_from is None: | |
180 # Values don't have shared definitions (since they're not nullable), so no | |
181 # need to do anything special. | |
182 value = copy.copy(value) | |
183 value.imported_from = import_item | |
184 module.values[value.GetSpec()] = value | |
185 | |
186 return import_item | |
187 | |
188 def StructToData(struct): | |
189 return { | |
190 istr(0, 'name'): struct.name, | |
191 istr(1, 'fields'): map(FieldToData, struct.fields) | |
192 } | |
193 | |
194 def StructFromData(module, data): | |
195 struct = mojom.Struct(module=module) | |
196 struct.name = data['name'] | |
197 struct.attributes = data['attributes'] | |
198 struct.spec = 'x:' + module.namespace + '.' + struct.name | |
199 module.kinds[struct.spec] = struct | |
200 struct.enums = map(lambda enum: | |
201 EnumFromData(module, enum, struct), data['enums']) | |
202 struct.constants = map(lambda constant: | |
203 ConstantFromData(module, constant, struct), data['constants']) | |
204 # Stash fields data here temporarily. | |
205 struct.fields_data = data['fields'] | |
206 return struct | |
207 | |
208 def UnionToData(union): | |
209 return { | |
210 istr(0, 'name'): union.name, | |
211 istr(1, 'fields'): map(FieldToData, union.fields) | |
212 } | |
213 | |
214 def UnionFromData(module, data): | |
215 union = mojom.Union(module=module) | |
216 union.name = data['name'] | |
217 union.spec = 'x:' + module.namespace + '.' + union.name | |
218 module.kinds[union.spec] = union | |
219 # Stash fields data here temporarily. | |
220 union.fields_data = data['fields'] | |
221 return union | |
222 | |
223 def FieldToData(field): | |
224 data = { | |
225 istr(0, 'name'): field.name, | |
226 istr(1, 'kind'): KindToData(field.kind) | |
227 } | |
228 if field.ordinal != None: | |
229 data[istr(2, 'ordinal')] = field.ordinal | |
230 if field.default != None: | |
231 data[istr(3, 'default')] = field.default | |
232 return data | |
233 | |
234 def FieldFromData(module, data, struct): | |
235 field = mojom.Field() | |
236 field.name = data['name'] | |
237 field.kind = KindFromData( | |
238 module.kinds, data['kind'], (module.namespace, struct.name)) | |
239 field.ordinal = data.get('ordinal') | |
240 field.default = FixupExpression( | |
241 module, data.get('default'), (module.namespace, struct.name), field.kind) | |
242 return field | |
243 | |
244 def ParameterToData(parameter): | |
245 data = { | |
246 istr(0, 'name'): parameter.name, | |
247 istr(1, 'kind'): parameter.kind.spec | |
248 } | |
249 if parameter.ordinal != None: | |
250 data[istr(2, 'ordinal')] = parameter.ordinal | |
251 if parameter.default != None: | |
252 data[istr(3, 'default')] = parameter.default | |
253 return data | |
254 | |
255 def ParameterFromData(module, data, interface): | |
256 parameter = mojom.Parameter() | |
257 parameter.name = data['name'] | |
258 parameter.kind = KindFromData( | |
259 module.kinds, data['kind'], (module.namespace, interface.name)) | |
260 parameter.ordinal = data.get('ordinal') | |
261 parameter.default = data.get('default') | |
262 return parameter | |
263 | |
264 def MethodToData(method): | |
265 data = { | |
266 istr(0, 'name'): method.name, | |
267 istr(1, 'parameters'): map(ParameterToData, method.parameters) | |
268 } | |
269 if method.ordinal != None: | |
270 data[istr(2, 'ordinal')] = method.ordinal | |
271 if method.response_parameters != None: | |
272 data[istr(3, 'response_parameters')] = map( | |
273 ParameterToData, method.response_parameters) | |
274 return data | |
275 | |
276 def MethodFromData(module, data, interface): | |
277 method = mojom.Method(interface, data['name'], ordinal=data.get('ordinal')) | |
278 method.default = data.get('default') | |
279 method.parameters = map(lambda parameter: | |
280 ParameterFromData(module, parameter, interface), data['parameters']) | |
281 if data.has_key('response_parameters'): | |
282 method.response_parameters = map( | |
283 lambda parameter: ParameterFromData(module, parameter, interface), | |
284 data['response_parameters']) | |
285 return method | |
286 | |
287 def InterfaceToData(interface): | |
288 return { | |
289 istr(0, 'name'): interface.name, | |
290 istr(1, 'client'): interface.client, | |
291 istr(2, 'methods'): map(MethodToData, interface.methods) | |
292 } | |
293 | |
294 def InterfaceFromData(module, data): | |
295 interface = mojom.Interface(module=module) | |
296 interface.name = data['name'] | |
297 interface.spec = 'x:' + module.namespace + '.' + interface.name | |
298 interface.client = data['client'] if data.has_key('client') else None | |
299 module.kinds[interface.spec] = interface | |
300 interface.enums = map(lambda enum: | |
301 EnumFromData(module, enum, interface), data['enums']) | |
302 interface.constants = map(lambda constant: | |
303 ConstantFromData(module, constant, interface), data['constants']) | |
304 # Stash methods data here temporarily. | |
305 interface.methods_data = data['methods'] | |
306 return interface | |
307 | |
308 def EnumFieldFromData(module, enum, data, parent_kind): | |
309 field = mojom.EnumField() | |
310 field.name = data['name'] | |
311 # TODO(mpcomplete): FixupExpression should be done in the second pass, | |
312 # so constants and enums can refer to each other. | |
313 # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or | |
314 # vice versa? | |
315 if parent_kind: | |
316 field.value = FixupExpression( | |
317 module, data['value'], (module.namespace, parent_kind.name), enum) | |
318 else: | |
319 field.value = FixupExpression( | |
320 module, data['value'], (module.namespace, ), enum) | |
321 value = mojom.EnumValue(module, enum, field) | |
322 module.values[value.GetSpec()] = value | |
323 return field | |
324 | |
325 def EnumFromData(module, data, parent_kind): | |
326 enum = mojom.Enum(module=module) | |
327 enum.name = data['name'] | |
328 name = enum.name | |
329 if parent_kind: | |
330 name = parent_kind.name + '.' + name | |
331 enum.spec = 'x:%s.%s' % (module.namespace, name) | |
332 enum.parent_kind = parent_kind | |
333 | |
334 enum.fields = map( | |
335 lambda field: EnumFieldFromData(module, enum, field, parent_kind), | |
336 data['fields']) | |
337 module.kinds[enum.spec] = enum | |
338 return enum | |
339 | |
340 def ConstantFromData(module, data, parent_kind): | |
341 constant = mojom.Constant() | |
342 constant.name = data['name'] | |
343 if parent_kind: | |
344 scope = (module.namespace, parent_kind.name) | |
345 else: | |
346 scope = (module.namespace, ) | |
347 # TODO(mpcomplete): maybe we should only support POD kinds. | |
348 constant.kind = KindFromData(module.kinds, data['kind'], scope) | |
349 constant.value = FixupExpression(module, data.get('value'), scope, None) | |
350 | |
351 value = mojom.ConstantValue(module, parent_kind, constant) | |
352 module.values[value.GetSpec()] = value | |
353 return constant | |
354 | |
355 def ModuleToData(module): | |
356 return { | |
357 istr(0, 'name'): module.name, | |
358 istr(1, 'namespace'): module.namespace, | |
359 istr(2, 'structs'): map(StructToData, module.structs), | |
360 istr(3, 'interfaces'): map(InterfaceToData, module.interfaces), | |
361 istr(4, 'unions'): map(UnionToData, module.unions), | |
362 } | |
363 | |
364 def ModuleFromData(data): | |
365 module = mojom.Module() | |
366 module.kinds = {} | |
367 for kind in mojom.PRIMITIVES: | |
368 module.kinds[kind.spec] = kind | |
369 | |
370 module.values = {} | |
371 | |
372 module.name = data['name'] | |
373 module.namespace = data['namespace'] | |
374 module.attributes = data['attributes'] | |
375 # Imports must come first, because they add to module.kinds which is used | |
376 # by by the others. | |
377 module.imports = map( | |
378 lambda import_data: ImportFromData(module, import_data), | |
379 data['imports']) | |
380 | |
381 # First pass collects kinds. | |
382 module.enums = map( | |
383 lambda enum: EnumFromData(module, enum, None), data['enums']) | |
384 module.structs = map( | |
385 lambda struct: StructFromData(module, struct), data['structs']) | |
386 module.unions = map( | |
387 lambda union: UnionFromData(module, struct), data.get('unions', [])) | |
388 module.interfaces = map( | |
389 lambda interface: InterfaceFromData(module, interface), | |
390 data['interfaces']) | |
391 module.constants = map( | |
392 lambda constant: ConstantFromData(module, constant, None), | |
393 data['constants']) | |
394 | |
395 # Second pass expands fields and methods. This allows fields and parameters | |
396 # to refer to kinds defined anywhere in the mojom. | |
397 for struct in module.structs: | |
398 struct.fields = map(lambda field: | |
399 FieldFromData(module, field, struct), struct.fields_data) | |
400 del struct.fields_data | |
401 for union in module.unions: | |
402 union.fields = map(lambda field: | |
403 FieldFromData(module, field, union), union.fields_data) | |
404 del union.fields_data | |
405 for interface in module.interfaces: | |
406 interface.methods = map(lambda method: | |
407 MethodFromData(module, method, interface), interface.methods_data) | |
408 del interface.methods_data | |
409 | |
410 return module | |
411 | |
412 def OrderedModuleFromData(data): | |
413 module = ModuleFromData(data) | |
414 for interface in module.interfaces: | |
415 next_ordinal = 0 | |
416 for method in interface.methods: | |
417 if method.ordinal is None: | |
418 method.ordinal = next_ordinal | |
419 next_ordinal = method.ordinal + 1 | |
420 return module | |
OLD | NEW |