blob: a0971d5975d22e99a33dde3803266e8f800aed2e [file] [log] [blame]
# Copyright 2014 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 dart source files from a mojom.Module."""
import os
import re
import shutil
import sys
import mojom.fileutil as fileutil
import mojom.generate.constant_resolver as resolver
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
GENERATOR_PREFIX = 'dart'
_kind_to_dart_default_value = {
mojom.BOOL: "false",
mojom.INT8: "0",
mojom.UINT8: "0",
mojom.INT16: "0",
mojom.UINT16: "0",
mojom.INT32: "0",
mojom.UINT32: "0",
mojom.FLOAT: "0.0",
mojom.HANDLE: "null",
mojom.DCPIPE: "null",
mojom.DPPIPE: "null",
mojom.MSGPIPE: "null",
mojom.SHAREDBUFFER: "null",
mojom.NULLABLE_HANDLE: "null",
mojom.NULLABLE_DCPIPE: "null",
mojom.NULLABLE_DPPIPE: "null",
mojom.NULLABLE_MSGPIPE: "null",
mojom.NULLABLE_SHAREDBUFFER: "null",
mojom.INT64: "0",
mojom.UINT64: "0",
mojom.DOUBLE: "0.0",
mojom.STRING: "null",
mojom.NULLABLE_STRING: "null"
}
_kind_to_dart_decl_type = {
mojom.BOOL: "bool",
mojom.INT8: "int",
mojom.UINT8: "int",
mojom.INT16: "int",
mojom.UINT16: "int",
mojom.INT32: "int",
mojom.UINT32: "int",
mojom.FLOAT: "double",
mojom.HANDLE: "core.MojoHandle",
mojom.DCPIPE: "core.MojoDataPipeConsumer",
mojom.DPPIPE: "core.MojoDataPipeProducer",
mojom.MSGPIPE: "core.MojoMessagePipeEndpoint",
mojom.SHAREDBUFFER: "core.MojoSharedBuffer",
mojom.NULLABLE_HANDLE: "core.MojoHandle",
mojom.NULLABLE_DCPIPE: "core.MojoDataPipeConsumer",
mojom.NULLABLE_DPPIPE: "core.MojoDataPipeProducer",
mojom.NULLABLE_MSGPIPE: "core.MojoMessagePipeEndpoint",
mojom.NULLABLE_SHAREDBUFFER: "core.MojoSharedBuffer",
mojom.INT64: "int",
mojom.UINT64: "int",
mojom.DOUBLE: "double",
mojom.STRING: "String",
mojom.NULLABLE_STRING: "String"
}
_spec_to_decode_method = {
mojom.BOOL.spec: 'decodeBool',
mojom.DCPIPE.spec: 'decodeConsumerHandle',
mojom.DOUBLE.spec: 'decodeDouble',
mojom.DPPIPE.spec: 'decodeProducerHandle',
mojom.FLOAT.spec: 'decodeFloat',
mojom.HANDLE.spec: 'decodeHandle',
mojom.INT16.spec: 'decodeInt16',
mojom.INT32.spec: 'decodeInt32',
mojom.INT64.spec: 'decodeInt64',
mojom.INT8.spec: 'decodeInt8',
mojom.MSGPIPE.spec: 'decodeMessagePipeHandle',
mojom.NULLABLE_DCPIPE.spec: 'decodeConsumerHandle',
mojom.NULLABLE_DPPIPE.spec: 'decodeProducerHandle',
mojom.NULLABLE_HANDLE.spec: 'decodeHandle',
mojom.NULLABLE_MSGPIPE.spec: 'decodeMessagePipeHandle',
mojom.NULLABLE_SHAREDBUFFER.spec: 'decodeSharedBufferHandle',
mojom.NULLABLE_STRING.spec: 'decodeString',
mojom.SHAREDBUFFER.spec: 'decodeSharedBufferHandle',
mojom.STRING.spec: 'decodeString',
mojom.UINT16.spec: 'decodeUint16',
mojom.UINT32.spec: 'decodeUint32',
mojom.UINT64.spec: 'decodeUint64',
mojom.UINT8.spec: 'decodeUint8',
}
_spec_to_encode_method = {
mojom.BOOL.spec: 'encodeBool',
mojom.DCPIPE.spec: 'encodeConsumerHandle',
mojom.DOUBLE.spec: 'encodeDouble',
mojom.DPPIPE.spec: 'encodeProducerHandle',
mojom.FLOAT.spec: 'encodeFloat',
mojom.HANDLE.spec: 'encodeHandle',
mojom.INT16.spec: 'encodeInt16',
mojom.INT32.spec: 'encodeInt32',
mojom.INT64.spec: 'encodeInt64',
mojom.INT8.spec: 'encodeInt8',
mojom.MSGPIPE.spec: 'encodeMessagePipeHandle',
mojom.NULLABLE_DCPIPE.spec: 'encodeConsumerHandle',
mojom.NULLABLE_DPPIPE.spec: 'encodeProducerHandle',
mojom.NULLABLE_HANDLE.spec: 'encodeHandle',
mojom.NULLABLE_MSGPIPE.spec: 'encodeMessagePipeHandle',
mojom.NULLABLE_SHAREDBUFFER.spec: 'encodeSharedBufferHandle',
mojom.NULLABLE_STRING.spec: 'encodeString',
mojom.SHAREDBUFFER.spec: 'encodeSharedBufferHandle',
mojom.STRING.spec: 'encodeString',
mojom.UINT16.spec: 'encodeUint16',
mojom.UINT32.spec: 'encodeUint32',
mojom.UINT64.spec: 'encodeUint64',
mojom.UINT8.spec: 'encodeUint8',
}
def GetDartType(kind):
if kind.imported_from:
return kind.imported_from["unique_name"] + "." + GetNameForElement(kind)
return GetNameForElement(kind)
def DartDefaultValue(field):
if field.default:
if mojom.IsStructKind(field.kind):
assert field.default == "default"
return "new %s()" % GetDartType(field.kind)
return ExpressionToText(field.default)
if field.kind in mojom.PRIMITIVES:
return _kind_to_dart_default_value[field.kind]
if mojom.IsStructKind(field.kind):
return "null"
if mojom.IsUnionKind(field.kind):
return "null"
if mojom.IsArrayKind(field.kind):
return "null"
if mojom.IsMapKind(field.kind):
return "null"
if mojom.IsInterfaceKind(field.kind) or \
mojom.IsInterfaceRequestKind(field.kind):
return "null"
if mojom.IsEnumKind(field.kind):
return "0"
def DartDeclType(kind):
if kind in mojom.PRIMITIVES:
return _kind_to_dart_decl_type[kind]
if mojom.IsStructKind(kind):
return GetDartType(kind)
if mojom.IsUnionKind(kind):
return GetDartType(kind)
if mojom.IsArrayKind(kind):
array_type = DartDeclType(kind.kind)
return "List<" + array_type + ">"
if mojom.IsMapKind(kind):
key_type = DartDeclType(kind.key_kind)
value_type = DartDeclType(kind.value_kind)
return "Map<"+ key_type + ", " + value_type + ">"
if mojom.IsInterfaceKind(kind) or \
mojom.IsInterfaceRequestKind(kind):
return "Object"
if mojom.IsEnumKind(kind):
return "int"
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)])
def CamelCase(name):
uccc = UpperCamelCase(name)
return uccc[0].lower() + uccc[1:]
def ConstantStyle(name):
components = NameToComponent(name)
if components[0] == 'k' and len(components) > 1:
components = components[1:]
# variable cannot starts with a digit.
if components[0][0].isdigit():
components[0] = '_' + components[0]
return '_'.join([x.upper() for x in components])
def DotToUnderscore(name):
return name.replace('.', '_')
def GetNameForElement(element):
if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or
mojom.IsStructKind(element) or mojom.IsUnionKind(element)):
return UpperCamelCase(element.name)
if mojom.IsInterfaceRequestKind(element):
return GetNameForElement(element.kind)
if isinstance(element, (mojom.Method,
mojom.Parameter,
mojom.Field)):
return CamelCase(element.name)
if isinstance(element, mojom.EnumValue):
return (GetNameForElement(element.enum) + '.' +
ConstantStyle(element.name))
if isinstance(element, (mojom.NamedValue,
mojom.Constant,
mojom.EnumField)):
return ConstantStyle(element.name)
raise Exception('Unexpected element: %s' % element)
def GetUnionFieldTagName(element):
if not isinstance(element, mojom.UnionField):
raise Exception('Unexpected element: %s is not a union field.' % element)
return CamelCase(element.name)
def GetInterfaceResponseName(method):
return UpperCamelCase(method.name + 'Response')
def GetDartTrueFalse(value):
return 'true' if value else 'false'
def GetArrayNullabilityFlags(kind):
"""Returns nullability flags for an array type, see codec.dart.
As we have dedicated decoding functions for arrays, we have to pass
nullability information about both the array itself, as well as the array
element type there.
"""
assert mojom.IsArrayKind(kind)
ARRAY_NULLABLE = 'bindings.kArrayNullable'
ELEMENT_NULLABLE = 'bindings.kElementNullable'
NOTHING_NULLABLE = 'bindings.kNothingNullable'
flags_to_set = []
if mojom.IsNullableKind(kind):
flags_to_set.append(ARRAY_NULLABLE)
if mojom.IsNullableKind(kind.kind):
flags_to_set.append(ELEMENT_NULLABLE)
if not flags_to_set:
flags_to_set = [NOTHING_NULLABLE]
return ' | '.join(flags_to_set)
def AppendDecodeParams(initial_params, kind, bit):
""" Appends standard parameters for decode calls. """
params = list(initial_params)
if (kind == mojom.BOOL):
params.append(str(bit))
if mojom.IsReferenceKind(kind):
if mojom.IsArrayKind(kind):
params.append(GetArrayNullabilityFlags(kind))
else:
params.append(GetDartTrueFalse(mojom.IsNullableKind(kind)))
if mojom.IsInterfaceKind(kind):
params.append('%sProxy.newFromEndpoint' % GetDartType(kind))
if mojom.IsArrayKind(kind) and mojom.IsInterfaceKind(kind.kind):
params.append('%sProxy.newFromEndpoint' % GetDartType(kind.kind))
if mojom.IsInterfaceRequestKind(kind):
params.append('%sStub.newFromEndpoint' % GetDartType(kind.kind))
if mojom.IsArrayKind(kind) and mojom.IsInterfaceRequestKind(kind.kind):
params.append('%sStub.newFromEndpoint' % GetDartType(kind.kind.kind))
if mojom.IsArrayKind(kind):
params.append(GetArrayExpectedLength(kind))
return params
def AppendEncodeParams(initial_params, kind, bit):
""" Appends standard parameters shared between encode and decode calls. """
params = list(initial_params)
if (kind == mojom.BOOL):
params.append(str(bit))
if mojom.IsReferenceKind(kind):
if mojom.IsArrayKind(kind):
params.append(GetArrayNullabilityFlags(kind))
else:
params.append(GetDartTrueFalse(mojom.IsNullableKind(kind)))
if mojom.IsArrayKind(kind):
params.append(GetArrayExpectedLength(kind))
return params
def DecodeMethod(kind, offset, bit):
def _DecodeMethodName(kind):
if mojom.IsArrayKind(kind):
return _DecodeMethodName(kind.kind) + 'Array'
if mojom.IsEnumKind(kind):
return _DecodeMethodName(mojom.INT32)
if mojom.IsInterfaceRequestKind(kind):
return 'decodeInterfaceRequest'
if mojom.IsInterfaceKind(kind):
return 'decodeServiceInterface'
return _spec_to_decode_method[kind.spec]
methodName = _DecodeMethodName(kind)
params = AppendDecodeParams([ str(offset) ], kind, bit)
return '%s(%s)' % (methodName, ', '.join(params))
def EncodeMethod(kind, variable, offset, bit):
def _EncodeMethodName(kind):
if mojom.IsStructKind(kind):
return 'encodeStruct'
if mojom.IsUnionKind(kind):
return 'encodeUnion'
if mojom.IsArrayKind(kind):
return _EncodeMethodName(kind.kind) + 'Array'
if mojom.IsEnumKind(kind):
return _EncodeMethodName(mojom.INT32)
if mojom.IsInterfaceRequestKind(kind):
return 'encodeInterfaceRequest'
if mojom.IsInterfaceKind(kind):
return 'encodeInterface'
return _spec_to_encode_method[kind.spec]
methodName = _EncodeMethodName(kind)
params = AppendEncodeParams([ variable, str(offset) ], kind, bit)
return '%s(%s)' % (methodName, ', '.join(params))
def TranslateConstants(token):
if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
# Both variable and enum constants are constructed like:
# NamespaceUid.Struct.Enum_CONSTANT_NAME
name = ""
if token.imported_from:
name = token.imported_from["unique_name"] + "."
if token.parent_kind:
name = name + token.parent_kind.name + "."
if isinstance(token, mojom.EnumValue):
name = name + token.enum.name + "_"
return name + token.name
if isinstance(token, mojom.BuiltinValue):
if token.value == "double.INFINITY" or token.value == "float.INFINITY":
return "double.INFINITY";
if token.value == "double.NEGATIVE_INFINITY" or \
token.value == "float.NEGATIVE_INFINITY":
return "double.NEGATIVE_INFINITY";
if token.value == "double.NAN" or token.value == "float.NAN":
return "double.NAN";
# Strip leading '+'.
if token[0] == '+':
token = token[1:]
return token
def ExpressionToText(token):
if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
return str(token.resolved_value)
return TranslateConstants(token)
def GetArrayKind(kind, size = None):
if size is None:
return mojom.Array(kind)
else:
array = mojom.Array(kind, 0)
array.dart_map_size = size
return array
def GetArrayExpectedLength(kind):
if mojom.IsArrayKind(kind) and kind.length is not None:
return getattr(kind, 'dart_map_size', str(kind.length))
else:
return 'bindings.kUnspecifiedArrayLength'
def IsPointerArrayKind(kind):
if not mojom.IsArrayKind(kind):
return False
sub_kind = kind.kind
return mojom.IsObjectKind(sub_kind)
def ParseStringAttribute(attribute):
assert isinstance(attribute, basestring)
return attribute
def GetPackage(module):
if module.attributes and 'DartPackage' in module.attributes:
return ParseStringAttribute(module.attributes['DartPackage'])
# Default package.
return 'mojom'
def GetImportUri(module):
package = GetPackage(module);
elements = module.namespace.split('.')
elements.append("%s" % module.name)
return os.path.join(package, *elements)
class Generator(generator.Generator):
dart_filters = {
'array_expected_length': GetArrayExpectedLength,
'array': GetArrayKind,
'decode_method': DecodeMethod,
'default_value': DartDefaultValue,
'encode_method': EncodeMethod,
'expression_to_text': ExpressionToText,
'is_map_kind': mojom.IsMapKind,
'is_nullable_kind': mojom.IsNullableKind,
'is_pointer_array_kind': IsPointerArrayKind,
'is_struct_kind': mojom.IsStructKind,
'is_union_kind': mojom.IsUnionKind,
'dart_true_false': GetDartTrueFalse,
'dart_type': DartDeclType,
'name': GetNameForElement,
'tag_name': GetUnionFieldTagName,
'interface_response_name': GetInterfaceResponseName,
'dot_to_underscore': DotToUnderscore,
}
def GetParameters(self, args):
return {
"namespace": self.module.namespace,
"imports": self.GetImports(args),
"kinds": self.module.kinds,
"enums": self.module.enums,
"module": resolver.ResolveConstants(self.module, ExpressionToText),
"structs": self.GetStructs() + self.GetStructsFromMethods(),
"unions": self.GetUnions(),
"interfaces": self.GetInterfaces(),
"imported_interfaces": self.GetImportedInterfaces(),
"imported_from": self.ImportedFrom(),
}
@UseJinja("dart_templates/module.lib.tmpl", filters=dart_filters)
def GenerateLibModule(self, args):
return self.GetParameters(args)
def GenerateFiles(self, args):
elements = self.module.namespace.split('.')
elements.append("%s.dart" % self.module.name)
package_name = GetPackage(self.module)
lib_module = self.GenerateLibModule(args)
pkg_path = os.path.join("dart-pkg", package_name, "lib", *elements)
self.Write(lib_module, pkg_path)
gen_path = os.path.join("dart-gen", "mojom/lib", *elements)
full_gen_path = os.path.join(self.output_dir, gen_path)
self.Write(lib_module, gen_path)
link = self.MatchMojomFilePath("%s.dart" % self.module.name)
full_link_path = os.path.join(self.output_dir, link)
if os.path.exists(full_link_path):
os.unlink(full_link_path)
fileutil.EnsureDirectoryExists(os.path.dirname(full_link_path))
try:
if sys.platform == "win32":
shutil.copy(full_gen_path, full_link_path)
else:
os.symlink(full_gen_path, full_link_path)
except OSError as e:
# Errno 17 is file already exists. If the link fails because file already
# exists assume another instance of this script tried to create the same
# file and continue on.
if e.errno != 17:
raise e
def GetImports(self, args):
used_names = set()
for each_import in self.module.imports:
simple_name = each_import["module_name"].split(".")[0]
# Since each import is assigned a library in Dart, they need to have
# unique names.
unique_name = simple_name
counter = 0
while unique_name in used_names:
counter += 1
unique_name = simple_name + str(counter)
used_names.add(unique_name)
each_import["unique_name"] = unique_name + '_mojom'
counter += 1
each_import["rebased_path"] = GetImportUri(each_import['module'])
return self.module.imports
def GetImportedInterfaces(self):
interface_to_import = {}
for each_import in self.module.imports:
for each_interface in each_import["module"].interfaces:
name = each_interface.name
interface_to_import[name] = each_import["unique_name"] + "." + name
return interface_to_import
def ImportedFrom(self):
interface_to_import = {}
for each_import in self.module.imports:
for each_interface in each_import["module"].interfaces:
name = each_interface.name
interface_to_import[name] = each_import["unique_name"] + "."
return interface_to_import