This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1394 lines
43 KiB
1394 lines
43 KiB
"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, 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 = "",', |
|
' i;', |
|
'', |
|
' for (i = 0; i < expectation.parts.length; i++) {', |
|
' escapedParts += expectation.parts[i] instanceof Array', |
|
' ? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1])', |
|
' : classEscape(expectation.parts[i]);', |
|
' }', |
|
'', |
|
' 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 = new Array(expected.length),', |
|
' i, j;', |
|
'', |
|
' for (i = 0; i < expected.length; i++) {', |
|
' descriptions[i] = describeExpectation(expected[i]);', |
|
' }', |
|
'', |
|
' 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 !== void 0 ? options : {};', |
|
'', |
|
' var 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$buildStructuredError(', |
|
' [peg$otherExpectation(description)],', |
|
' input.substring(peg$savedPos, peg$currPos),', |
|
' location', |
|
' );', |
|
' }', |
|
'', |
|
' function error(message, location) {', |
|
' location = location !== void 0 ? 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], 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$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 { |
|
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(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'); |
|
} |
|
|
|
var generators = { |
|
bare: function() { |
|
return [ |
|
generateGeneratedByComment(), |
|
'(function() {', |
|
' "use strict";', |
|
'', |
|
indent2(toplevelCode), |
|
'', |
|
indent2('return ' + generateParserObject() + ';'), |
|
'})()' |
|
].join('\n'); |
|
}, |
|
|
|
commonjs: function() { |
|
var parts = [], |
|
dependencyVars = objects.keys(options.dependencies), |
|
requires = arrays.map( |
|
dependencyVars, |
|
function(variable) { |
|
return variable |
|
+ ' = require("' |
|
+ js.stringEscape(options.dependencies[variable]) |
|
+ '")'; |
|
} |
|
); |
|
|
|
parts.push([ |
|
generateGeneratedByComment(), |
|
'', |
|
'"use strict";', |
|
'' |
|
].join('\n')); |
|
|
|
if (requires.length > 0) { |
|
parts.push('var ' + requires.join(', ') + ';'); |
|
parts.push(''); |
|
} |
|
|
|
parts.push([ |
|
toplevelCode, |
|
'', |
|
'module.exports = ' + generateParserObject() + ';', |
|
'' |
|
].join('\n')); |
|
|
|
return parts.join('\n'); |
|
}, |
|
|
|
amd: function() { |
|
var dependencyIds = objects.values(options.dependencies), |
|
dependencyVars = objects.keys(options.dependencies), |
|
dependencies = '[' |
|
+ arrays.map( |
|
dependencyIds, |
|
function(id) { return '"' + js.stringEscape(id) + '"'; } |
|
).join(', ') |
|
+ ']', |
|
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() { |
|
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;
|
|
|