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