Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / mojo / public / tools / bindings / generators / mojom_python_generator.py
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.module as mojom
12 from mojom.generate.template_expander import UseJinja
13
14 _kind_to_type = {
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',
38 }
39
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
42 # array.array)
43 _kind_to_typecode_for_native_array = {
44   mojom.INT8:   'b',
45   mojom.UINT8:  'B',
46   mojom.INT16:  'h',
47   mojom.UINT16: 'H',
48   mojom.INT32:  'i',
49   mojom.UINT32: 'I',
50   mojom.FLOAT:  'f',
51   mojom.DOUBLE: 'd',
52 }
53
54 _kind_to_typecode = dict(_kind_to_typecode_for_native_array)
55 _kind_to_typecode.update({
56   mojom.INT64:                 'q',
57   mojom.UINT64:                'Q',
58   mojom.HANDLE:                'i',
59   mojom.DCPIPE:                'i',
60   mojom.DPPIPE:                'i',
61   mojom.MSGPIPE:               'i',
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',
68 })
69
70
71 def NameToComponent(name):
72   # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar ->
73   # HTTP_Entry2_FooBar)
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('_')]
79
80 def UpperCamelCase(name):
81   return ''.join([x.capitalize() for x in NameToComponent(name)])
82
83 def CamelCase(name):
84   uccc = UpperCamelCase(name)
85   return uccc[0].lower() + uccc[1:]
86
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])
92
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,
101                           mojom.Constant)):
102     return ConstantStyle(element.name)
103   raise Exception('Unexpected element: ' % element)
104
105 def ExpressionToText(token):
106   if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
107     return str(token.computed_value)
108
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\')';
117
118   if token in ['true', 'false']:
119     return str(token == 'true')
120
121   return token
122
123 def GetStructClass(kind):
124   name = []
125   if kind.imported_from:
126     name.append(kind.imported_from['python_module'])
127   name.append(GetNameForElement(kind))
128   return '.'.join(name)
129
130 def GetFieldType(kind, field=None):
131   if mojom.IsAnyArrayKind(kind):
132     arguments = []
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))
147
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)
153
154   if mojom.IsEnumKind(kind):
155     return GetFieldType(mojom.INT32)
156
157   return _kind_to_type.get(kind, '_descriptor.TYPE_NONE')
158
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))
167   if field.default:
168     if mojom.IsStructKind(field.kind):
169       arguments.append('default_value=True')
170     else:
171       arguments.append('default_value=%s' % ExpressionToText(field.default))
172   return '_descriptor.%s(%s)' % (class_name, ', '.join(arguments))
173
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])
180
181 def ComputeStaticValues(module):
182   in_progress = set()
183   computed = set()
184
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)
189       if not field:
190         raise RuntimeError(
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
200     else:
201       print named_value
202
203   def ResolveConstant(constant):
204     if constant in computed:
205       return
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)
211     else:
212       computed_value = ExpressionToText(constant.value)
213     constant.computed_value = computed_value
214     in_progress.remove(constant)
215     computed.add(constant)
216
217   def ResolveEnum(enum):
218     def ResolveEnumField(enum, field, default_value):
219       if field in computed:
220         return
221       if field in in_progress:
222         raise RuntimeError('Circular dependency for enum: %s' % enum.name)
223       in_progress.add(field)
224       if field.value:
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)
229         else:
230           raise RuntimeError('Unexpected value: %s' % field.value)
231       else:
232         computed_value = default_value
233       field.computed_value = computed_value
234       in_progress.remove(field)
235       computed.add(field)
236
237     current_value = 0
238     for field in enum.fields:
239       ResolveEnumField(enum, field, current_value)
240       current_value = field.computed_value + 1
241
242   for constant in module.constants:
243     ResolveConstant(constant)
244
245   for enum in module.enums:
246     ResolveEnum(enum)
247
248   for struct in module.structs:
249     for constant in struct.constants:
250       ResolveConstant(constant)
251     for enum in struct.enums:
252       ResolveEnum(enum)
253     for field in struct.fields:
254       if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):
255         field.default.computed_value = GetComputedValue(field.default)
256
257   return module
258
259
260 class Generator(generator.Generator):
261
262   python_filters = {
263     'expression_to_text': ExpressionToText,
264     'field_group': GetFieldGroup,
265     'name': GetNameForElement,
266   }
267
268   @UseJinja('python_templates/module.py.tmpl', filters=python_filters)
269   def GeneratePythonModule(self):
270     return {
271       'imports': self.GetImports(),
272       'enums': self.module.enums,
273       'module': ComputeStaticValues(self.module),
274       'structs': self.GetStructs(),
275     }
276
277   def GenerateFiles(self, args):
278     self.Write(self.GeneratePythonModule(),
279                '%s.py' % self.module.name.replace('.mojom', '_mojom'))
280
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
285
286   def GetJinjaParameters(self):
287     return {
288       'lstrip_blocks': True,
289       'trim_blocks': True,
290     }