blob: 0b9afd197a01eb53a5c6b767bee33d7de642a75d [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (c) 2014-2018 Apple Inc. All rights reserved.
# Copyright (c) 2014 University of Washington. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
import logging
import re
import string
from operator import methodcaller
from string import Template
try:
from .cpp_generator import CppGenerator
from .cpp_generator_templates import CppGeneratorTemplates as CppTemplates
from .generator import Generator, ucfirst
from .models import EnumType, ObjectType, PrimitiveType, AliasedType, ArrayType, Frameworks
except ValueError:
from cpp_generator import CppGenerator
from cpp_generator_templates import CppGeneratorTemplates as CppTemplates
from generator import Generator, ucfirst
from models import EnumType, ObjectType, PrimitiveType, AliasedType, ArrayType, Frameworks
log = logging.getLogger('global')
class CppProtocolTypesHeaderGenerator(CppGenerator):
def __init__(self, *args, **kwargs):
CppGenerator.__init__(self, *args, **kwargs)
def output_filename(self):
return "%sProtocolObjects.h" % self.protocol_name()
def generate_output(self):
domains = self.domains_to_generate()
self.calculate_types_requiring_shape_assertions(domains)
header_args = {
'includes': self._generate_secondary_header_includes(),
'typedefs': '',
}
sections = []
sections.append(self.generate_license())
sections.append(Template(CppTemplates.HeaderPrelude).substitute(None, **header_args))
sections.append('namespace Protocol {')
sections.append(self._generate_versions(domains))
sections.append(self._generate_forward_declarations(domains))
sections.append(self._generate_typedefs(domains))
sections.extend(self._generate_enum_constant_value_conversion_methods())
builder_sections = list(map(self._generate_builders_for_domain, domains))
sections.extend([section for section in builder_sections if len(section) > 0])
sections.append(self._generate_forward_declarations_for_binding_traits(domains))
sections.extend(self._generate_declarations_for_enum_conversion_methods(domains))
sections.append('} // namespace Protocol')
sections.append(Template(CppTemplates.HeaderPostlude).substitute(None, **header_args))
sections.extend(self._generate_hash_declarations(domains))
return "\n\n".join(sections)
# Private methods.
# FIXME: move builders out of classes, uncomment forward declaration
def _generate_secondary_header_includes(self):
header_includes = [
(["JavaScriptCore", "WebKit"], ("JavaScriptCore", "inspector/InspectorProtocolTypes.h")),
(["JavaScriptCore", "WebKit"], ("WTF", "wtf/Assertions.h"))
]
return '\n'.join(self.generate_includes_from_entries(header_includes))
def _generate_versions(self, domains):
sections = []
for domain in domains:
version = self.version_for_domain(domain)
if not version:
continue
domain_lines = []
domain_lines.append('namespace %s {' % domain.domain_name)
if isinstance(version, int):
domain_lines.append('static const unsigned VERSION = %s;' % version)
domain_lines.append('} // %s' % domain.domain_name)
sections.append(self.wrap_with_guard_for_domain(domain, '\n'.join(domain_lines)))
if len(sections) == 0:
return ''
return """// Versions.
%s
// End of versions.
""" % '\n\n'.join(sections)
def _generate_forward_declarations(self, domains):
sections = []
for domain in domains:
declaration_types = [decl.type for decl in self.type_declarations_for_domain(domain)]
object_types = [_type for _type in declaration_types if isinstance(_type, ObjectType)]
enum_types = [_type for _type in declaration_types if isinstance(_type, EnumType)]
sorted(object_types, key=methodcaller('raw_name'))
sorted(enum_types, key=methodcaller('raw_name'))
if len(object_types) + len(enum_types) == 0:
continue
domain_lines = []
domain_lines.append('namespace %s {' % domain.domain_name)
# Forward-declare all classes so the type builders won't break if rearranged.
domain_lines.extend('class %s;' % object_type.raw_name() for object_type in object_types)
domain_lines.extend('enum class %s;' % enum_type.raw_name() for enum_type in enum_types)
domain_lines.append('} // %s' % domain.domain_name)
sections.append(self.wrap_with_guard_for_domain(domain, '\n'.join(domain_lines)))
if len(sections) == 0:
return ''
else:
return """// Forward declarations.
%s
// End of forward declarations.
""" % '\n\n'.join(sections)
def _generate_typedefs(self, domains):
sections = list(map(self._generate_typedefs_for_domain, domains))
sections = [text for text in sections if len(text) > 0]
if len(sections) == 0:
return ''
else:
return """// Typedefs.
%s
// End of typedefs.""" % '\n\n'.join(sections)
def _generate_typedefs_for_domain(self, domain):
type_declarations = self.type_declarations_for_domain(domain)
primitive_declarations = [decl for decl in type_declarations if isinstance(decl.type, AliasedType)]
array_declarations = [decl for decl in type_declarations if isinstance(decl.type, ArrayType)]
if len(primitive_declarations) == 0 and len(array_declarations) == 0:
return ''
sections = []
for declaration in primitive_declarations:
primitive_name = CppGenerator.cpp_name_for_primitive_type(declaration.type.aliased_type)
typedef_lines = []
if len(declaration.description) > 0:
typedef_lines.append('/* %s */' % declaration.description)
typedef_lines.append('typedef %s %s;' % (primitive_name, declaration.type_name))
sections.append('\n'.join(typedef_lines))
for declaration in array_declarations:
element_type = CppGenerator.cpp_protocol_type_for_type(declaration.type.element_type)
typedef_lines = []
if len(declaration.description) > 0:
typedef_lines.append('/* %s */' % declaration.description)
typedef_lines.append('typedef JSON::ArrayOf<%s> %s;' % (element_type, declaration.type_name))
sections.append('\n'.join(typedef_lines))
lines = []
lines.append('namespace %s {' % domain.domain_name)
lines.append('\n'.join(sections))
lines.append('} // %s' % domain.domain_name)
return self.wrap_with_guard_for_domain(domain, '\n'.join(lines))
def _generate_enum_constant_value_conversion_methods(self):
if not self.assigned_enum_values():
return []
return_type = 'String'
return_type_with_export_macro = [return_type]
export_macro = self.model().framework.setting('export_macro', None)
if export_macro is not None:
return_type_with_export_macro[:0] = [export_macro]
lines = []
lines.append('namespace %s {' % self.helpers_namespace())
lines.append('\n'.join([
'%s getEnumConstantValue(int code);' % ' '.join(return_type_with_export_macro),
'',
'template<typename T> %s getEnumConstantValue(T enumValue)' % return_type,
'{',
' return getEnumConstantValue(static_cast<int>(enumValue));',
'}',
]))
lines.append('} // namespace %s' % self.helpers_namespace())
return lines
def _generate_builders_for_domain(self, domain):
sections = []
type_declarations = self.type_declarations_for_domain(domain)
for type_declaration in type_declarations:
if isinstance(type_declaration.type, EnumType):
sections.append(self._generate_struct_for_enum_declaration(type_declaration))
elif isinstance(type_declaration.type, ObjectType):
sections.append(self._generate_class_for_object_declaration(type_declaration, domain))
sections = [section for section in sections if len(section) > 0]
if len(sections) == 0:
return ''
lines = []
lines.append('namespace %s {' % domain.domain_name)
lines.append('\n'.join(sections))
lines.append('} // %s' % domain.domain_name)
return self.wrap_with_guard_for_domain(domain, '\n'.join(lines))
def _generate_class_for_object_declaration(self, type_declaration, domain):
if len(type_declaration.type_members) == 0:
return ''
enum_members = [member for member in type_declaration.type_members if isinstance(member.type, EnumType) and member.type.is_anonymous]
required_members = [member for member in type_declaration.type_members if not member.is_optional]
optional_members = [member for member in type_declaration.type_members if member.is_optional]
object_name = type_declaration.type_name
lines = []
if len(type_declaration.description) > 0:
lines.append('/* %s */' % type_declaration.description)
base_class = 'JSON::Object'
if not Generator.type_has_open_fields(type_declaration.type):
base_class = base_class + 'Base'
lines.append('class %s : public %s {' % (object_name, base_class))
lines.append('public:')
for enum_member in enum_members:
lines.append(' // Named after property name \'%s\' while generating %s.' % (enum_member.member_name, object_name))
lines.append(self._generate_struct_for_anonymous_enum_member(enum_member))
lines.append(self._generate_builder_state_enum(type_declaration))
constructor_example = []
constructor_example.append(' * Ref<%s> result = %s::create()' % (object_name, object_name))
for member in required_members:
constructor_example.append(' * .set%s(...)' % ucfirst(member.member_name))
constructor_example.append(' * .release()')
builder_args = {
'objectType': object_name,
'constructorExample': '\n'.join(constructor_example) + ';',
}
lines.append(Template(CppTemplates.ProtocolObjectBuilderDeclarationPrelude).substitute(None, **builder_args))
for type_member in required_members:
lines.append(self._generate_builder_setter_for_member(type_member, domain))
lines.append(Template(CppTemplates.ProtocolObjectBuilderDeclarationPostlude).substitute(None, **builder_args))
for member in optional_members:
lines.append(self._generate_unchecked_setter_for_member(member, domain))
if Generator.type_has_open_fields(type_declaration.type):
lines.append('')
lines.append(' // Property names for type generated as open.')
open_members = Generator.open_fields(type_declaration)
for type_member in open_members:
export_macro = self.model().framework.setting('export_macro', None)
lines.append(' %s static const char* %s;' % (export_macro, ucfirst(type_member.member_name)))
lines.append('};')
lines.append('')
return '\n'.join(lines)
def _generate_struct_for_enum_declaration(self, enum_declaration):
lines = []
lines.append('/* %s */' % enum_declaration.description)
lines.extend(self._generate_struct_for_enum_type(enum_declaration.type_name, enum_declaration.type))
return '\n'.join(lines)
def _generate_struct_for_anonymous_enum_member(self, enum_member):
def apply_indentation(line):
if line.startswith(('#', '/*', '*/', '//')) or len(line) is 0:
return line
else:
return ' ' + line
indented_lines = list(map(apply_indentation, self._generate_struct_for_enum_type(enum_member.member_name, enum_member.type)))
return '\n'.join(indented_lines)
def _generate_struct_for_enum_type(self, enum_name, enum_type):
lines = []
enum_name = ucfirst(enum_name)
lines.append('enum class %s {' % enum_name)
for enum_value in enum_type.enum_values():
lines.append(' %s = %s,' % (Generator.stylized_name_for_enum_value(enum_value), self.encoding_for_enum_value(enum_value)))
lines.append('}; // enum class %s' % enum_name)
return lines # The caller may want to adjust indentation, so don't join these lines.
def _generate_builder_state_enum(self, type_declaration):
lines = []
required_members = [member for member in type_declaration.type_members if not member.is_optional]
enum_values = []
lines.append(' enum {')
lines.append(' NoFieldsSet = 0,')
for i in range(len(required_members)):
enum_value = "%sSet" % ucfirst(required_members[i].member_name)
enum_values.append(enum_value)
lines.append(' %s = 1 << %d,' % (enum_value, i))
if len(enum_values) > 0:
lines.append(' AllFieldsSet = (%s)' % ' | '.join(enum_values))
else:
lines.append(' AllFieldsSet = 0')
lines.append(' };')
lines.append('')
return '\n'.join(lines)
def _generate_builder_setter_for_member(self, type_member, domain):
setter_args = {
'camelName': ucfirst(type_member.member_name),
'keyedSet': CppGenerator.cpp_setter_method_for_type(type_member.type),
'name': type_member.member_name,
'parameterType': CppGenerator.cpp_type_for_type_member(type_member),
'helpersNamespace': self.helpers_namespace(),
}
lines = []
lines.append('')
lines.append(' Builder<STATE | %(camelName)sSet>& set%(camelName)s(%(parameterType)s value)' % setter_args)
lines.append(' {')
lines.append(' COMPILE_ASSERT(!(STATE & %(camelName)sSet), property_%(name)s_already_set);' % setter_args)
if isinstance(type_member.type, EnumType):
lines.append(' m_result->%(keyedSet)s("%(name)s"_s, Inspector::Protocol::%(helpersNamespace)s::getEnumConstantValue(value));' % setter_args)
else:
lines.append(' m_result->%(keyedSet)s("%(name)s"_s, value);' % setter_args)
lines.append(' return castState<%(camelName)sSet>();' % setter_args)
lines.append(' }')
return '\n'.join(lines)
def _generate_unchecked_setter_for_member(self, type_member, domain):
setter_args = {
'camelName': ucfirst(type_member.member_name),
'keyedSet': CppGenerator.cpp_setter_method_for_type(type_member.type),
'name': type_member.member_name,
'parameterType': CppGenerator.cpp_type_for_type_member(type_member),
'helpersNamespace': self.helpers_namespace(),
}
lines = []
lines.append('')
lines.append(' void set%(camelName)s(%(parameterType)s value)' % setter_args)
lines.append(' {')
if isinstance(type_member.type, EnumType):
lines.append(' JSON::ObjectBase::%(keyedSet)s("%(name)s"_s, Inspector::Protocol::%(helpersNamespace)s::getEnumConstantValue(value));' % setter_args)
elif CppGenerator.should_use_references_for_type(type_member.type):
lines.append(' JSON::ObjectBase::%(keyedSet)s("%(name)s"_s, WTFMove(value));' % setter_args)
else:
lines.append(' JSON::ObjectBase::%(keyedSet)s("%(name)s"_s, value);' % setter_args)
lines.append(' }')
return '\n'.join(lines)
def _generate_forward_declarations_for_binding_traits(self, domains):
# A list of (builder_type, needs_runtime_cast)
type_arguments = []
for domain in domains:
type_declarations = self.type_declarations_for_domain(domain)
declarations_to_generate = [decl for decl in type_declarations if self.type_needs_shape_assertions(decl.type)]
for type_declaration in declarations_to_generate:
for type_member in type_declaration.type_members:
if isinstance(type_member.type, EnumType):
type_arguments.append((CppGenerator.cpp_protocol_type_for_type_member(type_member, type_declaration), False))
if isinstance(type_declaration.type, ObjectType):
type_arguments.append((CppGenerator.cpp_protocol_type_for_type(type_declaration.type), Generator.type_needs_runtime_casts(type_declaration.type)))
struct_keywords = ['struct']
function_keywords = ['static void']
export_macro = self.model().framework.setting('export_macro', None)
if export_macro is not None:
struct_keywords.append(export_macro)
#function_keywords[1:1] = [export_macro]
lines = []
for argument in type_arguments:
lines.append('template<> %s BindingTraits<%s> {' % (' '.join(struct_keywords), argument[0]))
if argument[1]:
lines.append('static RefPtr<%s> runtimeCast(RefPtr<JSON::Value>&& value);' % argument[0])
lines.append('%s assertValueHasExpectedType(JSON::Value*);' % ' '.join(function_keywords))
lines.append('};')
return '\n'.join(lines)
def _generate_declarations_for_enum_conversion_methods(self, domains):
sections = []
sections.append('\n'.join([
'namespace %s {' % self.helpers_namespace(),
'',
'template<typename ProtocolEnumType>',
'Optional<ProtocolEnumType> parseEnumValueFromString(const String&);',
]))
def return_type_with_export_macro(cpp_protocol_type):
enum_return_type = 'Optional<%s>' % cpp_protocol_type
result_terms = [enum_return_type]
export_macro = self.model().framework.setting('export_macro', None)
if export_macro is not None:
result_terms[:0] = [export_macro]
return ' '.join(result_terms)
def type_member_is_anonymous_enum_type(type_member):
return isinstance(type_member.type, EnumType) and type_member.type.is_anonymous
for domain in domains:
type_declarations = self.type_declarations_for_domain(domain)
declaration_types = [decl.type for decl in type_declarations]
object_types = [_type for _type in declaration_types if isinstance(_type, ObjectType)]
enum_types = [_type for _type in declaration_types if isinstance(_type, EnumType)]
if len(object_types) + len(enum_types) == 0:
continue
sorted(object_types, key=methodcaller('raw_name'))
sorted(enum_types, key=methodcaller('raw_name'))
domain_lines = []
domain_lines.append("// Enums in the '%s' Domain" % domain.domain_name)
for enum_type in enum_types:
cpp_protocol_type = CppGenerator.cpp_protocol_type_for_type(enum_type)
domain_lines.append('template<>')
domain_lines.append('%s parseEnumValueFromString<%s>(const String&);' % (return_type_with_export_macro(cpp_protocol_type), cpp_protocol_type))
for object_type in object_types:
for enum_member in filter(type_member_is_anonymous_enum_type, object_type.members):
cpp_protocol_type = CppGenerator.cpp_protocol_type_for_type_member(enum_member, object_type.declaration())
domain_lines.append('template<>')
domain_lines.append('%s parseEnumValueFromString<%s>(const String&);' % (return_type_with_export_macro(cpp_protocol_type), cpp_protocol_type))
if len(domain_lines) == 1:
continue # No real declarations to emit, just the domain comment. Skip.
sections.append(self.wrap_with_guard_for_domain(domain, '\n'.join(domain_lines)))
if len(sections) == 1:
return [] # No real sections to emit, just the namespace and template declaration. Skip.
sections.append('} // namespace %s' % self.helpers_namespace())
return ['\n\n'.join(sections)]
def _generate_hash_declarations(self, domains):
lines = []
for domain in domains:
type_declarations = self.type_declarations_for_domain(domain)
declaration_types = [decl.type for decl in type_declarations]
enum_types = list(filter(lambda _type: isinstance(_type, EnumType), declaration_types))
if len(enum_types) == 0:
continue
if len(lines) == 0:
lines.append('namespace WTF {')
lines.append('')
lines.append('template<typename T> struct DefaultHash;')
lines.append('')
lines.append("// Hash declarations in the '%s' Domain" % domain.domain_name)
for enum_type in enum_types:
lines.append('template<>')
lines.append('struct DefaultHash<Inspector::Protocol::%s::%s> {' % (domain.domain_name, enum_type.raw_name()))
lines.append(' typedef IntHash<Inspector::Protocol::%s::%s> Hash;' % (domain.domain_name, enum_type.raw_name()))
lines.append('};')
if len(lines) == 0:
return []
lines.append('')
lines.append('} // namespace WTF')
return ['\n'.join(lines)]