| # Copyright 2013 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Generates C++ source files from a mojom.Module.""" |
| import os |
| import sys |
| from functools import partial |
| from generators.mojom_cpp_generator import _NameFormatter as CppNameFormatter |
| from generators.mojom_cpp_generator import Generator as CppGenerator |
| from generators.mojom_cpp_generator import IsNativeOnlyKind, NamespaceToArray |
| import mojom.generate.generator as generator |
| import mojom.generate.module as mojom |
| import mojom.generate.pack as pack |
| from mojom.generate.template_expander import UseJinja, UseJinjaForImportedTemplate |
| |
| _kind_to_proto_type = { |
| mojom.BOOL: "bool", |
| mojom.INT8: "int32", |
| mojom.UINT8: "uint32", |
| mojom.INT16: "int32", |
| mojom.UINT16: "uint32", |
| mojom.INT32: "int32", |
| mojom.UINT32: "uint32", |
| mojom.FLOAT: "float", |
| mojom.INT64: "int64", |
| mojom.UINT64: "uint64", |
| mojom.DOUBLE: "double", |
| mojom.NULLABLE_BOOL: "bool", |
| mojom.NULLABLE_INT8: "int32", |
| mojom.NULLABLE_UINT8: "uint32", |
| mojom.NULLABLE_INT16: "int32", |
| mojom.NULLABLE_UINT16: "uint32", |
| mojom.NULLABLE_INT32: "int32", |
| mojom.NULLABLE_UINT32: "uint32", |
| mojom.NULLABLE_FLOAT: "float", |
| mojom.NULLABLE_INT64: "int64", |
| mojom.NULLABLE_UINT64: "uint64", |
| mojom.NULLABLE_DOUBLE: "double", |
| } |
| |
| _kind_to_cpp_proto_type = { |
| mojom.BOOL: "bool", |
| mojom.INT8: "::google::protobuf::int32", |
| mojom.UINT8: "::google::protobuf::uint32", |
| mojom.INT16: "::google::protobuf::int32", |
| mojom.UINT16: "::google::protobuf::uint32", |
| mojom.INT32: "::google::protobuf::int32", |
| mojom.UINT32: "::google::protobuf::uint32", |
| mojom.FLOAT: "float", |
| mojom.INT64: "::google::protobuf::int64", |
| mojom.UINT64: "::google::protobuf::int64", |
| mojom.DOUBLE: "double", |
| mojom.NULLABLE_BOOL: "bool", |
| mojom.NULLABLE_INT8: "::google::protobuf::int32", |
| mojom.NULLABLE_UINT8: "::google::protobuf::uint32", |
| mojom.NULLABLE_INT16: "::google::protobuf::int32", |
| mojom.NULLABLE_UINT16: "::google::protobuf::uint32", |
| mojom.NULLABLE_INT32: "::google::protobuf::int32", |
| mojom.NULLABLE_UINT32: "::google::protobuf::uint32", |
| mojom.NULLABLE_FLOAT: "float", |
| mojom.NULLABLE_INT64: "::google::protobuf::int64", |
| mojom.NULLABLE_UINT64: "::google::protobuf::int64", |
| mojom.NULLABLE_DOUBLE: "double", |
| } |
| |
| |
| class _NameFormatter(CppNameFormatter): |
| """A formatter for the names of kinds or values.""" |
| |
| def __init__(self, *args, **kwargs): |
| super(_NameFormatter, self).__init__(*args, **kwargs) |
| |
| def FormatForProto(self, |
| omit_namespace_for_module=None, |
| flatten_nested_kind=False): |
| return self.Format(".", |
| prefixed=True, |
| omit_namespace_for_module=omit_namespace_for_module, |
| flatten_nested_kind=flatten_nested_kind) |
| |
| |
| class Generator(CppGenerator): |
| def __init__(self, *args, **kwargs): |
| super(Generator, self).__init__(*args, **kwargs) |
| self.needs_mojolpm_proto = False |
| self.enum_name_cache = dict() |
| |
| def _GetAllExtraTraitsHeaders(self): |
| extra_headers = set() |
| for typemap in self._GetAllTypemaps(): |
| extra_headers.update(typemap.get("traits_headers", [])) |
| extra_headers.update(self._GetExtraTraitsHeaders()) |
| return sorted(extra_headers) |
| |
| def _GetAllTypemaps(self): |
| """Returns the typemaps for types needed in this module. |
| """ |
| all_typemaps = [] |
| seen_types = set() |
| |
| def AddKind(kind): |
| if (mojom.IsIntegralKind(kind) or mojom.IsStringKind(kind) |
| or mojom.IsDoubleKind(kind) or mojom.IsFloatKind(kind) |
| or mojom.IsAnyHandleKind(kind) or mojom.IsInterfaceKind(kind) |
| or mojom.IsAssociatedKind(kind) or mojom.IsPendingRemoteKind(kind) |
| or mojom.IsPendingReceiverKind(kind)): |
| pass |
| elif mojom.IsArrayKind(kind): |
| AddKind(kind.kind) |
| elif mojom.IsMapKind(kind): |
| AddKind(kind.key_kind) |
| AddKind(kind.value_kind) |
| else: |
| name = self._GetFullMojomNameForKind(kind) |
| if name in seen_types: |
| return |
| seen_types.add(name) |
| |
| typemap = self.typemap.get(name, None) |
| if typemap: |
| all_typemaps.append(typemap) |
| if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): |
| for field in kind.fields: |
| AddKind(field.kind) |
| |
| for kind in self.module.enums + self.module.structs + self.module.unions: |
| AddKind(kind) |
| |
| return all_typemaps |
| |
| def _ProtoImports(self): |
| """Scans all of the types used in this module to check which of the imports |
| are needed for the generated proto files. This is somewhat different to the |
| general case, since the generated proto files don't reference response |
| parameters. |
| """ |
| all_imports = self.module.imports |
| seen_imports = set() |
| seen_types = set() |
| |
| def AddKind(kind): |
| if (mojom.IsIntegralKind(kind) or mojom.IsStringKind(kind) |
| or mojom.IsDoubleKind(kind) or mojom.IsFloatKind(kind)): |
| pass |
| elif (mojom.IsAnyHandleKind(kind)): |
| self.needs_mojolpm_proto = True |
| elif mojom.IsArrayKind(kind): |
| AddKind(kind.kind) |
| elif mojom.IsMapKind(kind): |
| AddKind(kind.key_kind) |
| AddKind(kind.value_kind) |
| elif (mojom.IsStructKind(kind) or mojom.IsUnionKind(kind) |
| or mojom.IsEnumKind(kind)): |
| name = self._GetFullMojomNameForKind(kind) |
| if name in seen_types: |
| return |
| seen_types.add(name) |
| if kind.module in all_imports: |
| seen_imports.add(kind.module) |
| elif (mojom.IsPendingRemoteKind(kind) or mojom.IsPendingReceiverKind(kind) |
| or mojom.IsPendingAssociatedRemoteKind(kind) |
| or mojom.IsPendingAssociatedReceiverKind(kind)): |
| AddKind(kind.kind) |
| |
| for kind in self.module.structs + self.module.unions: |
| for field in kind.fields: |
| AddKind(field.kind) |
| |
| for interface in self.module.interfaces: |
| for method in interface.methods: |
| for parameter in method.parameters: |
| AddKind(parameter.kind) |
| if method.response_parameters: |
| for parameter in method.response_parameters: |
| AddKind(parameter.kind) |
| |
| import_files = list( |
| map(lambda x: '{}.mojolpm.proto'.format(x.path), seen_imports)) |
| if self.needs_mojolpm_proto: |
| import_files.append('mojo/public/tools/fuzzers/mojolpm.proto') |
| import_files.sort() |
| |
| return import_files |
| |
| def _GetJinjaExports(self): |
| all_enums = list(self.module.enums) |
| for struct in self.module.structs: |
| all_enums.extend(struct.enums) |
| for interface in self.module.interfaces: |
| all_enums.extend(interface.enums) |
| |
| return { |
| "all_enums": all_enums, |
| "all_extra_traits_headers": self._GetAllExtraTraitsHeaders(), |
| "enums": self.module.enums, |
| "extra_public_headers": self._GetExtraPublicHeaders(), |
| "extra_traits_headers": self._GetExtraTraitsHeaders(), |
| "imports": self.module.imports, |
| "interfaces": self.module.interfaces, |
| "module": self.module, |
| "module_namespace": self.module.namespace, |
| "namespaces_as_array": NamespaceToArray(self.module.namespace), |
| "proto_imports": self._ProtoImports(), |
| "structs": self.module.structs, |
| "unions": self.module.unions, |
| } |
| |
| @staticmethod |
| def GetTemplatePrefix(): |
| return "mojolpm_templates" |
| |
| def GetFilters(self): |
| cpp_filters = { |
| "camel_to_under": generator.ToLowerSnakeCase, |
| "contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces, |
| "cpp_wrapper_call_type": self._GetCppWrapperCallType, |
| "cpp_wrapper_param_type": self._GetCppWrapperParamType, |
| "cpp_wrapper_proto_type": self._GetCppWrapperProtoType, |
| "cpp_wrapper_type": self._GetCppWrapperType, |
| "default_value": self._DefaultValue, |
| "default_constructor_args": self._DefaultConstructorArgs, |
| "enum_field_name": self._EnumFieldName, |
| "get_name_for_kind": self._GetNameForKind, |
| "get_qualified_name_for_kind": self._GetQualifiedNameForKind, |
| "has_duplicate_values": self._EnumHasDuplicateValues, |
| "nullable_is_same_kind": self._NullableIsSameKind, |
| "proto_field_type": self._GetProtoFieldType, |
| "proto_id": self._GetProtoId, |
| "is_array_kind": mojom.IsArrayKind, |
| "is_bool_kind": mojom.IsBoolKind, |
| "is_default_constructible": self._IsDefaultConstructible, |
| "is_value_kind": mojom.IsValueKind, |
| "is_enum_kind": mojom.IsEnumKind, |
| "is_double_kind": mojom.IsDoubleKind, |
| "is_float_kind": mojom.IsFloatKind, |
| "is_integral_kind": mojom.IsIntegralKind, |
| "is_interface_kind": mojom.IsInterfaceKind, |
| "is_nullable_value_kind_packed_field": |
| pack.IsNullableValueKindPackedField, |
| "is_primary_nullable_value_kind_packed_field": |
| pack.IsPrimaryNullableValueKindPackedField, |
| "is_receiver_kind": self._IsReceiverKind, |
| "is_pending_associated_receiver_kind": |
| mojom.IsPendingAssociatedReceiverKind, |
| "is_pending_receiver_kind": mojom.IsPendingReceiverKind, |
| "is_pending_associated_remote_kind": |
| mojom.IsPendingAssociatedRemoteKind, |
| "is_pending_remote_kind": mojom.IsPendingRemoteKind, |
| "is_platform_handle_kind": mojom.IsPlatformHandleKind, |
| "is_native_only_kind": IsNativeOnlyKind, |
| "is_any_handle_kind": mojom.IsAnyHandleKind, |
| "is_any_interface_kind": mojom.IsAnyInterfaceKind, |
| "is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind, |
| "is_associated_kind": mojom.IsAssociatedKind, |
| "is_float_kind": mojom.IsFloatKind, |
| "is_hashable": self._IsHashableKind, |
| "is_map_kind": mojom.IsMapKind, |
| "is_move_only_kind": self._IsMoveOnlyKind, |
| "is_non_const_ref_kind": self._IsNonConstRefKind, |
| "is_nullable_kind": mojom.IsNullableKind, |
| "is_object_kind": mojom.IsObjectKind, |
| "is_reference_kind": mojom.IsReferenceKind, |
| "is_string_kind": mojom.IsStringKind, |
| "is_struct_kind": mojom.IsStructKind, |
| "is_typemapped_kind": self._IsTypemappedKind, |
| "is_union_kind": mojom.IsUnionKind, |
| "to_unnullable_kind": self._ToUnnullableKind, |
| "under_to_camel": partial(self._UnderToCamel, digits_split=True) |
| } |
| return cpp_filters |
| |
| @UseJinja("mojolpm.proto.tmpl") |
| def _GenerateMojolpmProto(self): |
| return self._GetJinjaExports() |
| |
| @UseJinja("mojolpm.h.tmpl") |
| def _GenerateMojolpmHeader(self): |
| return self._GetJinjaExports() |
| |
| @UseJinja("mojolpm.cc.tmpl") |
| def _GenerateMojolpmSource(self): |
| return self._GetJinjaExports() |
| |
| def GenerateFiles(self, args): |
| self.module.Stylize(generator.Stylizer()) |
| |
| if self.generate_non_variant_code: |
| self.WriteWithComment(self._GenerateMojolpmProto(), |
| "%s.mojolpm.proto" % self.module.path) |
| else: |
| self.WriteWithComment(self._GenerateMojolpmHeader(), |
| "%s-mojolpm.h" % self.module.path) |
| self.WriteWithComment(self._GenerateMojolpmSource(), |
| "%s-mojolpm.cc" % self.module.path) |
| |
| def _GetCppProtoNameForKind(self, |
| kind, |
| flatten_nested_kind=False, |
| add_same_module_namespaces=False): |
| name = _NameFormatter(kind, self.variant).FormatForCpp( |
| flatten_nested_kind=flatten_nested_kind, |
| omit_namespace_for_module=(None if add_same_module_namespaces else |
| self.module)) |
| if name.startswith('::'): |
| name = 'mojolpm' + name |
| return name |
| |
| def _GetProtoNameForKind(self, |
| kind, |
| flatten_nested_kind=False, |
| add_same_module_namespaces=False): |
| name = _NameFormatter(kind, self.variant).FormatForProto( |
| flatten_nested_kind=flatten_nested_kind, |
| omit_namespace_for_module=(None if add_same_module_namespaces else |
| self.module)) |
| if name.startswith('.'): |
| name = 'mojolpm' + name |
| return name |
| |
| def _IsMoveOnlyKind(self, kind): |
| if self._IsTypemappedKind(kind): |
| if mojom.IsEnumKind(kind): |
| return False |
| return self.typemap[self._GetFullMojomNameForKind(kind)]["move_only"] |
| if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): |
| return True |
| if mojom.IsArrayKind(kind): |
| return self._IsMoveOnlyKind(kind.kind) |
| if mojom.IsMapKind(kind): |
| return (self._IsMoveOnlyKind(kind.value_kind) |
| or self._IsMoveOnlyKind(kind.key_kind)) |
| if mojom.IsAnyHandleOrInterfaceKind(kind): |
| return True |
| return False |
| |
| def _GetNativeTypeName(self, typemapped_kind): |
| return self.typemap[self._GetFullMojomNameForKind( |
| typemapped_kind)]["typename"] |
| |
| def _FormatConstantDeclaration(self, constant, nested=False): |
| if mojom.IsStringKind(constant.kind): |
| if nested: |
| return "const char %s[]" % constant.name |
| return "%sextern const char %s[]" % \ |
| ((self.export_attribute + " ") if self.export_attribute else "", |
| constant.name) |
| return "constexpr %s %s = %s" % (GetCppPodType( |
| constant.kind), constant.name, self._ConstantValue(constant)) |
| |
| def _IsMoveOnlyKind(self, kind): |
| if self._IsTypemappedKind(kind): |
| if mojom.IsEnumKind(kind): |
| return False |
| return self.typemap[self._GetFullMojomNameForKind(kind)]["move_only"] |
| if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): |
| return True |
| if mojom.IsArrayKind(kind): |
| return self._IsMoveOnlyKind(kind.kind) |
| if mojom.IsMapKind(kind): |
| return (self._IsMoveOnlyKind(kind.value_kind) |
| or self._IsMoveOnlyKind(kind.key_kind)) |
| if mojom.IsAnyHandleOrInterfaceKind(kind): |
| return True |
| return False |
| |
| def _IsNonConstRefKind(self, kind): |
| if self._IsTypemappedKind(kind): |
| return self.typemap[self._GetFullMojomNameForKind(kind)]["non_const_ref"] |
| return False |
| |
| def _GetCppWrapperProtoType(self, kind, add_same_module_namespaces=False): |
| if (mojom.IsEnumKind(kind) or mojom.IsStructKind(kind) |
| or mojom.IsUnionKind(kind)): |
| return self._GetCppProtoNameForKind( |
| kind, add_same_module_namespaces=add_same_module_namespaces) |
| elif (mojom.IsPendingRemoteKind(kind) or mojom.IsPendingReceiverKind(kind) |
| or mojom.IsPendingAssociatedRemoteKind(kind) |
| or mojom.IsPendingAssociatedReceiverKind(kind)): |
| return "uint32_t" |
| elif mojom.IsStringKind(kind): |
| return "std::string" |
| elif mojom.IsGenericHandleKind(kind): |
| return "mojolpm::Handle" |
| elif mojom.IsDataPipeConsumerKind(kind): |
| return "mojolpm::DataPipeConsumerHandle" |
| elif mojom.IsDataPipeProducerKind(kind): |
| return "mojolpm::DataPipeProducerHandle" |
| elif mojom.IsMessagePipeKind(kind): |
| return "mojolpm::MessagePipeHandle" |
| elif mojom.IsSharedBufferKind(kind): |
| return "mojolpm::SharedBufferHandle" |
| elif mojom.IsPlatformHandleKind(kind): |
| return "mojolpm::PlatformHandle" |
| |
| if not kind in _kind_to_cpp_proto_type: |
| raise Exception("Unrecognized kind %s" % kind.spec) |
| return _kind_to_cpp_proto_type[kind] |
| |
| def _GetProtoFieldType(self, kind, quantified=True): |
| # TODO(markbrand): This will not handle array<array> or array<map> |
| # TODO(markbrand): This also will not handle array<x, 10> |
| unquantified = '' |
| if (mojom.IsEnumKind(kind) or mojom.IsStructKind(kind) |
| or mojom.IsUnionKind(kind)): |
| unquantified = self._GetProtoNameForKind(kind) |
| elif mojom.IsArrayKind(kind): |
| return "repeated %sEntry" % self._GetProtoFieldType(kind.kind, |
| quantified=False) |
| elif mojom.IsMapKind(kind): |
| return ("map<%sKey, %sValue>" % |
| (self._GetProtoFieldType(kind.key_kind, quantified=False), |
| self._GetProtoFieldType(kind.value_kind, quantified=False))) |
| elif (mojom.IsPendingRemoteKind(kind) or mojom.IsPendingReceiverKind(kind) |
| or mojom.IsPendingAssociatedRemoteKind(kind) |
| or mojom.IsPendingAssociatedReceiverKind(kind)): |
| unquantified = "uint32" |
| elif mojom.IsStringKind(kind): |
| unquantified = "string" |
| elif mojom.IsGenericHandleKind(kind): |
| unquantified = "mojolpm.Handle" |
| elif mojom.IsDataPipeConsumerKind(kind): |
| unquantified = "mojolpm.DataPipeConsumerHandle" |
| elif mojom.IsDataPipeProducerKind(kind): |
| unquantified = "mojolpm.DataPipeProducerHandle" |
| elif mojom.IsMessagePipeKind(kind): |
| unquantified = "mojolpm.MessagePipeHandle" |
| elif mojom.IsSharedBufferKind(kind): |
| unquantified = "mojolpm.SharedBufferHandle" |
| elif mojom.IsPlatformHandleKind(kind): |
| unquantified = "mojolpm.PlatformHandle" |
| else: |
| unquantified = _kind_to_proto_type[kind] |
| |
| if quantified and mojom.IsNullableKind(kind): |
| return 'optional %s' % unquantified |
| elif quantified: |
| return 'required %s' % unquantified |
| else: |
| return unquantified |
| |
| def _GetProtoId(self, name, kind=''): |
| # We reserve ids [0,15] |
| # Protobuf implementation reserves [19000,19999] |
| # Max proto id is 2^29-1 |
| string = '{}@{}'.format(name, self._GetProtoFieldType(kind, False)).lower() |
| # 32-bit fnv-1a |
| fnv = 2166136261 |
| for c in string: |
| fnv = fnv ^ ord(c) |
| fnv = (fnv * 16777619) & 0xffffffff |
| # xor-fold to 29-bits |
| fnv = (fnv >> 29) ^ (fnv & 0x1fffffff) |
| # now use a modulo to reduce to [0,2^29-1 - 1016] |
| fnv = fnv % 536869895 |
| # now we move out the disallowed ranges |
| fnv = fnv + 15 |
| if fnv >= 19000: |
| fnv += 1000 |
| return fnv |
| |
| def _NullableIsSameKind(self, kind): |
| if self._IsTypemappedKind(kind): |
| if not self.typemap[self._GetFullMojomNameForKind( |
| kind)]["nullable_is_same_type"]: |
| return False |
| if mojom.IsArrayKind(kind): |
| return False |
| if mojom.IsMapKind(kind): |
| return False |
| if mojom.IsStringKind(kind): |
| return False |
| if mojom.IsValueKind(kind): |
| return False |
| return True |
| |
| def _EnumHasDuplicateValues(self, kind): |
| values = dict() |
| i = 0 |
| for field in kind.fields: |
| value = None |
| if field.value: |
| if isinstance(field.value, str): |
| # field.value is an integer value stored as a string |
| value = int(field.value, 0) |
| else: |
| # field.value is a direct reference to another enum value, so it has |
| # to be a duplicate |
| assert isinstance(field.value, mojom.EnumValue) |
| return True |
| else: |
| # If there is no provided value, then the value is simply the next one |
| value = i |
| |
| assert (value != None) |
| # If the value appears in the enum already, then it's a duplicate. |
| if value in values.values(): |
| return True |
| values[field.name] = value |
| i = value + 1 |
| |
| return False |
| |
| def _DefaultConstructorArgs(self, kind): |
| if mojom.IsNullableKind(kind) or self._IsDefaultConstructible(kind): |
| return "" |
| return "mojo::internal::DefaultConstructTag()" |
| |
| def _EnumFieldName(self, name, kind): |
| # The WebFeature enum has entries that differ only by the casing of the |
| # names. Protobuf doesn't support this, so we add the value to the end of |
| # the name in these cases to disambiguate. |
| if kind not in self.enum_name_cache: |
| field_names = dict() |
| lower_field_names = set() |
| for field in kind.fields: |
| new_field_name = field.name |
| if new_field_name.lower() in lower_field_names: |
| new_field_name = '{}_{}'.format(new_field_name, field.numeric_value) |
| lower_field_names.add(new_field_name.lower()) |
| field_names[field.name] = new_field_name |
| self.enum_name_cache[kind] = field_names |
| return self.enum_name_cache[kind][name] |
| |
| def _ToUnnullableKind(self, kind): |
| assert mojom.IsNullableKind(kind) |
| return kind.MakeUnnullableKind() |