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