PageRenderTime 54ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/js/src/builtin/js2c.py

https://bitbucket.org/Jiten/mozilla-central
Python | 348 lines | 281 code | 27 blank | 40 comment | 32 complexity | 12a4b22ecc44cb5c503d18b4aaa11db5 MD5 | raw file
Possible License(s): JSON, Apache-2.0, 0BSD, LGPL-3.0, BSD-2-Clause, AGPL-1.0, MPL-2.0, GPL-2.0, LGPL-2.1, MIT, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2012 the V8 project authors. All rights reserved.
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are
  6. # met:
  7. #
  8. # * Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # * Redistributions in binary form must reproduce the above
  11. # copyright notice, this list of conditions and the following
  12. # disclaimer in the documentation and/or other materials provided
  13. # with the distribution.
  14. # * Neither the name of Google Inc. nor the names of its
  15. # contributors may be used to endorse or promote products derived
  16. # from this software without specific prior written permission.
  17. #
  18. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. # This is a utility for converting JavaScript source code into C-style
  30. # char arrays. It is used for embedded JavaScript code in the V8
  31. # library.
  32. import os, re, sys, string
  33. import jsmin
  34. import bz2
  35. def ToCAsciiArray(lines):
  36. result = []
  37. for chr in lines:
  38. value = ord(chr)
  39. assert value < 128
  40. result.append(str(value))
  41. return ", ".join(result)
  42. def ToCArray(lines):
  43. result = []
  44. for chr in lines:
  45. result.append(str(ord(chr)))
  46. return ", ".join(result)
  47. def RemoveCommentsAndTrailingWhitespace(lines):
  48. lines = re.sub(r'//.*\n', '\n', lines) # end-of-line comments
  49. lines = re.sub(re.compile(r'/\*.*?\*/', re.DOTALL), '', lines) # comments.
  50. lines = re.sub(r'\s+\n+', '\n', lines) # trailing whitespace
  51. return lines
  52. def ReadFile(filename):
  53. file = open(filename, "rt")
  54. try:
  55. lines = file.read()
  56. finally:
  57. file.close()
  58. return lines
  59. def ReadLines(filename):
  60. result = []
  61. for line in open(filename, "rt"):
  62. if '#' in line:
  63. line = line[:line.index('#')]
  64. line = line.strip()
  65. if len(line) > 0:
  66. result.append(line)
  67. return result
  68. def LoadConfigFrom(name):
  69. import ConfigParser
  70. config = ConfigParser.ConfigParser()
  71. config.read(name)
  72. return config
  73. def ParseValue(string):
  74. string = string.strip()
  75. if string.startswith('[') and string.endswith(']'):
  76. return string.lstrip('[').rstrip(']').split()
  77. else:
  78. return string
  79. EVAL_PATTERN = re.compile(r'\beval\s*\(')
  80. WITH_PATTERN = re.compile(r'\bwith\s*\(')
  81. def Validate(lines, file):
  82. lines = RemoveCommentsAndTrailingWhitespace(lines)
  83. # Because of simplified context setup, eval and with is not
  84. # allowed in the natives files.
  85. eval_match = EVAL_PATTERN.search(lines)
  86. if eval_match:
  87. raise ("Eval disallowed in natives: %s" % file)
  88. with_match = WITH_PATTERN.search(lines)
  89. if with_match:
  90. raise ("With statements disallowed in natives: %s" % file)
  91. def ExpandConstants(lines, constants):
  92. for key, value in constants:
  93. lines = key.sub(str(value), lines)
  94. return lines
  95. def ExpandMacros(lines, macros):
  96. # We allow macros to depend on the previously declared macros, but
  97. # we don't allow self-dependecies or recursion.
  98. for name_pattern, macro in reversed(macros):
  99. pattern_match = name_pattern.search(lines, 0)
  100. while pattern_match is not None:
  101. # Scan over the arguments
  102. height = 1
  103. start = pattern_match.start()
  104. end = pattern_match.end()
  105. assert lines[end - 1] == '('
  106. last_match = end
  107. arg_index = [0] # Wrap state into array, to work around Python "scoping"
  108. mapping = { }
  109. def add_arg(str):
  110. # Remember to expand recursively in the arguments
  111. replacement = ExpandMacros(str.strip(), macros)
  112. mapping[macro.args[arg_index[0]]] = replacement
  113. arg_index[0] += 1
  114. while end < len(lines) and height > 0:
  115. # We don't count commas at higher nesting levels.
  116. if lines[end] == ',' and height == 1:
  117. add_arg(lines[last_match:end])
  118. last_match = end + 1
  119. elif lines[end] in ['(', '{', '[']:
  120. height = height + 1
  121. elif lines[end] in [')', '}', ']']:
  122. height = height - 1
  123. end = end + 1
  124. # Remember to add the last match.
  125. add_arg(lines[last_match:end-1])
  126. result = macro.expand(mapping)
  127. # Replace the occurrence of the macro with the expansion
  128. lines = lines[:start] + result + lines[end:]
  129. pattern_match = name_pattern.search(lines, start + len(result))
  130. return lines
  131. class TextMacro:
  132. def __init__(self, args, body):
  133. self.args = args
  134. self.body = body
  135. def expand(self, mapping):
  136. result = self.body
  137. for key, value in mapping.items():
  138. result = result.replace(key, value)
  139. return result
  140. class PythonMacro:
  141. def __init__(self, args, fun):
  142. self.args = args
  143. self.fun = fun
  144. def expand(self, mapping):
  145. args = []
  146. for arg in self.args:
  147. args.append(mapping[arg])
  148. return str(self.fun(*args))
  149. CONST_PATTERN = re.compile(r'^const\s+([a-zA-Z0-9_]+)\s*=\s*([^;]*);$')
  150. MACRO_PATTERN = re.compile(r'^macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$')
  151. PYTHON_MACRO_PATTERN = re.compile(r'^python\s+macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$')
  152. def ReadMacros(lines):
  153. constants = []
  154. macros = []
  155. for line in lines:
  156. hash = line.find('#')
  157. if hash != -1: line = line[:hash]
  158. line = line.strip()
  159. if len(line) is 0: continue
  160. const_match = CONST_PATTERN.match(line)
  161. if const_match:
  162. name = const_match.group(1)
  163. value = const_match.group(2).strip()
  164. constants.append((re.compile("\\b%s\\b" % name), value))
  165. else:
  166. macro_match = MACRO_PATTERN.match(line)
  167. if macro_match:
  168. name = macro_match.group(1)
  169. args = [match.strip() for match in macro_match.group(2).split(',')]
  170. body = macro_match.group(3).strip()
  171. macros.append((re.compile("\\b%s\\(" % name), TextMacro(args, body)))
  172. else:
  173. python_match = PYTHON_MACRO_PATTERN.match(line)
  174. if python_match:
  175. name = python_match.group(1)
  176. args = [match.strip() for match in python_match.group(2).split(',')]
  177. body = python_match.group(3).strip()
  178. fun = eval("lambda " + ",".join(args) + ': ' + body)
  179. macros.append((re.compile("\\b%s\\(" % name), PythonMacro(args, fun)))
  180. else:
  181. raise ("Illegal line: " + line)
  182. return (constants, macros)
  183. HEADER_TEMPLATE = """\
  184. /* This Source Code Form is subject to the terms of the Mozilla Public
  185. * License, v. 2.0. If a copy of the MPL was not distributed with this
  186. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  187. namespace js {
  188. namespace selfhosted {
  189. static const char sources[] = { %(sources_data)s };
  190. %(raw_sources_declaration)s\
  191. uint32_t GetRawScriptsSize() {
  192. return %(raw_total_length)i;
  193. }
  194. } // selfhosted
  195. } // js
  196. """
  197. RAW_SOURCES_COMPRESSION_DECLARATION = """\
  198. static const char* raw_sources = NULL;
  199. """
  200. RAW_SOURCES_DECLARATION = """\
  201. static const char* raw_sources = reinterpret_cast<const char*>(sources);
  202. """
  203. GET_INDEX_CASE = """\
  204. if (strcmp(name, "%(id)s") == 0) return %(i)i;
  205. """
  206. GET_RAW_SCRIPT_SOURCE_CASE = """\
  207. if (index == %(i)i) return Vector<const char>(raw_sources + %(offset)i, %(raw_length)i);
  208. """
  209. GET_SCRIPT_NAME_CASE = """\
  210. if (index == %(i)i) return Vector<const char>("%(name)s", %(length)i);
  211. """
  212. def JS2C(source, target, env):
  213. ids = []
  214. debugger_ids = []
  215. modules = []
  216. # Locate the macros file name.
  217. consts = []
  218. macros = []
  219. for s in source:
  220. if 'macros.py' == (os.path.split(str(s))[1]):
  221. (consts, macros) = ReadMacros(ReadLines(str(s)))
  222. else:
  223. modules.append(s)
  224. minifier = jsmin.JavaScriptMinifier()
  225. module_offset = 0
  226. all_sources = []
  227. for module in modules:
  228. filename = str(module)
  229. debugger = filename.endswith('-debugger.js')
  230. lines = ReadFile(filename)
  231. lines = ExpandConstants(lines, consts)
  232. lines = ExpandMacros(lines, macros)
  233. Validate(lines, filename)
  234. lines = minifier.JSMinify(lines)
  235. id = (os.path.split(filename)[1])[:-3]
  236. if debugger: id = id[:-9]
  237. raw_length = len(lines)
  238. if debugger:
  239. debugger_ids.append((id, raw_length, module_offset))
  240. else:
  241. ids.append((id, raw_length, module_offset))
  242. all_sources.append(lines)
  243. module_offset += raw_length
  244. total_length = raw_total_length = module_offset
  245. if env['COMPRESSION'] == 'off':
  246. raw_sources_declaration = RAW_SOURCES_DECLARATION
  247. sources_data = ToCAsciiArray("".join(all_sources))
  248. else:
  249. raw_sources_declaration = RAW_SOURCES_COMPRESSION_DECLARATION
  250. if env['COMPRESSION'] == 'bz2':
  251. all_sources = bz2.compress("".join(all_sources))
  252. total_length = len(all_sources)
  253. sources_data = ToCArray(all_sources)
  254. # Build debugger support functions
  255. get_index_cases = [ ]
  256. get_raw_script_source_cases = [ ]
  257. get_script_name_cases = [ ]
  258. i = 0
  259. for (id, raw_length, module_offset) in debugger_ids + ids:
  260. native_name = "native %s.js" % id
  261. get_index_cases.append(GET_INDEX_CASE % { 'id': id, 'i': i })
  262. get_raw_script_source_cases.append(GET_RAW_SCRIPT_SOURCE_CASE % {
  263. 'offset': module_offset,
  264. 'raw_length': raw_length,
  265. 'i': i
  266. })
  267. get_script_name_cases.append(GET_SCRIPT_NAME_CASE % {
  268. 'name': native_name,
  269. 'length': len(native_name),
  270. 'i': i
  271. })
  272. i = i + 1
  273. # Emit result
  274. output = open(str(target[0]), "w")
  275. output.write(HEADER_TEMPLATE % {
  276. 'builtin_count': len(ids) + len(debugger_ids),
  277. 'debugger_count': len(debugger_ids),
  278. 'sources_data': sources_data,
  279. 'raw_sources_declaration': raw_sources_declaration,
  280. 'raw_total_length': raw_total_length,
  281. 'total_length': total_length,
  282. 'get_index_cases': "".join(get_index_cases),
  283. 'get_raw_script_source_cases': "".join(get_raw_script_source_cases),
  284. 'get_script_name_cases': "".join(get_script_name_cases),
  285. 'type': env['TYPE']
  286. })
  287. output.close()
  288. def main():
  289. natives = sys.argv[1]
  290. type = sys.argv[2]
  291. compression = sys.argv[3]
  292. source_files = sys.argv[4:]
  293. JS2C(source_files, [natives], { 'TYPE': type, 'COMPRESSION': compression })
  294. if __name__ == "__main__":
  295. main()