]> git.saurik.com Git - apple/javascriptcore.git/blobdiff - inspector/scripts/codegen/generator.py
JavaScriptCore-7601.1.46.3.tar.gz
[apple/javascriptcore.git] / inspector / scripts / codegen / generator.py
diff --git a/inspector/scripts/codegen/generator.py b/inspector/scripts/codegen/generator.py
new file mode 100755 (executable)
index 0000000..a1923fe
--- /dev/null
@@ -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()