text: "use strict";
  
  var arrays = require("../../utils/arrays"),
      asts   = require("../asts"),
      op     = require("../opcodes"),
      js     = require("../javascript");
  
  /* Generates parser JavaScript code. */
  function generateJavascript(ast, options) {
    /* These only indent non-empty lines to avoid trailing whitespace. */
    function indent2(code)  { return code.replace(/^(.+)$/gm, '  $1');         }
    function indent4(code)  { return code.replace(/^(.+)$/gm, '    $1');       }
    function indent8(code)  { return code.replace(/^(.+)$/gm, '        $1');   }
    function indent10(code) { return code.replace(/^(.+)$/gm, '          $1'); }
  
    function generateTables() {
      if (options.optimize === "size") {
        return [
          'peg$consts = [',
             indent2(ast.consts.join(',\n')),
          '],',
          '',
          'peg$bytecode = [',
             indent2(arrays.map(ast.rules, function(rule) {
               return 'peg$decode("'
                     + js.stringEscape(arrays.map(
                         rule.bytecode,
                         function(b) { return String.fromCharCode(b + 32); }
                       ).join(''))
                     + '")';
             }).join(',\n')),
          '],'
        ].join('\n');
      } else {
        return arrays.map(
          ast.consts,
          function(c, i) { return 'peg$c' + i + ' = ' + c + ','; }
        ).join('\n');
      }
    }
  
    function generateRuleHeader(ruleNameCode, ruleIndexCode) {
      var parts = [];
  
      parts.push('');
  
      if (options.trace) {
        parts.push([
          'peg$tracer.trace({',
          '  type:     "rule.enter",',
          '  rule:     ' + ruleNameCode + ',',
          '  location: peg$computeLocation(startPos, startPos)',
          '});',
          ''
        ].join('\n'));
      }
  
      if (options.cache) {
        parts.push([
          'var key    = peg$currPos * ' + ast.rules.length + ' + ' + ruleIndexCode + ',',
          '    cached = peg$resultsCache[key];',
          '',
          'if (cached) {',
          '  peg$currPos = cached.nextPos;',
          '',
        ].join('\n'));
  
        if (options.trace) {
          parts.push([
            'if (cached.result !== peg$FAILED) {',
            '  peg$tracer.trace({',
            '    type:   "rule.match",',
            '    rule:   ' + ruleNameCode + ',',
            '    result: cached.result,',
            '    location: peg$computeLocation(startPos, peg$currPos)',
            '  });',
            '} else {',
            '  peg$tracer.trace({',
            '    type: "rule.fail",',
            '    rule: ' + ruleNameCode + ',',
            '    location: peg$computeLocation(startPos, startPos)',
            '  });',
            '}',
            ''
          ].join('\n'));
        }
  
        parts.push([
          '  return cached.result;',
          '}',
          ''
        ].join('\n'));
      }
  
      return parts.join('\n');
    }
  
    function generateRuleFooter(ruleNameCode, resultCode) {
      var parts = [];
  
      if (options.cache) {
        parts.push([
          '',
          'peg$resultsCache[key] = { nextPos: peg$currPos, result: ' + resultCode + ' };'
        ].join('\n'));
      }
  
      if (options.trace) {
        parts.push([
            '',
            'if (' + resultCode + ' !== peg$FAILED) {',
            '  peg$tracer.trace({',
            '    type:   "rule.match",',
            '    rule:   ' + ruleNameCode + ',',
            '    result: ' + resultCode + ',',
            '    location: peg$computeLocation(startPos, peg$currPos)',
            '  });',
            '} else {',
            '  peg$tracer.trace({',
            '    type: "rule.fail",',
            '    rule: ' + ruleNameCode + ',',
            '    location: peg$computeLocation(startPos, startPos)',
            '  });',
            '}'
        ].join('\n'));
      }
  
      parts.push([
        '',
        'return ' + resultCode + ';'
      ].join('\n'));
  
      return parts.join('\n');
    }
  
    function generateInterpreter() {
      var parts = [];
  
      function generateCondition(cond, argsLength) {
        var baseLength      = argsLength + 3,
            thenLengthCode = 'bc[ip + ' + (baseLength - 2) + ']',
            elseLengthCode = 'bc[ip + ' + (baseLength - 1) + ']';
  
        return [
          'ends.push(end);',
          'ips.push(ip + ' + baseLength + ' + ' + thenLengthCode + ' + ' + elseLengthCode + ');',
          '',
          'if (' + cond + ') {',
          '  end = ip + ' + baseLength + ' + ' + thenLengthCode + ';',
          '  ip += ' + baseLength + ';',
          '} else {',
          '  end = ip + ' + baseLength + ' + ' + thenLengthCode + ' + ' + elseLengthCode + ';',
          '  ip += ' + baseLength + ' + ' + thenLengthCode + ';',
          '}',
          '',
          'break;'
        ].join('\n');
      }
  
      function generateLoop(cond) {
        var baseLength     = 2,
            bodyLengthCode = 'bc[ip + ' + (baseLength - 1) + ']';
  
        return [
          'if (' + cond + ') {',
          '  ends.push(end);',
          '  ips.push(ip);',
          '',
          '  end = ip + ' + baseLength + ' + ' + bodyLengthCode + ';',
          '  ip += ' + baseLength + ';',
          '} else {',
          '  ip += ' + baseLength + ' + ' + bodyLengthCode + ';',
          '}',
          '',
          'break;'
        ].join('\n');
      }
  
      function generateCall() {
        var baseLength       = 4,
            paramsLengthCode = 'bc[ip + ' + (baseLength - 1) + ']';
  
        return [
          'params = bc.slice(ip + ' + baseLength + ', ip + ' + baseLength + ' + ' + paramsLengthCode + ');',
          'for (i = 0; i < ' + paramsLengthCode + '; i++) {',
          '  params[i] = stack[stack.length - 1 - params[i]];',
          '}',
          '',
          'stack.splice(',
          '  stack.length - bc[ip + 2],',
          '  bc[ip + 2],',
          '  peg$consts[bc[ip + 1]].apply(null, params)',
          ');',
          '',
          'ip += ' + baseLength + ' + ' + paramsLengthCode + ';',
          'break;'
        ].join('\n');
      }
  
      parts.push([
        'function peg$decode(s) {',
        '  var bc = new Array(s.length), i;',
        '',
        '  for (i = 0; i < s.length; i++) {',
        '    bc[i] = s.charCodeAt(i) - 32;',
        '  }',
        '',
        '  return bc;',
        '}',
        '',
        'function peg$parseRule(index) {',
      ].join('\n'));
  
      if (options.trace) {
        parts.push([
          '  var bc       = peg$bytecode[index],',
          '      ip       = 0,',
          '      ips      = [],',
          '      end      = bc.length,',
          '      ends     = [],',
          '      stack    = [],',
          '      startPos = peg$currPos,',
          '      params, i;',
        ].join('\n'));
      } else {
        parts.push([
          '  var bc    = peg$bytecode[index],',
          '      ip    = 0,',
          '      ips   = [],',
          '      end   = bc.length,',
          '      ends  = [],',
          '      stack = [],',
          '      params, i;',
        ].join('\n'));
      }
  
      parts.push(indent2(generateRuleHeader('peg$ruleNames[index]', 'index')));
  
      parts.push([
        /*
         * The point of the outer loop and the |ips| & |ends| stacks is to avoid
         * recursive calls for interpreting parts of bytecode. In other words, we
         * implement the |interpret| operation of the abstract machine without
         * function calls. Such calls would likely slow the parser down and more
         * importantly cause stack overflows for complex grammars.
         */
        '  while (true) {',
        '    while (ip < end) {',
        '      switch (bc[ip]) {',
        '        case ' + op.PUSH + ':',               // PUSH c
        '          stack.push(peg$consts[bc[ip + 1]]);',
        '          ip += 2;',
        '          break;',
        '',
        '        case ' + op.PUSH_UNDEFINED + ':',     // PUSH_UNDEFINED
        '          stack.push(void 0);',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.PUSH_NULL + ':',          // PUSH_NULL
        '          stack.push(null);',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.PUSH_FAILED + ':',        // PUSH_FAILED
        '          stack.push(peg$FAILED);',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.PUSH_EMPTY_ARRAY + ':',   // PUSH_EMPTY_ARRAY
        '          stack.push([]);',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.PUSH_CURR_POS + ':',      // PUSH_CURR_POS
        '          stack.push(peg$currPos);',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.POP + ':',                // POP
        '          stack.pop();',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.POP_CURR_POS + ':',       // POP_CURR_POS
        '          peg$currPos = stack.pop();',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.POP_N + ':',              // POP_N n
        '          stack.length -= bc[ip + 1];',
        '          ip += 2;',
        '          break;',
        '',
        '        case ' + op.NIP + ':',                // NIP
        '          stack.splice(-2, 1);',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.APPEND + ':',             // APPEND
        '          stack[stack.length - 2].push(stack.pop());',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.WRAP + ':',               // WRAP n
        '          stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1]));',
        '          ip += 2;',
        '          break;',
        '',
        '        case ' + op.TEXT + ':',               // TEXT
        '          stack.push(input.substring(stack.pop(), peg$currPos));',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.IF + ':',                 // IF t, f
                   indent10(generateCondition('stack[stack.length - 1]', 0)),
        '',
        '        case ' + op.IF_ERROR + ':',           // IF_ERROR t, f
                   indent10(generateCondition(
                     'stack[stack.length - 1] === peg$FAILED',
                     0
                   )),
        '',
        '        case ' + op.IF_NOT_ERROR + ':',       // IF_NOT_ERROR t, f
                   indent10(
                     generateCondition('stack[stack.length - 1] !== peg$FAILED',
                     0
                   )),
        '',
        '        case ' + op.WHILE_NOT_ERROR + ':',    // WHILE_NOT_ERROR b
                   indent10(generateLoop('stack[stack.length - 1] !== peg$FAILED')),
        '',
        '        case ' + op.MATCH_ANY + ':',          // MATCH_ANY a, f, ...
                   indent10(generateCondition('input.length > peg$currPos', 0)),
        '',
        '        case ' + op.MATCH_STRING + ':',       // MATCH_STRING s, a, f, ...
                   indent10(generateCondition(
                     'input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]',
                     1
                   )),
        '',
        '        case ' + op.MATCH_STRING_IC + ':',    // MATCH_STRING_IC s, a, f, ...
                   indent10(generateCondition(
                     'input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]',
                     1
                   )),
        '',
        '        case ' + op.MATCH_REGEXP + ':',       // MATCH_REGEXP r, a, f, ...
                   indent10(generateCondition(
                     'peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))',
                     1
                   )),
        '',
        '        case ' + op.ACCEPT_N + ':',           // ACCEPT_N n
        '          stack.push(input.substr(peg$currPos, bc[ip + 1]));',
        '          peg$currPos += bc[ip + 1];',
        '          ip += 2;',
        '          break;',
        '',
        '        case ' + op.ACCEPT_STRING + ':',      // ACCEPT_STRING s
        '          stack.push(peg$consts[bc[ip + 1]]);',
        '          peg$currPos += peg$consts[bc[ip + 1]].length;',
        '          ip += 2;',
        '          break;',
        '',
        '        case ' + op.FAIL + ':',               // FAIL e
        '          stack.push(peg$FAILED);',
        '          if (peg$silentFails === 0) {',
        '            peg$fail(peg$consts[bc[ip + 1]]);',
        '          }',
        '          ip += 2;',
        '          break;',
        '',
        '        case ' + op.LOAD_SAVED_POS + ':',     // LOAD_SAVED_POS p
        '          peg$savedPos = stack[stack.length - 1 - bc[ip + 1]];',
        '          ip += 2;',
        '          break;',
        '',
        '        case ' + op.UPDATE_SAVED_POS + ':',   // UPDATE_SAVED_POS
        '          peg$savedPos = peg$currPos;',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.CALL + ':',               // CALL f, n, pc, p1, p2, ..., pN
                   indent10(generateCall()),
        '',
        '        case ' + op.RULE + ':',               // RULE r
        '          stack.push(peg$parseRule(bc[ip + 1]));',
        '          ip += 2;',
        '          break;',
        '',
        '        case ' + op.SILENT_FAILS_ON + ':',    // SILENT_FAILS_ON
        '          peg$silentFails++;',
        '          ip++;',
        '          break;',
        '',
        '        case ' + op.SILENT_FAILS_OFF + ':',   // SILENT_FAILS_OFF
        '          peg$silentFails--;',
        '          ip++;',
        '          break;',
        '',
        '        default:',
        '          throw new Error("Invalid opcode: " + bc[ip] + ".");',
        '      }',
        '    }',
        '',
        '    if (ends.length > 0) {',
        '      end = ends.pop();',
        '      ip = ips.pop();',
        '    } else {',
        '      break;',
        '    }',
        '  }'
      ].join('\n'));
  
      parts.push(indent2(generateRuleFooter('peg$ruleNames[index]', 'stack[0]')));
      parts.push('}');
  
      return parts.join('\n');
    }
  
    function generateRuleFunction(rule) {
      var parts = [], code;
  
      function c(i) { return "peg$c" + i; } // |consts[i]| of the abstract machine
      function s(i) { return "s"     + i; } // |stack[i]| of the abstract machine
  
      var stack = {
            sp:    -1,
            maxSp: -1,
  
            push: function(exprCode) {
              var code = s(++this.sp) + ' = ' + exprCode + ';';
  
              if (this.sp > this.maxSp) { this.maxSp = this.sp; }
  
              return code;
            },
  
            pop: function() {
              var n, values;
  
              if (arguments.length === 0) {
                return s(this.sp--);
              } else {
                n = arguments[0];
                values = arrays.map(arrays.range(this.sp - n + 1, this.sp + 1), s);
                this.sp -= n;
  
                return values;
              }
            },
  
            top: function() {
              return s(this.sp);
            },
  
            index: function(i) {
              return s(this.sp - i);
            }
          };
  
      function compile(bc) {
        var ip    = 0,
            end   = bc.length,
            parts = [],
            value;
  
        function compileCondition(cond, argCount) {
          var baseLength = argCount + 3,
              thenLength = bc[ip + baseLength - 2],
              elseLength = bc[ip + baseLength - 1],
              baseSp     = stack.sp,
              thenCode, elseCode, thenSp, elseSp;
  
          ip += baseLength;
          thenCode = compile(bc.slice(ip, ip + thenLength));
          thenSp = stack.sp;
          ip += thenLength;
  
          if (elseLength > 0) {
            stack.sp = baseSp;
            elseCode = compile(bc.slice(ip, ip + elseLength));
            elseSp = stack.sp;
            ip += elseLength;
  
            if (thenSp !== elseSp) {
              throw new Error(
                "Branches of a condition must move the stack pointer in the same way."
              );
            }
          }
  
          parts.push('if (' + cond + ') {');
          parts.push(indent2(thenCode));
          if (elseLength > 0) {
            parts.push('} else {');
            parts.push(indent2(elseCode));
          }
          parts.push('}');
        }
  
        function compileLoop(cond) {
          var baseLength = 2,
              bodyLength = bc[ip + baseLength - 1],
              baseSp     = stack.sp,
              bodyCode, bodySp;
  
          ip += baseLength;
          bodyCode = compile(bc.slice(ip, ip + bodyLength));
          bodySp = stack.sp;
          ip += bodyLength;
  
          if (bodySp !== baseSp) {
            throw new Error("Body of a loop can't move the stack pointer.");
          }
  
          parts.push('while (' + cond + ') {');
          parts.push(indent2(bodyCode));
          parts.push('}');
        }
  
        function compileCall() {
          var baseLength   = 4,
              paramsLength = bc[ip + baseLength - 1];
  
          var value = c(bc[ip + 1]) + '('
                + arrays.map(
                    bc.slice(ip + baseLength, ip + baseLength + paramsLength),
                    function(p) { return stack.index(p); }
                  ).join(', ')
                + ')';
          stack.pop(bc[ip + 2]);
          parts.push(stack.push(value));
          ip += baseLength + paramsLength;
        }
  
        while (ip < end) {
          switch (bc[ip]) {
            case op.PUSH:               // PUSH c
              parts.push(stack.push(c(bc[ip + 1])));
              ip += 2;
              break;
  
            case op.PUSH_CURR_POS:      // PUSH_CURR_POS
              parts.push(stack.push('peg$currPos'));
              ip++;
              break;
  
            case op.PUSH_UNDEFINED:      // PUSH_UNDEFINED
              parts.push(stack.push('void 0'));
              ip++;
              break;
  
            case op.PUSH_NULL:          // PUSH_NULL
              parts.push(stack.push('null'));
              ip++;
              break;
  
            case op.PUSH_FAILED:        // PUSH_FAILED
              parts.push(stack.push('peg$FAILED'));
              ip++;
              break;
  
            case op.PUSH_EMPTY_ARRAY:   // PUSH_EMPTY_ARRAY
              parts.push(stack.push('[]'));
              ip++;
              break;
  
            case op.POP:                // POP
              stack.pop();
              ip++;
              break;
  
            case op.POP_CURR_POS:       // POP_CURR_POS
              parts.push('peg$currPos = ' + stack.pop() + ';');
              ip++;
              break;
  
            case op.POP_N:              // POP_N n
              stack.pop(bc[ip + 1]);
              ip += 2;
              break;
  
            case op.NIP:                // NIP
              value = stack.pop();
              stack.pop();
              parts.push(stack.push(value));
              ip++;
              break;
  
            case op.APPEND:             // APPEND
              value = stack.pop();
              parts.push(stack.top() + '.push(' + value + ');');
              ip++;
              break;
  
            case op.WRAP:               // WRAP n
              parts.push(
                stack.push('[' + stack.pop(bc[ip + 1]).join(', ') + ']')
              );
              ip += 2;
              break;
  
            case op.TEXT:               // TEXT
              parts.push(
                stack.push('input.substring(' + stack.pop() + ', peg$currPos)')
              );
              ip++;
              break;
  
            case op.IF:                 // IF t, f
              compileCondition(stack.top(), 0);
              break;
  
            case op.IF_ERROR:           // IF_ERROR t, f
              compileCondition(stack.top() + ' === peg$FAILED', 0);
              break;
  
            case op.IF_NOT_ERROR:       // IF_NOT_ERROR t, f
              compileCondition(stack.top() + ' !== peg$FAILED', 0);
              break;
  
            case op.WHILE_NOT_ERROR:    // WHILE_NOT_ERROR b
              compileLoop(stack.top() + ' !== peg$FAILED', 0);
              break;
  
            case op.MATCH_ANY:          // MATCH_ANY a, f, ...
              compileCondition('input.length > peg$currPos', 0);
              break;
  
            case op.MATCH_STRING:       // MATCH_STRING s, a, f, ...
              compileCondition(
                eval(ast.consts[bc[ip + 1]]).length > 1
                  ? 'input.substr(peg$currPos, '
                      + eval(ast.consts[bc[ip + 1]]).length
                      + ') === '
                      + c(bc[ip + 1])
                  : 'input.charCodeAt(peg$currPos) === '
                      + eval(ast.consts[bc[ip + 1]]).charCodeAt(0),
                1
              );
              break;
  
            case op.MATCH_STRING_IC:    // MATCH_STRING_IC s, a, f, ...
              compileCondition(
                'input.substr(peg$currPos, '
                  + eval(ast.consts[bc[ip + 1]]).length
                  + ').toLowerCase() === '
                  + c(bc[ip + 1]),
                1
              );
              break;
  
            case op.MATCH_REGEXP:       // MATCH_REGEXP r, a, f, ...
              compileCondition(
                c(bc[ip + 1]) + '.test(input.charAt(peg$currPos))',
                1
              );
              break;
  
            case op.ACCEPT_N:           // ACCEPT_N n
              parts.push(stack.push(
                bc[ip + 1] > 1
                  ? 'input.substr(peg$currPos, ' + bc[ip + 1] + ')'
                  : 'input.charAt(peg$currPos)'
              ));
              parts.push(
                bc[ip + 1] > 1
                  ? 'peg$currPos += ' + bc[ip + 1] + ';'
                  : 'peg$currPos++;'
              );
              ip += 2;
              break;
  
            case op.ACCEPT_STRING:      // ACCEPT_STRING s
              parts.push(stack.push(c(bc[ip + 1])));
              parts.push(
                eval(ast.consts[bc[ip + 1]]).length > 1
                  ? 'peg$currPos += ' + eval(ast.consts[bc[ip + 1]]).length + ';'
                  : 'peg$currPos++;'
              );
              ip += 2;
              break;
  
            case op.FAIL:               // FAIL e
              parts.push(stack.push('peg$FAILED'));
              parts.push('if (peg$silentFails === 0) { peg$fail(' + c(bc[ip + 1]) + '); }');
              ip += 2;
              break;
  
            case op.LOAD_SAVED_POS:     // LOAD_SAVED_POS p
              parts.push('peg$savedPos = ' + stack.index(bc[ip + 1]) + ';');
              ip += 2;
              break;
  
            case op.UPDATE_SAVED_POS:   // UPDATE_SAVED_POS
              parts.push('peg$savedPos = peg$currPos;');
              ip++;
              break;
  
            case op.CALL:               // CALL f, n, pc, p1, p2, ..., pN
              compileCall();
              break;
  
            case op.RULE:               // RULE r
              parts.push(stack.push("peg$parse" + ast.rules[bc[ip + 1]].name + "()"));
              ip += 2;
              break;
  
            case op.SILENT_FAILS_ON:    // SILENT_FAILS_ON
              parts.push('peg$silentFails++;');
              ip++;
              break;
  
            case op.SILENT_FAILS_OFF:   // SILENT_FAILS_OFF
              parts.push('peg$silentFails--;');
              ip++;
              break;
  
            default:
              throw new Error("Invalid opcode: " + bc[ip] + ".");
          }
        }
  
        return parts.join('\n');
      }
  
      code = compile(rule.bytecode);
  
      parts.push('function peg$parse' + rule.name + '() {');
  
      if (options.trace) {
        parts.push([
          '  var ' + arrays.map(arrays.range(0, stack.maxSp + 1), s).join(', ') + ',',
          '      startPos = peg$currPos;'
        ].join('\n'));
      } else {
        parts.push(
          '  var ' + arrays.map(arrays.range(0, stack.maxSp + 1), s).join(', ') + ';'
        );
      }
  
      parts.push(indent2(generateRuleHeader(
        '"' + js.stringEscape(rule.name) + '"',
        asts.indexOfRule(ast, rule.name)
      )));
      parts.push(indent2(code));
      parts.push(indent2(generateRuleFooter(
        '"' + js.stringEscape(rule.name) + '"',
        s(0)
      )));
  
      parts.push('}');
  
      return parts.join('\n');
    }
  
    var parts = [],
        startRuleIndices,   startRuleIndex,
        startRuleFunctions, startRuleFunction,
        ruleNames;
  
    parts.push([
      '(function() {',
      '  "use strict";',
      '',
      '  /*',
      '   * Generated by PEG.js 0.9.0.',
      '   *',
      '   * http://pegjs.org/',
      '   */',
      '',
      '  function peg$subclass(child, parent) {',
      '    function ctor() { this.constructor = child; }',
      '    ctor.prototype = parent.prototype;',
      '    child.prototype = new ctor();',
      '  }',
      '',
      '  function peg$SyntaxError(message, expected, found, location) {',
      '    this.message  = message;',
      '    this.expected = expected;',
      '    this.found    = found;',
      '    this.location = location;',
      '    this.name     = "SyntaxError";',
      '',
      '    if (typeof Error.captureStackTrace === "function") {',
      '      Error.captureStackTrace(this, peg$SyntaxError);',
      '    }',
      '  }',
      '',
      '  peg$subclass(peg$SyntaxError, Error);',
      ''
    ].join('\n'));
  
    if (options.trace) {
      parts.push([
        '  function peg$DefaultTracer() {',
        '    this.indentLevel = 0;',
        '  }',
        '',
        '  peg$DefaultTracer.prototype.trace = function(event) {',
        '    var that = this;',
        '',
        '    function log(event) {',
        '      function repeat(string, n) {',
        '         var result = "", i;',
        '',
        '         for (i = 0; i < n; i++) {',
        '           result += string;',
        '         }',
        '',
        '         return result;',
        '      }',
        '',
        '      function pad(string, length) {',
        '        return string + repeat(" ", length - string.length);',
        '      }',
        '',
        '      if (typeof console === "object") {',   // IE 8-10
        '        console.log(',
        '          event.location.start.line + ":" + event.location.start.column + "-"',
        '            + event.location.end.line + ":" + event.location.end.column + " "',
        '            + pad(event.type, 10) + " "',
        '            + repeat("  ", that.indentLevel) + event.rule',
        '        );',
        '      }',
        '    }',
        '',
        '    switch (event.type) {',
        '      case "rule.enter":',
        '        log(event);',
        '        this.indentLevel++;',
        '        break;',
        '',
        '      case "rule.match":',
        '        this.indentLevel--;',
        '        log(event);',
        '        break;',
        '',
        '      case "rule.fail":',
        '        this.indentLevel--;',
        '        log(event);',
        '        break;',
        '',
        '      default:',
        '        throw new Error("Invalid event type: " + event.type + ".");',
        '    }',
        '  };',
        ''
      ].join('\n'));
    }
  
    parts.push([
      '  function peg$parse(input) {',
      '    var options = arguments.length > 1 ? arguments[1] : {},',
      '        parser  = this,',
      '',
      '        peg$FAILED = {},',
      ''
    ].join('\n'));
  
    if (options.optimize === "size") {
      startRuleIndices = '{ '
                       + arrays.map(
                           options.allowedStartRules,
                           function(r) { return r + ': ' + asts.indexOfRule(ast, r); }
                         ).join(', ')
                       + ' }';
      startRuleIndex = asts.indexOfRule(ast, options.allowedStartRules[0]);
  
      parts.push([
        '        peg$startRuleIndices = ' + startRuleIndices + ',',
        '        peg$startRuleIndex   = ' + startRuleIndex + ','
      ].join('\n'));
    } else {
      startRuleFunctions = '{ '
                       + arrays.map(
                           options.allowedStartRules,
                           function(r) { return r + ': peg$parse' + r; }
                         ).join(', ')
                       + ' }';
      startRuleFunction = 'peg$parse' + options.allowedStartRules[0];
  
      parts.push([
        '        peg$startRuleFunctions = ' + startRuleFunctions + ',',
        '        peg$startRuleFunction  = ' + startRuleFunction + ','
      ].join('\n'));
    }
  
    parts.push('');
  
    parts.push(indent8(generateTables()));
  
    parts.push([
      '',
      '        peg$currPos          = 0,',
      '        peg$savedPos         = 0,',
      '        peg$posDetailsCache  = [{ line: 1, column: 1, seenCR: false }],',
      '        peg$maxFailPos       = 0,',
      '        peg$maxFailExpected  = [],',
      '        peg$silentFails      = 0,',   // 0 = report failures, > 0 = silence failures
      ''
    ].join('\n'));
  
    if (options.cache) {
      parts.push([
        '        peg$resultsCache = {},',
        ''
      ].join('\n'));
    }
  
    if (options.trace) {
      if (options.optimize === "size") {
        ruleNames = '['
                  + arrays.map(
                      ast.rules,
                      function(r) { return '"' + js.stringEscape(r.name) + '"'; }
                    ).join(', ')
                  + ']';
  
        parts.push([
          '        peg$ruleNames = ' + ruleNames + ',',
          ''
        ].join('\n'));
      }
  
      parts.push([
        '        peg$tracer = "tracer" in options ? options.tracer : new peg$DefaultTracer(),',
        ''
      ].join('\n'));
    }
  
    parts.push([
      '        peg$result;',
      ''
    ].join('\n'));
  
    if (options.optimize === "size") {
      parts.push([
        '    if ("startRule" in options) {',
        '      if (!(options.startRule in peg$startRuleIndices)) {',
        '        throw new Error("Can\'t start parsing from rule \\"" + options.startRule + "\\".");',
        '      }',
        '',
        '      peg$startRuleIndex = peg$startRuleIndices[options.startRule];',
        '    }'
      ].join('\n'));
    } else {
      parts.push([
        '    if ("startRule" in options) {',
        '      if (!(options.startRule in peg$startRuleFunctions)) {',
        '        throw new Error("Can\'t start parsing from rule \\"" + options.startRule + "\\".");',
        '      }',
        '',
        '      peg$startRuleFunction = peg$startRuleFunctions[options.startRule];',
        '    }'
      ].join('\n'));
    }
  
    parts.push([
      '',
      '    function text() {',
      '      return input.substring(peg$savedPos, peg$currPos);',
      '    }',
      '',
      '    function location() {',
      '      return peg$computeLocation(peg$savedPos, peg$currPos);',
      '    }',
      '',
      '    function expected(description) {',
      '      throw peg$buildException(',
      '        null,',
      '        [{ type: "other", description: description }],',
      '        input.substring(peg$savedPos, peg$currPos),',
      '        peg$computeLocation(peg$savedPos, peg$currPos)',
      '      );',
      '    }',
      '',
      '    function error(message) {',
      '      throw peg$buildException(',
      '        message,',
      '        null,',
      '        input.substring(peg$savedPos, peg$currPos),',
      '        peg$computeLocation(peg$savedPos, peg$currPos)',
      '      );',
      '    }',
      '',
      '    function peg$computePosDetails(pos) {',
      '      var details = peg$posDetailsCache[pos],',
      '          p, ch;',
      '',
      '      if (details) {',
      '        return details;',
      '      } else {',
      '        p = pos - 1;',
      '        while (!peg$posDetailsCache[p]) {',
      '          p--;',
      '        }',
      '',
      '        details = peg$posDetailsCache[p];',
      '        details = {',
      '          line:   details.line,',
      '          column: details.column,',
      '          seenCR: details.seenCR',
      '        };',
      '',
      '        while (p < pos) {',
      '          ch = input.charAt(p);',
      '          if (ch === "\\n") {',
      '            if (!details.seenCR) { details.line++; }',
      '            details.column = 1;',
      '            details.seenCR = false;',
      '          } else if (ch === "\\r" || ch === "\\u2028" || ch === "\\u2029") {',
      '            details.line++;',
      '            details.column = 1;',
      '            details.seenCR = true;',
      '          } else {',
      '            details.column++;',
      '            details.seenCR = false;',
      '          }',
      '',
      '          p++;',
      '        }',
      '',
      '        peg$posDetailsCache[pos] = details;',
      '        return details;',
      '      }',
      '    }',
      '',
      '    function peg$computeLocation(startPos, endPos) {',
      '      var startPosDetails = peg$computePosDetails(startPos),',
      '          endPosDetails   = peg$computePosDetails(endPos);',
      '',
      '      return {',
      '        start: {',
      '          offset: startPos,',
      '          line:   startPosDetails.line,',
      '          column: startPosDetails.column',
      '        },',
      '        end: {',
      '          offset: endPos,',
      '          line:   endPosDetails.line,',
      '          column: endPosDetails.column',
      '        }',
      '      };',
      '    }',
      '',
      '    function peg$fail(expected) {',
      '      if (peg$currPos < peg$maxFailPos) { return; }',
      '',
      '      if (peg$currPos > peg$maxFailPos) {',
      '        peg$maxFailPos = peg$currPos;',
      '        peg$maxFailExpected = [];',
      '      }',
      '',
      '      peg$maxFailExpected.push(expected);',
      '    }',
      '',
      '    function peg$buildException(message, expected, found, location) {',
      '      function cleanupExpected(expected) {',
      '        var i = 1;',
      '',
      '        expected.sort(function(a, b) {',
      '          if (a.description < b.description) {',
      '            return -1;',
      '          } else if (a.description > b.description) {',
      '            return 1;',
      '          } else {',
      '            return 0;',
      '          }',
      '        });',
      '',
      /*
       * This works because the bytecode generator guarantees that every
       * expectation object exists only once, so it's enough to use |===| instead
       * of deeper structural comparison.
       */
      '        while (i < expected.length) {',
      '          if (expected[i - 1] === expected[i]) {',
      '            expected.splice(i, 1);',
      '          } else {',
      '            i++;',
      '          }',
      '        }',
      '      }',
      '',
      '      function buildMessage(expected, found) {',
      '        function stringEscape(s) {',
      '          function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }',
      '',
      /*
       * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
       * literal except for the closing quote character, backslash, carriage
       * return, line separator, paragraph separator, and line feed. Any character
       * may appear in the form of an escape sequence.
       *
       * For portability, we also escape all control and non-ASCII characters.
       * Note that "\0" and "\v" escape sequences are not used because JSHint does
       * not like the first and IE the second.
       */
      '          return s',
      '            .replace(/\\\\/g,   \'\\\\\\\\\')',   // backslash
      '            .replace(/"/g,    \'\\\\"\')',        // closing double quote
      '            .replace(/\\x08/g, \'\\\\b\')',       // backspace
      '            .replace(/\\t/g,   \'\\\\t\')',       // horizontal tab
      '            .replace(/\\n/g,   \'\\\\n\')',       // line feed
      '            .replace(/\\f/g,   \'\\\\f\')',       // form feed
      '            .replace(/\\r/g,   \'\\\\r\')',       // carriage return
      '            .replace(/[\\x00-\\x07\\x0B\\x0E\\x0F]/g, function(ch) { return \'\\\\x0\' + hex(ch); })',
      '            .replace(/[\\x10-\\x1F\\x80-\\xFF]/g,    function(ch) { return \'\\\\x\'  + hex(ch); })',
      '            .replace(/[\\u0100-\\u0FFF]/g,         function(ch) { return \'\\\\u0\' + hex(ch); })',
      '            .replace(/[\\u1000-\\uFFFF]/g,         function(ch) { return \'\\\\u\'  + hex(ch); });',
      '        }',
      '',
      '        var expectedDescs = new Array(expected.length),',
      '            expectedDesc, foundDesc, i;',
      '',
      '        for (i = 0; i < expected.length; i++) {',
      '          expectedDescs[i] = expected[i].description;',
      '        }',
      '',
      '        expectedDesc = expected.length > 1',
      '          ? expectedDescs.slice(0, -1).join(", ")',
      '              + " or "',
      '              + expectedDescs[expected.length - 1]',
      '          : expectedDescs[0];',
      '',
      '        foundDesc = found ? "\\"" + stringEscape(found) + "\\"" : "end of input";',
      '',
      '        return "Expected " + expectedDesc + " but " + foundDesc + " found.";',
      '      }',
      '',
      '      if (expected !== null) {',
      '        cleanupExpected(expected);',
      '      }',
      '',
      '      return new peg$SyntaxError(',
      '        message !== null ? message : buildMessage(expected, found),',
      '        expected,',
      '        found,',
      '        location',
      '      );',
      '    }',
      ''
    ].join('\n'));
  
    if (options.optimize === "size") {
      parts.push(indent4(generateInterpreter()));
      parts.push('');
    } else {
      arrays.each(ast.rules, function(rule) {
        parts.push(indent4(generateRuleFunction(rule)));
        parts.push('');
      });
    }
  
    if (ast.initializer) {
      parts.push(indent4(ast.initializer.code));
      parts.push('');
    }
  
    if (options.optimize === "size") {
      parts.push('    peg$result = peg$parseRule(peg$startRuleIndex);');
    } else {
      parts.push('    peg$result = peg$startRuleFunction();');
    }
  
    parts.push([
      '',
      '    if (peg$result !== peg$FAILED && peg$currPos === input.length) {',
      '      return peg$result;',
      '    } else {',
      '      if (peg$result !== peg$FAILED && peg$currPos < input.length) {',
      '        peg$fail({ type: "end", description: "end of input" });',
      '      }',
      '',
      '      throw peg$buildException(',
      '        null,',
      '        peg$maxFailExpected,',
      '        peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,',
      '        peg$maxFailPos < input.length',
      '          ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)',
      '          : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)',
      '      );',
      '    }',
      '  }',
      '',
      '  return {'
    ].join('\n'));
  
    if (options.trace) {
      parts.push([
        '    SyntaxError:   peg$SyntaxError,',
        '    DefaultTracer: peg$DefaultTracer,',
        '    parse:         peg$parse'
      ].join('\n'));
    } else {
      parts.push([
        '    SyntaxError: peg$SyntaxError,',
        '    parse:       peg$parse'
      ].join('\n'));
    }
  
    parts.push([
      '  };',
      '})()'
    ].join('\n'));
  
    ast.code = parts.join('\n');
  }
  
  module.exports = generateJavascript;
  