| "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; |