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.
1178 lines
35 KiB
JavaScript
1178 lines
35 KiB
JavaScript
"use strict";
|
|
|
|
var arrays = require("../../utils/arrays"),
|
|
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 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, 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) {',
|
|
' 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 }],',
|
|
' peg$computeLocation(peg$savedPos, peg$currPos)',
|
|
' );',
|
|
' }',
|
|
'',
|
|
' function error(message) {',
|
|
' throw peg$buildException(',
|
|
' message,',
|
|
' null,',
|
|
' 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, 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) {',
|
|
' 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(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$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 = generateJS;
|