blob: dff41873da2b65a77114cf5d41e87a4c0d30637c [file] [log] [blame]
#!/usr/bin/python
# Copyright (C) 2014 Apple Inc. 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. ``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
# 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 argparse
import filecmp
import fnmatch
import os
import re
import shutil
import sys
import datetime
import json
parser = argparse.ArgumentParser()
parser.add_argument('input_file', nargs='*', help='Input JS files which builtins generated from')
parser.add_argument('--input-directory', help='All JS files will be used as input from this directory.')
parser.add_argument('--output', help='path to output cpp or h file')
parser.add_argument('--prefix', default='JSC', help='prefix used for public macros')
parser.add_argument('--namespace', default='JSC', help='C++ namespace')
args = parser.parse_args()
copyrightText = """ *
* 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. ``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
* 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.
*/\n
"""
generatorString = "/* Generated by %s do not hand edit. */\n" % os.path.basename(__file__)
functionHeadRegExp = re.compile(r"(?:function|constructor)\s+\w+\s*\(.*?\)", re.MULTILINE | re.S)
functionNameRegExp = re.compile(r"(?:function|constructor)\s+(\w+)\s*\(", re.MULTILINE | re.S)
functionIsConstructorRegExp = re.compile(r"^constructor", re.MULTILINE | re.S)
functionParameterFinder = re.compile(r"^(?:function|constructor)\s+(?:\w+)\s*\(((?:\s*\w+)?\s*(?:\s*,\s*\w+)*)?\s*\)", re.MULTILINE | re.S)
multilineCommentRegExp = re.compile(r"\/\*.*?\*\/", re.MULTILINE | re.S)
singleLineCommentRegExp = re.compile(r"\/\/.*?\n", re.MULTILINE | re.S)
def getCopyright(source):
copyrightBlock = multilineCommentRegExp.findall(source)[0]
copyrightBlock = copyrightBlock[:copyrightBlock.index("Redistribution")]
copyRightLines = []
for line in copyrightBlock.split("\n"):
line = line.replace("/*", "")
line = line.replace("*/", "")
line = line.replace("*", "")
line = line.replace("Copyright", "")
line = line.replace("copyright", "")
line = line.replace("(C)", "")
line = line.replace("(c)", "")
line = line.strip()
if len(line) == 0:
continue
copyRightLines.append(line)
return list(set(copyRightLines))
class Function(object):
def __init__(self, name, source, isConstructor, parameters):
self.name = name
self.source = source
self.isConstructor = isConstructor
self.parameters = parameters
def mangleName(self, object):
qName = object + "." + self.name
mangledName = ""
i = 0
while i < len(qName):
if qName[i] == '.':
mangledName = mangledName + qName[i + 1].upper()
i = i + 1
else:
mangledName = mangledName + qName[i]
i = i + 1
if self.isConstructor:
mangledName = mangledName + "Constructor"
return mangledName
def getFunctions(source):
source = multilineCommentRegExp.sub("/**/", singleLineCommentRegExp.sub("//\n", source))
matches = [ f for f in functionHeadRegExp.finditer(source)]
functionBounds = []
start = 0
end = 0
for match in matches:
start = match.start()
if start < end:
continue
end = match.end()
while source[end] != '{':
end = end + 1
depth = 1
end = end + 1
while depth > 0:
if source[end] == '{':
depth = depth + 1
elif source[end] == '}':
depth = depth - 1
end = end + 1
functionBounds.append((start, end))
functions = [source[start:end].strip() for (start, end) in functionBounds]
result = []
for function in functions:
function = multilineCommentRegExp.sub("", function)
functionName = functionNameRegExp.findall(function)[0]
functionIsConstructor = functionIsConstructorRegExp.match(function) != None
functionParameters = functionParameterFinder.findall(function)[0].split(',')
if len(functionParameters[0]) == 0:
functionParameters = []
result.append(Function(functionName, function, functionIsConstructor, functionParameters))
return result
def writeIncludeDirectives(writer, headerNames):
for headerName in headerNames:
writer.write("#include " + headerName + "\n")
def generateCode(source):
inputFile = open(source, "r")
baseName = os.path.basename(source).replace(".js", "")
source = ""
for line in inputFile:
source = source + line
if sys.platform == "cygwin":
source = source.replace("\r\n", "\n")
return (baseName, getFunctions(source), getCopyright(source))
builtins = []
copyrights = []
(output_base, _) = os.path.splitext(args.output)
if args.input_directory:
for file in os.listdir(args.input_directory):
args.input_file.append(os.path.join(args.input_directory, file))
for file in args.input_file:
if fnmatch.fnmatch(file, '*.js'):
(baseName, functions, objectCopyrights) = generateCode(file)
copyrights.extend(objectCopyrights)
builtins.append((baseName, functions))
namespace = args.namespace
macroPrefix = args.prefix
scopeName = os.path.splitext(os.path.basename(args.output))[0]
includeGuard = scopeName + "_H"
headerName = scopeName + ".h"
headerIncludes = ["\"BuiltinUtils.h\""] if namespace == "JSC" else ["<builtins/BuiltinUtils.h>"]
contentIncludes = ["\"BuiltinExecutables.h\"", "\"Executable.h\"", "\"JSCellInlines.h\"", "\"VM.h\""] if namespace == "JSC" else ["<runtime/Executable.h>", "<runtime/StructureInlines.h>", "<runtime/JSCJSValueInlines.h>", "<runtime/JSCellInlines.h>", "<runtime/VM.h>", "\"WebCoreJSClientData.h\""]
copyrights = list(set(copyrights))
copyrightBody = ""
for copyright in copyrights:
copyrightBody = copyrightBody +" * Copyright (C) " + copyright + "\n"
builtinsHeader = open(output_base + ".h.tmp", "w")
builtinsImplementation = open(output_base + ".cpp.tmp", "w")
copyrightText = "/*\n" + copyrightBody + copyrightText
builtinsHeader.write("""%s
%s
#ifndef %s
#define %s
""" % (generatorString, copyrightText, includeGuard, includeGuard))
writeIncludeDirectives(builtinsHeader, headerIncludes)
builtinsHeader.write("""
namespace JSC {
class FunctionExecutable;
}
namespace %s {
""" % namespace)
codeReferences = []
for (objectName, functions) in builtins:
print("Generating bindings for the %s builtin." % objectName)
builtinsHeader.write("/* %s functions */\n" % objectName)
for function in functions:
name = function.name
mangledName = function.mangleName(objectName)
mangledName = mangledName[0].lower() + mangledName[1:] + "Code"
codeReferences.append((mangledName, function))
builtinsHeader.write("extern const char* s_%s;\n" % mangledName)
builtinsHeader.write("extern const int s_%sLength;\n" % mangledName)
builtinsHeader.write("extern const JSC::ConstructAbility s_%sConstructAbility;\n" % mangledName)
builtinsHeader.write("\n")
builtinsHeader.write("#define %s_FOREACH_%s_BUILTIN(macro) \\\n" % (macroPrefix, objectName.replace(".", "_").upper()))
for function in functions:
mangledName = function.mangleName(objectName)
builtinsHeader.write(" macro(%s, %s, %d) \\\n" % (function.name, mangledName, len(function.parameters)))
builtinsHeader.write("\n")
for function in functions:
builtinsHeader.write("#define %s_BUILTIN_%s 1\n" % (macroPrefix, function.mangleName(objectName).upper()))
builtinsHeader.write("\n\n")
names = []
builtinsHeader.write("#define %s_FOREACH_BUILTIN(macro)\\\n" % macroPrefix)
for (codeReference, function) in codeReferences:
builtinsHeader.write(" macro(%s, %s, s_%sLength) \\\n" % (codeReference, function.name, codeReference))
names.append(function.name)
builtinsHeader.write("\n\n")
builtinsHeader.write("#define %s_FOREACH_BUILTIN_FUNCTION_NAME(macro) \\\n" % macroPrefix)
for name in sorted(set(names)):
builtinsHeader.write(" macro(%s) \\\n" % name)
builtinsHeader.write("""
#define DECLARE_BUILTIN_GENERATOR(codeName, functionName, argumentCount) \\
JSC::FunctionExecutable* codeName##Generator(JSC::VM&);
%s_FOREACH_BUILTIN(DECLARE_BUILTIN_GENERATOR)
#undef DECLARE_BUILTIN_GENERATOR
#define %s_BUILTIN_EXISTS(name) defined %s_BUILTIN_ ## name
}
#endif // %s
""" % (macroPrefix, macroPrefix, macroPrefix, includeGuard))
builtinsImplementation.write("""%s
%s
#include "config.h"
#include "%s"
""" % (generatorString, copyrightText, headerName))
writeIncludeDirectives(builtinsImplementation, contentIncludes)
builtinsImplementation.write("""
namespace %s {
""" % (namespace))
for (codeReference, function) in codeReferences:
source = function.source
source = "(function " + source[source.index("("):] + ")"
lines = json.dumps(source)[1:-1].split("\\n")
sourceLength = len(source)
source = ""
for line in lines:
source = source + (" \"%s\\n\" \\\n" % line)
builtinsImplementation.write("const char* s_%s =\n%s;\n\n" % (codeReference, source))
builtinsImplementation.write("const int s_%sLength = %d;\n\n" % (codeReference, sourceLength + 1)) # + 1 for \n
constructAbility = "JSC::ConstructAbility::CannotConstruct"
if function.isConstructor:
constructAbility = "JSC::ConstructAbility::CanConstruct"
builtinsImplementation.write("const JSC::ConstructAbility s_%sConstructAbility = %s;\n\n" % (codeReference, constructAbility)) # + 1 for \n
builtinsImplementation.write("""
#define DEFINE_BUILTIN_GENERATOR(codeName, functionName, argumentCount) \\
JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \\
""");
if (namespace == "JSC"):
builtinsImplementation.write("""{\\
return vm.builtinExecutables()->codeName##Executable()->link(vm, vm.builtinExecutables()->codeName##Source()); \\
""")
else:
builtinName = scopeName[0].lower() + scopeName[1:]
builtinsImplementation.write("""{\\
JSVMClientData* clientData = static_cast<JSVMClientData*>(vm.clientData); \\
return clientData->builtinFunctions().%s().codeName##Executable()->link(vm, clientData->builtinFunctions().%s().codeName##Source()); \\
"""% (builtinName, builtinName))
builtinsImplementation.write("""}
%s_FOREACH_BUILTIN(DEFINE_BUILTIN_GENERATOR)
#undef DEFINE_BUILTIN_GENERATOR
}
"""% (macroPrefix))
builtinsHeader.close()
builtinsImplementation.close()
if (not os.path.exists(output_base + ".h")) or (not filecmp.cmp(output_base + ".h.tmp", output_base + ".h", shallow=False)):
if (os.path.exists(output_base + ".h")):
os.remove(output_base + ".h")
os.rename(output_base + ".h.tmp", output_base + ".h")
else:
os.remove(output_base + ".h.tmp")
if (not os.path.exists(output_base + ".cpp")) or (not filecmp.cmp(output_base + ".cpp.tmp", output_base + ".cpp", shallow=False)):
if (os.path.exists(output_base + ".cpp")):
os.remove(output_base + ".cpp")
os.rename(output_base + ".cpp.tmp", output_base + ".cpp")
else:
os.remove(output_base + ".cpp.tmp")