Unreviewed, rolling out r221327.
[WebKit-https.git] / Source / JavaScriptCore / Scripts / builtins / 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 functionNameRegExp = re.compile(r"function\s+(\w+)\s*\(", re.MULTILINE | re.DOTALL)
50 functionParameterFinder = re.compile(r"^function\s+(?:\w+)\s*\(((?:\s*\w+)?\s*(?:\s*,\s*\w+)*)?\s*\)", re.MULTILINE | re.DOTALL)
51
52 multilineCommentRegExp = re.compile(r"\/\*.*?\*\/", re.MULTILINE | re.DOTALL)
53 singleLineCommentRegExp = re.compile(r"\/\/.*?\n", re.MULTILINE | re.DOTALL)
54 keyValueAnnotationCommentRegExp = re.compile(r"^\/\/ @(\w+)=([^=]+?)\n", re.MULTILINE | re.DOTALL)
55 flagAnnotationCommentRegExp = re.compile(r"^\/\/ @(\w+)[^=]*?\n", re.MULTILINE | re.DOTALL)
56 lineWithOnlySingleLineCommentRegExp = re.compile(r"^\s*\/\/\n", re.MULTILINE | re.DOTALL)
57 lineWithTrailingSingleLineCommentRegExp = re.compile(r"\s*\/\/\n", re.MULTILINE | re.DOTALL)
58 multipleEmptyLinesRegExp = re.compile(r"\n{2,}", re.MULTILINE | re.DOTALL)
59
60 class ParseException(Exception):
61     pass
62
63
64 class Framework:
65     def __init__(self, name):
66         self._settings = _FRAMEWORK_CONFIG_MAP[name]
67         self.name = name
68
69     def setting(self, key, default=''):
70         return self._settings.get(key, default)
71
72     @staticmethod
73     def fromString(frameworkString):
74         if frameworkString == "JavaScriptCore":
75             return Frameworks.JavaScriptCore
76
77         if frameworkString == "WebCore":
78             return Frameworks.WebCore
79
80         raise ParseException("Unknown framework: %s" % frameworkString)
81
82
83 class Frameworks:
84     JavaScriptCore = Framework("JavaScriptCore")
85     WebCore = Framework("WebCore")
86
87
88 class BuiltinObject:
89     def __init__(self, object_name, annotations, functions):
90         self.object_name = object_name
91         self.annotations = annotations
92         self.functions = functions
93         self.collection = None  # Set by the owning BuiltinsCollection
94
95         for function in self.functions:
96             function.object = self
97
98
99 class BuiltinFunction:
100     def __init__(self, function_name, function_source, parameters, is_constructor, is_global_private, intrinsic):
101         self.function_name = function_name
102         self.function_source = function_source
103         self.parameters = parameters
104         self.is_constructor = is_constructor
105         self.is_global_private = is_global_private
106         self.intrinsic = intrinsic
107         self.object = None  # Set by the owning BuiltinObject
108
109     @staticmethod
110     def fromString(function_string):
111         function_source = multilineCommentRegExp.sub("", function_string)
112
113         intrinsic = "NoIntrinsic"
114         intrinsicMatch = functionIntrinsicRegExp.search(function_source)
115         if intrinsicMatch:
116             intrinsic = intrinsicMatch.group(1)
117             function_source = functionIntrinsicRegExp.sub("", function_source)
118
119         if os.getenv("CONFIGURATION", "Debug").startswith("Debug"):
120             function_source = lineWithOnlySingleLineCommentRegExp.sub("", function_source)
121             function_source = lineWithTrailingSingleLineCommentRegExp.sub("\n", function_source)
122             function_source = multipleEmptyLinesRegExp.sub("\n", function_source)
123
124         function_name = functionNameRegExp.findall(function_source)[0]
125         is_constructor = functionIsConstructorRegExp.match(function_source) != None
126         is_global_private = functionGlobalPrivateRegExp.match(function_source) != None
127         parameters = [s.strip() for s in functionParameterFinder.findall(function_source)[0].split(',')]
128         if len(parameters[0]) == 0:
129             parameters = []
130
131         return BuiltinFunction(function_name, function_source, parameters, is_constructor, is_global_private, intrinsic)
132
133     def __str__(self):
134         interface = "%s(%s)" % (self.function_name, ', '.join(self.parameters))
135         if self.is_constructor:
136             interface = interface + " [Constructor]"
137
138         return interface
139
140
141 class BuiltinsCollection:
142     def __init__(self, framework_name):
143         self._copyright_lines = set()
144         self.objects = []
145         self.framework = Framework.fromString(framework_name)
146         log.debug("Created new Builtins collection.")
147
148     def parse_builtins_file(self, filename, text):
149         log.debug("Parsing builtins file: %s" % filename)
150
151         parsed_copyrights = set(self._parse_copyright_lines(text))
152         self._copyright_lines = self._copyright_lines.union(parsed_copyrights)
153
154         log.debug("Found copyright lines:")
155         for line in self._copyright_lines:
156             log.debug(line)
157         log.debug("")
158
159         object_annotations = self._parse_annotations(text)
160
161         object_name, ext = os.path.splitext(os.path.basename(filename))
162         log.debug("Parsing object: %s" % object_name)
163
164         parsed_functions = self._parse_functions(text)
165         for function in parsed_functions:
166             function.object = object_name
167
168         log.debug("Parsed functions:")
169         for func in parsed_functions:
170             log.debug(func)
171         log.debug("")
172
173         new_object = BuiltinObject(object_name, object_annotations, parsed_functions)
174         new_object.collection = self
175         self.objects.append(new_object)
176
177     def copyrights(self):
178         owner_to_years = dict()
179         copyrightYearRegExp = re.compile(r"(\d{4})[, ]{0,2}")
180         ownerStartRegExp = re.compile(r"[^\d, ]")
181
182         # Returns deduplicated copyrights keyed on the owner.
183         for line in self._copyright_lines:
184             years = set(copyrightYearRegExp.findall(line))
185             ownerIndex = ownerStartRegExp.search(line).start()
186             owner = line[ownerIndex:]
187             log.debug("Found years: %s and owner: %s" % (years, owner))
188             if owner not in owner_to_years:
189                 owner_to_years[owner] = set()
190
191             owner_to_years[owner] = owner_to_years[owner].union(years)
192
193         result = []
194
195         for owner, years in owner_to_years.items():
196             sorted_years = list(years)
197             sorted_years.sort()
198             result.append("%s %s" % (', '.join(sorted_years), owner))
199
200         return result
201
202     def all_functions(self):
203         result = []
204         for object in self.objects:
205             result.extend(object.functions)
206
207         result.sort()
208         return result
209
210     def all_internal_functions(self):
211         result = []
212         for object in [o for o in self.objects if 'internal' in o.annotations]:
213             result.extend(object.functions)
214
215         result.sort()
216         return result
217
218     # Private methods.
219
220     def _parse_copyright_lines(self, text):
221         licenseBlock = multilineCommentRegExp.findall(text)[0]
222         licenseBlock = licenseBlock[:licenseBlock.index("Redistribution")]
223
224         copyrightLines = [Templates.DefaultCopyright]
225         for line in licenseBlock.split("\n"):
226             line = line.replace("/*", "")
227             line = line.replace("*/", "")
228             line = line.replace("*", "")
229             line = line.replace("Copyright", "")
230             line = line.replace("copyright", "")
231             line = line.replace("(C)", "")
232             line = line.replace("(c)", "")
233             line = line.strip()
234
235             if len(line) == 0:
236                 continue
237
238             copyrightLines.append(line)
239
240         return copyrightLines
241
242     def _parse_annotations(self, text):
243         annotations = {}
244
245         for match in keyValueAnnotationCommentRegExp.finditer(text):
246             (key, value) = match.group(1, 2)
247             log.debug("Found annotation: '%s' => '%s'" % (key, value))
248             if key in annotations:
249                 raise ParseException("Duplicate annotation found: %s" % key)
250
251             annotations[key] = value
252
253         for match in flagAnnotationCommentRegExp.finditer(text):
254             key = match.group(1)
255             log.debug("Found annotation: '%s' => 'TRUE'" % key)
256             if key in annotations:
257                 raise ParseException("Duplicate annotation found: %s" % key)
258
259             annotations[key] = True
260
261         return annotations
262
263     def _parse_functions(self, text):
264         text = multilineCommentRegExp.sub("/**/", singleLineCommentRegExp.sub("//\n", text))
265
266         matches = [func for func in functionHeadRegExp.finditer(text)]
267         functionBounds = []
268         start = 0
269         end = 0
270         for match in matches:
271             start = match.start()
272             if start < end:
273                 continue
274             end = match.end()
275             while text[end] != '{':
276                 end = end + 1
277             depth = 1
278             end = end + 1
279             while depth > 0:
280                 if text[end] == '{':
281                     depth = depth + 1
282                 elif text[end] == '}':
283                     depth = depth - 1
284                 end = end + 1
285             functionBounds.append((start, end))
286
287         functionStrings = [text[start:end].strip() for (start, end) in functionBounds]
288         return map(BuiltinFunction.fromString, functionStrings)