| Index: tools/js2c.py
|
| diff --git a/tools/js2c.py b/tools/js2c.py
|
| index f67d053ad26360c07cf190fefea4c7cb73ce6ab6..17182109207f9e51c55bea62c3e30d07e94ebc95 100755
|
| --- a/tools/js2c.py
|
| +++ b/tools/js2c.py
|
| @@ -32,24 +32,23 @@
|
| # library.
|
|
|
| import os, re, sys, string
|
| +import optparse
|
| import jsmin
|
| import bz2
|
| +import textwrap
|
|
|
|
|
| -def ToCAsciiArray(lines):
|
| - result = []
|
| - for chr in lines:
|
| - value = ord(chr)
|
| - assert value < 128
|
| - result.append(str(value))
|
| - return ", ".join(result)
|
| +class Error(Exception):
|
| + def __init__(self, msg):
|
| + Exception.__init__(self, msg)
|
|
|
|
|
| -def ToCArray(lines):
|
| +def ToCArray(byte_sequence):
|
| result = []
|
| - for chr in lines:
|
| + for chr in byte_sequence:
|
| result.append(str(ord(chr)))
|
| - return ", ".join(result)
|
| + joined = ", ".join(result)
|
| + return textwrap.fill(joined, 80)
|
|
|
|
|
| def RemoveCommentsAndTrailingWhitespace(lines):
|
| @@ -68,46 +67,19 @@ def ReadFile(filename):
|
| return lines
|
|
|
|
|
| -def ReadLines(filename):
|
| - result = []
|
| - for line in open(filename, "rt"):
|
| - if '#' in line:
|
| - line = line[:line.index('#')]
|
| - line = line.strip()
|
| - if len(line) > 0:
|
| - result.append(line)
|
| - return result
|
| -
|
| -
|
| -def LoadConfigFrom(name):
|
| - import ConfigParser
|
| - config = ConfigParser.ConfigParser()
|
| - config.read(name)
|
| - return config
|
| -
|
| -
|
| -def ParseValue(string):
|
| - string = string.strip()
|
| - if string.startswith('[') and string.endswith(']'):
|
| - return string.lstrip('[').rstrip(']').split()
|
| - else:
|
| - return string
|
| -
|
| -
|
| EVAL_PATTERN = re.compile(r'\beval\s*\(')
|
| WITH_PATTERN = re.compile(r'\bwith\s*\(')
|
|
|
| -
|
| -def Validate(lines, file):
|
| - lines = RemoveCommentsAndTrailingWhitespace(lines)
|
| +def Validate(lines):
|
| # Because of simplified context setup, eval and with is not
|
| # allowed in the natives files.
|
| - eval_match = EVAL_PATTERN.search(lines)
|
| - if eval_match:
|
| - raise ("Eval disallowed in natives: %s" % file)
|
| - with_match = WITH_PATTERN.search(lines)
|
| - if with_match:
|
| - raise ("With statements disallowed in natives: %s" % file)
|
| + if EVAL_PATTERN.search(lines):
|
| + raise Error("Eval disallowed in natives.")
|
| + if WITH_PATTERN.search(lines):
|
| + raise Error("With statements disallowed in natives.")
|
| +
|
| + # Pass lines through unchanged.
|
| + return lines
|
|
|
|
|
| def ExpandConstants(lines, constants):
|
| @@ -187,7 +159,7 @@ PYTHON_MACRO_PATTERN = re.compile(r'^python\s+macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*
|
| def ReadMacros(lines):
|
| constants = []
|
| macros = []
|
| - for line in lines:
|
| + for line in lines.split('\n'):
|
| hash = line.find('#')
|
| if hash != -1: line = line[:hash]
|
| line = line.strip()
|
| @@ -213,13 +185,13 @@ def ReadMacros(lines):
|
| fun = eval("lambda " + ",".join(args) + ': ' + body)
|
| macros.append((re.compile("\\b%s\\(" % name), PythonMacro(args, fun)))
|
| else:
|
| - raise ("Illegal line: " + line)
|
| + raise Error("Illegal line: " + line)
|
| return (constants, macros)
|
|
|
| INLINE_MACRO_PATTERN = re.compile(r'macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*\n')
|
| INLINE_MACRO_END_PATTERN = re.compile(r'endmacro\s*\n')
|
|
|
| -def ExpandInlineMacros(lines, filename):
|
| +def ExpandInlineMacros(lines):
|
| pos = 0
|
| while True:
|
| macro_match = INLINE_MACRO_PATTERN.search(lines, pos)
|
| @@ -230,7 +202,7 @@ def ExpandInlineMacros(lines, filename):
|
| args = [match.strip() for match in macro_match.group(2).split(',')]
|
| end_macro_match = INLINE_MACRO_END_PATTERN.search(lines, macro_match.end());
|
| if end_macro_match is None:
|
| - raise ("Macro %s unclosed in %s" % (name, filename))
|
| + raise Error("Macro %s unclosed" % name)
|
| body = lines[macro_match.end():end_macro_match.start()]
|
|
|
| # remove macro definition
|
| @@ -245,6 +217,7 @@ def ExpandInlineMacros(lines, filename):
|
| return s
|
| lines = ExpandMacroDefinition(lines, pos, name_pattern, macro, non_expander)
|
|
|
| +
|
| HEADER_TEMPLATE = """\
|
| // Copyright 2011 Google Inc. All Rights Reserved.
|
|
|
| @@ -259,7 +232,7 @@ HEADER_TEMPLATE = """\
|
| namespace v8 {
|
| namespace internal {
|
|
|
| - static const byte sources[] = { %(sources_data)s };
|
| +%(sources_declaration)s\
|
|
|
| %(raw_sources_declaration)s\
|
|
|
| @@ -311,6 +284,10 @@ namespace internal {
|
| } // v8
|
| """
|
|
|
| +SOURCES_DECLARATION = """\
|
| + static const byte sources[] = { %s };
|
| +"""
|
| +
|
|
|
| RAW_SOURCES_COMPRESSION_DECLARATION = """\
|
| static const char* raw_sources = NULL;
|
| @@ -336,97 +313,202 @@ GET_SCRIPT_NAME_CASE = """\
|
| if (index == %(i)i) return Vector<const char>("%(name)s", %(length)i);
|
| """
|
|
|
| -def JS2C(source, target, env):
|
| - ids = []
|
| - debugger_ids = []
|
| - modules = []
|
| - # Locate the macros file name.
|
| - consts = []
|
| - macros = []
|
| - for s in source:
|
| - if 'macros.py' == (os.path.split(str(s))[1]):
|
| - (consts, macros) = ReadMacros(ReadLines(str(s)))
|
| - else:
|
| - modules.append(s)
|
| -
|
| - minifier = jsmin.JavaScriptMinifier()
|
| -
|
| - module_offset = 0
|
| - all_sources = []
|
| - for module in modules:
|
| - filename = str(module)
|
| - debugger = filename.endswith('-debugger.js')
|
| - lines = ReadFile(filename)
|
| - lines = ExpandConstants(lines, consts)
|
| - lines = ExpandMacros(lines, macros)
|
| - lines = RemoveCommentsAndTrailingWhitespace(lines)
|
| - lines = ExpandInlineMacros(lines, filename)
|
| - Validate(lines, filename)
|
| - lines = minifier.JSMinify(lines)
|
| - id = (os.path.split(filename)[1])[:-3]
|
| - if debugger: id = id[:-9]
|
| - raw_length = len(lines)
|
| - if debugger:
|
| - debugger_ids.append((id, raw_length, module_offset))
|
| - else:
|
| - ids.append((id, raw_length, module_offset))
|
| - all_sources.append(lines)
|
| - module_offset += raw_length
|
| - total_length = raw_total_length = module_offset
|
| -
|
| - if env['COMPRESSION'] == 'off':
|
| - raw_sources_declaration = RAW_SOURCES_DECLARATION
|
| - sources_data = ToCAsciiArray("".join(all_sources))
|
| +
|
| +def BuildFilterChain(macro_filename):
|
| + """Build the chain of filter functions to be applied to the sources.
|
| +
|
| + Args:
|
| + macro_filename: Name of the macro file, if any.
|
| +
|
| + Returns:
|
| + A function (string -> string) that reads a source file and processes it.
|
| + """
|
| + filter_chain = [ReadFile]
|
| +
|
| + if macro_filename:
|
| + (consts, macros) = ReadMacros(ReadFile(macro_filename))
|
| + filter_chain.append(lambda l: ExpandConstants(l, consts))
|
| + filter_chain.append(lambda l: ExpandMacros(l, macros))
|
| +
|
| + filter_chain.extend([
|
| + RemoveCommentsAndTrailingWhitespace,
|
| + ExpandInlineMacros,
|
| + Validate,
|
| + jsmin.JavaScriptMinifier().JSMinify
|
| + ])
|
| +
|
| + def chain(f1, f2):
|
| + return lambda x: f2(f1(x))
|
| +
|
| + return reduce(chain, filter_chain)
|
| +
|
| +
|
| +class Sources:
|
| + def __init__(self):
|
| + self.names = []
|
| + self.modules = []
|
| + self.is_debugger_id = []
|
| +
|
| +
|
| +def IsDebuggerFile(filename):
|
| + return filename.endswith("-debugger.js")
|
| +
|
| +def IsMacroFile(filename):
|
| + return filename.endswith("macros.py")
|
| +
|
| +
|
| +def PrepareSources(source_files):
|
| + """Read, prepare and assemble the list of source files.
|
| +
|
| + Args:
|
| + sources: List of Javascript-ish source files. A file named macros.py
|
| + will be treated as a list of macros.
|
| +
|
| + Returns:
|
| + An instance of Sources.
|
| + """
|
| + macro_file = None
|
| + macro_files = filter(IsMacroFile, source_files)
|
| + assert len(macro_files) in [0, 1]
|
| + if macro_files:
|
| + source_files.remove(macro_files[0])
|
| + macro_file = macro_files[0]
|
| +
|
| + filters = BuildFilterChain(macro_file)
|
| +
|
| + # Sort 'debugger' sources first.
|
| + source_files = sorted(source_files,
|
| + lambda l,r: IsDebuggerFile(r) - IsDebuggerFile(l))
|
| +
|
| + result = Sources()
|
| + for source in source_files:
|
| + try:
|
| + lines = filters(source)
|
| + except Error as e:
|
| + raise Error("In file %s:\n%s" % (source, str(e)))
|
| +
|
| + result.modules.append(lines);
|
| +
|
| + is_debugger = IsDebuggerFile(source)
|
| + result.is_debugger_id.append(is_debugger);
|
| +
|
| + name = os.path.basename(source)[:-3]
|
| + result.names.append(name if not is_debugger else name[:-9]);
|
| + return result
|
| +
|
| +
|
| +def BuildMetadata(sources, source_bytes, native_type, omit):
|
| + """Build the meta data required to generate a libaries file.
|
| +
|
| + Args:
|
| + sources: A Sources instance with the prepared sources.
|
| + source_bytes: A list of source bytes.
|
| + (The concatenation of all sources; might be compressed.)
|
| + native_type: The parameter for the NativesCollection template.
|
| + omit: bool, whether we should omit the sources in the output.
|
| +
|
| + Returns:
|
| + A dictionary for use with HEADER_TEMPLATE.
|
| + """
|
| + total_length = len(source_bytes)
|
| + raw_sources = "".join(sources.modules)
|
| +
|
| + # The sources are expected to be ASCII-only.
|
| + assert not filter(lambda value: ord(value) >= 128, raw_sources)
|
| +
|
| + # Loop over modules and build up indices into the source blob:
|
| + get_index_cases = []
|
| + get_script_name_cases = []
|
| + get_raw_script_source_cases = []
|
| + offset = 0
|
| + for i in xrange(len(sources.modules)):
|
| + native_name = "native %s.js" % sources.names[i]
|
| + d = {
|
| + "i": i,
|
| + "id": sources.names[i],
|
| + "name": native_name,
|
| + "length": len(native_name),
|
| + "offset": offset,
|
| + "raw_length": len(sources.modules[i]),
|
| + }
|
| + get_index_cases.append(GET_INDEX_CASE % d)
|
| + get_script_name_cases.append(GET_SCRIPT_NAME_CASE % d)
|
| + get_raw_script_source_cases.append(GET_RAW_SCRIPT_SOURCE_CASE % d)
|
| + offset += len(sources.modules[i])
|
| + assert offset == len(raw_sources)
|
| +
|
| + # If we have the raw sources we can declare them accordingly.
|
| + have_raw_sources = source_bytes == raw_sources and not omit
|
| + raw_sources_declaration = (RAW_SOURCES_DECLARATION
|
| + if have_raw_sources else RAW_SOURCES_COMPRESSION_DECLARATION)
|
| +
|
| + metadata = {
|
| + "builtin_count": len(sources.modules),
|
| + "debugger_count": sum(sources.is_debugger_id),
|
| + "sources_declaration": SOURCES_DECLARATION % ToCArray(source_bytes),
|
| + "sources_data": ToCArray(source_bytes) if not omit else "",
|
| + "raw_sources_declaration": raw_sources_declaration,
|
| + "raw_total_length": sum(map(len, sources.modules)),
|
| + "total_length": total_length,
|
| + "get_index_cases": "".join(get_index_cases),
|
| + "get_raw_script_source_cases": "".join(get_raw_script_source_cases),
|
| + "get_script_name_cases": "".join(get_script_name_cases),
|
| + "type": native_type,
|
| + }
|
| + return metadata
|
| +
|
| +
|
| +def CompressMaybe(sources, compression_type):
|
| + """Take the prepared sources and generate a sequence of bytes.
|
| +
|
| + Args:
|
| + sources: A Sources instance with the prepared sourced.
|
| + compression_type: string, describing the desired compression.
|
| +
|
| + Returns:
|
| + A sequence of bytes.
|
| + """
|
| + sources_bytes = "".join(sources.modules)
|
| + if compression_type == "off":
|
| + return sources_bytes
|
| + elif compression_type == "bz2":
|
| + return bz2.compress(sources_bytes)
|
| else:
|
| - raw_sources_declaration = RAW_SOURCES_COMPRESSION_DECLARATION
|
| - if env['COMPRESSION'] == 'bz2':
|
| - all_sources = bz2.compress("".join(all_sources))
|
| - total_length = len(all_sources)
|
| - sources_data = ToCArray(all_sources)
|
| -
|
| - # Build debugger support functions
|
| - get_index_cases = [ ]
|
| - get_raw_script_source_cases = [ ]
|
| - get_script_name_cases = [ ]
|
| -
|
| - i = 0
|
| - for (id, raw_length, module_offset) in debugger_ids + ids:
|
| - native_name = "native %s.js" % id
|
| - get_index_cases.append(GET_INDEX_CASE % { 'id': id, 'i': i })
|
| - get_raw_script_source_cases.append(GET_RAW_SCRIPT_SOURCE_CASE % {
|
| - 'offset': module_offset,
|
| - 'raw_length': raw_length,
|
| - 'i': i
|
| - })
|
| - get_script_name_cases.append(GET_SCRIPT_NAME_CASE % {
|
| - 'name': native_name,
|
| - 'length': len(native_name),
|
| - 'i': i
|
| - })
|
| - i = i + 1
|
| -
|
| - # Emit result
|
| - output = open(str(target[0]), "w")
|
| - output.write(HEADER_TEMPLATE % {
|
| - 'builtin_count': len(ids) + len(debugger_ids),
|
| - 'debugger_count': len(debugger_ids),
|
| - 'sources_data': sources_data,
|
| - 'raw_sources_declaration': raw_sources_declaration,
|
| - 'raw_total_length': raw_total_length,
|
| - 'total_length': total_length,
|
| - 'get_index_cases': "".join(get_index_cases),
|
| - 'get_raw_script_source_cases': "".join(get_raw_script_source_cases),
|
| - 'get_script_name_cases': "".join(get_script_name_cases),
|
| - 'type': env['TYPE']
|
| - })
|
| + raise Error("Unknown compression type %s." % compression_type)
|
| +
|
| +
|
| +def JS2C(source, target, native_type, compression_type, raw_file, omit):
|
| + sources = PrepareSources(source)
|
| + sources_bytes = CompressMaybe(sources, compression_type)
|
| + metadata = BuildMetadata(sources, sources_bytes, native_type, omit)
|
| +
|
| + # Optionally emit raw file.
|
| + if raw_file:
|
| + output = open(raw_file, "w")
|
| + output.write(sources_bytes)
|
| + output.close()
|
| +
|
| + # Emit resulting source file.
|
| + output = open(target, "w")
|
| + output.write(HEADER_TEMPLATE % metadata)
|
| output.close()
|
|
|
| +
|
| def main():
|
| - natives = sys.argv[1]
|
| - type = sys.argv[2]
|
| - compression = sys.argv[3]
|
| - source_files = sys.argv[4:]
|
| - JS2C(source_files, [natives], { 'TYPE': type, 'COMPRESSION': compression })
|
| + parser = optparse.OptionParser()
|
| + parser.add_option("--raw", action="store",
|
| + help="file to write the processed sources array to.")
|
| + parser.add_option("--omit", dest="omit", action="store_true",
|
| + help="Omit the raw sources from the generated code.")
|
| + parser.set_usage("""js2c out.cc type compression sources.js ...
|
| + out.cc: C code to be generated.
|
| + type: type parameter for NativesCollection template.
|
| + compression: type of compression used. [off|bz2]
|
| + sources.js: JS internal sources or macros.py.""")
|
| + (options, args) = parser.parse_args()
|
| +
|
| + JS2C(args[3:], args[0], args[1], args[2], options.raw, options.omit)
|
| +
|
|
|
| if __name__ == "__main__":
|
| main()
|
|
|