You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1361 lines
41 KiB
JavaScript

"use strict";
let asts = require("../asts");
let op = require("../opcodes");
let 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 indent10(code) { return code.replace(/^(.+)$/gm, ' $1'); }
function generateTables() {
if (options.optimize === "size") {
return [
'var peg$consts = [',
indent2(ast.consts.join(',\n')),
'];',
'',
'var peg$bytecode = [',
indent2(ast.rules.map(rule =>
'peg$decode("'
+ js.stringEscape(rule.bytecode.map(
b => String.fromCharCode(b + 32)
).join(''))
+ '")'
).join(',\n')),
'];'
].join('\n');
} else {
return ast.consts.map((c, i) => 'var peg$c' + i + ' = ' + c + ';').join('\n');
}
}
function generateRuleHeader(ruleNameCode, ruleIndexCode) {
let 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 + ';',
'var 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) {
let 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() {
let parts = [];
function generateCondition(cond, argsLength) {
let baseLength = argsLength + 3;
let thenLengthCode = 'bc[ip + ' + (baseLength - 2) + ']';
let 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) {
let baseLength = 2;
let 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() {
let baseLength = 4;
let paramsLengthCode = 'bc[ip + ' + (baseLength - 1) + ']';
return [
'params = bc.slice(ip + ' + baseLength + ', ip + ' + baseLength + ' + ' + paramsLengthCode + ')',
' .map(function(p) { return stack[stack.length - 1 - p]; });',
'',
'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) {',
' return s.split("").map(function(ch) { return ch.charCodeAt(0) - 32; });',
'}',
'',
'function peg$parseRule(index) {'
].join('\n'));
if (options.trace) {
parts.push([
' var bc = peg$bytecode[index];',
' var ip = 0;',
' var ips = [];',
' var end = bc.length;',
' var ends = [];',
' var stack = [];',
' var startPos = peg$currPos;',
' var params;'
].join('\n'));
} else {
parts.push([
' var bc = peg$bytecode[index];',
' var ip = 0;',
' var ips = [];',
' var end = bc.length;',
' var ends = [];',
' var stack = [];',
' var params;'
].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(undefined);',
' 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) {
let parts = [];
let stackVars = [];
let 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
let stack = {
sp: -1,
maxSp: -1,
push: function(exprCode) {
let code = s(++this.sp) + ' = ' + exprCode + ';';
if (this.sp > this.maxSp) { this.maxSp = this.sp; }
return code;
},
pop: function(n) {
if (n === undefined) {
return s(this.sp--);
} else {
let values = Array(n);
for (var i = 0; i < n; i++) {
values[i] = s(this.sp - n + 1 + i);
}
this.sp -= n;
return values;
}
},
top: function() {
return s(this.sp);
},
index: function(i) {
return s(this.sp - i);
}
};
function compile(bc) {
let ip = 0;
let end = bc.length;
let parts = [];
let value;
function compileCondition(cond, argCount) {
let baseLength = argCount + 3;
let thenLength = bc[ip + baseLength - 2];
let elseLength = bc[ip + baseLength - 1];
let baseSp = stack.sp;
let 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) {
let baseLength = 2;
let bodyLength = bc[ip + baseLength - 1];
let baseSp = stack.sp;
let 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() {
let baseLength = 4;
let paramsLength = bc[ip + baseLength - 1];
let value = c(bc[ip + 1]) + '('
+ bc.slice(ip + baseLength, ip + baseLength + paramsLength).map(
p => 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('undefined'));
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 startPos = peg$currPos;');
}
for (let i = 0; i <= stack.maxSp; i++) {
stackVars[i] = s(i);
}
parts.push(' var ' + stackVars.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() {
let parts = [];
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, 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);',
'',
'peg$SyntaxError.buildMessage = function(expected, found) {',
' var DESCRIBE_EXPECTATION_FNS = {',
' literal: function(expectation) {',
' return "\\\"" + literalEscape(expectation.text) + "\\\"";',
' },',
'',
' "class": function(expectation) {',
' var escapedParts = expectation.parts.map(function(part) {',
' return Array.isArray(part)',
' ? classEscape(part[0]) + "-" + classEscape(part[1])',
' : classEscape(part);',
' });',
'',
' return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]";',
' },',
'',
' any: function(expectation) {',
' return "any character";',
' },',
'',
' end: function(expectation) {',
' return "end of input";',
' },',
'',
' other: function(expectation) {',
' return expectation.description;',
' }',
' };',
'',
' function hex(ch) {',
' return ch.charCodeAt(0).toString(16).toUpperCase();',
' }',
'',
' function literalEscape(s) {',
' return s',
' .replace(/\\\\/g, \'\\\\\\\\\')', // backslash
' .replace(/"/g, \'\\\\"\')', // closing double quote
' .replace(/\\0/g, \'\\\\0\')', // null
' .replace(/\\t/g, \'\\\\t\')', // horizontal tab
' .replace(/\\n/g, \'\\\\n\')', // line feed
' .replace(/\\r/g, \'\\\\r\')', // carriage return
' .replace(/[\\x00-\\x0F]/g, function(ch) { return \'\\\\x0\' + hex(ch); })',
' .replace(/[\\x10-\\x1F\\x7F-\\x9F]/g, function(ch) { return \'\\\\x\' + hex(ch); });',
' }',
'',
' function classEscape(s) {',
' return s',
' .replace(/\\\\/g, \'\\\\\\\\\')', // backslash
' .replace(/\\]/g, \'\\\\]\')', // closing bracket
' .replace(/\\^/g, \'\\\\^\')', // caret
' .replace(/-/g, \'\\\\-\')', // dash
' .replace(/\\0/g, \'\\\\0\')', // null
' .replace(/\\t/g, \'\\\\t\')', // horizontal tab
' .replace(/\\n/g, \'\\\\n\')', // line feed
' .replace(/\\r/g, \'\\\\r\')', // carriage return
' .replace(/[\\x00-\\x0F]/g, function(ch) { return \'\\\\x0\' + hex(ch); })',
' .replace(/[\\x10-\\x1F\\x7F-\\x9F]/g, function(ch) { return \'\\\\x\' + hex(ch); });',
' }',
'',
' function describeExpectation(expectation) {',
' return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);',
' }',
'',
' function describeExpected(expected) {',
' var descriptions = expected.map(describeExpectation);',
' var i, j;',
'',
' descriptions.sort();',
'',
' if (descriptions.length > 0) {',
' for (i = 1, j = 1; i < descriptions.length; i++) {',
' if (descriptions[i - 1] !== descriptions[i]) {',
' descriptions[j] = descriptions[i];',
' j++;',
' }',
' }',
' descriptions.length = j;',
' }',
'',
' switch (descriptions.length) {',
' case 1:',
' return descriptions[0];',
'',
' case 2:',
' return descriptions[0] + " or " + descriptions[1];',
'',
' default:',
' return descriptions.slice(0, -1).join(", ")',
' + ", or "',
' + descriptions[descriptions.length - 1];',
' }',
' }',
'',
' function describeFound(found) {',
' return found ? "\\"" + literalEscape(found) + "\\"" : "end of input";',
' }',
'',
' return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";',
'};',
''
].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 !== undefined ? options : {};',
'',
' var peg$FAILED = {};',
''
].join('\n'));
if (options.optimize === "size") {
let startRuleIndices = '{ '
+ options.allowedStartRules.map(
r => r + ': ' + asts.indexOfRule(ast, r)
).join(', ')
+ ' }';
let startRuleIndex = asts.indexOfRule(ast, options.allowedStartRules[0]);
parts.push([
' var peg$startRuleIndices = ' + startRuleIndices + ';',
' var peg$startRuleIndex = ' + startRuleIndex + ';'
].join('\n'));
} else {
let startRuleFunctions = '{ '
+ options.allowedStartRules.map(
r => r + ': peg$parse' + r
).join(', ')
+ ' }';
let startRuleFunction = 'peg$parse' + options.allowedStartRules[0];
parts.push([
' var peg$startRuleFunctions = ' + startRuleFunctions + ';',
' var peg$startRuleFunction = ' + startRuleFunction + ';'
].join('\n'));
}
parts.push('');
parts.push(indent2(generateTables()));
parts.push([
'',
' var peg$currPos = 0;',
' var peg$savedPos = 0;',
' var peg$posDetailsCache = [{ line: 1, column: 1 }];',
' var peg$maxFailPos = 0;',
' var peg$maxFailExpected = [];',
' var peg$silentFails = 0;', // 0 = report failures, > 0 = silence failures
''
].join('\n'));
if (options.cache) {
parts.push([
' var peg$resultsCache = {};',
''
].join('\n'));
}
if (options.trace) {
if (options.optimize === "size") {
let ruleNames = '['
+ ast.rules.map(
r => '"' + js.stringEscape(r.name) + '"'
).join(', ')
+ ']';
parts.push([
' var peg$ruleNames = ' + ruleNames + ';',
''
].join('\n'));
}
parts.push([
' var peg$tracer = "tracer" in options ? options.tracer : new peg$DefaultTracer();',
''
].join('\n'));
}
parts.push([
' var 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 !== undefined ? location : peg$computeLocation(peg$savedPos, peg$currPos)',
'',
' throw peg$buildStructuredError(',
' [peg$otherExpectation(description)],',
' input.substring(peg$savedPos, peg$currPos),',
' location',
' );',
' }',
'',
' function error(message, location) {',
' location = location !== undefined ? location : peg$computeLocation(peg$savedPos, peg$currPos)',
'',
' throw peg$buildSimpleError(message, location);',
' }',
'',
' function peg$literalExpectation(text, ignoreCase) {',
' return { type: "literal", text: text, ignoreCase: ignoreCase };',
' }',
'',
' function peg$classExpectation(parts, inverted, ignoreCase) {',
' return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase };',
' }',
'',
' function peg$anyExpectation() {',
' return { type: "any" };',
' }',
'',
' function peg$endExpectation() {',
' return { type: "end" };',
' }',
'',
' function peg$otherExpectation(description) {',
' return { type: "other", description: description };',
' }',
'',
' function peg$computePosDetails(pos) {',
' var details = peg$posDetailsCache[pos];',
' var 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);',
' var 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$buildSimpleError(message, location) {',
' return new peg$SyntaxError(message, null, null, location);',
' }',
'',
' function peg$buildStructuredError(expected, found, location) {',
' return new peg$SyntaxError(',
' peg$SyntaxError.buildMessage(expected, found),',
' expected,',
' found,',
' location',
' );',
' }',
''
].join('\n'));
if (options.optimize === "size") {
parts.push(indent2(generateInterpreter()));
parts.push('');
} else {
ast.rules.forEach(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(peg$endExpectation());',
' }',
'',
' throw peg$buildStructuredError(',
' 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)',
' );',
' }',
'}'
].join('\n'));
return parts.join('\n');
}
function generateWrapper(toplevelCode) {
function generateGeneratedByComment() {
return [
'// Generated by PEG.js 0.10.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');
}
let generators = {
bare: function() {
return [
generateGeneratedByComment(),
'(function() {',
' "use strict";',
'',
indent2(toplevelCode),
'',
indent2('return ' + generateParserObject() + ';'),
'})()'
].join('\n');
},
commonjs: function() {
let parts = [];
let dependencyVars = Object.keys(options.dependencies);
parts.push([
generateGeneratedByComment(),
'',
'"use strict";',
''
].join('\n'));
if (dependencyVars.length > 0) {
dependencyVars.forEach(variable => {
parts.push('var ' + variable
+ ' = require("'
+ js.stringEscape(options.dependencies[variable])
+ '");'
);
});
parts.push('');
}
parts.push([
toplevelCode,
'',
'module.exports = ' + generateParserObject() + ';',
''
].join('\n'));
return parts.join('\n');
},
amd: function() {
let dependencyVars = Object.keys(options.dependencies);
let dependencyIds = dependencyIds.map(v => options.dependencies[v]);
let dependencies = '['
+ dependencyIds.map(
id => '"' + js.stringEscape(id) + '"'
).join(', ')
+ ']';
let params = dependencyVars.join(', ');
return [
generateGeneratedByComment(),
'define(' + dependencies + ', function(' + params + ') {',
' "use strict";',
'',
indent2(toplevelCode),
'',
indent2('return ' + generateParserObject() + ';'),
'});',
''
].join('\n');
},
globals: function() {
return [
generateGeneratedByComment(),
'(function(root) {',
' "use strict";',
'',
indent2(toplevelCode),
'',
indent2('root.' + options.exportVar + ' = ' + generateParserObject() + ';'),
'})(this);',
''
].join('\n');
},
umd: function() {
let parts = [];
let dependencyVars = Object.keys(options.dependencies);
let dependencyIds = dependencyIds.map(v => options.dependencies[v]);
let dependencies = '['
+ dependencyIds.map(
id => '"' + js.stringEscape(id) + '"'
).join(', ')
+ ']';
let requires = dependencyIds.map(
id => 'require("' + js.stringEscape(id) + '")'
).join(', ');
let 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;