@entestat/formula
Version:
fast excel formula parser
191 lines (169 loc) • 5.71 kB
JavaScript
const FormulaError = require('../formulas/error');
const {FormulaHelpers} = require('../formulas/helpers');
const {DateHelper} = require("../silk/DateHelper");
const Prefix = {
unaryOp: (prefixes, value, isArray) => {
let sign = 1;
prefixes.forEach(prefix => {
if (prefix === '+') {
} else if (prefix === '-') {
sign = -sign;
} else {
throw new Error(`Unrecognized prefix: ${prefix}`);
}
});
if (value == null) {
value = 0;
}
// positive means no changes
if (sign === 1) {
return value;
}
// negative
try {
value = FormulaHelpers.acceptNumber(value, isArray);
} catch (e) {
if (e instanceof FormulaError) {
// parse number fails
if (Array.isArray(value))
value = value[0][0]
} else
throw e;
}
if (typeof value === "number" && isNaN(value)) return FormulaError.VALUE;
return -value;
}
};
const Postfix = {
percentOp: (value, postfix, isArray) => {
try {
value = FormulaHelpers.acceptNumber(value, isArray);
} catch (e) {
if (e instanceof FormulaError)
return e;
throw e;
}
if (postfix === '%') {
return value / 100;
}
throw new Error(`Unrecognized postfix: ${postfix}`);
}
};
const type2Number = {'boolean': 3, 'string': 2, 'number': 1};
const Infix = {
compareOp: (value1, infix, value2, isArray1, isArray2) => {
if(DateHelper.isDate(value1) && DateHelper.isDate(value2)){
return DateHelper.dateCompareOp(value1, infix, value2);
}
if(DateHelper.isDate(value1) && DateHelper.isNullableDate(value2)){
return DateHelper.dateCompareOp(value1, infix, value2);
}
if(DateHelper.isDate(value2) && DateHelper.isNullableDate(value2)){
return DateHelper.dateCompareOp(value1, infix, value2);
}
if (value1 == null) value1 = 0;
if (value2 == null) value2 = 0;
// for array: {1,2,3}, get the first element to compare
if (isArray1) {
value1 = value1[0][0];
}
if (isArray2) {
value2 = value2[0][0];
}
const type1 = typeof value1, type2 = typeof value2;
if (type1 === type2) {
// same type comparison
switch (infix) {
case '=':
return value1 === value2;
case '>':
return value1 > value2;
case '<':
return value1 < value2;
case '<>':
return value1 !== value2;
case '<=':
return value1 <= value2;
case '>=':
return value1 >= value2;
}
} else {
switch (infix) {
case '=':
return false;
case '>':
return type2Number[type1] > type2Number[type2];
case '<':
return type2Number[type1] < type2Number[type2];
case '<>':
return true;
case '<=':
return type2Number[type1] <= type2Number[type2];
case '>=':
return type2Number[type1] >= type2Number[type2];
}
}
throw Error('Infix.compareOp: Should not reach here.');
},
concatOp: (value1, infix, value2, isArray1, isArray2) => {
if (value1 == null) value1 = '';
if (value2 == null) value2 = '';
// for array: {1,2,3}, get the first element to concat
if (isArray1) {
value1 = value1[0][0];
}
if (isArray2) {
value2 = value2[0][0];
}
const type1 = typeof value1, type2 = typeof value2;
// convert boolean to string
if (type1 === 'boolean')
value1 = value1 ? 'TRUE' : 'FALSE';
if (type2 === 'boolean')
value2 = value2 ? 'TRUE' : 'FALSE';
return '' + value1 + value2;
},
mathOp: (value1, infix, value2, isArray1, isArray2) => {
if (value1 == null) value1 = 0;
if (value2 == null) value2 = 0;
if((infix === "+" || infix === "-") && (
(DateHelper.isDate(value1) && FormulaHelpers.isNumber(value2)) ||
(FormulaHelpers.isNumber(value1) && DateHelper.isDate(value2))
)){
return DateHelper.dateOp(infix, value1, value2);
}
try {
value1 = FormulaHelpers.acceptNumber(value1, isArray1);
value2 = FormulaHelpers.acceptNumber(value2, isArray2);
} catch (e) {
if (e instanceof FormulaError)
return e;
throw e;
}
switch (infix) {
case '+':
return value1 + value2;
case '-':
return value1 - value2;
case '*':
return value1 * value2;
case '/':
if (value2 === 0)
return FormulaError.DIV0;
return value1 / value2;
case '^':
return value1 ** value2;
}
throw Error('Infix.mathOp: Should not reach here.');
},
};
module.exports = {
Prefix,
Postfix,
Infix,
Operators: {
compareOp: ['<', '>', '=', '<>', '<=', '>='],
concatOp: ['&'],
mathOp: ['+', '-', '*', '/', '^'],
}
};