blob: e9b6bfb9735de334c6ac5d52fe6ea575675654ad [file] [log] [blame]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
'''Generates Go source files from a mojom.Module.'''
from itertools import chain
import os
import re
from mojom.generate.template_expander import UseJinja
import mojom.generate.generator as generator
import mojom.generate.module as mojom
import mojom.generate.pack as pack
class KindInfo(object):
def __init__(self, go_type, encode_suffix, decode_suffix, bit_size):
self.go_type = go_type
self.encode_suffix = encode_suffix
self.decode_suffix = decode_suffix
self.bit_size = bit_size
_kind_infos = {
mojom.BOOL: KindInfo('bool', 'Bool', 'Bool', 1),
mojom.INT8: KindInfo('int8', 'Int8', 'Int8', 8),
mojom.UINT8: KindInfo('uint8', 'Uint8', 'Uint8', 8),
mojom.INT16: KindInfo('int16', 'Int16', 'Int16', 16),
mojom.UINT16: KindInfo('uint16', 'Uint16', 'Uint16', 16),
mojom.INT32: KindInfo('int32', 'Int32', 'Int32', 32),
mojom.UINT32: KindInfo('uint32', 'Uint32', 'Uint32', 32),
mojom.FLOAT: KindInfo('float32', 'Float32', 'Float32', 32),
mojom.HANDLE: KindInfo(
'system.Handle', 'Handle', 'Handle', 32),
mojom.DCPIPE: KindInfo(
'system.ConsumerHandle', 'Handle', 'ConsumerHandle', 32),
mojom.DPPIPE: KindInfo(
'system.ProducerHandle', 'Handle', 'ProducerHandle', 32),
mojom.MSGPIPE: KindInfo(
'system.MessagePipeHandle', 'Handle', 'MessagePipeHandle', 32),
mojom.SHAREDBUFFER: KindInfo(
'system.SharedBufferHandle', 'Handle', 'SharedBufferHandle', 32),
mojom.NULLABLE_HANDLE: KindInfo(
'system.Handle', 'Handle', 'Handle', 32),
mojom.NULLABLE_DCPIPE: KindInfo(
'system.ConsumerHandle', 'Handle', 'ConsumerHandle', 32),
mojom.NULLABLE_DPPIPE: KindInfo(
'system.ProducerHandle', 'Handle', 'ProducerHandle', 32),
mojom.NULLABLE_MSGPIPE: KindInfo(
'system.MessagePipeHandle', 'Handle', 'MessagePipeHandle', 32),
mojom.NULLABLE_SHAREDBUFFER: KindInfo(
'system.SharedBufferHandle', 'Handle', 'SharedBufferHandle', 32),
mojom.INT64: KindInfo('int64', 'Int64', 'Int64', 64),
mojom.UINT64: KindInfo('uint64', 'Uint64', 'Uint64', 64),
mojom.DOUBLE: KindInfo('float64', 'Float64', 'Float64', 64),
mojom.STRING: KindInfo('string', 'String', 'String', 64),
mojom.NULLABLE_STRING: KindInfo('string', 'String', 'String', 64),
}
# _imports keeps track of the imports that the .go.mojom file needs to import.
_imports = {}
# _mojom_imports keeps a list of the other .mojom files imported by this one.
_mojom_imports = {}
# The mojom_types.mojom and service_describer.mojom files are special because
# they are used to generate mojom Type's and ServiceDescription implementations.
_service_describer_pkg_short = "service_describer"
_service_describer_pkg = "mojo/public/interfaces/bindings/%s" % \
_service_describer_pkg_short
_mojom_types_pkg_short = "mojom_types"
_mojom_types_pkg = "mojo/public/interfaces/bindings/%s" % _mojom_types_pkg_short
def GetBitSize(kind):
if isinstance(kind, (mojom.Union)):
return 128
if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct, mojom.Interface)):
return 64
if mojom.IsUnionKind(kind):
return 2*64
if isinstance(kind, (mojom.InterfaceRequest)):
kind = mojom.MSGPIPE
if isinstance(kind, mojom.Enum):
kind = mojom.INT32
return _kind_infos[kind].bit_size
# Returns go type corresponding to provided kind. If |nullable| is true
# and kind is nullable adds an '*' to type (example: ?string -> *string).
def GetGoType(kind, nullable = True):
if nullable and mojom.IsNullableKind(kind) and not mojom.IsUnionKind(kind):
return '*%s' % GetNonNullableGoType(kind)
return GetNonNullableGoType(kind)
# Returns go type corresponding to provided kind. Ignores nullability of
# top-level kind.
def GetNonNullableGoType(kind):
if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
return '%s' % GetFullName(kind)
if mojom.IsArrayKind(kind):
if kind.length:
return '[%s]%s' % (kind.length, GetGoType(kind.kind))
return '[]%s' % GetGoType(kind.kind)
if mojom.IsMapKind(kind):
return 'map[%s]%s' % (GetGoType(kind.key_kind), GetGoType(kind.value_kind))
if mojom.IsInterfaceKind(kind):
return '%s_Pointer' % GetFullName(kind)
if mojom.IsInterfaceRequestKind(kind):
return '%s_Request' % GetFullName(kind.kind)
if mojom.IsEnumKind(kind):
return GetNameForNestedElement(kind)
return _kind_infos[kind].go_type
def IsPointer(kind):
return mojom.IsObjectKind(kind) and not mojom.IsUnionKind(kind)
# Splits name to lower-cased parts used for camel-casing
# (example: HTTPEntry2FooBar -> ['http', 'entry2', 'foo', 'bar']).
def NameToComponent(name):
# insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar ->
# HTTP_Entry2_FooBar)
name = re.sub('([^_])([A-Z][^A-Z_]+)', r'\1_\2', name)
# insert '_' between non upper and start of upper blocks (e.g.,
# HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar)
name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name)
return [x.lower() for x in name.split('_')]
def UpperCamelCase(name):
return ''.join([x.capitalize() for x in NameToComponent(name)])
# Formats a name. If |exported| is true makes name camel-cased with first
# letter capital, otherwise does no camel-casing and makes first letter
# lower-cased (which is used for making internal names more readable).
def FormatName(name, exported=True):
if exported:
return UpperCamelCase(name)
# Leave '_' symbols for unexported names.
return name[0].lower() + name[1:]
# Returns full name of an imported element based on prebuilt dict |_imports|.
# If the |element| is not imported returns formatted name of it.
# |element| should have attr 'name'. |exported| argument is used to make
# |FormatName()| calls only.
def GetFullName(element, exported=True):
return GetQualifiedName(
element.name, GetPackageNameForElement(element), exported)
def GetUnqualifiedNameForElement(element, exported=True):
return FormatName(element.name, exported)
# Returns a name for nested elements like enum field or constant.
# The returned name consists of camel-cased parts separated by '_'.
def GetNameForNestedElement(element):
if element.parent_kind:
return "%s_%s" % (GetNameForElement(element.parent_kind),
FormatName(element.name))
return GetFullName(element)
def GetNameForElement(element, exported=True):
if (mojom.IsInterfaceKind(element) or mojom.IsStructKind(element)
or mojom.IsUnionKind(element)):
return GetFullName(element, exported)
if isinstance(element, (mojom.EnumField,
mojom.Field,
mojom.Method,
mojom.Parameter)):
return FormatName(element.name, exported)
if isinstance(element, (mojom.Enum,
mojom.Constant,
mojom.ConstantValue)):
return GetNameForNestedElement(element)
raise Exception('Unexpected element: %s' % element)
def ExpressionToText(token):
if isinstance(token, mojom.EnumValue):
return "%s_%s" % (GetNameForNestedElement(token.enum),
FormatName(token.name, True))
if isinstance(token, mojom.ConstantValue):
return GetNameForNestedElement(token)
if isinstance(token, mojom.Constant):
return ExpressionToText(token.value)
return token
def DecodeSuffix(kind):
if mojom.IsEnumKind(kind):
return DecodeSuffix(mojom.INT32)
if mojom.IsInterfaceKind(kind):
return 'Interface'
if mojom.IsInterfaceRequestKind(kind):
return DecodeSuffix(mojom.MSGPIPE)
return _kind_infos[kind].decode_suffix
def EncodeSuffix(kind):
if mojom.IsEnumKind(kind):
return EncodeSuffix(mojom.INT32)
if mojom.IsInterfaceKind(kind):
return 'Interface'
if mojom.IsInterfaceRequestKind(kind):
return EncodeSuffix(mojom.MSGPIPE)
return _kind_infos[kind].encode_suffix
# This helper assists in the production of mojom_types.Type for simple kinds.
# See _kind_infos above.
def GetMojomTypeValue(kind, typepkg=''):
if not kind in _kind_infos:
return ''
nullable = 'true' if mojom.IsNullableKind(kind) else 'false'
if kind == mojom.BOOL or kind == mojom.FLOAT or kind == mojom.DOUBLE or \
mojom.IsIntegralKind(kind):
kind_name = UpperCamelCase(_kind_infos[kind].decode_suffix.upper())
if kind == mojom.FLOAT:
kind_name = "Float"
elif kind == mojom.DOUBLE:
kind_name = "Double"
return '%sTypeSimpleType{%sSimpleType_%s}' % (typepkg, typepkg, kind_name)
elif mojom.IsAnyHandleKind(kind):
kind_name = 'Unspecified'
if kind == mojom.DCPIPE:
kind_name = 'DataPipeConsumer'
elif kind == mojom.DPPIPE:
kind_name = 'DataPipeProducer'
elif kind == mojom.MSGPIPE:
kind_name = 'MessagePipe'
elif kind == mojom.SHAREDBUFFER:
kind_name = 'SharedBuffer'
return '%sTypeHandleType{%sHandleType{' \
'Nullable: %s, Kind: %sHandleType_Kind_%s}}' % \
(typepkg, typepkg, nullable, typepkg, kind_name)
elif mojom.IsStringKind(kind):
return '%sTypeStringType{%sStringType{%s}}' % (typepkg, typepkg, nullable)
else:
raise Exception('Missing case for kind: %s' % kind)
def GetPackageName(module):
return module.name.split('.')[0]
def GetPackageNameForElement(element):
if not hasattr(element, 'imported_from') or not element.imported_from:
return ''
path = ''
if element.imported_from['module'].path:
path += GetPackagePath(element.imported_from['module'])
if path in _imports:
return _imports[path]
return ''
def GetQualifiedName(name, package=None, exported=True):
if not package:
return FormatName(name, exported)
return '%s.%s' % (package, FormatName(name, exported))
def GetPackagePath(module):
name = module.name.split('.')[0]
return '/'.join(module.path.split('/')[:-1] + [name])
def GetAllConstants(module):
data = [module] + module.structs + module.interfaces
constants = [x.constants for x in data]
return [i for i in chain.from_iterable(constants)]
def GetAllEnums(module):
data = [module] + module.structs + module.interfaces
enums = [x.enums for x in data]
return [i for i in chain.from_iterable(enums)]
# Adds an import required to use the provided |element|.
# The required import is stored at '_imports'.
# The mojom imports are also stored separately in '_mojom_imports'.
def AddImport(module, element):
if not isinstance(element, mojom.Kind):
return
if mojom.IsArrayKind(element) or mojom.IsInterfaceRequestKind(element):
AddImport(module, element.kind)
return
if mojom.IsMapKind(element):
AddImport(module, element.key_kind)
AddImport(module, element.value_kind)
return
if mojom.IsAnyHandleKind(element):
_imports['mojo/public/go/system'] = 'system'
return
if not hasattr(element, 'imported_from') or not element.imported_from:
return
imported = element.imported_from
if GetPackagePath(imported['module']) == GetPackagePath(module):
return
path = GetPackagePath(imported['module'])
if path in _imports:
return
name = GetPackageName(imported['module'])
while name in _imports.values(): # This avoids repeated names.
name += '_'
_imports[path] = name
_mojom_imports[path] = name
# The identifier cache is used by the Type generator to determine if a type has
# already been generated or not. This prevents over-generation of the same type
# when it is referred to in multiple ways.
identifier_cache = {}
def GetIdentifier(kind):
# Use the kind's module to determine the package name.
if hasattr(kind, 'module'):
package = GetPackageName(kind.module)
elif mojom.IsInterfaceRequestKind(kind):
package = GetPackageName(kind.kind.module)
else:
return ''
# Most kinds have a name, but those that don't should rely on their spec.
# Since spec can have : and ? characters, these must be replaced. Since ? is
# replaced with '', the caller must keep track of optionality on its own.
name_or_spec = (kind.name if hasattr(kind, 'name') else kind.spec)
package_unique = name_or_spec.replace(':', '_').replace('?', '')
return '%s_%s' % (package, package_unique)
def StoreIdentifier(identifier, cache_name):
if not cache_name in identifier_cache:
identifier_cache[cache_name] = {}
identifier_cache[cache_name][identifier] = True
return ''
def CheckIdentifier(identifier, cache_name):
if not cache_name in identifier_cache:
identifier_cache[cache_name] = {}
return identifier in identifier_cache[cache_name]
# Get the mojom type's identifier suffix.
def GetMojomTypeIdentifier(kind):
# Since this should be unique, it is based on the type's identifier.
return "%s__" % GetIdentifier(kind)
class Generator(generator.Generator):
go_filters = {
'array': lambda kind: mojom.Array(kind),
'bit_size': GetBitSize,
'decode_suffix': DecodeSuffix,
'encode_suffix': EncodeSuffix,
'go_type': GetGoType,
'expression_to_text': ExpressionToText,
'has_response': lambda method: method.response_parameters is not None,
'identifier': GetIdentifier,
'identifier_check': CheckIdentifier,
'identifier_store': StoreIdentifier,
'is_array': mojom.IsArrayKind,
'is_enum': mojom.IsEnumKind,
'is_handle': mojom.IsAnyHandleKind,
'is_interface': mojom.IsInterfaceKind,
'is_interface_request': mojom.IsInterfaceRequestKind,
'is_map': mojom.IsMapKind,
'is_none_or_empty': lambda array: array is None or len(array) == 0,
'is_nullable': mojom.IsNullableKind,
'is_pointer': IsPointer,
'is_object': mojom.IsObjectKind,
'is_struct': mojom.IsStructKind,
'is_union': mojom.IsUnionKind,
'qualified': GetQualifiedName,
'mojom_type': GetMojomTypeValue,
'mojom_type_identifier': GetMojomTypeIdentifier,
'name': GetNameForElement,
'unqualified_name': GetUnqualifiedNameForElement,
'package': GetPackageNameForElement,
'tab_indent': lambda s, size = 1: ('\n' + '\t' * size).join(s.splitlines())
}
# TODO: This value should be settable via arguments. If False, then mojom type
# information will not be generated.
should_gen_mojom_types = True
def GetParameters(self):
package = GetPackageName(self.module)
return {
'enums': GetAllEnums(self.module),
'imports': self.GetImports()[0],
'interfaces': self.GetInterfaces(),
'mojom_imports': self.GetMojomImports(),
'package': package,
'structs': self.GetStructs(),
'descpkg': '%s.' % _service_describer_pkg_short \
if package != _service_describer_pkg_short else '',
'typepkg': '%s.' % _mojom_types_pkg_short \
if package != _mojom_types_pkg_short else '',
'unions': self.GetUnions()
}
@UseJinja('go_templates/source.tmpl', filters=go_filters)
def GenerateSource(self):
return self.GetParameters()
def GenerateFiles(self, args):
self.Write(self.GenerateSource(), os.path.join("go", "src",
GetPackagePath(self.module), "%s.go" % self.module.name))
def GetJinjaParameters(self):
return {
'lstrip_blocks': True,
'trim_blocks': True,
}
def GetGlobals(self):
return {
'namespace': self.module.namespace,
'module': self.module,
'should_gen_mojom_types': self.should_gen_mojom_types,
}
# Scans |self.module| for elements that require imports and adds all found
# imports to '_imports' dict. Mojom imports are stored in the '_mojom_imports'
# dict. This operation is idempotent.
# Returns a tuple:
# - list of imports that should include the generated go file
# - the dictionary of _mojom_imports
def GetImports(self):
# Imports can only be used in structs, constants, enums, interfaces.
all_structs = list(self.module.structs)
for i in self.module.interfaces:
for method in i.methods:
all_structs.append(self._GetStructFromMethod(method))
if method.response_parameters:
all_structs.append(self._GetResponseStructFromMethod(method))
if (len(all_structs) > 0 or len(self.module.interfaces) > 0
or len(self.module.unions) > 0):
_imports['fmt'] = 'fmt'
_imports['mojo/public/go/bindings'] = 'bindings'
if len(self.module.interfaces) > 0:
_imports['mojo/public/go/system'] = 'system'
if len(all_structs) > 0:
_imports['sort'] = 'sort'
for union in self.module.unions:
for field in union.fields:
AddImport(self.module, field.kind)
for struct in all_structs:
for field in struct.fields:
AddImport(self.module, field.kind)
# TODO(rogulenko): add these after generating constants and struct defaults.
# if field.default:
# AddImport(self.module, field.default)
for enum in GetAllEnums(self.module):
for field in enum.fields:
if field.value:
AddImport(self.module, field.value)
# Mojom Type generation requires additional imports.
defInterface = len(self.module.interfaces) > 0
defOtherType = len(self.module.unions) + len(all_structs) + \
len(GetAllEnums(self.module)) > 0
if GetPackageName(self.module) != _mojom_types_pkg_short:
if defInterface:
# Each Interface has a service description that uses this.
_imports[_mojom_types_pkg] = _mojom_types_pkg_short
if defOtherType and self.should_gen_mojom_types:
# This import is needed only if generating mojom type definitions.
_imports[_mojom_types_pkg] = _mojom_types_pkg_short
if GetPackageName(self.module) != _service_describer_pkg_short and \
defInterface:
# Each Interface has a service description that uses this.
_imports[_service_describer_pkg] = _service_describer_pkg_short
# TODO(rogulenko): add these after generating constants and struct defaults.
# for constant in GetAllConstants(self.module):
# AddImport(self.module, constant.value)
imports_list = []
for i in _imports:
if i.split('/')[-1] == _imports[i]:
imports_list.append('"%s"' % i)
else:
imports_list.append('%s "%s"' % (_imports[i], i))
return sorted(imports_list), _mojom_imports
def GetMojomImports(self):
# GetImports (idempotent) prepares the _imports and _mojom_imports maps.
return self.GetImports()[1]
# Overrides the implementation from the base class in order to customize the
# struct and field names.
def _GetStructFromMethod(self, method):
params_class = "%s_%s_Params" % (GetNameForElement(method.interface),
GetNameForElement(method))
struct = mojom.Struct(params_class, module=method.interface.module)
for param in method.parameters:
struct.AddField("in%s" % GetNameForElement(param),
param.kind, param.ordinal, attributes=param.attributes)
return self._AddStructComputedData(False, struct)
# Overrides the implementation from the base class in order to customize the
# struct and field names.
def _GetResponseStructFromMethod(self, method):
params_class = "%s_%s_ResponseParams" % (
GetNameForElement(method.interface), GetNameForElement(method))
struct = mojom.Struct(params_class, module=method.interface.module)
for param in method.response_parameters:
struct.AddField("out%s" % GetNameForElement(param),
param.kind, param.ordinal, attributes=param.attributes)
return self._AddStructComputedData(False, struct)