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 30 # This is a utility for converting JavaScript source code into C-style 31 # char arrays. It is used for embedded JavaScript code in the V8 32 # library. 33 34 import os, re, sys, string 35 import jsmin 36 import bz2 37 38 39 def ToCAsciiArray(lines): 40 result = [] 41 for chr in lines: 42 value = ord(chr) 43 assert value < 128 44 result.append(str(value)) 45 return ", ".join(result) 46 47 48 def ToCArray(lines): 49 result = [] 50 for chr in lines: 51 result.append(str(ord(chr))) 52 return ", ".join(result) 53 54 55 def RemoveCommentsAndTrailingWhitespace(lines): 56 lines = re.sub(r'//.*\n', '\n', lines) # end-of-line comments 57 lines = re.sub(re.compile(r'/\*.*?\*/', re.DOTALL), '', lines) # comments. 58 lines = re.sub(r'\s+\n+', '\n', lines) # trailing whitespace 59 return lines 60 61 62 def ReadFile(filename): 63 file = open(filename, "rt") 64 try: 65 lines = file.read() 66 finally: 67 file.close() 68 return lines 69 70 71 def ReadLines(filename): 72 result = [] 73 for line in open(filename, "rt"): 74 if '#' in line: 75 line = line[:line.index('#')] 76 line = line.strip() 77 if len(line) > 0: 78 result.append(line) 79 return result 80 81 82 def LoadConfigFrom(name): 83 import ConfigParser 84 config = ConfigParser.ConfigParser() 85 config.read(name) 86 return config 87 88 89 def ParseValue(string): 90 string = string.strip() 91 if string.startswith('[') and string.endswith(']'): 92 return string.lstrip('[').rstrip(']').split() 93 else: 94 return string 95 96 97 EVAL_PATTERN = re.compile(r'\beval\s*\(') 98 WITH_PATTERN = re.compile(r'\bwith\s*\(') 99 100 101 def Validate(lines, file): 102 lines = RemoveCommentsAndTrailingWhitespace(lines) 103 # Because of simplified context setup, eval and with is not 104 # allowed in the natives files. 105 eval_match = EVAL_PATTERN.search(lines) 106 if eval_match: 107 raise ("Eval disallowed in natives: %s" % file) 108 with_match = WITH_PATTERN.search(lines) 109 if with_match: 110 raise ("With statements disallowed in natives: %s" % file) 111 112 113 def ExpandConstants(lines, constants): 114 for key, value in constants: 115 lines = key.sub(str(value), lines) 116 return lines 117 118 119 def ExpandMacroDefinition(lines, pos, name_pattern, macro, expander): 120 pattern_match = name_pattern.search(lines, pos) 121 while pattern_match is not None: 122 # Scan over the arguments 123 height = 1 124 start = pattern_match.start() 125 end = pattern_match.end() 126 assert lines[end - 1] == '(' 127 last_match = end 128 arg_index = [0] # Wrap state into array, to work around Python "scoping" 129 mapping = { } 130 def add_arg(str): 131 # Remember to expand recursively in the arguments 132 replacement = expander(str.strip()) 133 mapping[macro.args[arg_index[0]]] = replacement 134 arg_index[0] += 1 135 while end < len(lines) and height > 0: 136 # We don't count commas at higher nesting levels. 137 if lines[end] == ',' and height == 1: 138 add_arg(lines[last_match:end]) 139 last_match = end + 1 140 elif lines[end] in ['(', '{', '[']: 141 height = height + 1 142 elif lines[end] in [')', '}', ']']: 143 height = height - 1 144 end = end + 1 145 # Remember to add the last match. 146 add_arg(lines[last_match:end-1]) 147 result = macro.expand(mapping) 148 # Replace the occurrence of the macro with the expansion 149 lines = lines[:start] + result + lines[end:] 150 pattern_match = name_pattern.search(lines, start + len(result)) 151 return lines 152 153 def ExpandMacros(lines, macros): 154 # We allow macros to depend on the previously declared macros, but 155 # we don't allow self-dependecies or recursion. 156 for name_pattern, macro in reversed(macros): 157 def expander(s): 158 return ExpandMacros(s, macros) 159 lines = ExpandMacroDefinition(lines, 0, name_pattern, macro, expander) 160 return lines 161 162 class TextMacro: 163 def __init__(self, args, body): 164 self.args = args 165 self.body = body 166 def expand(self, mapping): 167 result = self.body 168 for key, value in mapping.items(): 169 result = result.replace(key, value) 170 return result 171 172 class PythonMacro: 173 def __init__(self, args, fun): 174 self.args = args 175 self.fun = fun 176 def expand(self, mapping): 177 args = [] 178 for arg in self.args: 179 args.append(mapping[arg]) 180 return str(self.fun(*args)) 181 182 CONST_PATTERN = re.compile(r'^const\s+([a-zA-Z0-9_]+)\s*=\s*([^;]*);$') 183 MACRO_PATTERN = re.compile(r'^macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$') 184 PYTHON_MACRO_PATTERN = re.compile(r'^python\s+macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$') 185 186 187 def ReadMacros(lines): 188 constants = [] 189 macros = [] 190 for line in lines: 191 hash = line.find('#') 192 if hash != -1: line = line[:hash] 193 line = line.strip() 194 if len(line) is 0: continue 195 const_match = CONST_PATTERN.match(line) 196 if const_match: 197 name = const_match.group(1) 198 value = const_match.group(2).strip() 199 constants.append((re.compile("\\b%s\\b" % name), value)) 200 else: 201 macro_match = MACRO_PATTERN.match(line) 202 if macro_match: 203 name = macro_match.group(1) 204 args = [match.strip() for match in macro_match.group(2).split(',')] 205 body = macro_match.group(3).strip() 206 macros.append((re.compile("\\b%s\\(" % name), TextMacro(args, body))) 207 else: 208 python_match = PYTHON_MACRO_PATTERN.match(line) 209 if python_match: 210 name = python_match.group(1) 211 args = [match.strip() for match in python_match.group(2).split(',')] 212 body = python_match.group(3).strip() 213 fun = eval("lambda " + ",".join(args) + ': ' + body) 214 macros.append((re.compile("\\b%s\\(" % name), PythonMacro(args, fun))) 215 else: 216 raise ("Illegal line: " + line) 217 return (constants, macros) 218 219 INLINE_MACRO_PATTERN = re.compile(r'macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*\n') 220 INLINE_MACRO_END_PATTERN = re.compile(r'endmacro\s*\n') 221 222 def ExpandInlineMacros(lines, filename): 223 pos = 0 224 while True: 225 macro_match = INLINE_MACRO_PATTERN.search(lines, pos) 226 if macro_match is None: 227 # no more macros 228 return lines 229 name = macro_match.group(1) 230 args = [match.strip() for match in macro_match.group(2).split(',')] 231 end_macro_match = INLINE_MACRO_END_PATTERN.search(lines, macro_match.end()); 232 if end_macro_match is None: 233 raise ("Macro %s unclosed in %s" % (name, filename)) 234 body = lines[macro_match.end():end_macro_match.start()] 235 236 # remove macro definition 237 lines = lines[:macro_match.start()] + lines[end_macro_match.end():] 238 name_pattern = re.compile("\\b%s\\(" % name) 239 macro = TextMacro(args, body) 240 241 # advance position to where the macro defintion was 242 pos = macro_match.start() 243 244 def non_expander(s): 245 return s 246 lines = ExpandMacroDefinition(lines, pos, name_pattern, macro, non_expander) 247 248 HEADER_TEMPLATE = """\ 249 // Copyright 2011 Google Inc. All Rights Reserved. 250 251 // This file was generated from .js source files by GYP. If you 252 // want to make changes to this file you should either change the 253 // javascript source files or the GYP script. 254 255 #include "v8.h" 256 #include "natives.h" 257 #include "utils.h" 258 259 namespace v8 { 260 namespace internal { 261 262 static const byte sources[] = { %(sources_data)s }; 263 264 %(raw_sources_declaration)s\ 265 266 template <> 267 int NativesCollection<%(type)s>::GetBuiltinsCount() { 268 return %(builtin_count)i; 269 } 270 271 template <> 272 int NativesCollection<%(type)s>::GetDebuggerCount() { 273 return %(debugger_count)i; 274 } 275 276 template <> 277 int NativesCollection<%(type)s>::GetIndex(const char* name) { 278 %(get_index_cases)s\ 279 return -1; 280 } 281 282 template <> 283 int NativesCollection<%(type)s>::GetRawScriptsSize() { 284 return %(raw_total_length)i; 285 } 286 287 template <> 288 Vector<const char> NativesCollection<%(type)s>::GetRawScriptSource(int index) { 289 %(get_raw_script_source_cases)s\ 290 return Vector<const char>("", 0); 291 } 292 293 template <> 294 Vector<const char> NativesCollection<%(type)s>::GetScriptName(int index) { 295 %(get_script_name_cases)s\ 296 return Vector<const char>("", 0); 297 } 298 299 template <> 300 Vector<const byte> NativesCollection<%(type)s>::GetScriptsSource() { 301 return Vector<const byte>(sources, %(total_length)i); 302 } 303 304 template <> 305 void NativesCollection<%(type)s>::SetRawScriptsSource(Vector<const char> raw_source) { 306 ASSERT(%(raw_total_length)i == raw_source.length()); 307 raw_sources = raw_source.start(); 308 } 309 310 } // internal 311 } // v8 312 """ 313 314 315 RAW_SOURCES_COMPRESSION_DECLARATION = """\ 316 static const char* raw_sources = NULL; 317 """ 318 319 320 RAW_SOURCES_DECLARATION = """\ 321 static const char* raw_sources = reinterpret_cast<const char*>(sources); 322 """ 323 324 325 GET_INDEX_CASE = """\ 326 if (strcmp(name, "%(id)s") == 0) return %(i)i; 327 """ 328 329 330 GET_RAW_SCRIPT_SOURCE_CASE = """\ 331 if (index == %(i)i) return Vector<const char>(raw_sources + %(offset)i, %(raw_length)i); 332 """ 333 334 335 GET_SCRIPT_NAME_CASE = """\ 336 if (index == %(i)i) return Vector<const char>("%(name)s", %(length)i); 337 """ 338 339 def JS2C(source, target, env): 340 ids = [] 341 debugger_ids = [] 342 modules = [] 343 # Locate the macros file name. 344 consts = [] 345 macros = [] 346 for s in source: 347 if 'macros.py' == (os.path.split(str(s))[1]): 348 (consts, macros) = ReadMacros(ReadLines(str(s))) 349 else: 350 modules.append(s) 351 352 minifier = jsmin.JavaScriptMinifier() 353 354 module_offset = 0 355 all_sources = [] 356 for module in modules: 357 filename = str(module) 358 debugger = filename.endswith('-debugger.js') 359 lines = ReadFile(filename) 360 lines = ExpandConstants(lines, consts) 361 lines = ExpandMacros(lines, macros) 362 lines = RemoveCommentsAndTrailingWhitespace(lines) 363 lines = ExpandInlineMacros(lines, filename) 364 Validate(lines, filename) 365 lines = minifier.JSMinify(lines) 366 id = (os.path.split(filename)[1])[:-3] 367 if debugger: id = id[:-9] 368 raw_length = len(lines) 369 if debugger: 370 debugger_ids.append((id, raw_length, module_offset)) 371 else: 372 ids.append((id, raw_length, module_offset)) 373 all_sources.append(lines) 374 module_offset += raw_length 375 total_length = raw_total_length = module_offset 376 377 if env['COMPRESSION'] == 'off': 378 raw_sources_declaration = RAW_SOURCES_DECLARATION 379 sources_data = ToCAsciiArray("".join(all_sources)) 380 else: 381 raw_sources_declaration = RAW_SOURCES_COMPRESSION_DECLARATION 382 if env['COMPRESSION'] == 'bz2': 383 all_sources = bz2.compress("".join(all_sources)) 384 total_length = len(all_sources) 385 sources_data = ToCArray(all_sources) 386 387 # Build debugger support functions 388 get_index_cases = [ ] 389 get_raw_script_source_cases = [ ] 390 get_script_name_cases = [ ] 391 392 i = 0 393 for (id, raw_length, module_offset) in debugger_ids + ids: 394 native_name = "native %s.js" % id 395 get_index_cases.append(GET_INDEX_CASE % { 'id': id, 'i': i }) 396 get_raw_script_source_cases.append(GET_RAW_SCRIPT_SOURCE_CASE % { 397 'offset': module_offset, 398 'raw_length': raw_length, 399 'i': i 400 }) 401 get_script_name_cases.append(GET_SCRIPT_NAME_CASE % { 402 'name': native_name, 403 'length': len(native_name), 404 'i': i 405 }) 406 i = i + 1 407 408 # Emit result 409 output = open(str(target[0]), "w") 410 output.write(HEADER_TEMPLATE % { 411 'builtin_count': len(ids) + len(debugger_ids), 412 'debugger_count': len(debugger_ids), 413 'sources_data': sources_data, 414 'raw_sources_declaration': raw_sources_declaration, 415 'raw_total_length': raw_total_length, 416 'total_length': total_length, 417 'get_index_cases': "".join(get_index_cases), 418 'get_raw_script_source_cases': "".join(get_raw_script_source_cases), 419 'get_script_name_cases': "".join(get_script_name_cases), 420 'type': env['TYPE'] 421 }) 422 output.close() 423 424 def main(): 425 natives = sys.argv[1] 426 type = sys.argv[2] 427 compression = sys.argv[3] 428 source_files = sys.argv[4:] 429 JS2C(source_files, [natives], { 'TYPE': type, 'COMPRESSION': compression }) 430 431 if __name__ == "__main__": 432 main() 433