da57118a43
Parsers can now be generated with support for tracing using the --trace CLI option or a boolean |trace| option to |PEG.buildParser|. This makes them trace their progress, which can be useful for debugging. Parsers generated with tracing support are called "tracing parsers". When a tracing parser executes, by default it traces the rules it enters and exits by writing messages to the console. For example, a parser built from this grammar: start = a / b a = "a" b = "b" will write this to the console when parsing input "b": 1:1 rule.enter start 1:1 rule.enter a 1:1 rule.fail a 1:1 rule.enter b 1:2 rule.match b 1:2 rule.match start You can customize tracing by passing a custom *tracer* to parser's |parse| method using the |tracer| option: parser.parse(input, { trace: tracer }); This will replace the built-in default tracer (which writes to the console) by the tracer you supplied. The tracer must be an object with a |trace| method. This method is called each time a tracing event happens. It takes one argument which is an object describing the tracing event. Currently, three events are supported: * rule.enter -- triggered when a rule is entered * rule.match -- triggered when a rule matches successfully * rule.fail -- triggered when a rule fails to match These events are triggered in nested pairs -- for each rule.enter event there is a matching rule.match or rule.fail event. The event object passed as an argument to |trace| contains these properties: * type -- event type * rule -- name of the rule the event is related to * offset -- parse position at the time of the event * line -- line at the time of the event * column -- column at the time of the event * result -- rule's match result (only for rule.match event) The whole tracing API is somewhat experimental (which is why it isn't documented properly yet) and I expect it will evolve over time as experience is gained. The default tracer is also somewhat bare-bones. I hope that PEG.js user community will develop more sophisticated tracers over time and I'll be able to integrate their best ideas into the default tracer.
1166 lines
35 KiB
JavaScript
1166 lines
35 KiB
JavaScript
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$trace({',
|
|
' type: "rule.enter",',
|
|
' rule: ' + ruleNameCode,
|
|
'});',
|
|
''
|
|
].join('\n'));
|
|
}
|
|
|
|
if (options.cache) {
|
|
parts.push([
|
|
'var key = peg$currPos * ' + ast.rules.length + ' + ' + ruleIndexCode + ',',
|
|
' cached = peg$cache[key];',
|
|
'',
|
|
'if (cached) {',
|
|
' peg$currPos = cached.nextPos;',
|
|
'',
|
|
].join('\n'));
|
|
|
|
if (options.trace) {
|
|
parts.push([
|
|
'if (cached.result !== peg$FAILED) {',
|
|
' peg$trace({',
|
|
' type: "rule.match",',
|
|
' rule: ' + ruleNameCode + ',',
|
|
' result: cached.result',
|
|
' });',
|
|
'} else {',
|
|
' peg$trace({',
|
|
' type: "rule.fail",',
|
|
' rule: ' + ruleNameCode,
|
|
' });',
|
|
'}',
|
|
''
|
|
].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$cache[key] = { nextPos: peg$currPos, result: ' + resultCode + ' };'
|
|
].join('\n'));
|
|
}
|
|
|
|
if (options.trace) {
|
|
parts.push([
|
|
'',
|
|
'if (' + resultCode + ' !== peg$FAILED) {',
|
|
' peg$trace({',
|
|
' type: "rule.match",',
|
|
' rule: ' + ruleNameCode + ',',
|
|
' result: ' + resultCode,
|
|
' });',
|
|
'} else {',
|
|
' peg$trace({',
|
|
' type: "rule.fail",',
|
|
' rule: ' + ruleNameCode,
|
|
' });',
|
|
'}'
|
|
].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) {',
|
|
' 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.REPORT_SAVED_POS + ':', // REPORT_SAVED_POS p
|
|
' peg$reportedPos = stack[stack.length - 1 - bc[ip + 1]];',
|
|
' ip += 2;',
|
|
' break;',
|
|
'',
|
|
' case ' + op.REPORT_CURR_POS + ':', // REPORT_CURR_POS
|
|
' peg$reportedPos = 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.REPORT_SAVED_POS: // REPORT_SAVED_POS p
|
|
parts.push('peg$reportedPos = ' + stack.index(bc[ip + 1]) + ';');
|
|
ip += 2;
|
|
break;
|
|
|
|
case op.REPORT_CURR_POS: // REPORT_CURR_POS
|
|
parts.push('peg$reportedPos = 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 + '() {',
|
|
' var ' + arrays.map(arrays.range(0, stack.maxSp + 1), s).join(', ') + ';',
|
|
].join('\n'));
|
|
|
|
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() {',
|
|
' /*',
|
|
' * Generated by PEG.js 0.8.0.',
|
|
' *',
|
|
' * http://pegjs.org/',
|
|
' */',
|
|
'',
|
|
' function peg$subclass(child, parent) {',
|
|
' function ctor() { this.constructor = child; }',
|
|
' ctor.prototype = parent.prototype;',
|
|
' child.prototype = new ctor();',
|
|
' }',
|
|
'',
|
|
' function peg$SyntaxError(message, expected, found, offset, line, column) {',
|
|
' this.message = message;',
|
|
' this.expected = expected;',
|
|
' this.found = found;',
|
|
' this.offset = offset;',
|
|
' this.line = line;',
|
|
' this.column = column;',
|
|
'',
|
|
' this.name = "SyntaxError";',
|
|
' }',
|
|
'',
|
|
' peg$subclass(peg$SyntaxError, Error);',
|
|
''
|
|
].join('\n'));
|
|
|
|
if (options.trace) {
|
|
parts.push([
|
|
' function peg$DefaultTracer() {',
|
|
' this.indentLevel = 0;',
|
|
' }',
|
|
'',
|
|
' peg$DefaultTracer.prototype.trace = function(event) {',
|
|
' var that = this;',
|
|
'',
|
|
' function log(event) {',
|
|
' function repeat(string, n) {',
|
|
' var result = "", i;',
|
|
'',
|
|
' for (i = 0; i < n; i++) {',
|
|
' result += string;',
|
|
' }',
|
|
'',
|
|
' return result;',
|
|
' }',
|
|
'',
|
|
' function pad(string, length) {',
|
|
' return string + repeat(" ", length - string.length);',
|
|
' }',
|
|
'',
|
|
' console.log(',
|
|
' event.line + ":" + event.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$reportedPos = 0,',
|
|
' peg$cachedPos = 0,',
|
|
' peg$cachedPosDetails = { 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$cache = {},',
|
|
''
|
|
].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$reportedPos, peg$currPos);',
|
|
' }',
|
|
'',
|
|
' function offset() {',
|
|
' return peg$reportedPos;',
|
|
' }',
|
|
'',
|
|
' function line() {',
|
|
' return peg$computePosDetails(peg$reportedPos).line;',
|
|
' }',
|
|
'',
|
|
' function column() {',
|
|
' return peg$computePosDetails(peg$reportedPos).column;',
|
|
' }',
|
|
'',
|
|
' function expected(description) {',
|
|
' throw peg$buildException(',
|
|
' null,',
|
|
' [{ type: "other", description: description }],',
|
|
' peg$reportedPos',
|
|
' );',
|
|
' }',
|
|
'',
|
|
' function error(message) {',
|
|
' throw peg$buildException(message, null, peg$reportedPos);',
|
|
' }',
|
|
'',
|
|
' function peg$computePosDetails(pos) {',
|
|
' function advance(details, startPos, endPos) {',
|
|
' var p, ch;',
|
|
'',
|
|
' for (p = startPos; p < endPos; p++) {',
|
|
' 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;',
|
|
' }',
|
|
' }',
|
|
' }',
|
|
'',
|
|
' if (peg$cachedPos !== pos) {',
|
|
' if (peg$cachedPos > pos) {',
|
|
' peg$cachedPos = 0;',
|
|
' peg$cachedPosDetails = { line: 1, column: 1, seenCR: false };',
|
|
' }',
|
|
' advance(peg$cachedPosDetails, peg$cachedPos, pos);',
|
|
' peg$cachedPos = pos;',
|
|
' }',
|
|
'',
|
|
' return peg$cachedPosDetails;',
|
|
' }',
|
|
'',
|
|
' 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, pos) {',
|
|
' 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.";',
|
|
' }',
|
|
'',
|
|
' var posDetails = peg$computePosDetails(pos),',
|
|
' found = pos < input.length ? input.charAt(pos) : null;',
|
|
'',
|
|
' if (expected !== null) {',
|
|
' cleanupExpected(expected);',
|
|
' }',
|
|
'',
|
|
' return new peg$SyntaxError(',
|
|
' message !== null ? message : buildMessage(expected, found),',
|
|
' expected,',
|
|
' found,',
|
|
' pos,',
|
|
' posDetails.line,',
|
|
' posDetails.column',
|
|
' );',
|
|
' }',
|
|
''
|
|
].join('\n'));
|
|
|
|
if (options.trace) {
|
|
parts.push([
|
|
' function peg$trace(event) {',
|
|
' var posDetails = peg$computePosDetails(peg$currPos);',
|
|
'',
|
|
' event.offset = peg$currPos;',
|
|
' event.line = posDetails.line;',
|
|
' event.column = posDetails.column;',
|
|
'',
|
|
' peg$tracer.trace(event);',
|
|
' }',
|
|
'',
|
|
].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);',
|
|
' }',
|
|
' }',
|
|
'',
|
|
' 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;
|