[GTK] Make script dialogs modal to the current web view only
[WebKit-https.git] / Source / JavaScriptCore / Scripts / wkbuiltins / builtins_model.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2015-2016 Apple Inc. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
8 # 1. Redistributions of source code must retain the above copyright
9 #    notice, this list of conditions and the following disclaimer.
10 # 2. Redistributions in binary form must reproduce the above copyright
11 #    notice, this list of conditions and the following disclaimer in the
12 #    documentation and/or other materials provided with the distribution.
13 #
14 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 # THE POSSIBILITY OF SUCH DAMAGE.
25
26 import logging
27 import re
28 import os
29
30 from builtins_templates import BuiltinsGeneratorTemplates as Templates
31
32 log = logging.getLogger('global')
33
34 _FRAMEWORK_CONFIG_MAP = {
35     "JavaScriptCore": {
36         "macro_prefix": "JSC",
37         "namespace": "JSC",
38     },
39     "WebCore": {
40         "macro_prefix": "WEBCORE",
41         "namespace": "WebCore",
42     },
43 }
44
45 functionHeadRegExp = re.compile(r"(?:@[\w|=\[\] \"\.]+\s*\n)*function\s+\w+\s*\(.*?\)", re.MULTILINE | re.DOTALL)
46 functionGlobalPrivateRegExp = re.compile(r".*^@globalPrivate", re.MULTILINE | re.DOTALL)
47 functionIntrinsicRegExp = re.compile(r".*^@intrinsic=(\w+)", re.MULTILINE | re.DOTALL)
48 functionIsConstructorRegExp = re.compile(r".*^@constructor", re.MULTILINE | re.DOTALL)
49 functionIsGetterRegExp = re.compile(r".*^@getter", re.MULTILINE | re.DOTALL)
50 functionNameRegExp = re.compile(r"function\s+(\w+)\s*\(", re.MULTILINE | re.DOTALL)
51 functionOverriddenNameRegExp = re.compile(r".*^@overriddenName=(\".+\")$", re.MULTILINE | re.DOTALL)
52 functionParameterFinder = re.compile(r"^function\s+(?:\w+)\s*\(((?:\s*\w+)?\s*(?:\s*,\s*\w+)*)?\s*\)", re.MULTILINE | re.DOTALL)
53
54 multilineCommentRegExp = re.compile(r"\/\*.*?\*\/", re.MULTILINE | re.DOTALL)
55 singleLineCommentRegExp = re.compile(r"\/\/.*?\n", re.MULTILINE | re.DOTALL)
56 keyValueAnnotationCommentRegExp = re.compile(r"^\/\/ @(\w+)=([^=]+?)\n", re.MULTILINE | re.DOTALL)
57 flagAnnotationCommentRegExp = re.compile(r"^\/\/ @(\w+)[^=]*?\n", re.MULTILINE | re.DOTALL)
58 lineWithOnlySingleLineCommentRegExp = re.compile(r"^\s*\/\/\n", re.MULTILINE | re.DOTALL)
59 lineWithTrailingSingleLineCommentRegExp = re.compile(r"\s*\/\/\n", re.MULTILINE | re.DOTALL)
60 leadingWhitespaceRegExp = re.compile(r"^ +", re.MULTILINE | re.DOTALL)
61 multipleEmptyLinesRegExp = re.compile(r"\n{2,}", re.MULTILINE | re.DOTALL)
62
63 class ParseException(Exception):
64     pass
65
66
67 class Framework:
68     def __init__(self, name):
69         self._settings = _FRAMEWORK_CONFIG_MAP[name]
70         self.name = name
71
72     def setting(self, key, default=''):
73         return self._settings.get(key, default)
74
75     @staticmethod
76     def fromString(frameworkString):
77         if frameworkString == "JavaScriptCore":
78             return Frameworks.JavaScriptCore
79
80         if frameworkString == "WebCore":
81             return Frameworks.WebCore
82
83         raise ParseException("Unknown framework: %s" % frameworkString)
84
85
86 class Frameworks:
87     JavaScriptCore = Framework("JavaScriptCore")
88     WebCore = Framework("WebCore")
89
90
91 class BuiltinObject:
92     def __init__(self, object_name, annotations, functions):
93         self.object_name = object_name
94         self.annotations = annotations
95         self.functions = functions
96         self.collection = None  # Set by the owning BuiltinsCollection
97
98         for function in self.functions:
99             function.object = self
100
101
102 class BuiltinFunction:
103     def __init__(self, function_name, function_source, parameters, is_constructor, is_global_private, intrinsic, overridden_name):
104         self.function_name = function_name
105         self.function_source = function_source
106         self.parameters = parameters
107         self.is_constructor = is_constructor
108         self.is_global_private = is_global_private
109         self.intrinsic = intrinsic
110         self.overridden_name = overridden_name
111         self.object = None  # Set by the owning BuiltinObject
112
113     @staticmethod
114     def fromString(function_string):
115         function_source = multilineCommentRegExp.sub("", function_string)
116
117         intrinsic = "NoIntrinsic"
118         intrinsicMatch = functionIntrinsicRegExp.search(function_source)
119         if intrinsicMatch:
120             intrinsic = intrinsicMatch.group(1)
121             function_source = functionIntrinsicRegExp.sub("", function_source)
122
123         overridden_name = None
124         overriddenNameMatch = functionOverriddenNameRegExp.search(function_source)
125         if overriddenNameMatch:
126             overridden_name = overriddenNameMatch.group(1)
127             function_source = functionOverriddenNameRegExp.sub("", function_source)
128
129         if not os.getenv("CONFIGURATION", "Debug").startswith("Debug"):
130             function_source = lineWithOnlySingleLineCommentRegExp.sub("", function_source)
131             function_source = lineWithTrailingSingleLineCommentRegExp.sub("\n", function_source)
132             function_source = leadingWhitespaceRegExp.sub("", function_source)
133             function_source = multipleEmptyLinesRegExp.sub("\n", function_source)
134
135         function_name = functionNameRegExp.findall(function_source)[0]
136         is_constructor = functionIsConstructorRegExp.match(function_source) != None
137         is_getter = functionIsGetterRegExp.match(function_source) != None
138         is_global_private = functionGlobalPrivateRegExp.match(function_source) != None
139         parameters = [s.strip() for s in functionParameterFinder.findall(function_source)[0].split(',')]
140         if len(parameters[0]) == 0:
141             parameters = []
142
143         if is_getter and not overridden_name:
144             overridden_name = "\"get %s\"" % (function_name)
145
146         if not overridden_name:
147             overridden_name = "static_cast<const char*>(nullptr)"
148
149         return BuiltinFunction(function_name, function_source, parameters, is_constructor, is_global_private, intrinsic, overridden_name)
150
151     def __str__(self):
152         interface = "%s(%s)" % (self.function_name, ', '.join(self.parameters))
153         if self.is_constructor:
154             interface = interface + " [Constructor]"
155
156         return interface
157
158
159 class BuiltinsCollection:
160     def __init__(self, framework_name):
161         self._copyright_lines = set()
162         self.objects = []
163         self.framework = Framework.fromString(framework_name)
164         log.debug("Created new Builtins collection.")
165
166     def parse_builtins_file(self, filename, text):
167         log.debug("Parsing builtins file: %s" % filename)
168
169         parsed_copyrights = set(self._parse_copyright_lines(text))
170         self._copyright_lines = self._copyright_lines.union(parsed_copyrights)
171
172         log.debug("Found copyright lines:")
173         for line in self._copyright_lines:
174             log.debug(line)
175         log.debug("")
176
177         object_annotations = self._parse_annotations(text)
178
179         object_name, ext = os.path.splitext(os.path.basename(filename))
180         log.debug("Parsing object: %s" % object_name)
181
182         parsed_functions = self._parse_functions(text)
183         for function in parsed_functions:
184             function.object = object_name
185
186         log.debug("Parsed functions:")
187         for func in parsed_functions:
188             log.debug(func)
189         log.debug("")
190
191         new_object = BuiltinObject(object_name, object_annotations, parsed_functions)
192         new_object.collection = self
193         self.objects.append(new_object)
194
195     def copyrights(self):
196         owner_to_years = dict()
197         copyrightYearRegExp = re.compile(r"(\d{4})[, ]{0,2}")
198         ownerStartRegExp = re.compile(r"[^\d, ]")
199
200         # Returns deduplicated copyrights keyed on the owner.
201         for line in self._copyright_lines:
202             years = set(copyrightYearRegExp.findall(line))
203             ownerIndex = ownerStartRegExp.search(line).start()
204             owner = line[ownerIndex:]
205             log.debug("Found years: %s and owner: %s" % (years, owner))
206             if owner not in owner_to_years:
207                 owner_to_years[owner] = set()
208
209             owner_to_years[owner] = owner_to_years[owner].union(years)
210
211         result = []
212
213         for owner, years in owner_to_years.items():
214             sorted_years = list(years)
215             sorted_years.sort()
216             result.append("%s %s" % (', '.join(sorted_years), owner))
217
218         return result
219
220     def all_functions(self):
221         result = []
222         for object in self.objects:
223             result.extend(object.functions)
224
225         result.sort()
226         return result
227
228     def all_internal_functions(self):
229         result = []
230         for object in [o for o in self.objects if 'internal' in o.annotations]:
231             result.extend(object.functions)
232
233         result.sort()
234         return result
235
236     # Private methods.
237
238     def _parse_copyright_lines(self, text):
239         licenseBlock = multilineCommentRegExp.findall(text)[0]
240         licenseBlock = licenseBlock[:licenseBlock.index("Redistribution")]
241
242         copyrightLines = [Templates.DefaultCopyright]
243         for line in licenseBlock.split("\n"):
244             line = line.replace("/*", "")
245             line = line.replace("*/", "")
246             line = line.replace("*", "")
247             line = line.replace("Copyright", "")
248             line = line.replace("copyright", "")
249             line = line.replace("(C)", "")
250             line = line.replace("(c)", "")
251             line = line.strip()
252
253             if len(line) == 0:
254                 continue
255
256             copyrightLines.append(line)
257
258         return copyrightLines
259
260     def _parse_annotations(self, text):
261         annotations = {}
262
263         for match in keyValueAnnotationCommentRegExp.finditer(text):
264             (key, value) = match.group(1, 2)
265             log.debug("Found annotation: '%s' => '%s'" % (key, value))
266             if key in annotations:
267                 raise ParseException("Duplicate annotation found: %s" % key)
268
269             annotations[key] = value
270
271         for match in flagAnnotationCommentRegExp.finditer(text):
272             key = match.group(1)
273             log.debug("Found annotation: '%s' => 'TRUE'" % key)
274             if key in annotations:
275                 raise ParseException("Duplicate annotation found: %s" % key)
276
277             annotations[key] = True
278
279         return annotations
280
281     def _parse_functions(self, text):
282         text = multilineCommentRegExp.sub("/**/", singleLineCommentRegExp.sub("//\n", text))
283
284         matches = [func for func in functionHeadRegExp.finditer(text)]
285         functionBounds = []
286         start = 0
287         end = 0
288         for match in matches:
289             start = match.start()
290             if start < end:
291                 continue
292             end = match.end()
293             while text[end] != '{':
294                 end = end + 1
295             depth = 1
296             end = end + 1
297             while depth > 0:
298                 if text[end] == '{':
299                     depth = depth + 1
300                 elif text[end] == '}':
301                     depth = depth - 1
302                 end = end + 1
303             functionBounds.append((start, end))
304
305         functionStrings = [text[start:end].strip() for (start, end) in functionBounds]
306         return map(BuiltinFunction.fromString, functionStrings)