@rowsncolumns/fast-formula-parser 6.2.40 → 6.2.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -10953,6 +10953,66 @@ const Criteria = Object.freeze({
10953
10953
  return false;
10954
10954
  }
10955
10955
  },
10956
+
10957
+ /**
10958
+ * Excel's array lift for the criteria slot of the criteria aggregates. When
10959
+ * any accepted criterion value is a multi-cell array or range
10960
+ * (`SUMIF(rng,{"x","y"},sum)`, `SUMIFS(sum,rng,"x",rng2,{">1",">3"})`,
10961
+ * `COUNTIF(rng,rng)`), `evaluate` runs once per broadcast element with that
10962
+ * element parsed as a scalar criterion and the results come back as a 2-D
10963
+ * array — so `SUM(COUNTIF(rng,{…}))` / `SUMPRODUCT(SUMIF(…,{…}))` fold it and a
10964
+ * per-element error (`#DIV/0!` from an empty AVERAGEIF slot) stays in its
10965
+ * slot. Vectors broadcast along a unit dimension; a slot a shorter vector
10966
+ * does not reach is `#N/A`. A 1×1 array is a scalar. With no array criterion
10967
+ * `evaluate` runs exactly once with the parsed scalar criteria.
10968
+ * @param {Array} criteriaValues accepted (unparsed) criterion values
10969
+ * @param {(parsed: Array) => *} evaluate
10970
+ */
10971
+ lift: (criteriaValues, evaluate) => {
10972
+ const shapes = criteriaValues.map((v) => {
10973
+ if (!Array.isArray(v)) return null;
10974
+ const shape = [v.length, v[0]?.length ?? 0];
10975
+ return shape[0] === 1 && shape[1] === 1 ? null : shape;
10976
+ });
10977
+ const scalarOf = (v) => (Array.isArray(v) ? v[0][0] : v);
10978
+ if (shapes.every((shape) => shape === null)) {
10979
+ return evaluate(criteriaValues.map((v) => Criteria.parse(scalarOf(v))));
10980
+ }
10981
+ let rows = 1;
10982
+ let cols = 1;
10983
+ for (const shape of shapes) {
10984
+ if (!shape) continue;
10985
+ if (shape[0] !== 1) rows = rows === 1 ? shape[0] : Math.max(rows, shape[0]);
10986
+ if (shape[1] !== 1) cols = cols === 1 ? shape[1] : Math.max(cols, shape[1]);
10987
+ }
10988
+ const MISSING = Symbol("missing");
10989
+ const pick = (v, shape, i, j) => {
10990
+ const r = shape[0] === 1 ? 0 : i;
10991
+ const c = shape[1] === 1 ? 0 : j;
10992
+ return r < v.length && c < v[r].length ? v[r][c] : MISSING;
10993
+ };
10994
+ const out = [];
10995
+ for (let i = 0; i < rows; i++) {
10996
+ const row = [];
10997
+ for (let j = 0; j < cols; j++) {
10998
+ const elements = criteriaValues.map((v, k) =>
10999
+ shapes[k] ? pick(v, shapes[k], i, j) : scalarOf(v),
11000
+ );
11001
+ if (elements.includes(MISSING)) {
11002
+ row.push(FormulaError.NA);
11003
+ continue;
11004
+ }
11005
+ try {
11006
+ row.push(evaluate(elements.map((e) => Criteria.parse(e))));
11007
+ } catch (e) {
11008
+ if (e instanceof FormulaError) row.push(e);
11009
+ else throw e;
11010
+ }
11011
+ }
11012
+ out.push(row);
11013
+ }
11014
+ return out;
11015
+ },
10956
11016
  });
10957
11017
 
10958
11018
  const Address = {
@@ -20678,23 +20738,26 @@ const StatisticalFunctions = {
20678
20738
  range = ranges[0];
20679
20739
  averageRange = ranges[1];
20680
20740
 
20681
- criteria = Criteria.parse(H$7.accept(H$7.retrieveArg(context, criteria)));
20682
-
20683
- let sum = 0,
20684
- cnt = 0;
20685
- range.forEach((row, rowNum) => {
20686
- row.forEach((value, colNum) => {
20687
- const valueToAdd = averageRange[rowNum][colNum];
20688
- throwIfFormulaError(valueToAdd);
20689
- if (typeof valueToAdd !== "number") return;
20690
- if (Criteria.matches(criteria, value)) {
20691
- sum += valueToAdd;
20692
- cnt++;
20693
- }
20741
+ const criteriaValue = H$7.accept(H$7.retrieveArg(context, criteria));
20742
+
20743
+ const averageMatching = (parsed) => {
20744
+ let sum = 0,
20745
+ cnt = 0;
20746
+ range.forEach((row, rowNum) => {
20747
+ row.forEach((value, colNum) => {
20748
+ const valueToAdd = averageRange[rowNum][colNum];
20749
+ throwIfFormulaError(valueToAdd);
20750
+ if (typeof valueToAdd !== "number") return;
20751
+ if (Criteria.matches(parsed, value)) {
20752
+ sum += valueToAdd;
20753
+ cnt++;
20754
+ }
20755
+ });
20694
20756
  });
20695
- });
20696
- if (cnt === 0) throw FormulaError.DIV0;
20697
- return sum / cnt;
20757
+ if (cnt === 0) throw FormulaError.DIV0;
20758
+ return sum / cnt;
20759
+ };
20760
+ return Criteria.lift([criteriaValue], ([parsed]) => averageMatching(parsed));
20698
20761
  },
20699
20762
 
20700
20763
  /**
@@ -20724,36 +20787,42 @@ const StatisticalFunctions = {
20724
20787
  assertSameShape(averageRange, criteriaRange);
20725
20788
  criteriaPairs.push({
20726
20789
  range: criteriaRange,
20727
- criteria: Criteria.parse(H$7.accept(H$7.retrieveArg(context, args[i + 1]))),
20790
+ criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
20728
20791
  });
20729
20792
  }
20730
20793
 
20731
- let sum = 0;
20732
- let count = 0;
20794
+ const averageMatching = (parsedCriteria) => {
20795
+ let sum = 0;
20796
+ let count = 0;
20733
20797
 
20734
- averageRange.forEach((row, rowNum) => {
20735
- row.forEach((valueToAdd, colNum) => {
20736
- throwIfFormulaError(valueToAdd);
20737
- if (typeof valueToAdd !== "number") {
20738
- return;
20739
- }
20798
+ averageRange.forEach((row, rowNum) => {
20799
+ row.forEach((valueToAdd, colNum) => {
20800
+ throwIfFormulaError(valueToAdd);
20801
+ if (typeof valueToAdd !== "number") {
20802
+ return;
20803
+ }
20740
20804
 
20741
- const meetsAllCriteria = criteriaPairs.every(({ range, criteria }) =>
20742
- Criteria.matches(criteria, range[rowNum][colNum]),
20743
- );
20805
+ const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
20806
+ Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
20807
+ );
20744
20808
 
20745
- if (meetsAllCriteria) {
20746
- sum += valueToAdd;
20747
- count++;
20748
- }
20809
+ if (meetsAllCriteria) {
20810
+ sum += valueToAdd;
20811
+ count++;
20812
+ }
20813
+ });
20749
20814
  });
20750
- });
20751
20815
 
20752
- if (count === 0) {
20753
- throw FormulaError.DIV0;
20754
- }
20816
+ if (count === 0) {
20817
+ throw FormulaError.DIV0;
20818
+ }
20755
20819
 
20756
- return sum / count;
20820
+ return sum / count;
20821
+ };
20822
+ return Criteria.lift(
20823
+ criteriaPairs.map((pair) => pair.criteria),
20824
+ averageMatching,
20825
+ );
20757
20826
  },
20758
20827
 
20759
20828
  LARGE: (array, kth) => {
@@ -20825,34 +20894,40 @@ const StatisticalFunctions = {
20825
20894
  assertSameShape(maxRange, criteriaRange);
20826
20895
  criteriaPairs.push({
20827
20896
  range: criteriaRange,
20828
- criteria: Criteria.parse(H$7.accept(H$7.retrieveArg(context, args[i + 1]))),
20897
+ criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
20829
20898
  });
20830
20899
  }
20831
20900
 
20832
- let max = Number.NEGATIVE_INFINITY;
20833
- let matchFound = false;
20901
+ const maxMatching = (parsedCriteria) => {
20902
+ let max = Number.NEGATIVE_INFINITY;
20903
+ let matchFound = false;
20834
20904
 
20835
- maxRange.forEach((row, rowNum) => {
20836
- row.forEach((valueToAdd, colNum) => {
20837
- throwIfFormulaError(valueToAdd);
20838
- if (typeof valueToAdd !== "number") {
20839
- return;
20840
- }
20905
+ maxRange.forEach((row, rowNum) => {
20906
+ row.forEach((valueToAdd, colNum) => {
20907
+ throwIfFormulaError(valueToAdd);
20908
+ if (typeof valueToAdd !== "number") {
20909
+ return;
20910
+ }
20841
20911
 
20842
- const meetsAllCriteria = criteriaPairs.every(({ range, criteria }) =>
20843
- Criteria.matches(criteria, range[rowNum][colNum]),
20844
- );
20912
+ const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
20913
+ Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
20914
+ );
20845
20915
 
20846
- if (meetsAllCriteria) {
20847
- if (valueToAdd > max) {
20848
- max = valueToAdd;
20916
+ if (meetsAllCriteria) {
20917
+ if (valueToAdd > max) {
20918
+ max = valueToAdd;
20919
+ }
20920
+ matchFound = true;
20849
20921
  }
20850
- matchFound = true;
20851
- }
20922
+ });
20852
20923
  });
20853
- });
20854
20924
 
20855
- return matchFound ? max : 0;
20925
+ return matchFound ? max : 0;
20926
+ };
20927
+ return Criteria.lift(
20928
+ criteriaPairs.map((pair) => pair.criteria),
20929
+ maxMatching,
20930
+ );
20856
20931
  },
20857
20932
 
20858
20933
  MINA: (...params) => {
@@ -20903,34 +20978,40 @@ const StatisticalFunctions = {
20903
20978
  assertSameShape(minRange, criteriaRange);
20904
20979
  criteriaPairs.push({
20905
20980
  range: criteriaRange,
20906
- criteria: Criteria.parse(H$7.accept(H$7.retrieveArg(context, args[i + 1]))),
20981
+ criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
20907
20982
  });
20908
20983
  }
20909
20984
 
20910
- let min = Number.POSITIVE_INFINITY;
20911
- let matchFound = false;
20985
+ const minMatching = (parsedCriteria) => {
20986
+ let min = Number.POSITIVE_INFINITY;
20987
+ let matchFound = false;
20912
20988
 
20913
- minRange.forEach((row, rowNum) => {
20914
- row.forEach((valueToAdd, colNum) => {
20915
- throwIfFormulaError(valueToAdd);
20916
- if (typeof valueToAdd !== "number") {
20917
- return;
20918
- }
20989
+ minRange.forEach((row, rowNum) => {
20990
+ row.forEach((valueToAdd, colNum) => {
20991
+ throwIfFormulaError(valueToAdd);
20992
+ if (typeof valueToAdd !== "number") {
20993
+ return;
20994
+ }
20919
20995
 
20920
- const meetsAllCriteria = criteriaPairs.every(({ range, criteria }) =>
20921
- Criteria.matches(criteria, range[rowNum][colNum]),
20922
- );
20996
+ const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
20997
+ Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
20998
+ );
20923
20999
 
20924
- if (meetsAllCriteria) {
20925
- if (valueToAdd < min) {
20926
- min = valueToAdd;
21000
+ if (meetsAllCriteria) {
21001
+ if (valueToAdd < min) {
21002
+ min = valueToAdd;
21003
+ }
21004
+ matchFound = true;
20927
21005
  }
20928
- matchFound = true;
20929
- }
21006
+ });
20930
21007
  });
20931
- });
20932
21008
 
20933
- return matchFound ? min : 0;
21009
+ return matchFound ? min : 0;
21010
+ };
21011
+ return Criteria.lift(
21012
+ criteriaPairs.map((pair) => pair.criteria),
21013
+ minMatching,
21014
+ );
20934
21015
  },
20935
21016
 
20936
21017
  NPV: (rate, ...values) => {
@@ -23231,26 +23312,30 @@ const MathFunctions = {
23231
23312
  range = ranges[0];
23232
23313
  sumRange = ranges[1];
23233
23314
 
23234
- criteria = Criteria.parse(H$6.accept(H$6.retrieveArg(context, criteria)));
23235
-
23236
- let sum = 0;
23237
- const rowCount = range.length;
23238
- for (let r = 0; r < rowCount; r++) {
23239
- const row = range[r];
23240
- const sumRow = sumRange[r];
23241
- const colCount = row.length;
23242
- for (let c = 0; c < colCount; c++) {
23243
- const valueToAdd = sumRow[c];
23244
- if (valueToAdd instanceof FormulaError) {
23245
- throw valueToAdd;
23246
- }
23247
- if (typeof valueToAdd !== "number") continue;
23248
- if (Criteria.matches(criteria, row[c])) {
23249
- sum += valueToAdd;
23315
+ const criteriaValue = H$6.accept(H$6.retrieveArg(context, criteria));
23316
+
23317
+ const sumMatching = (parsed) => {
23318
+ let sum = 0;
23319
+ const rowCount = range.length;
23320
+ for (let r = 0; r < rowCount; r++) {
23321
+ const row = range[r];
23322
+ const sumRow = sumRange[r];
23323
+ const colCount = row.length;
23324
+ for (let c = 0; c < colCount; c++) {
23325
+ const valueToAdd = sumRow[c];
23326
+ if (valueToAdd instanceof FormulaError) {
23327
+ throw valueToAdd;
23328
+ }
23329
+ if (typeof valueToAdd !== "number") continue;
23330
+ if (Criteria.matches(parsed, row[c])) {
23331
+ sum += valueToAdd;
23332
+ }
23250
23333
  }
23251
23334
  }
23252
- }
23253
- return sum;
23335
+ return sum;
23336
+ };
23337
+ // An array criterion spills one total per element (SUMPRODUCT(SUMIF(rng,{…},sum))).
23338
+ return Criteria.lift([criteriaValue], ([parsed]) => sumMatching(parsed));
23254
23339
  },
23255
23340
 
23256
23341
  /**
@@ -23285,37 +23370,43 @@ const MathFunctions = {
23285
23370
  }
23286
23371
  criteriaPairs.push({
23287
23372
  range: criteriaRange,
23288
- criteria: Criteria.parse(H$6.accept(H$6.retrieveArg(context, args[i + 1]))),
23373
+ criteria: H$6.accept(H$6.retrieveArg(context, args[i + 1])),
23289
23374
  });
23290
23375
  }
23291
23376
 
23292
- let sum = 0;
23293
- const rowCount = sumRange.length;
23294
- for (let r = 0; r < rowCount; r++) {
23295
- const row = sumRange[r];
23296
- const colCount = row.length;
23297
- for (let c = 0; c < colCount; c++) {
23298
- const valueToAdd = row[c];
23299
- if (valueToAdd instanceof FormulaError) {
23300
- throw valueToAdd;
23301
- }
23302
- if (typeof valueToAdd !== "number") {
23303
- continue;
23304
- }
23305
- let allCriteriaMet = true;
23306
- for (let i = 0; i < criteriaPairs.length; i++) {
23307
- const { range, criteria } = criteriaPairs[i];
23308
- if (!Criteria.matches(criteria, range[r][c])) {
23309
- allCriteriaMet = false;
23310
- break;
23377
+ const sumMatching = (parsedCriteria) => {
23378
+ let sum = 0;
23379
+ const rowCount = sumRange.length;
23380
+ for (let r = 0; r < rowCount; r++) {
23381
+ const row = sumRange[r];
23382
+ const colCount = row.length;
23383
+ for (let c = 0; c < colCount; c++) {
23384
+ const valueToAdd = row[c];
23385
+ if (valueToAdd instanceof FormulaError) {
23386
+ throw valueToAdd;
23387
+ }
23388
+ if (typeof valueToAdd !== "number") {
23389
+ continue;
23390
+ }
23391
+ let allCriteriaMet = true;
23392
+ for (let i = 0; i < criteriaPairs.length; i++) {
23393
+ if (!Criteria.matches(parsedCriteria[i], criteriaPairs[i].range[r][c])) {
23394
+ allCriteriaMet = false;
23395
+ break;
23396
+ }
23397
+ }
23398
+ if (allCriteriaMet) {
23399
+ sum += valueToAdd;
23311
23400
  }
23312
- }
23313
- if (allCriteriaMet) {
23314
- sum += valueToAdd;
23315
23401
  }
23316
23402
  }
23317
- }
23318
- return sum;
23403
+ return sum;
23404
+ };
23405
+ // Array criteria spill one total per broadcast element.
23406
+ return Criteria.lift(
23407
+ criteriaPairs.map((pair) => pair.criteria),
23408
+ sumMatching,
23409
+ );
23319
23410
  },
23320
23411
 
23321
23412
  SUMPRODUCT: (array1, ...arrays) => {
@@ -23908,7 +23999,23 @@ const LogicalFunctions = {
23908
23999
  // never raw `undefined` (misreported as "Function IFNA is not
23909
24000
  // implemented.").
23910
24001
  const blankToZero = (v) => (v === undefined || v === null ? 0 : v);
23911
- return FormulaError.NA.equals(value.value)
24002
+ const isNa = (cell) => cell instanceof FormulaError && FormulaError.NA.equals(cell);
24003
+
24004
+ // Excel lifts IFNA element-wise over an array / multi-cell range value, like IFERROR:
24005
+ // only the #N/A elements take the fallback, every other value or error passes through
24006
+ // (`IFNA(K1:K3,0)` → {#DIV/0!;0;0}, `SUM(IFNA(rng,0))` sums the non-#N/A cells).
24007
+ const hasValueProp = value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'value');
24008
+ if (hasValueProp && value.isArray) {
24009
+ const arrayValue = value.value;
24010
+ const handleCell = (cell) => (isNa(cell) ? blankToZero(H$4.accept(valueIfNa)) : blankToZero(cell));
24011
+ if (Array.isArray(arrayValue)) {
24012
+ return arrayValue.map((row) => (Array.isArray(row) ? row.map(handleCell) : handleCell(row)));
24013
+ }
24014
+ return handleCell(arrayValue);
24015
+ }
24016
+
24017
+ const actualValue = hasValueProp ? value.value : value;
24018
+ return isNa(actualValue)
23912
24019
  ? blankToZero(H$4.accept(valueIfNa))
23913
24020
  : blankToZero(H$4.accept(value));
23914
24021
  },
@@ -50565,6 +50672,16 @@ class Parser extends EmbeddedActionsParser {
50565
50672
  ) {
50566
50673
  return arr[0][0];
50567
50674
  }
50675
+ // Excel refuses a ragged constant (`{1,2;3}`) at entry — every row must have the width
50676
+ // of the first. A parse-level `#ERROR!`, like any other syntax error the host stores as
50677
+ // the cell value (rowsncolumns/spreadsheet#939 G-02).
50678
+ const width = arr[0].length;
50679
+ const ragged = arr.findIndex((row) => row.length !== width);
50680
+ if (ragged !== -1) {
50681
+ throw FormulaError.ERROR(
50682
+ `Array constant rows must have the same length: row 1 has ${width} element(s), row ${ragged + 1} has ${arr[ragged].length}`
50683
+ );
50684
+ }
50568
50685
  const array = this.utils.toArray(arr);
50569
50686
  return context.async
50570
50687
  ? this.utils.settleArrayItems(array, $.position)
@@ -52984,8 +53101,13 @@ const normalizeNumberResult = (value, precision, epsilon, excelPrecision) => {
52984
53101
  * @param {*} value
52985
53102
  * @return {boolean}
52986
53103
  */
53104
+ // A name's definition text: `=…` (the OOXML `<definedName>` content), or a bare `LAMBDA(…)` —
53105
+ // the shape Excel's Name Manager and the host's "Text or Formula" names store a lambda in, which
53106
+ // the call path (`Dbl(21)`) already accepts without the leading `=` (spreadsheet#939 G-07).
53107
+ const BARE_LAMBDA_TEXT = /^(_xlfn\.)?LAMBDA\s*\(/i;
52987
53108
  const isNamedFormulaText = (value) =>
52988
- typeof value === "string" && value.trimStart().startsWith("=");
53109
+ typeof value === "string" &&
53110
+ (value.trimStart().startsWith("=") || BARE_LAMBDA_TEXT.test(value.trimStart()));
52989
53111
 
52990
53112
  class FormulaParser {
52991
53113
  /**
@@ -53354,7 +53476,8 @@ class FormulaParser {
53354
53476
  if (this.namedFormulaStack.has(key)) {
53355
53477
  return FormulaError.REF;
53356
53478
  }
53357
- const source = text.trim().slice(1).trim();
53479
+ const trimmed = text.trim();
53480
+ const source = (trimmed.startsWith("=") ? trimmed.slice(1) : trimmed).trim();
53358
53481
  if (source.length === 0) {
53359
53482
  return FormulaError.NAME;
53360
53483
  }
@@ -53363,12 +53486,18 @@ class FormulaParser {
53363
53486
  this.namedFormulaParser.namedFormulaStack = this.namedFormulaStack;
53364
53487
  }
53365
53488
  this.namedFormulaStack.add(key);
53489
+ // A name bound to a LAMBDA (`Dbl` = `=LAMBDA(x,x*2)`) is the lambda VALUE wherever the
53490
+ // name is read — `MAP(A1:A3,Dbl)`, `LET(f,Dbl,f(3))` — so the definition parser must hand
53491
+ // it back instead of turning it into `#CALC!`; the referencing cell's own
53492
+ // `checkFormulaResult` still makes a bare `=Dbl` `#CALC!` (spreadsheet#939 G-07).
53493
+ this.namedFormulaParser.keepLambdaResult = true;
53366
53494
  try {
53367
53495
  return this.namedFormulaParser.parse(source, this.position, true);
53368
53496
  } catch (e) {
53369
53497
  if (e instanceof FormulaError) return e;
53370
53498
  return FormulaError.NAME;
53371
53499
  } finally {
53500
+ this.namedFormulaParser.keepLambdaResult = false;
53372
53501
  this.namedFormulaStack.delete(key);
53373
53502
  }
53374
53503
  }