UNPKG

odatafy-parser

Version:

generate odatafy ast from odata query parameters

165 lines (164 loc) 6.52 kB
"use strict"; 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;