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