UNPKG

@qrvey/formula-lang

Version:

QFormula support for qrvey projects

353 lines 16.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateAST = void 0; const constants_1 = require("../constants"); const utils_1 = require("../utils"); const syntax_errors_1 = require("./syntax-errors"); const errors_1 = require("../errors"); const primitiveFunctions_1 = require("../utils/primitiveFunctions"); const formula_parser_1 = require("./formula-parser"); const qformula_grammar_1 = require("../grammar/qformula.grammar"); const isAggregate_1 = require("../utils/isAggregate"); const PARSER_VERSION = '0.0.0'; const LANG_NAME = 'QrveyLang'; function calculateAST(program, tree, context) { var _a; const cursor = tree.cursor(); const startNode = cursor.node; if (!startNode) return; const syntaxErrors = (0, syntax_errors_1.calculateNodeSyntaxErrors)(program, startNode); const inference = { errorList: syntaxErrors, formulaVariables: [], formulaFunctions: [], flatBody: [], }; const body = transformNode(program, startNode, inference, context, cursor); const { errorList: errors, formulaVariables: variables, formulaFunctions, flatBody, } = inference; const primitive = (_a = body === null || body === void 0 ? void 0 : body.primitive) !== null && _a !== void 0 ? _a : constants_1.AST_PRIMITIVES.UNKNOWN; const programScope = validateProgramScope(body !== null && body !== void 0 ? body : inference.flatBody[0], inference, primitive, context); if (!programScope.isValid && programScope.error) errors.unshift(programScope.error); return { type: constants_1.AST_TYPES.program, exp: program, lang: LANG_NAME, version: PARSER_VERSION, body, from: startNode.from, to: startNode.to, errors, variables, formulaFunctions, flatBody, primitive, }; } exports.calculateAST = calculateAST; function transformNode(program, refNode, inference, context, cursor) { const node = refNode !== null && refNode !== void 0 ? refNode : cursor === null || cursor === void 0 ? void 0 : cursor.node; if (!node) return undefined; let resultNode; switch (node.name) { case 'BinaryExpression': resultNode = transformBinaryExpression(program, node, inference, context); break; case 'UnaryExpression': resultNode = transformUnaryExpression(program, node, inference, context); break; case 'ParenthesizedExpression': resultNode = transformParenthesizedExpression(program, node, inference, context); break; case 'Function': resultNode = (0, formula_parser_1.transformFunctionExpression)(program, node, inference, context, transformNode); break; case 'Number': resultNode = transformNumberValue(program, node); break; case 'Date': resultNode = transformDateValue(program, node); break; case 'String': resultNode = transformStringValue(program, node); break; case 'Boolean': resultNode = transformBooleanValue(program, node); break; case 'Variable': resultNode = transformVariableValue(program, node, inference, context); break; default: return (cursor === null || cursor === void 0 ? void 0 : cursor.next()) || node.firstChild ? transformNode(program, node.firstChild, inference, context, cursor) : undefined; } if (resultNode !== undefined) inference.flatBody.push(resultNode); validateIfAggregateHasError(node.name, inference, resultNode, context); return resultNode; } const VALID_OPERATIONS_BY_TYPE = { [constants_1.AST_PRIMITIVES.NUMBER]: [ '+', '-', '*', '/', '<', '<=', '>', '>=', '=', '<>', ], [constants_1.AST_PRIMITIVES.STRING]: ['=', '<>'], [constants_1.AST_PRIMITIVES.DATE]: ['=', '<>'], [constants_1.AST_PRIMITIVES.BOOLEAN]: [], [constants_1.AST_PRIMITIVES.UNKNOWN]: [], }; function operatorIsValidForOperation(operator, leftPrimitive, rightPrimitive) { const allowedOperators = calculateValidOperatorsByType(leftPrimitive, rightPrimitive); return allowedOperators.includes(operator); } function calculateValidOperatorsByType(leftPrimitive, rightPrimitive) { if (leftPrimitive === rightPrimitive && !Array.isArray(leftPrimitive)) { return VALID_OPERATIONS_BY_TYPE[leftPrimitive]; } return []; } function calculateBinaryElements(program, node, left, right) { const leftPrimitive = left === null || left === void 0 ? void 0 : left.primitive; const rightPrimitive = right === null || right === void 0 ? void 0 : right.primitive; const logicOperator = node.getChild('LogicOp'); const arithOperator = node.getChild('ArithOp'); const leftIsAValidNumber = leftPrimitive === constants_1.AST_PRIMITIVES.NUMBER; const rightIsAValidNumber = rightPrimitive === constants_1.AST_PRIMITIVES.NUMBER; const operatorNode = (logicOperator !== null && logicOperator !== void 0 ? logicOperator : arithOperator); const operator = (0, utils_1.getNodeValue)(program, operatorNode); const operatorPosition = !operatorNode ? undefined : (0, formula_parser_1.createPositionASTFromSyntaxNode)(operatorNode); const validOperators = calculateValidOperatorsByType(leftPrimitive, rightPrimitive); // primitive is number if arithOperator is not null and both left and right are numbers // primitive is boolean if logicOperator is not null and both left and right are booleans let primitive = constants_1.AST_PRIMITIVES.UNKNOWN; if (logicOperator) { if (operatorIsValidForOperation(operator, leftPrimitive, rightPrimitive)) { primitive = constants_1.AST_PRIMITIVES.BOOLEAN; } } else if (arithOperator && leftIsAValidNumber && rightIsAValidNumber) { primitive = constants_1.AST_PRIMITIVES.NUMBER; } const hasInvalidError = !left || !right || !operatorNode || (arithOperator && !leftIsAValidNumber && !rightIsAValidNumber) || primitive === constants_1.AST_PRIMITIVES.UNKNOWN; return { primitive, operator, operatorNode: operatorPosition, hasInvalidError, validOperators, }; } function transformBinaryExpression(program, node, inference, context) { const [leftNode, rightNode] = node.getChildren('Expression'); const left = transformNode(program, leftNode, inference, context); const right = transformNode(program, rightNode, inference, context); const { primitive, operator, operatorNode, hasInvalidError, validOperators, } = calculateBinaryElements(program, node, left, right); const binaryAST = { operator, operatorNode, type: constants_1.AST_TYPES.binaryExpression, left, right, isValidAggregate: (left === null || left === void 0 ? void 0 : left.isValidAggregate) && (right === null || right === void 0 ? void 0 : right.isValidAggregate), from: node.from, to: node.to, primitive, validOperators, }; if (hasInvalidError) { inference.errorList.push(new errors_1.NotAllowedOperationError(binaryAST)); } return binaryAST; } function transformUnaryExpression(program, node, inference, context) { const [rightNode] = node.getChildren('Expression'); const operator = node.getChild('ArithOp'); const right = transformNode(program, rightNode, inference, context); const primitive = (0, primitiveFunctions_1.primitiveIsIncluded)(right === null || right === void 0 ? void 0 : right.primitive, constants_1.AST_PRIMITIVES.NUMBER, constants_1.AST_PRIMITIVES.BOOLEAN) ? right.primitive : constants_1.AST_PRIMITIVES.UNKNOWN; const validOperators = (right === null || right === void 0 ? void 0 : right.primitive) === constants_1.AST_PRIMITIVES.NUMBER ? ['+', '-'] : []; const unaryAST = { operator: (0, utils_1.getNodeValue)(program, operator), operatorNode: (0, formula_parser_1.createPositionASTFromSyntaxNode)(operator), type: constants_1.AST_TYPES.unaryExpression, right, isValidAggregate: right === null || right === void 0 ? void 0 : right.isValidAggregate, from: node.from, to: node.to, primitive, validOperators, }; if (!rightNode) { inference.errorList.push(new errors_1.NotAllowedOperationError(unaryAST)); } return unaryAST; } function transformParenthesizedExpression(program, node, inference, context) { var _a; const expressionNode = node.getChild('Expression'); const parenthesisStart = node.getChild('ParenthesisStart'); const parenthesisEnd = node.getChild('ParenthesisEnd'); const resultNode = transformNode(program, expressionNode, inference, context); if (!parenthesisStart || !parenthesisEnd) { const [from, to] = [ !parenthesisStart ? node.from : parenthesisStart.from, !parenthesisStart ? node.to : parenthesisStart.to, ]; inference.errorList.push(new errors_1.MissingParenthesisError({ type: constants_1.AST_TYPES.parenthesisExpression, from, to, primitive: (_a = resultNode === null || resultNode === void 0 ? void 0 : resultNode.primitive) !== null && _a !== void 0 ? _a : constants_1.AST_PRIMITIVES.UNKNOWN, })); } return resultNode; } function createLiteralValue({ from, to }, dataType, value) { return { type: constants_1.AST_TYPES.literal, isValidAggregate: true, dataType, value, from, to, primitive: dataType, }; } // "MM/DD/YYYY" or "MM/DD/YYYY HH:mm:ss" const DATE_REGEXP = /^(0?\d|1[012])\/([0-3]?\d)\/(\d{2}|\d{4})(?:\s+(\d|[01]\d|2[0-3]):([0-5]\d):([0-5]\d))?$/; function transformDateValue(program, node) { const dateStr = (0, utils_1.getNodeValue)(program, node).slice(1, -1); const dateMatch = DATE_REGEXP.exec(dateStr); if (!dateMatch) return transformStringValue(program, node); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_full, dateMonth, dateDay, dateYear, dateHour, dateMin, dateSec] = dateMatch; const [yy, mm, dd, HH, MM, SS] = [ Number(dateYear), Number(dateMonth) - 1, Number(dateDay), Number(dateHour !== null && dateHour !== void 0 ? dateHour : 0), Number(dateMin !== null && dateMin !== void 0 ? dateMin : 0), Number(dateSec !== null && dateSec !== void 0 ? dateSec : 0), ]; const monthMaxDayAllowed = (0, utils_1.getMonthMaxDayAllowed)(mm, yy); if (dd > monthMaxDayAllowed) return transformStringValue(program, node); const msValue = Date.UTC(yy, mm, dd, HH, MM, SS); const dateValue = new Date(msValue); if (!(0, utils_1.isValidDate)(dateValue)) return transformStringValue(program, node); const value = dateValue.toISOString(); return createLiteralValue(node, constants_1.AST_PRIMITIVES.DATE, value); } function transformStringValue(program, node) { const value = (0, utils_1.getNodeValue)(program, node).slice(1, -1); return createLiteralValue(node, constants_1.AST_PRIMITIVES.STRING, value); } function transformNumberValue(program, node) { const value = parseFloat((0, utils_1.getNodeValue)(program, node)); return createLiteralValue(node, constants_1.AST_PRIMITIVES.NUMBER, value); } function transformBooleanValue(program, node) { const value = (0, utils_1.getNodeValue)(program, node); return createLiteralValue(node, constants_1.AST_PRIMITIVES.BOOLEAN, value); } function transformVariableValue(program, node, inference, context) { var _a, _b; let variableValue = (0, utils_1.getNodeValue)(program, node).slice(1, -1); const variableContext = (0, utils_1.getVariableContext)(variableValue, context); const variableType = (0, utils_1.getVariableType)(variableContext); if (variableType === constants_1.AST_TYPES.token) variableValue = `{{${variableValue}}}`; const variableExistInInference = inference.formulaVariables .map((v) => v.id) .includes(variableValue); if (!variableExistInInference) inference.formulaVariables.push(variableContext !== null && variableContext !== void 0 ? variableContext : { id: variableValue, label: variableValue, type: constants_1.AST_PRIMITIVES.STRING, }); const baseResult = { type: variableType, value: variableValue, isValidAggregate: !(0, isAggregate_1.isAggregateScope)(context), context: variableContext, from: node.from, to: node.to, primitive: (_a = variableContext === null || variableContext === void 0 ? void 0 : variableContext.type) !== null && _a !== void 0 ? _a : constants_1.AST_PRIMITIVES.UNKNOWN, }; //IF externalFormula AND NOT sampleData, due to sampleData will replace the externalFormulas into a Literal (in Transpiler) if (variableType === constants_1.AST_TYPES.externalFormula && !(context === null || context === void 0 ? void 0 : context.useSampleData)) { const isCircularDependency = (context === null || context === void 0 ? void 0 : context.currentFormulaId) && (context === null || context === void 0 ? void 0 : context.currentFormulaId) === variableValue; if (isCircularDependency) throw new errors_1.CircularDependencyError(); const parser = qformula_grammar_1.QFormulaLang.parser; const externalFunctionProgram = (_b = variableContext === null || variableContext === void 0 ? void 0 : variableContext.replacement) !== null && _b !== void 0 ? _b : ''; const tree = parser.parse(externalFunctionProgram); const ast = calculateAST(externalFunctionProgram, tree, context); const formulaBody = ast === null || ast === void 0 ? void 0 : ast.body; return Object.assign(Object.assign({ isExternalFormula: true }, baseResult), formulaBody); } return baseResult; } function validateProgramScope(currentNode, inference, result, context) { const isRaw = (0, isAggregate_1.isRawScope)(context); if (isRaw) { const hasSomeAggregated = inference.flatBody.some((item) => { return (0, isAggregate_1.isAggregateFunction)(item); }); return { isValid: !hasSomeAggregated, error: new errors_1.RowFunctionRequiredError(currentNode), }; } const isAggregated = (0, isAggregate_1.isAggregateScope)(context); if (isAggregated && currentNode) { let currentError; const hasAnExpectedResult = (0, primitiveFunctions_1.primitiveIsIncluded)(result, ...constants_1.EXPECTED_AGGREGATE_RESULT); if (!hasAnExpectedResult) currentError = new errors_1.AggregatedPrimitiveMismatchError(currentNode); const programIsAColumn = currentNode.type === constants_1.AST_TYPES.column || currentNode.type === constants_1.AST_TYPES.externalFormula; if (programIsAColumn) currentError = new errors_1.AggregatedFunctionRequiredError(currentNode); return { isValid: hasAnExpectedResult && !programIsAColumn, error: currentError, }; } return { isValid: false, error: new errors_1.RowFunctionRequiredError(currentNode), }; } function validateIfAggregateHasError(nodeName, inference, resultNode, context) { const hasError = constants_1.VALID_EXPRESSION_IN_AGGREGATE.includes(nodeName) && (0, isAggregate_1.isAggregateScope)(context) && resultNode && !resultNode.isValidAggregate; if (hasError) { inference.errorList.push(new errors_1.AggregatedFunctionRequiredError(resultNode)); } } //# sourceMappingURL=json-parser.js.map