@qrvey/formula-lang
Version:
QFormula support for qrvey projects
242 lines • 11.1 kB
JavaScript
"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