Implement recursive operator expression support
parent
a9e16640ca
commit
d26ae50244
@ -1,7 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const expressionParser = require("./expression");
|
||||
module.exports = function(body, options = {}) {
|
||||
let expressionParser, parserOptions;
|
||||
|
||||
module.exports = function(body) {
|
||||
return expressionParser.parse(body);
|
||||
if (options.tracer != null) {
|
||||
const pegjs = require("pegjs");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
let grammar = fs.readFileSync(path.join(__dirname, "../src/expression.pegjs"), {encoding: "utf8"});
|
||||
|
||||
expressionParser = pegjs.generate(grammar, {
|
||||
trace: true,
|
||||
format: "commonjs",
|
||||
dependencies: {
|
||||
"reorderOperatorExpressions": path.join(__dirname, "./reorder-operator-expressions")
|
||||
}
|
||||
});
|
||||
|
||||
parserOptions = {
|
||||
tracer: options.tracer
|
||||
};
|
||||
} else {
|
||||
expressionParser = require("./expression");
|
||||
|
||||
parserOptions = {};
|
||||
}
|
||||
|
||||
return expressionParser.parse(body, parserOptions);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function addPrecedences(operators) {
|
||||
return operators.map((item, i) => {
|
||||
return Object.assign({
|
||||
precedence: i
|
||||
}, item);
|
||||
});
|
||||
};
|
@ -0,0 +1,82 @@
|
||||
'use strict';
|
||||
|
||||
const util = require("util");
|
||||
|
||||
module.exports = function findOperatorSequences(tree, operatorMap) {
|
||||
let current = tree;
|
||||
let currentIndex = 0;
|
||||
let currentSequence;
|
||||
|
||||
let operators = [];
|
||||
let operands = [];
|
||||
let operatorSequences = [];
|
||||
let operatorSequenceMap = {};
|
||||
|
||||
function storeCurrentOperatorSequence() {
|
||||
operatorSequences.push(currentSequence);
|
||||
|
||||
if (operatorSequenceMap[currentSequence.precedence] == null) {
|
||||
operatorSequenceMap[currentSequence.precedence] = [];
|
||||
}
|
||||
|
||||
operatorSequenceMap[currentSequence.precedence].push(currentSequence);
|
||||
}
|
||||
|
||||
while (current != null) {
|
||||
operators.push(current.operator);
|
||||
|
||||
if (current.left != null) {
|
||||
operands.push(current.left);
|
||||
} else {
|
||||
/* Unary prefix operator */
|
||||
operands.push(null);
|
||||
}
|
||||
|
||||
if (operatorMap[current.operator] == null) {
|
||||
throw new Error(`Encountered unknown '${current.operator}' operator`);
|
||||
}
|
||||
|
||||
let associativity = operatorMap[current.operator].associativity;
|
||||
let precedence = operatorMap[current.operator].precedence;
|
||||
|
||||
if (currentSequence != null && precedence === currentSequence.precedence) {
|
||||
/* Next operator in the same sequence. */
|
||||
currentSequence.length += 1;
|
||||
} else {
|
||||
if (currentSequence != null) {
|
||||
/* We were previously working on a different operator. */
|
||||
storeCurrentOperatorSequence();
|
||||
}
|
||||
|
||||
currentSequence = {
|
||||
precedence: precedence,
|
||||
associativity: associativity,
|
||||
length: 1,
|
||||
firstIndex: currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
if (current.right == null) {
|
||||
throw new Error("An error occurred; encountered a `null` right operand, but this should never happen");
|
||||
} else {
|
||||
if (current.right.type === "operatorExpression") {
|
||||
current = current.right;
|
||||
currentIndex += 1;
|
||||
} else {
|
||||
/* Store the very last operand. */
|
||||
operands.push(current.right);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Store the very last sequence we were still working on. */
|
||||
storeCurrentOperatorSequence();
|
||||
|
||||
return {
|
||||
operators,
|
||||
operands,
|
||||
operatorSequences,
|
||||
operatorSequenceMap
|
||||
}
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function generateOperatorMap(operators) {
|
||||
return operators.reduce((map, item, i) => {
|
||||
item.operators.forEach((operator) => {
|
||||
map[operator] = item;
|
||||
});
|
||||
|
||||
return map;
|
||||
}, {});
|
||||
};
|
@ -0,0 +1,106 @@
|
||||
'use strict';
|
||||
|
||||
const util = require("util");
|
||||
|
||||
const generateOperatorMap = require("./generate-operator-map");
|
||||
const findOperatorSequences = require("./find-operator-sequences");
|
||||
const addPrecedences = require("./add-precedences");
|
||||
|
||||
module.exports = function reorderOperatorExpressions(tree, operators) {
|
||||
let operatorsWithPrecedence = addPrecedences(operators);
|
||||
let operatorMap = generateOperatorMap(operatorsWithPrecedence);
|
||||
|
||||
let sequenceResults = findOperatorSequences(tree, operatorMap);
|
||||
let modifiedOperands = sequenceResults.operands.slice();
|
||||
let relocatedOperands = new Array(modifiedOperands.length);
|
||||
|
||||
function lookupOperandIndex(i) {
|
||||
if (relocatedOperands[i] != null) {
|
||||
// FIXME: This shouldn't be recursive; instead, relocation pointers should be updated after a cascading relocation.
|
||||
return lookupOperandIndex(relocatedOperands[i]);
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
function getOperand(i) {
|
||||
let index = lookupOperandIndex(i);
|
||||
let operand = modifiedOperands[lookupOperandIndex(i)];
|
||||
|
||||
//console.log(`OPERAND AT INDEX ${index}: ${util.inspect(operand)}`)
|
||||
return modifiedOperands[lookupOperandIndex(i)];
|
||||
}
|
||||
|
||||
operatorsWithPrecedence.forEach((operator) => {
|
||||
let currentPrecedence = operator.precedence;
|
||||
let sequences = sequenceResults.operatorSequenceMap[currentPrecedence];
|
||||
|
||||
if (sequences != null) {
|
||||
sequences.forEach((sequence, sequenceIndex) => {
|
||||
let untilIndex = sequence.firstIndex + sequence.length;
|
||||
let associativity = sequence.associativity;
|
||||
|
||||
let root, current;
|
||||
|
||||
function addOperator(index) {
|
||||
let newNode = {
|
||||
type: "operatorExpression",
|
||||
operator: sequenceResults.operators[index],
|
||||
right: (associativity !== "right") ? getOperand(index + 1) : undefined,
|
||||
left: (associativity !== "left" && !operator.unary) ? getOperand(index) : undefined
|
||||
}
|
||||
|
||||
if (root == null) {
|
||||
root = newNode;
|
||||
} else {
|
||||
if (associativity === "left") {
|
||||
current.left = newNode;
|
||||
} else if (associativity === "right") {
|
||||
current.right = newNode;
|
||||
}
|
||||
}
|
||||
|
||||
current = newNode;
|
||||
}
|
||||
|
||||
if (associativity === "none") {
|
||||
/* Cannot occur more than once... */
|
||||
if (sequence.length > 1) {
|
||||
// FIXME: This does not yet detect multiple occurrences in different sequences...
|
||||
throw new Error(`Operators in series '${operators[sequence.precedence].join(", ")}' may only occur once in a series of operator expressions, but occurred ${sequence.length} times`);
|
||||
} else {
|
||||
addOperator(sequence.firstIndex);
|
||||
}
|
||||
} else {
|
||||
if (associativity === "left") {
|
||||
for (let i = untilIndex - 1; i >= sequence.firstIndex; i--) {
|
||||
addOperator(i);
|
||||
}
|
||||
|
||||
current.left = getOperand(sequence.firstIndex);
|
||||
} else if (associativity === "right") {
|
||||
for (let i = sequence.firstIndex; i < untilIndex; i++) {
|
||||
addOperator(i);
|
||||
}
|
||||
|
||||
current.right = getOperand(untilIndex);
|
||||
}
|
||||
}
|
||||
|
||||
let newLocation = lookupOperandIndex(sequence.firstIndex);
|
||||
modifiedOperands[newLocation] = root;
|
||||
|
||||
for (let i = sequence.firstIndex; i < untilIndex; i++) {
|
||||
modifiedOperands[i + 1] = null;
|
||||
relocatedOperands[i + 1] = newLocation;
|
||||
}
|
||||
|
||||
// console.log(`MODIFIED OPERANDS AFTER PRECEDENCE LEVEL ${currentPrecedence} (pos ${sequence.firstIndex} - ${sequence.firstIndex + sequence.length}):`, modifiedOperands.map((modOp) => {
|
||||
// return (modOp == null) ? "null" : `<${modOp.type} ${modOp.identifier || modOp.value || modOp.operator}>`;
|
||||
// }).join(", "))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return modifiedOperands[0];
|
||||
};
|
@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
const util = require("util");
|
||||
|
||||
const reorderOperatorExpressions = require("./index");
|
||||
|
||||
let operators = [{
|
||||
associativity: "right",
|
||||
operators: ["++"]
|
||||
}, {
|
||||
associativity: "left",
|
||||
operators: ["*", "/"]
|
||||
}, {
|
||||
associativity: "left",
|
||||
operators: ["+", "-"]
|
||||
}, {
|
||||
associativity: "right",
|
||||
operators: ["//"]
|
||||
}, {
|
||||
associativity: "left",
|
||||
operators: ["<", "<=", ">", ">="]
|
||||
}, {
|
||||
// NOTE: Special case, can only occur once in sequence!
|
||||
associativity: "none",
|
||||
operators: ["==", "!="]
|
||||
}, {
|
||||
associativity: "left",
|
||||
operators: ["&&"]
|
||||
}, {
|
||||
associativity: "left",
|
||||
operators: ["||"]
|
||||
}, {
|
||||
// NOTE: Special case, can only occur once in sequence!
|
||||
associativity: "none",
|
||||
operators: ["->"]
|
||||
}];
|
||||
|
||||
// let testData = {
|
||||
// type: "operatorExpression",
|
||||
// operator: "+",
|
||||
// left: { type: "variable", name: "a" },
|
||||
// right: {
|
||||
// type: "operatorExpression",
|
||||
// operator: "+",
|
||||
// left: { type: "variable", name: "b" },
|
||||
// right: {
|
||||
// type: "operatorExpression",
|
||||
// operator: "-",
|
||||
// left: { type: "variable", name: "c" },
|
||||
// right: {
|
||||
// type: "operatorExpression",
|
||||
// operator: "+",
|
||||
// left: { type: "variable", name: "d" },
|
||||
// right: {
|
||||
// type: "operatorExpression",
|
||||
// operator: "-",
|
||||
// left: { type: "variable", name: "e" },
|
||||
// right: {
|
||||
// type: "operatorExpression",
|
||||
// operator: "+",
|
||||
// left: { type: "variable", name: "f" },
|
||||
// right: { type: "variable", name: "g" }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
let testData = {
|
||||
type: "operatorExpression",
|
||||
operator: "+",
|
||||
left: { type: "variable", name: "a" },
|
||||
right: {
|
||||
type: "operatorExpression",
|
||||
operator: "*",
|
||||
left: { type: "variable", name: "b" },
|
||||
right: {
|
||||
type: "operatorExpression",
|
||||
operator: "&&",
|
||||
left: { type: "variable", name: "c" },
|
||||
right: {
|
||||
type: "operatorExpression",
|
||||
operator: "*",
|
||||
left: { type: "variable", name: "d" },
|
||||
right: {
|
||||
type: "operatorExpression",
|
||||
operator: "-",
|
||||
left: { type: "variable", name: "e" },
|
||||
right: {
|
||||
type: "operatorExpression",
|
||||
operator: "+",
|
||||
left: { type: "variable", name: "f" },
|
||||
right: { type: "variable", name: "g" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(util.inspect(reorderOperatorExpressions(testData, operators), {colors: true, depth: null}));
|
@ -0,0 +1,66 @@
|
||||
'use strict';
|
||||
|
||||
function stringifyNode(node) {
|
||||
switch (node.type) {
|
||||
case "operatorExpression":
|
||||
return stringifyOperator(node);
|
||||
case "numberLiteral":
|
||||
return stringifyNumberLiteral(node);
|
||||
case "stringLiteral":
|
||||
return stringifyStringLiteral(node);
|
||||
case "identifier":
|
||||
return stringifyIdentifier(node);
|
||||
default:
|
||||
throw new Error(`Unexpected node type '${node.type}'`);
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyOperator(node) {
|
||||
let result;
|
||||
|
||||
switch (node.operator) {
|
||||
case "+":
|
||||
case "-":
|
||||
case "/":
|
||||
case "*":
|
||||
case "++":
|
||||
case "//":
|
||||
case "&&":
|
||||
case "||":
|
||||
case "==":
|
||||
case "!=":
|
||||
case "<":
|
||||
case "<=":
|
||||
case ">":
|
||||
case ">=":
|
||||
result = `${stringifyNode(node.left)} ${node.operator} ${stringifyNode(node.right)}`;
|
||||
break;
|
||||
case "call":
|
||||
result = `${stringifyNode(node.left)} ${stringifyNode(node.right)}`;
|
||||
break;
|
||||
case "numericalNegate":
|
||||
result = `-${stringifyNode(node.right)}`;
|
||||
break;
|
||||
case "booleanNegate":
|
||||
result = `!${stringifyNode(node.right)}`;
|
||||
break;
|
||||
}
|
||||
|
||||
return `(${result})`;
|
||||
}
|
||||
|
||||
function stringifyStringLiteral(node) {
|
||||
return `"${node.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
|
||||
function stringifyNumberLiteral(node) {
|
||||
return `${node.value}`;
|
||||
}
|
||||
|
||||
function stringifyIdentifier(node) {
|
||||
return `${node.identifier}`;
|
||||
}
|
||||
|
||||
module.exports = function stringifyTree(tree) {
|
||||
return stringifyNode(tree);
|
||||
};
|
@ -0,0 +1 @@
|
||||
3 + builtins.isInt 4 10 && !12 + - 6 - 5
|
@ -0,0 +1 @@
|
||||
{ f = x: x; a=1; b=2; list = [ a f b ]; }
|
Loading…
Reference in New Issue