const FormulaError = require('./error');
const Collection = require('../grammar/type/collection');
const Types = {
NUMBER: 0,
ARRAY: 1,
BOOLEAN: 2,
STRING: 3,
RANGE_REF: 4, // can be 'A:C' or '1:4', not only 'A1:C3'
CELL_REF: 5,
COLLECTIONS: 6, // Unions of references
NUMBER_NO_BOOLEAN: 10,
};
const Factorials = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000, 355687428096000, 6402373705728000, 121645100408832000, 2432902008176640000, 51090942171709440000, 1124000727777607680000, 25852016738884976640000, 620448401733239439360000, 15511210043330985984000000, 403291461126605635584000000, 10888869450418352160768000000, 304888344611713860501504000000, 8841761993739701954543616000000, 265252859812191058636308480000000, 8222838654177922817725562880000000, 263130836933693530167218012160000000, 8683317618811886495518194401280000000, 295232799039604140847618609643520000000, 10333147966386144929666651337523200000000, 371993326789901217467999448150835200000000, 13763753091226345046315979581580902400000000, 523022617466601111760007224100074291200000000, 20397882081197443358640281739902897356800000000, 815915283247897734345611269596115894272000000000, 33452526613163807108170062053440751665152000000000, 1405006117752879898543142606244511569936384000000000, 60415263063373835637355132068513997507264512000000000, 2658271574788448768043625811014615890319638528000000000, 119622220865480194561963161495657715064383733760000000000, 5502622159812088949850305428800254892961651752960000000000, 258623241511168180642964355153611979969197632389120000000000, 12413915592536072670862289047373375038521486354677760000000000, 608281864034267560872252163321295376887552831379210240000000000, 30414093201713378043612608166064768844377641568960512000000000000, 1551118753287382280224243016469303211063259720016986112000000000000, 80658175170943878571660636856403766975289505440883277824000000000000, 4274883284060025564298013753389399649690343788366813724672000000000000, 230843697339241380472092742683027581083278564571807941132288000000000000, 12696403353658275925965100847566516959580321051449436762275840000000000000, 710998587804863451854045647463724949736497978881168458687447040000000000000, 40526919504877216755680601905432322134980384796226602145184481280000000000000, 2350561331282878571829474910515074683828862318181142924420699914240000000000000, 138683118545689835737939019720389406345902876772687432540821294940160000000000000, 8320987112741390144276341183223364380754172606361245952449277696409600000000000000, 507580213877224798800856812176625227226004528988036003099405939480985600000000000000, 31469973260387937525653122354950764088012280797258232192163168247821107200000000000000, 1982608315404440064116146708361898137544773690227268628106279599612729753600000000000000, 126886932185884164103433389335161480802865516174545192198801894375214704230400000000000000, 8247650592082470666723170306785496252186258551345437492922123134388955774976000000000000000, 544344939077443064003729240247842752644293064388798874532860126869671081148416000000000000000, 36471110918188685288249859096605464427167635314049524593701628500267962436943872000000000000000, 2480035542436830599600990418569171581047399201355367672371710738018221445712183296000000000000000, 171122452428141311372468338881272839092270544893520369393648040923257279754140647424000000000000000, 11978571669969891796072783721689098736458938142546425857555362864628009582789845319680000000000000000, 850478588567862317521167644239926010288584608120796235886430763388588680378079017697280000000000000000, 61234458376886086861524070385274672740778091784697328983823014963978384987221689274204160000000000000000, 4470115461512684340891257138125051110076800700282905015819080092370422104067183317016903680000000000000000, 330788544151938641225953028221253782145683251820934971170611926835411235700971565459250872320000000000000000, 24809140811395398091946477116594033660926243886570122837795894512655842677572867409443815424000000000000000000, 1885494701666050254987932260861146558230394535379329335672487982961844043495537923117729972224000000000000000000, 145183092028285869634070784086308284983740379224208358846781574688061991349156420080065207861248000000000000000000, 11324281178206297831457521158732046228731749579488251990048962825668835325234200766245086213177344000000000000000000, 894618213078297528685144171539831652069808216779571907213868063227837990693501860533361810841010176000000000000000000, 71569457046263802294811533723186532165584657342365752577109445058227039255480148842668944867280814080000000000000000000, 5797126020747367985879734231578109105412357244731625958745865049716390179693892056256184534249745940480000000000000000000, 475364333701284174842138206989404946643813294067993328617160934076743994734899148613007131808479167119360000000000000000000, 39455239697206586511897471180120610571436503407643446275224357528369751562996629334879591940103770870906880000000000000000000, 3314240134565353266999387579130131288000666286242049487118846032383059131291716864129885722968716753156177920000000000000000000, 281710411438055027694947944226061159480056634330574206405101912752560026159795933451040286452340924018275123200000000000000000000, 24227095383672732381765523203441259715284870552429381750838764496720162249742450276789464634901319465571660595200000000000000000000, 2107757298379527717213600518699389595229783738061356212322972511214654115727593174080683423236414793504734471782400000000000000000000, 185482642257398439114796845645546284380220968949399346684421580986889562184028199319100141244804501828416633516851200000000000000000000, 16507955160908461081216919262453619309839666236496541854913520707833171034378509739399912570787600662729080382999756800000000000000000000, 1485715964481761497309522733620825737885569961284688766942216863704985393094065876545992131370884059645617234469978112000000000000000000000, 135200152767840296255166568759495142147586866476906677791741734597153670771559994765685283954750449427751168336768008192000000000000000000000, 12438414054641307255475324325873553077577991715875414356840239582938137710983519518443046123837041347353107486982656753664000000000000000000000, 1156772507081641574759205162306240436214753229576413535186142281213246807121467315215203289516844845303838996289387078090752000000000000000000000, 108736615665674308027365285256786601004186803580182872307497374434045199869417927630229109214583415458560865651202385340530688000000000000000000000, 10329978488239059262599702099394727095397746340117372869212250571234293987594703124871765375385424468563282236864226607350415360000000000000000000000, 991677934870949689209571401541893801158183648651267795444376054838492222809091499987689476037000748982075094738965754305639874560000000000000000000000, 96192759682482119853328425949563698712343813919172976158104477319333745612481875498805879175589072651261284189679678167647067832320000000000000000000000, 9426890448883247745626185743057242473809693764078951663494238777294707070023223798882976159207729119823605850588608460429412647567360000000000000000000000, 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000, 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000];
const ReversedTypes = {};
Object.keys(Types).forEach((key) => {
ReversedTypes[Types[key]] = key;
});
/**
* Formula helpers.
*/
class FormulaHelpers {
constructor() {
this.Types = Types;
this.type2Number = {
number: Types.NUMBER,
boolean: Types.BOOLEAN,
string: Types.STRING,
object: -1
};
}
checkFunctionResult(result) {
const type = typeof result;
// number
if (type === 'number') {
if (isNaN(result)) {
return FormulaError.VALUE;
} else if (!isFinite(result)) {
return FormulaError.NUM;
}
}
if (result === undefined || result === null)
return FormulaError.NULL;
return result;
}
/**
* Flatten an array
* @param {Array} arr1
* @returns {*}
*/
flattenDeep(arr1) {
return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(this.flattenDeep(val)) : acc.concat(val), []);
}
/**
*
* @param obj
* @param isArray - if it is an array: [1,2,3], will extract the first element
* @param allowBoolean - Allow parse boolean into number
* @returns {number|FormulaError}
*/
acceptNumber(obj, isArray = true, allowBoolean = true) {
// check error
if (obj instanceof FormulaError)
return obj;
let number;
if (typeof obj === 'number')
number = obj;
// TRUE -> 1, FALSE -> 0
else if (typeof obj === 'boolean') {
if (allowBoolean) {
number = Number(obj);
} else {
throw FormulaError.VALUE;
}
}
// "123" -> 123
else if (typeof obj === 'string') {
if (obj.length === 0) {
throw FormulaError.VALUE;
}
number = Number(obj);
// Note: the unique never-equal-to-itself characteristic of NaN
if (number !== number) {
throw FormulaError.VALUE;
}
} else if (Array.isArray(obj)) {
if (!isArray) {
// for range ref, only allow single column range ref
if (obj[0].length === 1) {
number = this.acceptNumber(obj[0][0]);
} else {
throw FormulaError.VALUE;
}
} else {
number = this.acceptNumber(obj[0][0]);
}
} else {
throw Error('Unknown type in FormulaHelpers.acceptNumber')
}
return number;
}
/**
* Flatten parameters to 1D array.
* @see {@link FormulaHelpers.accept}
* @param {Array} params - Parameter that needs to flatten.
* @param {Types|null} valueType - The type each item should be,
* null if allows any type. This only applies to literals.
* @param {boolean} allowUnion - Allow union, e.g. (A1:C1, E4:F3)
* @param {function} hook - Invoked after parsing each item.
* of the array.
* @param {*} [defValue=null] - The value if an param is omitted. i.e. SUM(1,2,,,,,)
* @param {number} [minSize=1] - The minimum size of the parameters
*/
flattenParams(params, valueType, allowUnion, hook, defValue = null, minSize = 1) {
if (params.length < minSize)
throw FormulaError.ARG_MISSING([valueType]);
if (defValue == null) {
defValue = valueType === Types.NUMBER ? 0 : valueType == null ? null : '';
}
params.forEach(param => {
const {isCellRef, isRangeRef, isArray} = param;
const isUnion = param.value instanceof Collection;
const isLiteral = !isCellRef && !isRangeRef && !isArray && !isUnion;
const info = {isLiteral, isCellRef, isRangeRef, isArray, isUnion};
// single element
if (isLiteral) {
if (param.omitted)
param = defValue;
else
param = this.accept(param, valueType, defValue);
hook(param, info);
}
// cell reference of single range reference (A1:A1)
else if (isCellRef) {
hook(param.value, info);
}
// union
else if (isUnion) {
if (!allowUnion) throw FormulaError.VALUE;
param = param.value.data;
param = this.flattenDeep(param);
param.forEach(item => {
hook(item, info);
})
} else if (isRangeRef || isArray) {
param = this.flattenDeep(param.value);
param.forEach(item => {
hook(item, info);
})
}
});
}
/**
* Check if the param valid, return the parsed param.
* If type is not given, return the un-parsed param.
* @param {*} param
* @param {number|null} [type] - The expected type
* NUMBER: Expect a single number,
* ARRAY: Expect an flatten array,
* BOOLEAN: Expect a single boolean,
* STRING: Expect a single string,
* COLLECTIONS: Expect an Array of the above types
* null: Do not parse the value, return it directly.
* The collection is not a flatted array.
* @param {*} [defValue] - Default value if the param is not given.
* if undefined, this param is required, a Error will throw if not given.
* if null, and param is undefined, null will be returned.
* @param {boolean} [flat=true] - If the array should be flattened,
* only applicable when type is ARRAY.
* If false, collection is disallowed.
* @param {boolean} allowSingleValue - If pack single value into 2d array,
* only applicable when type is ARRAY.
* @return {string|number|boolean|{}|Array}
*/
accept(param, type = null, defValue, flat = true, allowSingleValue = false) {
// TODO: remove this array check
if (Array.isArray(type))
type = type[0];
if (param == null && defValue === undefined) {
throw FormulaError.ARG_MISSING([type]);
} else if (param == null)
return defValue;
if (typeof param !== "object" || Array.isArray(param))
return param;
const isArray = param.isArray;
if (param.value != null) param = param.value;
// return an un-parsed type.
if (type == null)
return param;
if (param instanceof FormulaError)
throw param;
if (type === Types.ARRAY) {
if (Array.isArray(param)) {
return flat ? this.flattenDeep(param) : param;
} else if (param instanceof Collection) {
throw FormulaError.VALUE;
} else if (allowSingleValue) {
return flat ? [param] : [[param]];
}
throw FormulaError.VALUE;
} else if (type === Types.COLLECTIONS) {
return param;
}
// the only possible type for expectSingle=true are: string, boolean, number;
// If array encountered, extract the first element.
// extract first element from array
if (isArray) {
param = param[0][0];
}
const paramType = this.type(param);
if (type === Types.STRING) {
if (paramType === Types.BOOLEAN)
param = param ? 'TRUE' : 'FALSE';
else
param = `${param}`
} else if (type === Types.BOOLEAN) {
if (paramType === Types.STRING)
throw FormulaError.VALUE;
if (paramType === Types.NUMBER)
param = Boolean(param);
} else if (type === Types.NUMBER) {
param = this.acceptNumber(param, false);
} else if (type === Types.NUMBER_NO_BOOLEAN) {
param = this.acceptNumber(param, false, false);
} else {
throw FormulaError.VALUE;
}
return param;
}
type(variable) {
let type = this.type2Number[typeof variable];
if (type === -1) {
if (Array.isArray(variable))
type = Types.ARRAY;
else if (variable.ref) {
if (variable.ref.from) {
type = Types.RANGE_REF;
} else {
type = Types.CELL_REF;
}
} else if (variable instanceof Collection)
type = Types.COLLECTIONS;
}
return type;
}
isRangeRef(param) {
return param.ref && param.ref.from;
}
isCellRef(param) {
return param.ref && !param.ref.from;
}
/**
* Helper function for SUMIF, AVERAGEIF,...
* @param context
* @param range1
* @param range2
*/
retrieveRanges(context, range1, range2) {
// process args
range2 = Address.extend(range1, range2);
// retrieve values
range1 = this.retrieveArg(context, range1);
range1 = H.accept(range1, Types.ARRAY, undefined, false, true);
if (range2 !== range1) {
range2 = this.retrieveArg(context, range2);
range2 = H.accept(range2, Types.ARRAY, undefined, false, true);
} else
range2 = range1;
return [range1, range2];
}
retrieveArg(context, arg) {
if (arg === null)
return {value: 0, isArray: false, omitted: true};
const res = context.utils.extractRefValue(arg);
return {value: res.val, isArray: res.isArray, ref: arg.ref};
}
}
const H = new FormulaHelpers();
const WildCard = {
/**
* @param {string|*} obj
* @returns {*}
*/
isWildCard: obj => {
if (typeof obj === "string")
return /[*?]/.test(obj);
return false;
},
toRegex: (lookupText, flags) => {
return RegExp(lookupText.replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape the special char for js regex
.replace(/([^~]??)[?]/g, '$1.') // ? => .
.replace(/([^~]??)[*]/g, '$1.*') // * => .*
.replace(/~([?*])/g, '$1'), flags); // ~* => * and ~? => ?
}
};
const Criteria = {
/**
* Parse criteria, support comparison and wild card match.
* @param {string|number} criteria
* @return {{op: string, value: string|number|boolean|RegExp, match: boolean|undefined}} - The parsed criteria.
*/
parse: (criteria) => {
const type = typeof criteria;
if (type === "string") {
// criteria = 'TRUE' or 'FALSE'
const upper = criteria.toUpperCase();
if (upper === 'TRUE' || upper === 'FALSE') {
// excel boolean
return {op: '=', value: upper === 'TRUE'};
}
const res = criteria.match(/(<>|>=|<=|>|<|=)(.*)/);
// is comparison
if (res) {
// [">10", ">", "10", index: 0, input: ">10", groups: undefined]
let op = res[1], value;
// string or boolean or error
if (isNaN(res[2])) {
const upper = res[2].toUpperCase();
if (upper === 'TRUE' || upper === 'FALSE') {
// excel boolean
value = upper === 'TRUE';
} else if (/#NULL!|#DIV\/0!|#VALUE!|#NAME\?|#NUM!|#N\/A|#REF!/.test(res[2])) {
// formula error
value = new FormulaError(res[2]);
} else {
// string, can be wildcard
value = res[2];
if (WildCard.isWildCard(value)) {
return {op: 'wc', value: WildCard.toRegex(value), match: op === '='}
}
}
} else {
// number
value = Number(res[2])
}
return {op, value};
} else if (WildCard.isWildCard(criteria)) {
return {op: 'wc', value: WildCard.toRegex(criteria), match: true}
} else {
return {op: '=', value: criteria}
}
} else if (type === "boolean" || type === 'number' || (Array.isArray(criteria)
|| criteria instanceof FormulaError)) {
return {op: '=', value: criteria}
} else {
throw Error(`Criteria.parse: type ${typeof criteria} not support`)
}
}
};
const Address = {
columnNumberToName: (number) => {
let dividend = number;
let name = '';
let modulo = 0;
while (dividend > 0) {
modulo = (dividend - 1) % 26;
name = String.fromCharCode('A'.charCodeAt(0) + modulo) + name;
dividend = Math.floor((dividend - modulo) / 26);
}
return name;
},
columnNameToNumber: (columnName) => {
columnName = columnName.toUpperCase();
const len = columnName.length;
let number = 0;
for (let i = 0; i < len; i++) {
const code = columnName.charCodeAt(i);
if (!isNaN(code)) {
number += (code - 64) * 26 ** (len - i - 1)
}
}
return number;
},
/**
* Extend range2 to match with the dimension in range1.
* @param {{ref: {}}} range1
* @param {{ref: {}}} [range2]
*/
extend: (range1, range2) => {
if (range2 == null) {
return range1;
}
let rowOffset, colOffset;
if (H.isCellRef(range1)) {
rowOffset = 0;
colOffset = 0;
} else if (H.isRangeRef(range1)) {
rowOffset = range1.ref.to.row - range1.ref.from.row;
colOffset = range1.ref.to.col - range1.ref.from.col;
} else throw Error('Address.extend should not reach here.');
// if range2 is a cell reference
if (H.isCellRef(range2)) {
if (rowOffset > 0 || colOffset > 0)
range2 = {
ref: {
from: {col: range2.ref.col, row: range2.ref.row},
to: {row: range2.ref.row + rowOffset, col: range2.ref.col + colOffset}
}
};
} else {
// range2 is a range reference
range2.ref.to.row = range2.ref.from.row + rowOffset;
range2.ref.to.col = range2.ref.from.col + colOffset;
}
return range2;
},
};
module.exports = {
FormulaHelpers: H,
Types,
ReversedTypes,
Factorials,
WildCard,
Criteria,
Address,
};