| OLD | NEW |
| (Empty) |
| 1 # Copyright 2015 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 Go source files from a mojom.Module.''' | |
| 6 | |
| 7 from itertools import chain | |
| 8 import os | |
| 9 import re | |
| 10 | |
| 11 from mojom.generate.template_expander import UseJinja | |
| 12 | |
| 13 import mojom.generate.generator as generator | |
| 14 import mojom.generate.module as mojom | |
| 15 import mojom.generate.pack as pack | |
| 16 | |
| 17 class KindInfo(object): | |
| 18 def __init__(self, go_type, encode_suffix, decode_suffix, bit_size): | |
| 19 self.go_type = go_type | |
| 20 self.encode_suffix = encode_suffix | |
| 21 self.decode_suffix = decode_suffix | |
| 22 self.bit_size = bit_size | |
| 23 | |
| 24 _kind_infos = { | |
| 25 mojom.BOOL: KindInfo('bool', 'Bool', 'Bool', 1), | |
| 26 mojom.INT8: KindInfo('int8', 'Int8', 'Int8', 8), | |
| 27 mojom.UINT8: KindInfo('uint8', 'Uint8', 'Uint8', 8), | |
| 28 mojom.INT16: KindInfo('int16', 'Int16', 'Int16', 16), | |
| 29 mojom.UINT16: KindInfo('uint16', 'Uint16', 'Uint16', 16), | |
| 30 mojom.INT32: KindInfo('int32', 'Int32', 'Int32', 32), | |
| 31 mojom.UINT32: KindInfo('uint32', 'Uint32', 'Uint32', 32), | |
| 32 mojom.FLOAT: KindInfo('float32', 'Float32', 'Float32', 32), | |
| 33 mojom.HANDLE: KindInfo( | |
| 34 'system.Handle', 'Handle', 'Handle', 32), | |
| 35 mojom.DCPIPE: KindInfo( | |
| 36 'system.ConsumerHandle', 'Handle', 'ConsumerHandle', 32), | |
| 37 mojom.DPPIPE: KindInfo( | |
| 38 'system.ProducerHandle', 'Handle', 'ProducerHandle', 32), | |
| 39 mojom.MSGPIPE: KindInfo( | |
| 40 'system.MessagePipeHandle', 'Handle', 'MessagePipeHandle', 32), | |
| 41 mojom.SHAREDBUFFER: KindInfo( | |
| 42 'system.SharedBufferHandle', 'Handle', 'SharedBufferHandle', 32), | |
| 43 mojom.NULLABLE_HANDLE: KindInfo( | |
| 44 'system.Handle', 'Handle', 'Handle', 32), | |
| 45 mojom.NULLABLE_DCPIPE: KindInfo( | |
| 46 'system.ConsumerHandle', 'Handle', 'ConsumerHandle', 32), | |
| 47 mojom.NULLABLE_DPPIPE: KindInfo( | |
| 48 'system.ProducerHandle', 'Handle', 'ProducerHandle', 32), | |
| 49 mojom.NULLABLE_MSGPIPE: KindInfo( | |
| 50 'system.MessagePipeHandle', 'Handle', 'MessagePipeHandle', 32), | |
| 51 mojom.NULLABLE_SHAREDBUFFER: KindInfo( | |
| 52 'system.SharedBufferHandle', 'Handle', 'SharedBufferHandle', 32), | |
| 53 mojom.INT64: KindInfo('int64', 'Int64', 'Int64', 64), | |
| 54 mojom.UINT64: KindInfo('uint64', 'Uint64', 'Uint64', 64), | |
| 55 mojom.DOUBLE: KindInfo('float64', 'Float64', 'Float64', 64), | |
| 56 mojom.STRING: KindInfo('string', 'String', 'String', 64), | |
| 57 mojom.NULLABLE_STRING: KindInfo('string', 'String', 'String', 64), | |
| 58 } | |
| 59 | |
| 60 | |
| 61 # The mojom_types.mojom and service_describer.mojom files are special because | |
| 62 # they are used to generate mojom Type's and ServiceDescription implementations. | |
| 63 _service_describer_pkg_short = "service_describer" | |
| 64 _service_describer_pkg = "mojo/public/interfaces/bindings/%s" % \ | |
| 65 _service_describer_pkg_short | |
| 66 _mojom_types_pkg_short = "mojom_types" | |
| 67 _mojom_types_pkg = "mojo/public/interfaces/bindings/%s" % _mojom_types_pkg_short | |
| 68 | |
| 69 def GetBitSize(kind): | |
| 70 if isinstance(kind, (mojom.Union)): | |
| 71 return 128 | |
| 72 if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct, mojom.Interface)): | |
| 73 return 64 | |
| 74 if mojom.IsUnionKind(kind): | |
| 75 return 2*64 | |
| 76 if isinstance(kind, (mojom.InterfaceRequest)): | |
| 77 kind = mojom.MSGPIPE | |
| 78 if isinstance(kind, mojom.Enum): | |
| 79 kind = mojom.INT32 | |
| 80 return _kind_infos[kind].bit_size | |
| 81 | |
| 82 # Returns go type corresponding to provided kind. If |nullable| is true | |
| 83 # and kind is nullable adds an '*' to type (example: ?string -> *string). | |
| 84 def GetGoType(kind, nullable = True): | |
| 85 if nullable and mojom.IsNullableKind(kind) and not mojom.IsUnionKind(kind): | |
| 86 return '*%s' % GetNonNullableGoType(kind) | |
| 87 return GetNonNullableGoType(kind) | |
| 88 | |
| 89 # Returns go type corresponding to provided kind. Ignores nullability of | |
| 90 # top-level kind. | |
| 91 def GetNonNullableGoType(kind): | |
| 92 if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): | |
| 93 return '%s' % GetFullName(kind) | |
| 94 if mojom.IsArrayKind(kind): | |
| 95 if kind.length: | |
| 96 return '[%s]%s' % (kind.length, GetGoType(kind.kind)) | |
| 97 return '[]%s' % GetGoType(kind.kind) | |
| 98 if mojom.IsMapKind(kind): | |
| 99 return 'map[%s]%s' % (GetGoType(kind.key_kind), GetGoType(kind.value_kind)) | |
| 100 if mojom.IsInterfaceKind(kind): | |
| 101 return '%s_Pointer' % GetFullName(kind) | |
| 102 if mojom.IsInterfaceRequestKind(kind): | |
| 103 return '%s_Request' % GetFullName(kind.kind) | |
| 104 if mojom.IsEnumKind(kind): | |
| 105 return GetNameForNestedElement(kind) | |
| 106 return _kind_infos[kind].go_type | |
| 107 | |
| 108 def IsPointer(kind): | |
| 109 return mojom.IsObjectKind(kind) and not mojom.IsUnionKind(kind) | |
| 110 | |
| 111 # Splits name to lower-cased parts used for camel-casing | |
| 112 # (example: HTTPEntry2FooBar -> ['http', 'entry2', 'foo', 'bar']). | |
| 113 def NameToComponent(name): | |
| 114 # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar -> | |
| 115 # HTTP_Entry2_FooBar) | |
| 116 name = re.sub('([^_])([A-Z][^A-Z0-9_]+)', r'\1_\2', name) | |
| 117 # insert '_' between non upper and start of upper blocks (e.g., | |
| 118 # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar) | |
| 119 name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name) | |
| 120 return [x.lower() for x in name.split('_')] | |
| 121 | |
| 122 def UpperCamelCase(name): | |
| 123 return ''.join([x.capitalize() for x in NameToComponent(name)]) | |
| 124 | |
| 125 # Formats a name. If |exported| is true makes name camel-cased with first | |
| 126 # letter capital, otherwise does no camel-casing and makes first letter | |
| 127 # lower-cased (which is used for making internal names more readable). | |
| 128 def FormatName(name, exported=True): | |
| 129 if exported: | |
| 130 return UpperCamelCase(name) | |
| 131 # Leave '_' symbols for unexported names. | |
| 132 return name[0].lower() + name[1:] | |
| 133 | |
| 134 # Returns full name of an imported element. | |
| 135 # If the |element| is not imported returns formatted name of it. | |
| 136 # |element| should have attr 'name'. |exported| argument is used to make | |
| 137 # |FormatName()| calls only. | |
| 138 def GetFullName(element, exported=True): | |
| 139 return GetQualifiedName( | |
| 140 element.name, GetPackageNameForElement(element), exported) | |
| 141 | |
| 142 def GetUnqualifiedNameForElement(element, exported=True): | |
| 143 return FormatName(element.name, exported) | |
| 144 | |
| 145 # Returns a name for nested elements like enum field or constant. | |
| 146 # The returned name consists of camel-cased parts separated by '_'. | |
| 147 def GetNameForNestedElement(element): | |
| 148 if element.parent_kind: | |
| 149 return "%s_%s" % (GetNameForElement(element.parent_kind), | |
| 150 FormatName(element.name)) | |
| 151 return GetFullName(element) | |
| 152 | |
| 153 def GetNameForElement(element, exported=True): | |
| 154 if (mojom.IsInterfaceKind(element) or mojom.IsStructKind(element) | |
| 155 or mojom.IsUnionKind(element)): | |
| 156 return GetFullName(element, exported) | |
| 157 if isinstance(element, (mojom.EnumField, | |
| 158 mojom.Field, | |
| 159 mojom.Method, | |
| 160 mojom.Parameter)): | |
| 161 element_name = (element.go_name if hasattr(element, "go_name") | |
| 162 else element.name) | |
| 163 return FormatName(element_name, exported) | |
| 164 if isinstance(element, (mojom.Enum, | |
| 165 mojom.Constant, | |
| 166 mojom.ConstantValue)): | |
| 167 return GetNameForNestedElement(element) | |
| 168 raise Exception('Unexpected element: %s' % element) | |
| 169 | |
| 170 def ExpressionToText(token): | |
| 171 if isinstance(token, mojom.EnumValue): | |
| 172 return "%s_%s" % (GetNameForNestedElement(token.enum), | |
| 173 FormatName(token.name, True)) | |
| 174 if isinstance(token, mojom.ConstantValue): | |
| 175 return GetNameForNestedElement(token) | |
| 176 if isinstance(token, mojom.Constant): | |
| 177 return ExpressionToText(token.value) | |
| 178 return token | |
| 179 | |
| 180 def DecodeSuffix(kind): | |
| 181 if mojom.IsEnumKind(kind): | |
| 182 return DecodeSuffix(mojom.INT32) | |
| 183 if mojom.IsInterfaceKind(kind): | |
| 184 return 'Interface' | |
| 185 if mojom.IsInterfaceRequestKind(kind): | |
| 186 return DecodeSuffix(mojom.MSGPIPE) | |
| 187 return _kind_infos[kind].decode_suffix | |
| 188 | |
| 189 def EncodeSuffix(kind): | |
| 190 if mojom.IsEnumKind(kind): | |
| 191 return EncodeSuffix(mojom.INT32) | |
| 192 if mojom.IsInterfaceKind(kind): | |
| 193 return 'Interface' | |
| 194 if mojom.IsInterfaceRequestKind(kind): | |
| 195 return EncodeSuffix(mojom.MSGPIPE) | |
| 196 return _kind_infos[kind].encode_suffix | |
| 197 | |
| 198 def GetPackageName(module): | |
| 199 return module.name.split('.')[0] | |
| 200 | |
| 201 def GetPackageNameForElement(element): | |
| 202 if not hasattr(element, 'imported_from') or not element.imported_from: | |
| 203 return '' | |
| 204 return element.imported_from.get('go_name', '') | |
| 205 | |
| 206 def GetTypeKeyForElement(element): | |
| 207 if not hasattr(element, 'type_key') or not element.type_key: | |
| 208 return '' | |
| 209 return element.type_key | |
| 210 | |
| 211 def GetQualifiedName(name, package=None, exported=True): | |
| 212 if not package: | |
| 213 return FormatName(name, exported) | |
| 214 return '%s.%s' % (package, FormatName(name, exported)) | |
| 215 | |
| 216 def GetPackagePath(module): | |
| 217 name = module.name.split('.')[0] | |
| 218 return '/'.join(module.path.split('/')[:-1] + [name]) | |
| 219 | |
| 220 def GetAllConstants(module): | |
| 221 data = [module] + module.structs + module.interfaces | |
| 222 constants = [x.constants for x in data] | |
| 223 return [i for i in chain.from_iterable(constants)] | |
| 224 | |
| 225 def GetAllEnums(module): | |
| 226 data = [module] + module.structs + module.interfaces | |
| 227 enums = [x.enums for x in data] | |
| 228 return [i for i in chain.from_iterable(enums)] | |
| 229 | |
| 230 def AddImport(imports, mojom_imports, module, element): | |
| 231 """Adds an import required to use the provided element. | |
| 232 | |
| 233 The required import is stored in the imports parameter. | |
| 234 The corresponding mojom import is stored in the mojom_imports parameter. | |
| 235 Each import is also updated to include a 'go_name' entry. The 'go_name' entry | |
| 236 is the name by which the imported module will be referred to in the generated | |
| 237 code. Because the import dictionary is accessible from the element's | |
| 238 imported_from field this allows us to generate the qualified name for the | |
| 239 element. | |
| 240 | |
| 241 Args: | |
| 242 imports: {dict<str, str>} The key is the path to the import and the value | |
| 243 is the go name. | |
| 244 mojom_imports: {dict<str, str>} The key is the path to the import and the | |
| 245 value is the go name. | |
| 246 module: {module.Module} the module being processed. | |
| 247 element: {module.Kind} the element whose import is to be tracked. | |
| 248 """ | |
| 249 if not isinstance(element, mojom.Kind): | |
| 250 return | |
| 251 | |
| 252 if mojom.IsArrayKind(element) or mojom.IsInterfaceRequestKind(element): | |
| 253 AddImport(imports, mojom_imports, module, element.kind) | |
| 254 return | |
| 255 if mojom.IsMapKind(element): | |
| 256 AddImport(imports, mojom_imports, module, element.key_kind) | |
| 257 AddImport(imports, mojom_imports, module, element.value_kind) | |
| 258 return | |
| 259 if mojom.IsAnyHandleKind(element): | |
| 260 imports['mojo/public/go/system'] = 'system' | |
| 261 return | |
| 262 | |
| 263 if not hasattr(element, 'imported_from') or not element.imported_from: | |
| 264 return | |
| 265 imported = element.imported_from | |
| 266 if GetPackagePath(imported['module']) == GetPackagePath(module): | |
| 267 return | |
| 268 path = GetPackagePath(imported['module']) | |
| 269 if path in imports: | |
| 270 return | |
| 271 name = GetPackageName(imported['module']) | |
| 272 while name in imports.values(): # This avoids repeated names. | |
| 273 name += '_' | |
| 274 imported['go_name'] = name | |
| 275 imports[path] = name | |
| 276 mojom_imports[path] = name | |
| 277 | |
| 278 class Generator(generator.Generator): | |
| 279 go_filters = { | |
| 280 'array': lambda kind: mojom.Array(kind), | |
| 281 'bit_size': GetBitSize, | |
| 282 'decode_suffix': DecodeSuffix, | |
| 283 'encode_suffix': EncodeSuffix, | |
| 284 'go_type': GetGoType, | |
| 285 'expression_to_text': ExpressionToText, | |
| 286 'has_response': lambda method: method.response_parameters is not None, | |
| 287 'is_array': mojom.IsArrayKind, | |
| 288 'is_enum': mojom.IsEnumKind, | |
| 289 'is_handle': mojom.IsAnyHandleKind, | |
| 290 'is_interface': mojom.IsInterfaceKind, | |
| 291 'is_interface_request': mojom.IsInterfaceRequestKind, | |
| 292 'is_map': mojom.IsMapKind, | |
| 293 'is_none_or_empty': lambda array: array is None or len(array) == 0, | |
| 294 'is_nullable': mojom.IsNullableKind, | |
| 295 'is_pointer': IsPointer, | |
| 296 'is_object': mojom.IsObjectKind, | |
| 297 'is_struct': mojom.IsStructKind, | |
| 298 'is_union': mojom.IsUnionKind, | |
| 299 'qualified': GetQualifiedName, | |
| 300 'mojom_type_key' : GetTypeKeyForElement, | |
| 301 'name': GetNameForElement, | |
| 302 'unqualified_name': GetUnqualifiedNameForElement, | |
| 303 'package': GetPackageNameForElement, | |
| 304 'tab_indent': lambda s, size = 1: ('\n' + '\t' * size).join(s.splitlines()) | |
| 305 } | |
| 306 | |
| 307 # If set to True, then mojom type information will be generated. | |
| 308 should_gen_mojom_types = False | |
| 309 | |
| 310 def GetParameters(self): | |
| 311 package = GetPackageName(self.module) | |
| 312 imports, mojom_imports = self.GetImports() | |
| 313 return { | |
| 314 'enums': GetAllEnums(self.module), | |
| 315 'imports': imports, | |
| 316 'interfaces': self.GetInterfaces(), | |
| 317 'mojom_imports': mojom_imports, | |
| 318 'package': package, | |
| 319 'structs': self.GetStructs(), | |
| 320 'descpkg': '%s.' % _service_describer_pkg_short \ | |
| 321 if package != _service_describer_pkg_short else '', | |
| 322 'typepkg': '%s.' % _mojom_types_pkg_short \ | |
| 323 if package != _mojom_types_pkg_short else '', | |
| 324 'unions': self.GetUnions(), | |
| 325 } | |
| 326 | |
| 327 @UseJinja('go_templates/source.tmpl', filters=go_filters) | |
| 328 def GenerateSource(self): | |
| 329 return self.GetParameters() | |
| 330 | |
| 331 def GenerateFiles(self, args): | |
| 332 self.should_gen_mojom_types = "--generate_type_info" in args | |
| 333 | |
| 334 self.Write(self.GenerateSource(), os.path.join("go", "src", | |
| 335 GetPackagePath(self.module), "%s.go" % self.module.name)) | |
| 336 | |
| 337 def GetJinjaParameters(self): | |
| 338 return { | |
| 339 'lstrip_blocks': True, | |
| 340 'trim_blocks': True, | |
| 341 } | |
| 342 | |
| 343 def GetGlobals(self): | |
| 344 return { | |
| 345 'namespace': self.module.namespace, | |
| 346 'module': self.module, | |
| 347 'should_gen_mojom_types': self.should_gen_mojom_types, | |
| 348 } | |
| 349 | |
| 350 def GetImports(self): | |
| 351 """Gets the current module's imports. | |
| 352 | |
| 353 Returns: | |
| 354 tuple(dict<str, str>, dict<str, str>) | |
| 355 The first element of the tuple is a dictionary mapping import paths to | |
| 356 import names. | |
| 357 The second element is a dictionary mapping import paths to import names | |
| 358 only for imported mojom files. | |
| 359 """ | |
| 360 imports = {} | |
| 361 mojom_imports = {} | |
| 362 # Imports are referred to by the imported_from field of imported kinds. | |
| 363 # Imported kinds can only be referred to in structs, constants, enums, | |
| 364 # unions and interfaces. | |
| 365 all_structs = list(self.module.structs) | |
| 366 for i in self.module.interfaces: | |
| 367 for method in i.methods: | |
| 368 all_structs.append(self._GetStructFromMethod(method)) | |
| 369 if method.response_parameters: | |
| 370 all_structs.append(self._GetResponseStructFromMethod(method)) | |
| 371 | |
| 372 if (len(all_structs) > 0 or len(self.module.interfaces) > 0 | |
| 373 or len(self.module.unions) > 0): | |
| 374 imports['fmt'] = 'fmt' | |
| 375 imports['mojo/public/go/bindings'] = 'bindings' | |
| 376 if len(self.module.interfaces) > 0: | |
| 377 imports['mojo/public/go/system'] = 'system' | |
| 378 if len(all_structs) > 0: | |
| 379 imports['sort'] = 'sort' | |
| 380 | |
| 381 for union in self.module.unions: | |
| 382 for field in union.fields: | |
| 383 AddImport(imports, mojom_imports, self.module, field.kind) | |
| 384 | |
| 385 for struct in all_structs: | |
| 386 for field in struct.fields: | |
| 387 AddImport(imports, mojom_imports, self.module, field.kind) | |
| 388 # TODO(rogulenko): add these after generating constants and struct defaults. | |
| 389 # if field.default: | |
| 390 # AddImport(imports, mojom_imports, self.module, field.default) | |
| 391 | |
| 392 for enum in GetAllEnums(self.module): | |
| 393 for field in enum.fields: | |
| 394 if field.value: | |
| 395 AddImport(imports, mojom_imports, self.module, field.value) | |
| 396 | |
| 397 # Mojom Type generation requires additional imports. | |
| 398 defInterface = len(self.module.interfaces) > 0 | |
| 399 defOtherType = len(self.module.unions) + len(all_structs) + \ | |
| 400 len(GetAllEnums(self.module)) > 0 | |
| 401 | |
| 402 if self.should_gen_mojom_types: | |
| 403 imports['bytes'] = 'bytes' | |
| 404 imports['compress/gzip'] = 'gzip' | |
| 405 imports['encoding/base64'] = 'base64' | |
| 406 imports['fmt'] = 'fmt' | |
| 407 imports['io/ioutil'] = 'ioutil' | |
| 408 imports['mojo/public/go/bindings'] = 'bindings' | |
| 409 | |
| 410 if GetPackageName(self.module) != _mojom_types_pkg_short: | |
| 411 if defInterface: | |
| 412 # Each Interface has a service description that uses this. | |
| 413 imports[_mojom_types_pkg] = _mojom_types_pkg_short | |
| 414 if defOtherType and self.should_gen_mojom_types: | |
| 415 # This import is needed only if generating mojom type definitions. | |
| 416 imports[_mojom_types_pkg] = _mojom_types_pkg_short | |
| 417 | |
| 418 if GetPackageName(self.module) != _service_describer_pkg_short and \ | |
| 419 defInterface: | |
| 420 # Each Interface has a service description that uses this. | |
| 421 imports[_service_describer_pkg] = _service_describer_pkg_short | |
| 422 | |
| 423 # TODO(rogulenko): add these after generating constants and struct defaults. | |
| 424 # for constant in GetAllConstants(self.module): | |
| 425 # AddImport(imports, mojom_imports, self.module, constant.value) | |
| 426 | |
| 427 return imports, mojom_imports | |
| 428 | |
| 429 # Overrides the implementation from the base class in order to customize the | |
| 430 # struct and field names. Since the Python objects representing the struct | |
| 431 # and fields are shared by all language generators we don't want to actually | |
| 432 # modify the |name| property. Instead we add a |go_name| property. | |
| 433 def _GetStructFromMethod(self, method): | |
| 434 self._AddStructComputedData(False, method.param_struct) | |
| 435 # Only generate the go_names if they have not already been generated. | |
| 436 if not hasattr(method.param_struct, "go_name"): | |
| 437 method.param_struct.go_name = "%s_%s_Params" % ( | |
| 438 GetNameForElement(method.interface), GetNameForElement(method)) | |
| 439 for field in method.param_struct.fields: | |
| 440 field.go_name = "in%s" % GetNameForElement(field) | |
| 441 return method.param_struct | |
| 442 | |
| 443 # Overrides the implementation from the base class in order to customize the | |
| 444 # struct and field names. Since the Python objects representing the struct | |
| 445 # and fields are shared by all language generators we don't want to actually | |
| 446 # modify the |name| property. Instead we add a |go_name| property. | |
| 447 def _GetResponseStructFromMethod(self, method): | |
| 448 self._AddStructComputedData(False, method.response_param_struct) | |
| 449 if not hasattr(method.response_param_struct, "go_name"): | |
| 450 # Only generate the go_names if they have not already been generated. | |
| 451 method.response_param_struct.go_name = "%s_%s_ResponseParams" % ( | |
| 452 GetNameForElement(method.interface), GetNameForElement(method)) | |
| 453 for field in method.response_param_struct.fields: | |
| 454 field.go_name = "out%s" % GetNameForElement(field) | |
| 455 return method.response_param_struct | |
| OLD | NEW |