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.
631 lines
16 KiB
631 lines
16 KiB
"use strict"; |
|
|
|
var arrays = require("../../utils/arrays"), |
|
objects = require("../../utils/objects"), |
|
asts = require("../asts"), |
|
visitor = require("../visitor"), |
|
op = require("../opcodes"), |
|
js = require("../js"); |
|
|
|
/* Generates bytecode. |
|
* |
|
* Instructions |
|
* ============ |
|
* |
|
* Stack Manipulation |
|
* ------------------ |
|
* |
|
* [0] PUSH c |
|
* |
|
* stack.push(consts[c]); |
|
* |
|
* [1] PUSH_UNDEFINED |
|
* |
|
* stack.push(undefined); |
|
* |
|
* [2] PUSH_NULL |
|
* |
|
* stack.push(null); |
|
* |
|
* [3] PUSH_FAILED |
|
* |
|
* stack.push(FAILED); |
|
* |
|
* [4] PUSH_EMPTY_ARRAY |
|
* |
|
* stack.push([]); |
|
* |
|
* [5] PUSH_CURR_POS |
|
* |
|
* stack.push(currPos); |
|
* |
|
* [6] POP |
|
* |
|
* stack.pop(); |
|
* |
|
* [7] POP_CURR_POS |
|
* |
|
* currPos = stack.pop(); |
|
* |
|
* [8] POP_N n |
|
* |
|
* stack.pop(n); |
|
* |
|
* [9] NIP |
|
* |
|
* value = stack.pop(); |
|
* stack.pop(); |
|
* stack.push(value); |
|
* |
|
* [10] APPEND |
|
* |
|
* value = stack.pop(); |
|
* array = stack.pop(); |
|
* array.push(value); |
|
* stack.push(array); |
|
* |
|
* [11] WRAP n |
|
* |
|
* stack.push(stack.pop(n)); |
|
* |
|
* [12] TEXT |
|
* |
|
* stack.push(input.substring(stack.pop(), currPos)); |
|
* |
|
* Conditions and Loops |
|
* -------------------- |
|
* |
|
* [13] IF t, f |
|
* |
|
* if (stack.top()) { |
|
* interpret(ip + 3, ip + 3 + t); |
|
* } else { |
|
* interpret(ip + 3 + t, ip + 3 + t + f); |
|
* } |
|
* |
|
* [14] IF_ERROR t, f |
|
* |
|
* if (stack.top() === FAILED) { |
|
* interpret(ip + 3, ip + 3 + t); |
|
* } else { |
|
* interpret(ip + 3 + t, ip + 3 + t + f); |
|
* } |
|
* |
|
* [15] IF_NOT_ERROR t, f |
|
* |
|
* if (stack.top() !== FAILED) { |
|
* interpret(ip + 3, ip + 3 + t); |
|
* } else { |
|
* interpret(ip + 3 + t, ip + 3 + t + f); |
|
* } |
|
* |
|
* [16] WHILE_NOT_ERROR b |
|
* |
|
* while(stack.top() !== FAILED) { |
|
* interpret(ip + 2, ip + 2 + b); |
|
* } |
|
* |
|
* Matching |
|
* -------- |
|
* |
|
* [17] MATCH_ANY a, f, ... |
|
* |
|
* if (input.length > currPos) { |
|
* interpret(ip + 3, ip + 3 + a); |
|
* } else { |
|
* interpret(ip + 3 + a, ip + 3 + a + f); |
|
* } |
|
* |
|
* [18] MATCH_STRING s, a, f, ... |
|
* |
|
* if (input.substr(currPos, consts[s].length) === consts[s]) { |
|
* interpret(ip + 4, ip + 4 + a); |
|
* } else { |
|
* interpret(ip + 4 + a, ip + 4 + a + f); |
|
* } |
|
* |
|
* [19] MATCH_STRING_IC s, a, f, ... |
|
* |
|
* if (input.substr(currPos, consts[s].length).toLowerCase() === consts[s]) { |
|
* interpret(ip + 4, ip + 4 + a); |
|
* } else { |
|
* interpret(ip + 4 + a, ip + 4 + a + f); |
|
* } |
|
* |
|
* [20] MATCH_REGEXP r, a, f, ... |
|
* |
|
* if (consts[r].test(input.charAt(currPos))) { |
|
* interpret(ip + 4, ip + 4 + a); |
|
* } else { |
|
* interpret(ip + 4 + a, ip + 4 + a + f); |
|
* } |
|
* |
|
* [21] ACCEPT_N n |
|
* |
|
* stack.push(input.substring(currPos, n)); |
|
* currPos += n; |
|
* |
|
* [22] ACCEPT_STRING s |
|
* |
|
* stack.push(consts[s]); |
|
* currPos += consts[s].length; |
|
* |
|
* [23] FAIL e |
|
* |
|
* stack.push(FAILED); |
|
* fail(consts[e]); |
|
* |
|
* Calls |
|
* ----- |
|
* |
|
* [24] LOAD_SAVED_POS p |
|
* |
|
* savedPos = stack[p]; |
|
* |
|
* [25] UPDATE_SAVED_POS |
|
* |
|
* savedPos = currPos; |
|
* |
|
* [26] CALL f, n, pc, p1, p2, ..., pN |
|
* |
|
* value = consts[f](stack[p1], ..., stack[pN]); |
|
* stack.pop(n); |
|
* stack.push(value); |
|
* |
|
* Rules |
|
* ----- |
|
* |
|
* [27] RULE r |
|
* |
|
* stack.push(parseRule(r)); |
|
* |
|
* Failure Reporting |
|
* ----------------- |
|
* |
|
* [28] SILENT_FAILS_ON |
|
* |
|
* silentFails++; |
|
* |
|
* [29] SILENT_FAILS_OFF |
|
* |
|
* silentFails--; |
|
*/ |
|
function generateBytecode(ast) { |
|
var consts = []; |
|
|
|
function addConst(value) { |
|
var index = arrays.indexOf(consts, value); |
|
|
|
return index === -1 ? consts.push(value) - 1 : index; |
|
} |
|
|
|
function addFunctionConst(params, code) { |
|
return addConst( |
|
"function(" + params.join(", ") + ") {" + code + "}" |
|
); |
|
} |
|
|
|
function buildSequence() { |
|
return Array.prototype.concat.apply([], arguments); |
|
} |
|
|
|
function buildCondition(condCode, thenCode, elseCode) { |
|
return condCode.concat( |
|
[thenCode.length, elseCode.length], |
|
thenCode, |
|
elseCode |
|
); |
|
} |
|
|
|
function buildLoop(condCode, bodyCode) { |
|
return condCode.concat([bodyCode.length], bodyCode); |
|
} |
|
|
|
function buildCall(functionIndex, delta, env, sp) { |
|
var params = arrays.map(objects.values(env), function(p) { return sp - p; }); |
|
|
|
return [op.CALL, functionIndex, delta, params.length].concat(params); |
|
} |
|
|
|
function buildSimplePredicate(expression, negative, context) { |
|
return buildSequence( |
|
[op.PUSH_CURR_POS], |
|
[op.SILENT_FAILS_ON], |
|
generate(expression, { |
|
sp: context.sp + 1, |
|
env: objects.clone(context.env), |
|
action: null |
|
}), |
|
[op.SILENT_FAILS_OFF], |
|
buildCondition( |
|
[negative ? op.IF_ERROR : op.IF_NOT_ERROR], |
|
buildSequence( |
|
[op.POP], |
|
[negative ? op.POP : op.POP_CURR_POS], |
|
[op.PUSH_UNDEFINED] |
|
), |
|
buildSequence( |
|
[op.POP], |
|
[negative ? op.POP_CURR_POS : op.POP], |
|
[op.PUSH_FAILED] |
|
) |
|
) |
|
); |
|
} |
|
|
|
function buildSemanticPredicate(code, negative, context) { |
|
var functionIndex = addFunctionConst(objects.keys(context.env), code); |
|
|
|
return buildSequence( |
|
[op.UPDATE_SAVED_POS], |
|
buildCall(functionIndex, 0, context.env, context.sp), |
|
buildCondition( |
|
[op.IF], |
|
buildSequence( |
|
[op.POP], |
|
negative ? [op.PUSH_FAILED] : [op.PUSH_UNDEFINED] |
|
), |
|
buildSequence( |
|
[op.POP], |
|
negative ? [op.PUSH_UNDEFINED] : [op.PUSH_FAILED] |
|
) |
|
) |
|
); |
|
} |
|
|
|
function buildAppendLoop(expressionCode) { |
|
return buildLoop( |
|
[op.WHILE_NOT_ERROR], |
|
buildSequence([op.APPEND], expressionCode) |
|
); |
|
} |
|
|
|
var generate = visitor.build({ |
|
grammar: function(node) { |
|
arrays.each(node.rules, generate); |
|
|
|
node.consts = consts; |
|
}, |
|
|
|
rule: function(node) { |
|
node.bytecode = generate(node.expression, { |
|
sp: -1, // stack pointer |
|
env: { }, // mapping of label names to stack positions |
|
action: null // action nodes pass themselves to children here |
|
}); |
|
}, |
|
|
|
named: function(node, context) { |
|
var nameIndex = addConst( |
|
'peg$otherExpectation("' + js.stringEscape(node.name) + '")' |
|
); |
|
|
|
/* |
|
* The code generated below is slightly suboptimal because |FAIL| pushes |
|
* to the stack, so we need to stick a |POP| in front of it. We lack a |
|
* dedicated instruction that would just report the failure and not touch |
|
* the stack. |
|
*/ |
|
return buildSequence( |
|
[op.SILENT_FAILS_ON], |
|
generate(node.expression, context), |
|
[op.SILENT_FAILS_OFF], |
|
buildCondition([op.IF_ERROR], [op.FAIL, nameIndex], []) |
|
); |
|
}, |
|
|
|
choice: function(node, context) { |
|
function buildAlternativesCode(alternatives, context) { |
|
return buildSequence( |
|
generate(alternatives[0], { |
|
sp: context.sp, |
|
env: objects.clone(context.env), |
|
action: null |
|
}), |
|
alternatives.length > 1 |
|
? buildCondition( |
|
[op.IF_ERROR], |
|
buildSequence( |
|
[op.POP], |
|
buildAlternativesCode(alternatives.slice(1), context) |
|
), |
|
[] |
|
) |
|
: [] |
|
); |
|
} |
|
|
|
return buildAlternativesCode(node.alternatives, context); |
|
}, |
|
|
|
action: function(node, context) { |
|
var env = objects.clone(context.env), |
|
emitCall = node.expression.type !== "sequence" |
|
|| node.expression.elements.length === 0, |
|
expressionCode = generate(node.expression, { |
|
sp: context.sp + (emitCall ? 1 : 0), |
|
env: env, |
|
action: node |
|
}), |
|
functionIndex = addFunctionConst(objects.keys(env), node.code); |
|
|
|
return emitCall |
|
? buildSequence( |
|
[op.PUSH_CURR_POS], |
|
expressionCode, |
|
buildCondition( |
|
[op.IF_NOT_ERROR], |
|
buildSequence( |
|
[op.LOAD_SAVED_POS, 1], |
|
buildCall(functionIndex, 1, env, context.sp + 2) |
|
), |
|
[] |
|
), |
|
[op.NIP] |
|
) |
|
: expressionCode; |
|
}, |
|
|
|
sequence: function(node, context) { |
|
function buildElementsCode(elements, context) { |
|
var processedCount, functionIndex; |
|
|
|
if (elements.length > 0) { |
|
processedCount = node.elements.length - elements.slice(1).length; |
|
|
|
return buildSequence( |
|
generate(elements[0], { |
|
sp: context.sp, |
|
env: context.env, |
|
action: null |
|
}), |
|
buildCondition( |
|
[op.IF_NOT_ERROR], |
|
buildElementsCode(elements.slice(1), { |
|
sp: context.sp + 1, |
|
env: context.env, |
|
action: context.action |
|
}), |
|
buildSequence( |
|
processedCount > 1 ? [op.POP_N, processedCount] : [op.POP], |
|
[op.POP_CURR_POS], |
|
[op.PUSH_FAILED] |
|
) |
|
) |
|
); |
|
} else { |
|
if (context.action) { |
|
functionIndex = addFunctionConst( |
|
objects.keys(context.env), |
|
context.action.code |
|
); |
|
|
|
return buildSequence( |
|
[op.LOAD_SAVED_POS, node.elements.length], |
|
buildCall( |
|
functionIndex, |
|
node.elements.length, |
|
context.env, |
|
context.sp |
|
), |
|
[op.NIP] |
|
); |
|
} else { |
|
return buildSequence([op.WRAP, node.elements.length], [op.NIP]); |
|
} |
|
} |
|
} |
|
|
|
return buildSequence( |
|
[op.PUSH_CURR_POS], |
|
buildElementsCode(node.elements, { |
|
sp: context.sp + 1, |
|
env: context.env, |
|
action: context.action |
|
}) |
|
); |
|
}, |
|
|
|
labeled: function(node, context) { |
|
var env = objects.clone(context.env); |
|
|
|
context.env[node.label] = context.sp + 1; |
|
|
|
return generate(node.expression, { |
|
sp: context.sp, |
|
env: env, |
|
action: null |
|
}); |
|
}, |
|
|
|
text: function(node, context) { |
|
return buildSequence( |
|
[op.PUSH_CURR_POS], |
|
generate(node.expression, { |
|
sp: context.sp + 1, |
|
env: objects.clone(context.env), |
|
action: null |
|
}), |
|
buildCondition( |
|
[op.IF_NOT_ERROR], |
|
buildSequence([op.POP], [op.TEXT]), |
|
[op.NIP] |
|
) |
|
); |
|
}, |
|
|
|
simple_and: function(node, context) { |
|
return buildSimplePredicate(node.expression, false, context); |
|
}, |
|
|
|
simple_not: function(node, context) { |
|
return buildSimplePredicate(node.expression, true, context); |
|
}, |
|
|
|
optional: function(node, context) { |
|
return buildSequence( |
|
generate(node.expression, { |
|
sp: context.sp, |
|
env: objects.clone(context.env), |
|
action: null |
|
}), |
|
buildCondition( |
|
[op.IF_ERROR], |
|
buildSequence([op.POP], [op.PUSH_NULL]), |
|
[] |
|
) |
|
); |
|
}, |
|
|
|
zero_or_more: function(node, context) { |
|
var expressionCode = generate(node.expression, { |
|
sp: context.sp + 1, |
|
env: objects.clone(context.env), |
|
action: null |
|
}); |
|
|
|
return buildSequence( |
|
[op.PUSH_EMPTY_ARRAY], |
|
expressionCode, |
|
buildAppendLoop(expressionCode), |
|
[op.POP] |
|
); |
|
}, |
|
|
|
one_or_more: function(node, context) { |
|
var expressionCode = generate(node.expression, { |
|
sp: context.sp + 1, |
|
env: objects.clone(context.env), |
|
action: null |
|
}); |
|
|
|
return buildSequence( |
|
[op.PUSH_EMPTY_ARRAY], |
|
expressionCode, |
|
buildCondition( |
|
[op.IF_NOT_ERROR], |
|
buildSequence(buildAppendLoop(expressionCode), [op.POP]), |
|
buildSequence([op.POP], [op.POP], [op.PUSH_FAILED]) |
|
) |
|
); |
|
}, |
|
|
|
group: function(node, context) { |
|
return generate(node.expression, { |
|
sp: context.sp, |
|
env: objects.clone(context.env), |
|
action: null |
|
}); |
|
}, |
|
|
|
semantic_and: function(node, context) { |
|
return buildSemanticPredicate(node.code, false, context); |
|
}, |
|
|
|
semantic_not: function(node, context) { |
|
return buildSemanticPredicate(node.code, true, context); |
|
}, |
|
|
|
rule_ref: function(node) { |
|
return [op.RULE, asts.indexOfRule(ast, node.name)]; |
|
}, |
|
|
|
literal: function(node) { |
|
var stringIndex, expectedIndex; |
|
|
|
if (node.value.length > 0) { |
|
stringIndex = addConst('"' |
|
+ js.stringEscape( |
|
node.ignoreCase ? node.value.toLowerCase() : node.value |
|
) |
|
+ '"' |
|
); |
|
expectedIndex = addConst( |
|
'peg$literalExpectation(' |
|
+ '"' + js.stringEscape(node.value) + '", ' |
|
+ node.ignoreCase |
|
+ ')' |
|
); |
|
|
|
/* |
|
* For case-sensitive strings the value must match the beginning of the |
|
* remaining input exactly. As a result, we can use |ACCEPT_STRING| and |
|
* save one |substr| call that would be needed if we used |ACCEPT_N|. |
|
*/ |
|
return buildCondition( |
|
node.ignoreCase |
|
? [op.MATCH_STRING_IC, stringIndex] |
|
: [op.MATCH_STRING, stringIndex], |
|
node.ignoreCase |
|
? [op.ACCEPT_N, node.value.length] |
|
: [op.ACCEPT_STRING, stringIndex], |
|
[op.FAIL, expectedIndex] |
|
); |
|
} else { |
|
stringIndex = addConst('""'); |
|
|
|
return [op.PUSH, stringIndex]; |
|
} |
|
}, |
|
|
|
"class": function(node) { |
|
var regexp, parts, regexpIndex, expectedIndex; |
|
|
|
if (node.parts.length > 0) { |
|
regexp = '/^[' |
|
+ (node.inverted ? '^' : '') |
|
+ arrays.map(node.parts, function(part) { |
|
return part instanceof Array |
|
? js.regexpClassEscape(part[0]) |
|
+ '-' |
|
+ js.regexpClassEscape(part[1]) |
|
: js.regexpClassEscape(part); |
|
}).join('') |
|
+ ']/' + (node.ignoreCase ? 'i' : ''); |
|
} else { |
|
/* |
|
* IE considers regexps /[]/ and /[^]/ as syntactically invalid, so we |
|
* translate them into equivalents it can handle. |
|
*/ |
|
regexp = node.inverted ? '/^[\\S\\s]/' : '/^(?!)/'; |
|
} |
|
|
|
parts = '[' |
|
+ arrays.map(node.parts, function(part) { |
|
return part instanceof Array |
|
? '["' + js.stringEscape(part[0]) + '", "' + js.stringEscape(part[1]) + '"]' |
|
: '"' + js.stringEscape(part) + '"'; |
|
}).join(', ') |
|
+ ']'; |
|
|
|
regexpIndex = addConst(regexp); |
|
expectedIndex = addConst( |
|
'peg$classExpectation(' |
|
+ parts + ', ' |
|
+ node.inverted + ', ' |
|
+ node.ignoreCase |
|
+ ')' |
|
); |
|
|
|
return buildCondition( |
|
[op.MATCH_REGEXP, regexpIndex], |
|
[op.ACCEPT_N, 1], |
|
[op.FAIL, expectedIndex] |
|
); |
|
}, |
|
|
|
any: function() { |
|
var expectedIndex = addConst('peg$anyExpectation()'); |
|
|
|
return buildCondition( |
|
[op.MATCH_ANY], |
|
[op.ACCEPT_N, 1], |
|
[op.FAIL, expectedIndex] |
|
); |
|
} |
|
}); |
|
|
|
generate(ast); |
|
} |
|
|
|
module.exports = generateBytecode;
|
|
|