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.
5 """Generates Python source files from a mojom.Module."""
8 from itertools import ifilter
10 import mojom.generate.generator as generator
11 import mojom.generate.module as mojom
12 from mojom.generate.template_expander import UseJinja
15 mojom.BOOL: '_descriptor.TYPE_BOOL',
16 mojom.INT8: '_descriptor.TYPE_INT8',
17 mojom.UINT8: '_descriptor.TYPE_UINT8',
18 mojom.INT16: '_descriptor.TYPE_INT16',
19 mojom.UINT16: '_descriptor.TYPE_UINT16',
20 mojom.INT32: '_descriptor.TYPE_INT32',
21 mojom.UINT32: '_descriptor.TYPE_UINT32',
22 mojom.INT64: '_descriptor.TYPE_INT64',
23 mojom.UINT64: '_descriptor.TYPE_UINT64',
24 mojom.FLOAT: '_descriptor.TYPE_FLOAT',
25 mojom.DOUBLE: '_descriptor.TYPE_DOUBLE',
26 mojom.STRING: '_descriptor.TYPE_STRING',
27 mojom.NULLABLE_STRING: '_descriptor.TYPE_NULLABLE_STRING',
28 mojom.HANDLE: '_descriptor.TYPE_HANDLE',
29 mojom.DCPIPE: '_descriptor.TYPE_HANDLE',
30 mojom.DPPIPE: '_descriptor.TYPE_HANDLE',
31 mojom.MSGPIPE: '_descriptor.TYPE_HANDLE',
32 mojom.SHAREDBUFFER: '_descriptor.TYPE_HANDLE',
33 mojom.NULLABLE_HANDLE: '_descriptor.TYPE_NULLABLE_HANDLE',
34 mojom.NULLABLE_DCPIPE: '_descriptor.TYPE_NULLABLE_HANDLE',
35 mojom.NULLABLE_DPPIPE: '_descriptor.TYPE_NULLABLE_HANDLE',
36 mojom.NULLABLE_MSGPIPE: '_descriptor.TYPE_NULLABLE_HANDLE',
37 mojom.NULLABLE_SHAREDBUFFER: '_descriptor.TYPE_NULLABLE_HANDLE',
40 # int64 integers are not handled by array.array. int64/uint64 array are
41 # supported but storage is not optimized (ie. they are plain python list, not
43 _kind_to_typecode_for_native_array = {
54 _kind_to_typecode = dict(_kind_to_typecode_for_native_array)
55 _kind_to_typecode.update({
62 mojom.SHAREDBUFFER: 'i',
63 mojom.NULLABLE_HANDLE: 'i',
64 mojom.NULLABLE_DCPIPE: 'i',
65 mojom.NULLABLE_DPPIPE: 'i',
66 mojom.NULLABLE_MSGPIPE: 'i',
67 mojom.NULLABLE_SHAREDBUFFER: 'i',
71 def NameToComponent(name):
72 # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar ->
74 name = re.sub('([^_])([A-Z][^A-Z_]+)', r'\1_\2', name)
75 # insert '_' between non upper and start of upper blocks (e.g.,
76 # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar)
77 name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name)
78 return [x.lower() for x in name.split('_')]
80 def UpperCamelCase(name):
81 return ''.join([x.capitalize() for x in NameToComponent(name)])
84 uccc = UpperCamelCase(name)
85 return uccc[0].lower() + uccc[1:]
87 def ConstantStyle(name):
88 components = NameToComponent(name)
89 if components[0] == 'k':
90 components = components[1:]
91 return '_'.join([x.upper() for x in components])
93 def GetNameForElement(element):
94 if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or
95 mojom.IsStructKind(element)):
96 return UpperCamelCase(element.name)
97 if isinstance(element, mojom.EnumValue):
98 return (GetNameForElement(element.enum) + '.' +
99 ConstantStyle(element.name))
100 if isinstance(element, (mojom.NamedValue,
102 return ConstantStyle(element.name)
103 raise Exception('Unexpected element: ' % element)
105 def ExpressionToText(token):
106 if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
107 return str(token.computed_value)
109 if isinstance(token, mojom.BuiltinValue):
110 if token.value == 'double.INFINITY' or token.value == 'float.INFINITY':
111 return 'float(\'inf\')';
112 if (token.value == 'double.NEGATIVE_INFINITY' or
113 token.value == 'float.NEGATIVE_INFINITY'):
114 return 'float(\'-inf\')'
115 if token.value == 'double.NAN' or token.value == 'float.NAN':
116 return 'float(\'nan\')';
118 if token in ['true', 'false']:
119 return str(token == 'true')
123 def GetStructClass(kind):
125 if kind.imported_from:
126 name.append(kind.imported_from['python_module'])
127 name.append(GetNameForElement(kind))
128 return '.'.join(name)
130 def GetFieldType(kind, field=None):
131 if mojom.IsAnyArrayKind(kind):
133 if kind.kind in _kind_to_typecode_for_native_array:
134 arguments.append('%r' %_kind_to_typecode_for_native_array[kind.kind])
135 elif kind.kind != mojom.BOOL:
136 arguments.append(GetFieldType(kind.kind))
137 if mojom.IsNullableKind(kind):
138 arguments.append('nullable=True')
139 if mojom.IsFixedArrayKind(kind):
140 arguments.append('length=%d' % kind.length)
141 array_type = 'GenericArrayType'
142 if kind.kind == mojom.BOOL:
143 array_type = 'BooleanArrayType'
144 elif kind.kind in _kind_to_typecode_for_native_array:
145 array_type = 'NativeArrayType'
146 return '_descriptor.%s(%s)' % (array_type, ', '.join(arguments))
148 if mojom.IsStructKind(kind):
149 arguments = [ GetStructClass(kind) ]
150 if mojom.IsNullableKind(kind):
151 arguments.append('nullable=True')
152 return '_descriptor.StructType(%s)' % ', '.join(arguments)
154 if mojom.IsEnumKind(kind):
155 return GetFieldType(mojom.INT32)
157 return _kind_to_type.get(kind, '_descriptor.TYPE_NONE')
159 def GetFieldDescriptor(packed_field):
160 field = packed_field.field
161 class_name = 'SingleFieldGroup'
162 if field.kind == mojom.BOOL:
163 class_name = 'FieldDescriptor'
164 arguments = [ '%r' % field.name ]
165 arguments.append(GetFieldType(field.kind, field))
166 arguments.append(str(packed_field.field.ordinal))
168 if mojom.IsStructKind(field.kind):
169 arguments.append('default_value=True')
171 arguments.append('default_value=%s' % ExpressionToText(field.default))
172 return '_descriptor.%s(%s)' % (class_name, ', '.join(arguments))
174 def GetFieldGroup(byte):
175 if len(byte.packed_fields) > 1:
176 descriptors = map(GetFieldDescriptor, byte.packed_fields)
177 return '_descriptor.BooleanGroup([%s])' % ', '.join(descriptors)
178 assert len(byte.packed_fields) == 1
179 return GetFieldDescriptor(byte.packed_fields[0])
181 def ComputeStaticValues(module):
185 def GetComputedValue(named_value):
186 if isinstance(named_value, mojom.EnumValue):
187 field = next(ifilter(lambda field: field.name == named_value.name,
188 named_value.enum.fields), None)
191 'Unable to get computed value for field %s of enum %s' %
192 (named_value.name, named_value.enum.name))
193 if field not in computed:
194 ResolveEnum(named_value.enum)
195 return field.computed_value
196 elif isinstance(named_value, mojom.ConstantValue):
197 ResolveConstant(named_value.constant)
198 named_value.computed_value = named_value.constant.computed_value
199 return named_value.computed_value
203 def ResolveConstant(constant):
204 if constant in computed:
206 if constant in in_progress:
207 raise RuntimeError('Circular dependency for constant: %s' % constant.name)
208 in_progress.add(constant)
209 if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):
210 computed_value = GetComputedValue(constant.value)
212 computed_value = ExpressionToText(constant.value)
213 constant.computed_value = computed_value
214 in_progress.remove(constant)
215 computed.add(constant)
217 def ResolveEnum(enum):
218 def ResolveEnumField(enum, field, default_value):
219 if field in computed:
221 if field in in_progress:
222 raise RuntimeError('Circular dependency for enum: %s' % enum.name)
223 in_progress.add(field)
225 if isinstance(field.value, mojom.EnumValue):
226 computed_value = GetComputedValue(field.value)
227 elif isinstance(field.value, str):
228 computed_value = int(field.value, 0)
230 raise RuntimeError('Unexpected value: %s' % field.value)
232 computed_value = default_value
233 field.computed_value = computed_value
234 in_progress.remove(field)
238 for field in enum.fields:
239 ResolveEnumField(enum, field, current_value)
240 current_value = field.computed_value + 1
242 for constant in module.constants:
243 ResolveConstant(constant)
245 for enum in module.enums:
248 for struct in module.structs:
249 for constant in struct.constants:
250 ResolveConstant(constant)
251 for enum in struct.enums:
253 for field in struct.fields:
254 if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):
255 field.default.computed_value = GetComputedValue(field.default)
260 class Generator(generator.Generator):
263 'expression_to_text': ExpressionToText,
264 'field_group': GetFieldGroup,
265 'name': GetNameForElement,
268 @UseJinja('python_templates/module.py.tmpl', filters=python_filters)
269 def GeneratePythonModule(self):
271 'imports': self.GetImports(),
272 'enums': self.module.enums,
273 'module': ComputeStaticValues(self.module),
274 'structs': self.GetStructs(),
277 def GenerateFiles(self, args):
278 self.Write(self.GeneratePythonModule(),
279 '%s.py' % self.module.name.replace('.mojom', '_mojom'))
281 def GetImports(self):
282 for each in self.module.imports:
283 each['python_module'] = each['module_name'].replace('.mojom', '_mojom')
284 return self.module.imports
286 def GetJinjaParameters(self):
288 'lstrip_blocks': True,