pegjs/lib/compiler/passes/generate-js.js
David Majda 057a93fbc7 Move the "Generated by ..." comment out of wrapping functions
The wrapping functions are also generated by PEG.js, so the comment
should be above them to mark them as such. This shouldn't cause any
problems technically.
2016-05-03 16:06:21 +02:00

1239 lines
37 KiB
JavaScript

"use strict";
var arrays = require("../../utils/arrays"),
objects = require("../../utils/objects"),
asts = require("../asts"),
op = require("../opcodes"),
js = require("../js");
/* Generates parser JavaScript code. */
function generateJS(ast, options) {
/* These only indent non-empty lines to avoid trailing whitespace. */
function indent2(code) { return code.replace(/^(.+)$/gm, ' $1'); }
function indent6(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(n) {
var values;
if (n === void 0) {
return s(this.sp--);
} else {
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');
}
function generateToplevel() {
var parts = [],
startRuleIndices, startRuleIndex,
startRuleFunctions, startRuleFunction,
ruleNames;
parts.push([
'function peg$subclass(child, parent) {',
' function ctor() { this.constructor = child; }',
' ctor.prototype = parent.prototype;',
' child.prototype = new ctor();',
'}',
'',
'function peg$SyntaxError(message, expected, location) {',
' this.message = message;',
' this.expected = expected;',
' 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, options) {',
' options = options !== void 0 ? options : {};',
'',
' var 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(indent6(generateTables()));
parts.push([
'',
' peg$currPos = 0,',
' peg$savedPos = 0,',
' peg$posDetailsCache = [{ line: 1, column: 1 }],',
' 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, location) {',
' location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos)',
'',
' throw peg$buildException(',
' null,',
' [{ type: "other", description: description }],',
' location',
' );',
' }',
'',
' function error(message, location) {',
' location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos)',
'',
' throw peg$buildException(message, null, location);',
' }',
'',
' function peg$computePosDetails(pos) {',
' var details = peg$posDetailsCache[pos], p;',
'',
' if (details) {',
' return details;',
' } else {',
' p = pos - 1;',
' while (!peg$posDetailsCache[p]) {',
' p--;',
' }',
'',
' details = peg$posDetailsCache[p];',
' details = {',
' line: details.line,',
' column: details.column',
' };',
'',
' while (p < pos) {',
' if (input.charCodeAt(p) === 10) {',
' details.line++;',
' details.column = 1;',
' } else {',
' details.column++;',
' }',
'',
' 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, location) {',
' function cleanupExpected(expected) {',
' var i, j;',
'',
' 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.
*/
' if (expected.length > 0) {',
' for (i = 1, j = 1; i < expected.length; i++) {',
' if (expected[i - 1] !== expected[i]) {',
' expected[j] = expected[i];',
' j++;',
' }',
' }',
' expected.length = j;',
' }',
' }',
'',
' function buildMessage(expected) {',
' var expectedDescs = new Array(expected.length),',
' expectedDesc, 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];',
'',
' return "Expected " + expectedDesc + ".";',
' }',
'',
' if (expected !== null) {',
' cleanupExpected(expected);',
' }',
'',
' return new peg$SyntaxError(',
' message !== null ? message : buildMessage(expected),',
' expected,',
' location',
' );',
' }',
''
].join('\n'));
if (options.optimize === "size") {
parts.push(indent2(generateInterpreter()));
parts.push('');
} else {
arrays.each(ast.rules, function(rule) {
parts.push(indent2(generateRuleFunction(rule)));
parts.push('');
});
}
if (ast.initializer) {
parts.push(indent2(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$computeLocation(peg$maxFailPos, peg$maxFailPos)',
' );',
' }',
'}'
].join('\n'));
return parts.join('\n');
}
function generateWrapper(toplevelCode) {
function generateGeneratedByComment() {
return [
'/*',
' * Generated by PEG.js 0.9.0.',
' *',
' * http://pegjs.org/',
' */'
].join('\n');
}
function generateParserObject() {
return options.trace
? [
'{',
' SyntaxError: peg$SyntaxError,',
' DefaultTracer: peg$DefaultTracer,',
' parse: peg$parse',
'}'
].join('\n')
: [
'{',
' SyntaxError: peg$SyntaxError,',
' parse: peg$parse',
'}'
].join('\n');
}
var generators = {
bare: function() {
return [
generateGeneratedByComment(),
'(function() {',
' "use strict";',
'',
indent2(toplevelCode),
'',
indent2('return ' + generateParserObject() + ';'),
'})()'
].join('\n');
},
umd: function() {
var parts = [],
dependencyIds = objects.values(options.dependencies),
dependencyVars = objects.keys(options.dependencies),
dependencies = '['
+ arrays.map(
dependencyIds,
function(id) { return '"' + js.stringEscape(id) + '"'; }
).join(', ')
+ ']',
requires = arrays.map(
dependencyIds,
function(id) { return 'require("' + js.stringEscape(id) + '")'; }
).join(', '),
params = dependencyVars.join(', ');
parts.push([
generateGeneratedByComment(),
'(function(root, factory) {',
' if (typeof define === "function" && define.amd) {',
' define(' + dependencies + ', factory);',
' } else if (typeof module === "object" && module.exports) {',
' module.exports = factory(' + requires + ');'
].join('\n'));
if (options.exportVar !== null) {
parts.push([
' } else {',
' root.' + options.exportVar + ' = factory();'
].join('\n'));
}
parts.push([
' }',
'})(this, function(' + params + ') {',
' "use strict";',
'',
indent2(toplevelCode),
'',
indent2('return ' + generateParserObject() + ';'),
'});',
''
].join('\n'));
return parts.join('\n');
}
};
return generators[options.format]();
}
ast.code = generateWrapper(generateToplevel());
}
module.exports = generateJS;