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.8.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";', ' }', '', ' 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);', ' }', '', ' 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;