UNPKG

@qrvey/formula-lang

Version:

QFormula support for qrvey projects

242 lines 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TranspileAST = exports.Transpiler = void 0; const constants_1 = require("../constants"); const definitions_1 = require("../errors/definitions"); const functions_1 = require("../functions"); const utils_1 = require("../utils"); const validateFuncStructure_1 = require("./validateFuncStructure"); const columnTranspilation_1 = require("./columnTranspilation"); const unshiftCustomFunctions_1 = require("./unshiftCustomFunctions"); const formulaErrorList_1 = require("./formulaErrorList"); const scripts_1 = require("../utils/elasticsearch/scripts"); const dictionary_1 = require("../errors/dictionary"); const escapeCharacters_1 = require("../utils/escapeCharacters"); const addDecimalPointIfNeeded_1 = require("../utils/addDecimalPointIfNeeded"); const isAggregate_1 = require("../utils/isAggregate"); class Transpiler { constructor(ast, engine, globalContext) { this.ast = ast; this.engine = engine; this.globalContext = globalContext; this.customFunctionList = new Set(); this.aggregatedMap = new Map(); this.formulaErrorList = new formulaErrorList_1.FormulaErrorList(); } get() { const { body } = this.ast; const result = body ? this.processNode(body) : undefined; const expression = (0, unshiftCustomFunctions_1.unshiftCustomFunctions)(this.customFunctionList, result); const details = { script: result === null || result === void 0 ? void 0 : result.value, paramsDefinitions: this.aggregatedMap.size ? Array.from(this.aggregatedMap.values()) : undefined, customFunctions: Array.from(this.customFunctionList).join('\n'), }; return { valid: !this.formulaErrorList.hasErrors && body !== undefined, errors: this.formulaErrorList.get(), details, expression: this.formulaErrorList.hasErrors ? undefined : expression, }; } processNode(expression) { var _a; try { const { type } = expression !== null && expression !== void 0 ? expression : { type: constants_1.AST_TYPES.unknown }; const nodes = { [constants_1.AST_TYPES.token]: this.literal, [constants_1.AST_TYPES.literal]: this.literal, [constants_1.AST_TYPES.column]: ((_a = this.globalContext) === null || _a === void 0 ? void 0 : _a.useSampleData) ? this.conversionFromColumnToLiteral : this.column, [constants_1.AST_TYPES.externalFormula]: this.conversionFromColumnToLiteral, [constants_1.AST_TYPES.unaryExpression]: this.unaryExpression, [constants_1.AST_TYPES.binaryExpression]: this.binaryExpression, [constants_1.AST_TYPES.functionCall]: this.functionCall, }; if (!nodes[type]) return { value: null, type, dataType: constants_1.AST_PRIMITIVES.UNKNOWN, node: expression, }; return nodes[type].call(this, expression); } catch (error) { this.formulaErrorList.push(error); return { value: '', type: constants_1.AST_TYPES.unknown, dataType: constants_1.AST_PRIMITIVES.UNKNOWN, node: expression, }; } } conversionFromColumnToLiteral(data) { var _a, _b, _c, _d, _e; const value = (((_a = this.globalContext) === null || _a === void 0 ? void 0 : _a.sampleData) || {})[(_c = (_b = data.context) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : '']; return this.literal(Object.assign(Object.assign({}, data), { value: value === null ? 'null' : value, dataType: (_e = (_d = data.context) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : constants_1.AST_PRIMITIVES.STRING })); } column(data) { var _a, _b; if (this.engine === constants_1.ENGINES.ELASTICSEARCH) this.customFunctionList.add(scripts_1.getValueScript); if (((_a = data === null || data === void 0 ? void 0 : data.context) === null || _a === void 0 ? void 0 : _a.type) === constants_1.AST_PRIMITIVES.DATE && ((_b = this.globalContext) === null || _b === void 0 ? void 0 : _b.timezone)) { const customFunction = (0, utils_1.customTimezone)(this.engine); if (customFunction) this.customFunctionList.add(customFunction); } return (0, columnTranspilation_1.columnTranspilation)(this.engine, data, this.globalContext); } literal(expression) { const { dataType, type } = expression; let { value } = expression; if (value === 'null') return { value, dataType, type, node: expression }; if (dataType === constants_1.AST_PRIMITIVES.DATE) value = (0, utils_1.customDateCast)(this.engine)(value); if (dataType === constants_1.AST_PRIMITIVES.STRING) { value = (0, escapeCharacters_1.escapeCharacters)(value, this.engine); } const isElasticsearchNumber = this.engine === constants_1.ENGINES.ELASTICSEARCH && dataType === constants_1.AST_PRIMITIVES.NUMBER; if (isElasticsearchNumber && (0, utils_1.isLongNumber)(value)) { value = `${value}L`; } else { value = (0, addDecimalPointIfNeeded_1.addDecimalPointIfNeeded)(value, this.engine); } return { value, dataType, type, node: expression }; } unaryExpression(expression) { if (!expression.right) return { value: '', type: expression.type, dataType: constants_1.AST_PRIMITIVES.STRING, node: expression, }; const { value: rightResult } = this.processNode(expression.right); const value = `(${expression.operator} ${rightResult})`; return { value, dataType: expression.primitive, type: expression.type, node: expression, }; } binaryExpression(expression) { var _a, _b; const { left, operator, right, type } = expression; if (!left || !right) return { value: '', type, dataType: constants_1.AST_PRIMITIVES.STRING, node: expression, }; const { value: leftResult } = this.processNode(left); const { value: rightResult } = this.processNode(right); const value = `(${leftResult} ${(_b = (_a = constants_1.CustomOperators[this.engine]) === null || _a === void 0 ? void 0 : _a[operator]) !== null && _b !== void 0 ? _b : operator} ${rightResult})`; return { value, dataType: expression.primitive, type, node: expression, }; } functionCall(node) { const { name, arguments: args } = node; const currentFunctionList = (0, isAggregate_1.isAggregateScope)(this.globalContext) ? functions_1.AGGREGATE_FUNCTION_LIST : functions_1.ROW_FUNCTION_LIST; const functionExists = currentFunctionList.includes(name); if (!functionExists) throw new definitions_1.UnknownFunctionError(node); const func = functions_1.functionList[name]; const customFunction = (0, utils_1.customFunction)(this.engine, name); if (customFunction) this.customFunctionList.add(customFunction); const aggregatedMap = (0, utils_1.aggregateFunction)(this.engine, this.aggregatedMap, node); if (aggregatedMap) this.aggregatedMap = aggregatedMap; const parameters = args.map((param) => this.processNode(param)); const value = this.executer(func, parameters, node); return { value, dataType: node.primitive, type: node.type, node }; } executer(func, args, node) { (0, validateFuncStructure_1.validateFuncStructure)(func, args, node, { context: this.globalContext, }); args = args.map((item) => item.value); return func.transpiler[this.engine](...args); } } exports.Transpiler = Transpiler; function TranspileAST(ast, engine, context) { var _a, _b; const hasSyntaxErrors = ((_b = (_a = ast.errors) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0; const transpiler = new Transpiler(ast, engine, context); const transpileResult = transpiler.get(); const errors = postTranspileErrors(transpileResult.errors, ast.errors); return Object.assign(Object.assign({}, transpileResult), { valid: transpileResult.valid && !hasSyntaxErrors, errors }); } exports.TranspileAST = TranspileAST; function postTranspileErrors(transpiledErrors, astErrors) { const baseErrors = joinErrors(transpiledErrors, astErrors); if (!baseErrors) return undefined; const postProcessedErrors = postProcessErrors(baseErrors); const errors = removeOverlappingErrors(postProcessedErrors); return errors; } function joinErrors(transpiledErrors, astErrors) { return transpiledErrors || (astErrors && (astErrors === null || astErrors === void 0 ? void 0 : astErrors.length) > 0) ? [...(transpiledErrors !== null && transpiledErrors !== void 0 ? transpiledErrors : []), ...(astErrors !== null && astErrors !== void 0 ? astErrors : [])] : undefined; } function postProcessErrors(errors) { const resultErrors = []; errors.forEach((error) => { var _a; if (!error.node) return; // clean those errors without node if (((_a = error.node) === null || _a === void 0 ? void 0 : _a.type) === constants_1.AST_TYPES.functionCall && error.node.functionIdentifier) { const functionIdentifier = error.node .functionIdentifier; error.node.from = functionIdentifier.from; error.node.to = functionIdentifier.to; } else if (error.code === dictionary_1.ERROR_DICTIONARY.notAllowedOperation.code) { const nodePosition = error.node .operatorNode; if (nodePosition) { error.node.from = nodePosition.from; error.node.to = nodePosition.to; } } resultErrors.push(error); }); return resultErrors; } function removeOverlappingErrors(baseErrors) { const errors = []; for (const baseError of baseErrors) { const existError = errors.some((error) => error.node && baseError.node && error.node.from >= baseError.node.from && error.node.to <= baseError.node.to); if (!existError) { errors.push(baseError); } } return errors; } //# sourceMappingURL=index.js.map