OLD | NEW |
| (Empty) |
1 # Copyright 2014 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 """Generates Python source files from a mojom.Module.""" | |
6 | |
7 import re | |
8 from itertools import ifilter | |
9 | |
10 import mojom.generate.generator as generator | |
11 import mojom.generate.data as data | |
12 import mojom.generate.module as mojom | |
13 from mojom.generate.template_expander import UseJinja | |
14 | |
15 _kind_to_type = { | |
16 mojom.BOOL: '_descriptor.TYPE_BOOL', | |
17 mojom.INT8: '_descriptor.TYPE_INT8', | |
18 mojom.UINT8: '_descriptor.TYPE_UINT8', | |
19 mojom.INT16: '_descriptor.TYPE_INT16', | |
20 mojom.UINT16: '_descriptor.TYPE_UINT16', | |
21 mojom.INT32: '_descriptor.TYPE_INT32', | |
22 mojom.UINT32: '_descriptor.TYPE_UINT32', | |
23 mojom.INT64: '_descriptor.TYPE_INT64', | |
24 mojom.UINT64: '_descriptor.TYPE_UINT64', | |
25 mojom.FLOAT: '_descriptor.TYPE_FLOAT', | |
26 mojom.DOUBLE: '_descriptor.TYPE_DOUBLE', | |
27 mojom.STRING: '_descriptor.TYPE_STRING', | |
28 mojom.NULLABLE_STRING: '_descriptor.TYPE_NULLABLE_STRING', | |
29 mojom.HANDLE: '_descriptor.TYPE_HANDLE', | |
30 mojom.DCPIPE: '_descriptor.TYPE_HANDLE', | |
31 mojom.DPPIPE: '_descriptor.TYPE_HANDLE', | |
32 mojom.MSGPIPE: '_descriptor.TYPE_HANDLE', | |
33 mojom.SHAREDBUFFER: '_descriptor.TYPE_HANDLE', | |
34 mojom.NULLABLE_HANDLE: '_descriptor.TYPE_NULLABLE_HANDLE', | |
35 mojom.NULLABLE_DCPIPE: '_descriptor.TYPE_NULLABLE_HANDLE', | |
36 mojom.NULLABLE_DPPIPE: '_descriptor.TYPE_NULLABLE_HANDLE', | |
37 mojom.NULLABLE_MSGPIPE: '_descriptor.TYPE_NULLABLE_HANDLE', | |
38 mojom.NULLABLE_SHAREDBUFFER: '_descriptor.TYPE_NULLABLE_HANDLE', | |
39 } | |
40 | |
41 # int64 integers are not handled by array.array. int64/uint64 array are | |
42 # supported but storage is not optimized (ie. they are plain python list, not | |
43 # array.array) | |
44 _kind_to_typecode_for_native_array = { | |
45 mojom.INT8: 'b', | |
46 mojom.UINT8: 'B', | |
47 mojom.INT16: 'h', | |
48 mojom.UINT16: 'H', | |
49 mojom.INT32: 'i', | |
50 mojom.UINT32: 'I', | |
51 mojom.FLOAT: 'f', | |
52 mojom.DOUBLE: 'd', | |
53 } | |
54 | |
55 | |
56 def NameToComponent(name): | |
57 # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar -> | |
58 # HTTP_Entry2_FooBar) | |
59 name = re.sub('([^_])([A-Z][^A-Z_]+)', r'\1_\2', name) | |
60 # insert '_' between non upper and start of upper blocks (e.g., | |
61 # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar) | |
62 name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name) | |
63 return [x.lower() for x in name.split('_')] | |
64 | |
65 def UpperCamelCase(name): | |
66 return ''.join([x.capitalize() for x in NameToComponent(name)]) | |
67 | |
68 def CamelCase(name): | |
69 uccc = UpperCamelCase(name) | |
70 return uccc[0].lower() + uccc[1:] | |
71 | |
72 def ConstantStyle(name): | |
73 components = NameToComponent(name) | |
74 if components[0] == 'k': | |
75 components = components[1:] | |
76 return '_'.join([x.upper() for x in components]) | |
77 | |
78 def FieldStyle(name): | |
79 components = NameToComponent(name) | |
80 return '_'.join([x.lower() for x in components]) | |
81 | |
82 def GetNameForElement(element): | |
83 if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or | |
84 mojom.IsStructKind(element) or isinstance(element, mojom.Method)): | |
85 return UpperCamelCase(element.name) | |
86 if isinstance(element, mojom.EnumValue): | |
87 return (GetNameForElement(element.enum) + '.' + | |
88 ConstantStyle(element.name)) | |
89 if isinstance(element, (mojom.NamedValue, | |
90 mojom.Constant)): | |
91 return ConstantStyle(element.name) | |
92 if isinstance(element, mojom.Field): | |
93 return FieldStyle(element.name) | |
94 raise Exception('Unexpected element: %s' % element) | |
95 | |
96 def ExpressionToText(token): | |
97 if isinstance(token, (mojom.EnumValue, mojom.NamedValue)): | |
98 return str(token.computed_value) | |
99 | |
100 if isinstance(token, mojom.BuiltinValue): | |
101 if token.value == 'double.INFINITY' or token.value == 'float.INFINITY': | |
102 return 'float(\'inf\')'; | |
103 if (token.value == 'double.NEGATIVE_INFINITY' or | |
104 token.value == 'float.NEGATIVE_INFINITY'): | |
105 return 'float(\'-inf\')' | |
106 if token.value == 'double.NAN' or token.value == 'float.NAN': | |
107 return 'float(\'nan\')'; | |
108 | |
109 if token in ['true', 'false']: | |
110 return str(token == 'true') | |
111 | |
112 return token | |
113 | |
114 def GetFullyQualifiedName(kind): | |
115 name = [] | |
116 if kind.imported_from: | |
117 name.append(kind.imported_from['python_module']) | |
118 name.append(GetNameForElement(kind)) | |
119 return '.'.join(name) | |
120 | |
121 def GetFieldType(kind, field=None): | |
122 if mojom.IsArrayKind(kind): | |
123 arguments = [] | |
124 if kind.kind in _kind_to_typecode_for_native_array: | |
125 arguments.append('%r' % _kind_to_typecode_for_native_array[kind.kind]) | |
126 elif kind.kind != mojom.BOOL: | |
127 arguments.append(GetFieldType(kind.kind)) | |
128 if mojom.IsNullableKind(kind): | |
129 arguments.append('nullable=True') | |
130 if kind.length is not None: | |
131 arguments.append('length=%d' % kind.length) | |
132 array_type = 'GenericArrayType' | |
133 if kind.kind == mojom.BOOL: | |
134 array_type = 'BooleanArrayType' | |
135 elif kind.kind in _kind_to_typecode_for_native_array: | |
136 array_type = 'NativeArrayType' | |
137 return '_descriptor.%s(%s)' % (array_type, ', '.join(arguments)) | |
138 | |
139 if mojom.IsMapKind(kind): | |
140 arguments = [ | |
141 GetFieldType(kind.key_kind), | |
142 GetFieldType(kind.value_kind), | |
143 ] | |
144 if mojom.IsNullableKind(kind): | |
145 arguments.append('nullable=True') | |
146 return '_descriptor.MapType(%s)' % ', '.join(arguments) | |
147 | |
148 if mojom.IsStructKind(kind): | |
149 arguments = [ 'lambda: %s' % GetFullyQualifiedName(kind) ] | |
150 if mojom.IsNullableKind(kind): | |
151 arguments.append('nullable=True') | |
152 return '_descriptor.StructType(%s)' % ', '.join(arguments) | |
153 | |
154 if mojom.IsEnumKind(kind): | |
155 return GetFieldType(mojom.INT32) | |
156 | |
157 if mojom.IsInterfaceKind(kind): | |
158 arguments = [ 'lambda: %s' % GetFullyQualifiedName(kind) ] | |
159 if mojom.IsNullableKind(kind): | |
160 arguments.append('nullable=True') | |
161 return '_descriptor.InterfaceType(%s)' % ', '.join(arguments) | |
162 | |
163 if mojom.IsInterfaceRequestKind(kind): | |
164 arguments = [] | |
165 if mojom.IsNullableKind(kind): | |
166 arguments.append('nullable=True') | |
167 return '_descriptor.InterfaceRequestType(%s)' % ', '.join(arguments) | |
168 | |
169 return _kind_to_type[kind] | |
170 | |
171 def GetFieldDescriptor(packed_field): | |
172 field = packed_field.field | |
173 class_name = 'SingleFieldGroup' | |
174 if field.kind == mojom.BOOL: | |
175 class_name = 'FieldDescriptor' | |
176 arguments = [ '%r' % GetNameForElement(field) ] | |
177 arguments.append(GetFieldType(field.kind, field)) | |
178 arguments.append(str(packed_field.index)) | |
179 arguments.append(str(packed_field.ordinal)) | |
180 if field.default: | |
181 if mojom.IsStructKind(field.kind): | |
182 arguments.append('default_value=True') | |
183 else: | |
184 arguments.append('default_value=%s' % ExpressionToText(field.default)) | |
185 return '_descriptor.%s(%s)' % (class_name, ', '.join(arguments)) | |
186 | |
187 def GetFieldGroup(byte): | |
188 if byte.packed_fields[0].field.kind == mojom.BOOL: | |
189 descriptors = map(GetFieldDescriptor, byte.packed_fields) | |
190 return '_descriptor.BooleanGroup([%s])' % ', '.join(descriptors) | |
191 assert len(byte.packed_fields) == 1 | |
192 return GetFieldDescriptor(byte.packed_fields[0]) | |
193 | |
194 def GetResponseStructFromMethod(method): | |
195 return generator.GetDataHeader( | |
196 False, generator.GetResponseStructFromMethod(method)) | |
197 | |
198 def GetStructFromMethod(method): | |
199 return generator.GetDataHeader( | |
200 False, generator.GetStructFromMethod(method)) | |
201 | |
202 def ComputeStaticValues(module): | |
203 in_progress = set() | |
204 computed = set() | |
205 | |
206 def GetComputedValue(named_value): | |
207 if isinstance(named_value, mojom.EnumValue): | |
208 field = next(ifilter(lambda field: field.name == named_value.name, | |
209 named_value.enum.fields), None) | |
210 if not field: | |
211 raise RuntimeError( | |
212 'Unable to get computed value for field %s of enum %s' % | |
213 (named_value.name, named_value.enum.name)) | |
214 if field not in computed: | |
215 ResolveEnum(named_value.enum) | |
216 return field.computed_value | |
217 elif isinstance(named_value, mojom.ConstantValue): | |
218 ResolveConstant(named_value.constant) | |
219 named_value.computed_value = named_value.constant.computed_value | |
220 return named_value.computed_value | |
221 else: | |
222 print named_value | |
223 | |
224 def ResolveConstant(constant): | |
225 if constant in computed: | |
226 return | |
227 if constant in in_progress: | |
228 raise RuntimeError('Circular dependency for constant: %s' % constant.name) | |
229 in_progress.add(constant) | |
230 if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)): | |
231 computed_value = GetComputedValue(constant.value) | |
232 else: | |
233 computed_value = ExpressionToText(constant.value) | |
234 constant.computed_value = computed_value | |
235 in_progress.remove(constant) | |
236 computed.add(constant) | |
237 | |
238 def ResolveEnum(enum): | |
239 def ResolveEnumField(enum, field, default_value): | |
240 if field in computed: | |
241 return | |
242 if field in in_progress: | |
243 raise RuntimeError('Circular dependency for enum: %s' % enum.name) | |
244 in_progress.add(field) | |
245 if field.value: | |
246 if isinstance(field.value, mojom.EnumValue): | |
247 computed_value = GetComputedValue(field.value) | |
248 elif isinstance(field.value, str): | |
249 computed_value = int(field.value, 0) | |
250 else: | |
251 raise RuntimeError('Unexpected value: %s' % field.value) | |
252 else: | |
253 computed_value = default_value | |
254 field.computed_value = computed_value | |
255 in_progress.remove(field) | |
256 computed.add(field) | |
257 | |
258 current_value = 0 | |
259 for field in enum.fields: | |
260 ResolveEnumField(enum, field, current_value) | |
261 current_value = field.computed_value + 1 | |
262 | |
263 for constant in module.constants: | |
264 ResolveConstant(constant) | |
265 | |
266 for enum in module.enums: | |
267 ResolveEnum(enum) | |
268 | |
269 for struct in module.structs: | |
270 for constant in struct.constants: | |
271 ResolveConstant(constant) | |
272 for enum in struct.enums: | |
273 ResolveEnum(enum) | |
274 for field in struct.fields: | |
275 if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)): | |
276 field.default.computed_value = GetComputedValue(field.default) | |
277 | |
278 return module | |
279 | |
280 def MojomToPythonImport(mojom): | |
281 return mojom.replace('.mojom', '_mojom') | |
282 | |
283 class Generator(generator.Generator): | |
284 | |
285 python_filters = { | |
286 'expression_to_text': ExpressionToText, | |
287 'field_group': GetFieldGroup, | |
288 'fully_qualified_name': GetFullyQualifiedName, | |
289 'name': GetNameForElement, | |
290 'response_struct_from_method': GetResponseStructFromMethod, | |
291 'struct_from_method': GetStructFromMethod, | |
292 } | |
293 | |
294 @UseJinja('python_templates/module.py.tmpl', filters=python_filters) | |
295 def GeneratePythonModule(self): | |
296 return { | |
297 'enums': self.module.enums, | |
298 'imports': self.GetImports(), | |
299 'interfaces': self.GetQualifiedInterfaces(), | |
300 'module': ComputeStaticValues(self.module), | |
301 'structs': self.GetStructs(), | |
302 } | |
303 | |
304 def GenerateFiles(self, args): | |
305 import_path = MojomToPythonImport(self.module.name) | |
306 self.Write(self.GeneratePythonModule(), | |
307 self.MatchMojomFilePath('%s.py' % import_path)) | |
308 | |
309 def GetImports(self): | |
310 for each in self.module.imports: | |
311 each['python_module'] = MojomToPythonImport(each['module_name']) | |
312 return self.module.imports | |
313 | |
314 def GetQualifiedInterfaces(self): | |
315 """ | |
316 Returns the list of interfaces of the module. Each interface that has a | |
317 client will have a reference to the representation of the client interface | |
318 in the 'qualified_client' field. | |
319 """ | |
320 interfaces = self.module.interfaces | |
321 all_interfaces = [] + interfaces | |
322 for each in self.GetImports(): | |
323 all_interfaces += [data.KindFromImport(x, each) for x in | |
324 each['module'].interfaces]; | |
325 interfaces_by_name = dict((x.name, x) for x in all_interfaces) | |
326 for interface in interfaces: | |
327 if interface.client: | |
328 assert interface.client in interfaces_by_name, ( | |
329 'Unable to find interface %s declared as client of %s.' % | |
330 (interface.client, interface.name)) | |
331 interface.qualified_client = interfaces_by_name[interface.client] | |
332 return sorted(interfaces, key=lambda i: (bool(i.client), i.name)) | |
333 | |
334 def GetJinjaParameters(self): | |
335 return { | |
336 'lstrip_blocks': True, | |
337 'trim_blocks': True, | |
338 } | |
OLD | NEW |