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
7import re
8from itertools import ifilter
9
10import mojom.generate.generator as generator
11import mojom.generate.module as mojom
12from 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
71def 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
80def UpperCamelCase(name):
81  return ''.join([x.capitalize() for x in NameToComponent(name)])
82
83def CamelCase(name):
84  uccc = UpperCamelCase(name)
85  return uccc[0].lower() + uccc[1:]
86
87def 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
93def 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
105def 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
123def 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
130def 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
159def 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
174def 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
181def 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
260class 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    }
291