X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/2d39b0e377c0896910ee49ae70082ba665faf986..ed1e77d3adeb83d26fd1dfb16dd84cabdcefd250:/inspector/scripts/codegen/generator.py diff --git a/inspector/scripts/codegen/generator.py b/inspector/scripts/codegen/generator.py new file mode 100755 index 0000000..a1923fe --- /dev/null +++ b/inspector/scripts/codegen/generator.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python +# +# Copyright (c) 2014 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 os.path +import re +from string import Template + +from generator_templates import GeneratorTemplates as Templates +from models import PrimitiveType, ObjectType, ArrayType, EnumType, AliasedType, Frameworks + +log = logging.getLogger('global') + + +def ucfirst(str): + return str[:1].upper() + str[1:] + +_ALWAYS_UPPERCASED_ENUM_VALUE_SUBSTRINGS = set(['API', 'CSS', 'DOM', 'HTML', 'XHR', 'XML']) + +# These objects are built manually by creating and setting InspectorValues. +# Before sending these over the protocol, their shapes are checked against the specification. +# So, any types referenced by these types require debug-only assertions that check values. +# Calculating necessary assertions is annoying, and adds a lot of complexity to the generator. + +# FIXME: This should be converted into a property in JSON. +_TYPES_NEEDING_RUNTIME_CASTS = set([ + "Runtime.RemoteObject", + "Runtime.PropertyDescriptor", + "Runtime.InternalPropertyDescriptor", + "Runtime.CollectionEntry", + "Debugger.FunctionDetails", + "Debugger.CallFrame", + "Canvas.TraceLog", + "Canvas.ResourceInfo", + "Canvas.ResourceState", + # This should be a temporary hack. TimelineEvent should be created via generated C++ API. + "Timeline.TimelineEvent", + # For testing purposes only. + "Test.TypeNeedingCast" +]) + +# FIXME: This should be converted into a property in JSON. +_TYPES_WITH_OPEN_FIELDS = set([ + "Timeline.TimelineEvent", + # InspectorStyleSheet not only creates this property but wants to read it and modify it. + "CSS.CSSProperty", + # InspectorResourceAgent needs to update mime-type. + "Network.Response", + # For testing purposes only. + "Test.OpenParameterBundle" +]) + + +class Generator: + def __init__(self, model, input_filepath): + self._model = model + self._input_filepath = input_filepath + + def model(self): + return self._model + + def generate_license(self): + return Template(Templates.CopyrightBlock).substitute(None, inputFilename=os.path.basename(self._input_filepath)) + + # These methods are overridden by subclasses. + def non_supplemental_domains(self): + return filter(lambda domain: not domain.is_supplemental, self.model().domains) + + def domains_to_generate(self): + return self.non_supplemental_domains() + + def generate_output(self): + pass + + def output_filename(self): + pass + + def encoding_for_enum_value(self, enum_value): + if not hasattr(self, "_assigned_enum_values"): + self._traverse_and_assign_enum_values() + + return self._enum_value_encodings[enum_value] + + def assigned_enum_values(self): + if not hasattr(self, "_assigned_enum_values"): + self._traverse_and_assign_enum_values() + + return self._assigned_enum_values[:] # Slice. + + @staticmethod + def type_needs_runtime_casts(_type): + return _type.qualified_name() in _TYPES_NEEDING_RUNTIME_CASTS + + @staticmethod + def type_has_open_fields(_type): + return _type.qualified_name() in _TYPES_WITH_OPEN_FIELDS + + def type_needs_shape_assertions(self, _type): + if not hasattr(self, "_types_needing_shape_assertions"): + self.calculate_types_requiring_shape_assertions(self.model().domains) + + return _type in self._types_needing_shape_assertions + + # To restrict the domains over which we compute types needing assertions, call this method + # before generating any output with the desired domains parameter. The computed + # set of types will not be automatically regenerated on subsequent calls to + # Generator.types_needing_shape_assertions(). + def calculate_types_requiring_shape_assertions(self, domains): + domain_names = map(lambda domain: domain.domain_name, domains) + log.debug("> Calculating types that need shape assertions (eligible domains: %s)" % ", ".join(domain_names)) + + # Mutates the passed-in set; this simplifies checks to prevent infinite recursion. + def gather_transitively_referenced_types(_type, gathered_types): + if _type in gathered_types: + return + + if isinstance(_type, ObjectType): + log.debug("> Adding type %s to list of types needing shape assertions." % _type.qualified_name()) + gathered_types.add(_type) + for type_member in _type.members: + gather_transitively_referenced_types(type_member.type, gathered_types) + elif isinstance(_type, EnumType): + log.debug("> Adding type %s to list of types needing shape assertions." % _type.qualified_name()) + gathered_types.add(_type) + elif isinstance(_type, AliasedType): + gather_transitively_referenced_types(_type.aliased_type, gathered_types) + elif isinstance(_type, ArrayType): + gather_transitively_referenced_types(_type.element_type, gathered_types) + + found_types = set() + for domain in domains: + for declaration in domain.type_declarations: + if declaration.type.qualified_name() in _TYPES_NEEDING_RUNTIME_CASTS: + log.debug("> Gathering types referenced by %s to generate shape assertions." % declaration.type.qualified_name()) + gather_transitively_referenced_types(declaration.type, found_types) + + self._types_needing_shape_assertions = found_types + + # Private helper instance methods. + def _traverse_and_assign_enum_values(self): + self._enum_value_encodings = {} + self._assigned_enum_values = [] + all_types = [] + + domains = self.non_supplemental_domains() + + for domain in domains: + for type_declaration in domain.type_declarations: + all_types.append(type_declaration.type) + for type_member in type_declaration.type_members: + all_types.append(type_member.type) + + for domain in domains: + for event in domain.events: + all_types.extend([parameter.type for parameter in event.event_parameters]) + + for domain in domains: + for command in domain.commands: + all_types.extend([parameter.type for parameter in command.call_parameters]) + all_types.extend([parameter.type for parameter in command.return_parameters]) + + for _type in all_types: + if not isinstance(_type, EnumType): + continue + map(self._assign_encoding_for_enum_value, _type.enum_values()) + + def _assign_encoding_for_enum_value(self, enum_value): + if enum_value in self._enum_value_encodings: + return + + self._enum_value_encodings[enum_value] = len(self._assigned_enum_values) + self._assigned_enum_values.append(enum_value) + + # Miscellaneous text manipulation routines. + def wrap_with_guard_for_domain(self, domain, text): + if self.model().framework is Frameworks.WebInspector: + return text + guard = domain.feature_guard + if guard: + return Generator.wrap_with_guard(guard, text) + return text + + @staticmethod + def wrap_with_guard(guard, text): + return '\n'.join([ + '#if %s' % guard, + text, + '#endif // %s' % guard, + ]) + + @staticmethod + def stylized_name_for_enum_value(enum_value): + regex = '(%s)' % "|".join(_ALWAYS_UPPERCASED_ENUM_VALUE_SUBSTRINGS) + + def replaceCallback(match): + return match.group(1).upper() + + # Split on hyphen, introduce camelcase, and force uppercasing of acronyms. + subwords = map(ucfirst, enum_value.split('-')) + return re.sub(re.compile(regex, re.IGNORECASE), replaceCallback, "".join(subwords)) + + @staticmethod + def js_name_for_parameter_type(_type): + _type = _type + if isinstance(_type, AliasedType): + _type = _type.aliased_type # Fall through. + if isinstance(_type, EnumType): + _type = _type.primitive_type # Fall through. + + if isinstance(_type, (ArrayType, ObjectType)): + return 'object' + if isinstance(_type, PrimitiveType): + if _type.qualified_name() in ['object', 'any']: + return 'object' + elif _type.qualified_name() in ['integer', 'number']: + return 'number' + else: + return _type.qualified_name()