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