| 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 |