js2c.py 11.7 KB
Newer Older
1
#!/usr/bin/env python
2
#
3
# Copyright 2012 the V8 project authors. All rights reserved.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#     * Neither the name of Google Inc. nor the names of its
#       contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# This is a utility for converting JavaScript source code into C-style
# char arrays. It is used for embedded JavaScript code in the V8
# library.

import os, re, sys, string
35
import jsmin
36
import bz2
37 38


39
def ToCAsciiArray(lines):
40 41 42 43 44
  result = []
  for chr in lines:
    value = ord(chr)
    assert value < 128
    result.append(str(value))
45 46 47 48 49 50 51
  return ", ".join(result)


def ToCArray(lines):
  result = []
  for chr in lines:
    result.append(str(ord(chr)))
52 53 54
  return ", ".join(result)


55 56 57 58 59 60 61
def RemoveCommentsAndTrailingWhitespace(lines):
  lines = re.sub(r'//.*\n', '\n', lines) # end-of-line comments
  lines = re.sub(re.compile(r'/\*.*?\*/', re.DOTALL), '', lines) # comments.
  lines = re.sub(r'\s+\n+', '\n', lines) # trailing whitespace
  return lines


62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
def ReadFile(filename):
  file = open(filename, "rt")
  try:
    lines = file.read()
  finally:
    file.close()
  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


97 98
EVAL_PATTERN = re.compile(r'\beval\s*\(')
WITH_PATTERN = re.compile(r'\bwith\s*\(')
99 100 101 102 103 104 105 106 107 108 109 110 111 112


def Validate(lines, file):
  lines = RemoveCommentsAndTrailingWhitespace(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)


113
def ExpandConstants(lines, constants):
vitalyr@chromium.org's avatar
vitalyr@chromium.org committed
114 115
  for key, value in constants:
    lines = key.sub(str(value), lines)
116 117
  return lines

118

119
def ExpandMacros(lines, macros):
vitalyr@chromium.org's avatar
vitalyr@chromium.org committed
120 121 122 123 124
  # We allow macros to depend on the previously declared macros, but
  # we don't allow self-dependecies or recursion.
  for name_pattern, macro in reversed(macros):
    pattern_match = name_pattern.search(lines, 0)
    while pattern_match is not None:
125 126
      # Scan over the arguments
      height = 1
vitalyr@chromium.org's avatar
vitalyr@chromium.org committed
127 128 129
      start = pattern_match.start()
      end = pattern_match.end()
      assert lines[end - 1] == '('
130
      last_match = end
131
      arg_index = [0]  # Wrap state into array, to work around Python "scoping"
132 133 134 135
      mapping = { }
      def add_arg(str):
        # Remember to expand recursively in the arguments
        replacement = ExpandMacros(str.strip(), macros)
136 137
        mapping[macro.args[arg_index[0]]] = replacement
        arg_index[0] += 1
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
      while end < len(lines) and height > 0:
        # We don't count commas at higher nesting levels.
        if lines[end] == ',' and height == 1:
          add_arg(lines[last_match:end])
          last_match = end + 1
        elif lines[end] in ['(', '{', '[']:
          height = height + 1
        elif lines[end] in [')', '}', ']']:
          height = height - 1
        end = end + 1
      # Remember to add the last match.
      add_arg(lines[last_match:end-1])
      result = macro.expand(mapping)
      # Replace the occurrence of the macro with the expansion
      lines = lines[:start] + result + lines[end:]
vitalyr@chromium.org's avatar
vitalyr@chromium.org committed
153
      pattern_match = name_pattern.search(lines, start + len(result))
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
  return lines

class TextMacro:
  def __init__(self, args, body):
    self.args = args
    self.body = body
  def expand(self, mapping):
    result = self.body
    for key, value in mapping.items():
        result = result.replace(key, value)
    return result

class PythonMacro:
  def __init__(self, args, fun):
    self.args = args
    self.fun = fun
  def expand(self, mapping):
    args = []
    for arg in self.args:
      args.append(mapping[arg])
    return str(self.fun(*args))

176 177 178
CONST_PATTERN = re.compile(r'^const\s+([a-zA-Z0-9_]+)\s*=\s*([^;]*);$')
MACRO_PATTERN = re.compile(r'^macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$')
PYTHON_MACRO_PATTERN = re.compile(r'^python\s+macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$')
179

vitalyr@chromium.org's avatar
vitalyr@chromium.org committed
180

181
def ReadMacros(lines):
vitalyr@chromium.org's avatar
vitalyr@chromium.org committed
182 183
  constants = []
  macros = []
184 185 186 187 188 189 190 191 192
  for line in lines:
    hash = line.find('#')
    if hash != -1: line = line[:hash]
    line = line.strip()
    if len(line) is 0: continue
    const_match = CONST_PATTERN.match(line)
    if const_match:
      name = const_match.group(1)
      value = const_match.group(2).strip()
vitalyr@chromium.org's avatar
vitalyr@chromium.org committed
193
      constants.append((re.compile("\\b%s\\b" % name), value))
194 195 196 197
    else:
      macro_match = MACRO_PATTERN.match(line)
      if macro_match:
        name = macro_match.group(1)
198
        args = [match.strip() for match in macro_match.group(2).split(',')]
199
        body = macro_match.group(3).strip()
vitalyr@chromium.org's avatar
vitalyr@chromium.org committed
200
        macros.append((re.compile("\\b%s\\(" % name), TextMacro(args, body)))
201 202 203 204
      else:
        python_match = PYTHON_MACRO_PATTERN.match(line)
        if python_match:
          name = python_match.group(1)
205
          args = [match.strip() for match in python_match.group(2).split(',')]
206 207
          body = python_match.group(3).strip()
          fun = eval("lambda " + ",".join(args) + ': ' + body)
vitalyr@chromium.org's avatar
vitalyr@chromium.org committed
208
          macros.append((re.compile("\\b%s\\(" % name), PythonMacro(args, fun)))
209 210 211 212 213 214
        else:
          raise ("Illegal line: " + line)
  return (constants, macros)


HEADER_TEMPLATE = """\
215
// Copyright 2011 Google Inc. All Rights Reserved.
216

217
// This file was generated from .js source files by GYP.  If you
218
// want to make changes to this file you should either change the
219
// javascript source files or the GYP script.
220 221 222

#include "v8.h"
#include "natives.h"
223
#include "utils.h"
224 225 226 227

namespace v8 {
namespace internal {

228 229 230
  static const byte sources[] = { %(sources_data)s };

%(raw_sources_declaration)s\
231

232 233
  template <>
  int NativesCollection<%(type)s>::GetBuiltinsCount() {
234 235 236
    return %(builtin_count)i;
  }

237
  template <>
238 239
  int NativesCollection<%(type)s>::GetDebuggerCount() {
    return %(debugger_count)i;
240 241
  }

242 243
  template <>
  int NativesCollection<%(type)s>::GetIndex(const char* name) {
244 245 246 247
%(get_index_cases)s\
    return -1;
  }

248
  template <>
249 250 251 252 253 254 255
  int NativesCollection<%(type)s>::GetRawScriptsSize() {
    return %(raw_total_length)i;
  }

  template <>
  Vector<const char> NativesCollection<%(type)s>::GetRawScriptSource(int index) {
%(get_raw_script_source_cases)s\
256 257 258
    return Vector<const char>("", 0);
  }

259 260
  template <>
  Vector<const char> NativesCollection<%(type)s>::GetScriptName(int index) {
261 262 263 264
%(get_script_name_cases)s\
    return Vector<const char>("", 0);
  }

265 266 267 268 269 270 271 272 273 274 275
  template <>
  Vector<const byte> NativesCollection<%(type)s>::GetScriptsSource() {
    return Vector<const byte>(sources, %(total_length)i);
  }

  template <>
  void NativesCollection<%(type)s>::SetRawScriptsSource(Vector<const char> raw_source) {
    ASSERT(%(raw_total_length)i == raw_source.length());
    raw_sources = raw_source.start();
  }

276 277 278 279 280
}  // internal
}  // v8
"""


281 282 283 284 285 286 287
RAW_SOURCES_COMPRESSION_DECLARATION = """\
  static const char* raw_sources = NULL;
"""


RAW_SOURCES_DECLARATION = """\
  static const char* raw_sources = reinterpret_cast<const char*>(sources);
288 289 290
"""


291
GET_INDEX_CASE = """\
292 293 294 295
    if (strcmp(name, "%(id)s") == 0) return %(i)i;
"""


296 297
GET_RAW_SCRIPT_SOURCE_CASE = """\
    if (index == %(i)i) return Vector<const char>(raw_sources + %(offset)i, %(raw_length)i);
298 299 300
"""


301
GET_SCRIPT_NAME_CASE = """\
302 303 304 305 306
    if (index == %(i)i) return Vector<const char>("%(name)s", %(length)i);
"""

def JS2C(source, target, env):
  ids = []
307
  debugger_ids = []
308 309
  modules = []
  # Locate the macros file name.
310 311
  consts = []
  macros = []
312 313 314 315 316 317
  for s in source:
    if 'macros.py' == (os.path.split(str(s))[1]):
      (consts, macros) = ReadMacros(ReadLines(str(s)))
    else:
      modules.append(s)

318 319
  minifier = jsmin.JavaScriptMinifier()

320 321
  module_offset = 0
  all_sources = []
322 323
  for module in modules:
    filename = str(module)
324
    debugger = filename.endswith('-debugger.js')
325
    lines = ReadFile(filename)
326 327
    lines = ExpandConstants(lines, consts)
    lines = ExpandMacros(lines, macros)
328
    Validate(lines, filename)
329
    lines = minifier.JSMinify(lines)
330
    id = (os.path.split(filename)[1])[:-3]
331
    if debugger: id = id[:-9]
332
    raw_length = len(lines)
333
    if debugger:
334
      debugger_ids.append((id, raw_length, module_offset))
335
    else:
336 337 338 339 340 341 342 343 344 345 346 347 348 349
      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))
  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)
350

351
  # Build debugger support functions
352
  get_index_cases = [ ]
353
  get_raw_script_source_cases = [ ]
354 355 356
  get_script_name_cases = [ ]

  i = 0
357
  for (id, raw_length, module_offset) in debugger_ids + ids:
358
    native_name = "native %s.js" % id
359 360 361 362 363 364 365 366 367 368 369
    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
        })
370 371 372 373 374
    i = i + 1

  # Emit result
  output = open(str(target[0]), "w")
  output.write(HEADER_TEMPLATE % {
375 376
    'builtin_count': len(ids) + len(debugger_ids),
    'debugger_count': len(debugger_ids),
377 378 379 380
    'sources_data': sources_data,
    'raw_sources_declaration': raw_sources_declaration,
    'raw_total_length': raw_total_length,
    'total_length': total_length,
381
    'get_index_cases': "".join(get_index_cases),
382
    'get_raw_script_source_cases': "".join(get_raw_script_source_cases),
383 384
    'get_script_name_cases': "".join(get_script_name_cases),
    'type': env['TYPE']
385 386 387 388
  })
  output.close()

def main():
389
  natives = sys.argv[1]
390
  type = sys.argv[2]
391 392 393
  compression = sys.argv[3]
  source_files = sys.argv[4:]
  JS2C(source_files, [natives], { 'TYPE': type, 'COMPRESSION': compression })
394 395 396

if __name__ == "__main__":
  main()