odatafy-parser
Version:
generate odatafy ast from odata query parameters
165 lines (164 loc) • 6.52 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.processAST = exports.cleanAST = exports.parseMathExpression = exports.replaceMathSymbols = exports.escapeFunctions = exports.escapeStrings = exports.getIdentifier = exports.Prefixes = void 0;
const find_matching_bracket_1 = __importDefault(require("find-matching-bracket"));
const crypto_1 = __importDefault(require("crypto"));
const mathjs_1 = require("mathjs");
const nodes_1 = require("../types/nodes");
//STR and FUNC_ESCAPE switched, because % is reserved in mathjs and in mathExpressions, strings can only occur in functions, so the string escape symbol does not matter
//TODO adjust the grammar, so that strings cannot be part of a mathExpr
var Prefixes;
(function (Prefixes) {
Prefixes["Str_Escape"] = "%";
Prefixes["Func_Escape"] = "$";
})(Prefixes = exports.Prefixes || (exports.Prefixes = {}));
const FUNC_ESCAPE_REGEX = /\$(.*?)\$/;
function getIdentifier(prefix) {
return `${prefix}${crypto_1.default.randomBytes(20).toString('hex')}${prefix}`;
}
exports.getIdentifier = getIdentifier;
function escapeStrings(expr) {
let escaped = expr;
const stringResolve = {};
while (escaped.includes("'")) {
const regex = /'.*?'/;
const identifier = getIdentifier(Prefixes.Str_Escape);
const toEscape = escaped.match(regex);
if (toEscape == null) {
break;
}
stringResolve[identifier] = toEscape[0].toString();
escaped = escaped.replace(regex, identifier);
}
return [escaped, stringResolve];
}
exports.escapeStrings = escapeStrings;
function escapeFunctions(expr) {
let escaped = expr;
const functionResolve = {};
const regex = /contains\(/g;
while ([...escaped.matchAll(regex)].length > 0) {
const matches = [...escaped.matchAll(regex)];
const end = (0, find_matching_bracket_1.default)(escaped, matches[0].index + (matches[0][0].length - 1));
const identifier = getIdentifier(Prefixes.Func_Escape);
const toEscape = escaped.substring(matches[0].index, end + 1);
functionResolve[identifier] = toEscape;
escaped =
escaped.substring(0, matches[0].index) +
identifier +
escaped.substring(end + 1);
}
return [escaped, functionResolve];
}
exports.escapeFunctions = escapeFunctions;
function replaceMathSymbols(mathExpr) {
mathExpr = mathExpr.replace(/ add /g, ' + ');
mathExpr = mathExpr.replace(/ sub /g, ' - ');
mathExpr = mathExpr.replace(/ mul /g, ' * ');
//element wise division is later resolved to integer division, because mathjs does not support integer division
mathExpr = mathExpr.replace(/ div /g, ' .* ');
mathExpr = mathExpr.replace(/ divby /g, ' / ');
//mathjs knows mod as mod
return mathExpr;
}
exports.replaceMathSymbols = replaceMathSymbols;
function parseMathExpression(mathExpr) {
const [strResult, strResolver] = escapeStrings(mathExpr);
const [funcResult, funcResolver] = escapeFunctions(strResult);
const preparedMathExpr = replaceMathSymbols(funcResult);
const mathExprAst = (0, mathjs_1.parse)(preparedMathExpr);
const resolvedAst = mathExprAst.transform((node, _path, _parent) => {
if (node.type == 'SymbolNode') {
const symbolNode = node;
if (FUNC_ESCAPE_REGEX.test(symbolNode.name)) {
//resolve function
symbolNode.name = funcResolver[symbolNode.name];
//resolve strings in function, if there are any
Object.keys(strResolver).forEach((key) => {
symbolNode.name = symbolNode.name.replace(key, strResolver[key]);
});
}
}
return node;
});
return JSON.parse(JSON.stringify(resolvedAst));
}
exports.parseMathExpression = parseMathExpression;
function cleanAST(ast) {
let cleanedNode;
switch (ast.mathjs) {
case nodes_1.NodeTypes.OperatorNode:
if (ast.args) {
if (ast.args.length == 1) {
cleanedNode = {
nodeType: nodes_1.NodeTypes.OperatorNode,
op: ast.fn,
right: cleanAST(ast.args[0])
};
}
else {
cleanedNode = {
nodeType: nodes_1.NodeTypes.OperatorNode,
op: ast.fn,
left: cleanAST(ast.args[0]),
right: cleanAST(ast.args[1])
};
}
cleanedNode.op = (cleanedNode.op
.replace('add', nodes_1.OperatorNodeOperators.Add)
.replace('subtract', 'sub')
.replace('divide', 'divby')
.replace('dotMultiply', 'div')
.replace('multiply', 'mul')
.replace('mod', 'mod'));
}
break;
case nodes_1.NodeTypes.ConstantNode:
cleanedNode = {
nodeType: nodes_1.NodeTypes.ConstantNode,
type: Number.isInteger(ast.value)
? nodes_1.ConstantNodeTypes.Integer
: nodes_1.ConstantNodeTypes.Decimal,
value: ast.value
};
break;
case nodes_1.NodeTypes.SymbolNode:
cleanedNode = {
nodeType: nodes_1.NodeTypes.SymbolNode,
type: nodes_1.SymbolNodeTypes.Identifier,
value: ast.name
};
break;
case 'ParenthesisNode':
cleanedNode = cleanAST(ast.content);
break;
}
return cleanedNode;
}
exports.cleanAST = cleanAST;
function processAST(ast) {
if (ast.type == 'mathExpr') {
ast = cleanAST(parseMathExpression(ast.value));
}
//and / or args are inverted because of the preprocessing
if (ast.op == 'and' || ast.op == 'or') {
const temp = ast.right;
ast.right = ast.left;
ast.left = temp;
}
if (ast.left) {
ast.left = processAST(ast.left);
}
if (ast.right) {
ast.right = processAST(ast.right);
}
return ast;
}
exports.processAST = processAST;
function astPostProc(ast) {
return processAST(ast);
}
exports.default = astPostProc;