UNPKG

@qrvey/formula-lang

Version:

QFormula support for qrvey projects

223 lines 9.79 kB
import { ENGINES, AST_TYPES, CustomOperators, AST_PRIMITIVES, } from '../constants'; import { UnknownFunctionError } from '../errors/definitions'; import { functionList } from '../functions'; import { customFunction as customFunctionUtil, customDateCast, customTimezone, isLongNumber, } from '../utils'; import { validateFuncStructure } from './validateFuncStructure'; import { columnTranspilation } from './columnTranspilation'; import { unshiftCustomFunctions } from './unshiftCustomFunctions'; import { FormulaErrorList } from './formulaErrorList'; import { getValueScript } from '../utils/elasticsearch/scripts'; import { ERROR_DICTIONARY } from '../errors/dictionary'; import { escapeCharacters } from '../utils/escapeCharacters'; import { addDecimalPointIfNeeded } from '../utils/addDecimalPointIfNeeded'; export class Transpiler { constructor(ast, engine, globalContext) { this.ast = ast; this.engine = engine; this.globalContext = globalContext; this.customFunctionList = new Set(); this.formulaErrorList = new FormulaErrorList(); } get() { const { body } = this.ast; const result = body ? this.processNode(body) : undefined; const expression = unshiftCustomFunctions(this.customFunctionList, result); const details = { script: result === null || result === void 0 ? void 0 : result.value, 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: AST_TYPES.unknown }; const nodes = { [AST_TYPES.token]: this.literal, [AST_TYPES.literal]: this.literal, [AST_TYPES.column]: ((_a = this.globalContext) === null || _a === void 0 ? void 0 : _a.useSampleData) ? this.conversionFromColumnToLiteral : this.column, [AST_TYPES.externalFormula]: this.conversionFromColumnToLiteral, [AST_TYPES.unaryExpression]: this.unaryExpression, [AST_TYPES.binaryExpression]: this.binaryExpression, [AST_TYPES.functionCall]: this.functionCall, }; if (!nodes[type]) return { value: null, type, dataType: AST_PRIMITIVES.UNKNOWN, node: expression, }; return nodes[type].call(this, expression); } catch (error) { this.formulaErrorList.push(error); return { value: '', type: AST_TYPES.unknown, dataType: 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 : AST_PRIMITIVES.STRING })); } column(data) { var _a, _b; if (this.engine === ENGINES.ELASTICSEARCH) this.customFunctionList.add(getValueScript); if (((_a = data === null || data === void 0 ? void 0 : data.context) === null || _a === void 0 ? void 0 : _a.type) === AST_PRIMITIVES.DATE && ((_b = this.globalContext) === null || _b === void 0 ? void 0 : _b.timezone)) { const customFunction = customTimezone(this.engine); if (customFunction) this.customFunctionList.add(customFunction); } return 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 === AST_PRIMITIVES.DATE) value = customDateCast(this.engine)(value); if (dataType === AST_PRIMITIVES.STRING) { value = escapeCharacters(value, this.engine); } const isElasticsearchNumber = this.engine === ENGINES.ELASTICSEARCH && dataType === AST_PRIMITIVES.NUMBER; if (isElasticsearchNumber && isLongNumber(value)) { value = `${value}L`; } else { value = addDecimalPointIfNeeded(value, this.engine); } return { value, dataType, type, node: expression }; } unaryExpression(expression) { if (!expression.right) return { value: '', type: expression.type, dataType: 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: AST_PRIMITIVES.STRING, node: expression, }; const { value: leftResult } = this.processNode(left); const { value: rightResult } = this.processNode(right); const value = `(${leftResult} ${(_b = (_a = 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 func = functionList[name]; if (!func) throw new UnknownFunctionError(node); const customFunction = customFunctionUtil(this.engine, name); if (customFunction) this.customFunctionList.add(customFunction); 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) { validateFuncStructure(func, args, node); args = args.map((item) => item.value); return func.transpiler[this.engine](...args); } } export 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 }); } 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) === 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 === 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