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