@rowsncolumns/fast-formula-parser 6.2.41 → 6.2.44

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
@@ -5649,6 +5649,7 @@ const FORMULA_LOCALE_DATA = {
5649
5649
  "ISNUMBER": "ESTNUM",
5650
5650
  "ISO.CEILING": "ISO.PLAFOND",
5651
5651
  "ISODD": "EST.IMPAIR",
5652
+ "ISOMITTED": "ISOMITTED",
5652
5653
  "ISOWEEKNUM": "NO.SEMAINE.ISO",
5653
5654
  "ISPMT": "ISPMT",
5654
5655
  "ISREF": "ESTREF",
@@ -10953,6 +10954,66 @@ const Criteria = Object.freeze({
10953
10954
  return false;
10954
10955
  }
10955
10956
  },
10957
+
10958
+ /**
10959
+ * Excel's array lift for the criteria slot of the criteria aggregates. When
10960
+ * any accepted criterion value is a multi-cell array or range
10961
+ * (`SUMIF(rng,{"x","y"},sum)`, `SUMIFS(sum,rng,"x",rng2,{">1",">3"})`,
10962
+ * `COUNTIF(rng,rng)`), `evaluate` runs once per broadcast element with that
10963
+ * element parsed as a scalar criterion and the results come back as a 2-D
10964
+ * array — so `SUM(COUNTIF(rng,{…}))` / `SUMPRODUCT(SUMIF(…,{…}))` fold it and a
10965
+ * per-element error (`#DIV/0!` from an empty AVERAGEIF slot) stays in its
10966
+ * slot. Vectors broadcast along a unit dimension; a slot a shorter vector
10967
+ * does not reach is `#N/A`. A 1×1 array is a scalar. With no array criterion
10968
+ * `evaluate` runs exactly once with the parsed scalar criteria.
10969
+ * @param {Array} criteriaValues accepted (unparsed) criterion values
10970
+ * @param {(parsed: Array) => *} evaluate
10971
+ */
10972
+ lift: (criteriaValues, evaluate) => {
10973
+ const shapes = criteriaValues.map((v) => {
10974
+ if (!Array.isArray(v)) return null;
10975
+ const shape = [v.length, v[0]?.length ?? 0];
10976
+ return shape[0] === 1 && shape[1] === 1 ? null : shape;
10977
+ });
10978
+ const scalarOf = (v) => (Array.isArray(v) ? v[0][0] : v);
10979
+ if (shapes.every((shape) => shape === null)) {
10980
+ return evaluate(criteriaValues.map((v) => Criteria.parse(scalarOf(v))));
10981
+ }
10982
+ let rows = 1;
10983
+ let cols = 1;
10984
+ for (const shape of shapes) {
10985
+ if (!shape) continue;
10986
+ if (shape[0] !== 1) rows = rows === 1 ? shape[0] : Math.max(rows, shape[0]);
10987
+ if (shape[1] !== 1) cols = cols === 1 ? shape[1] : Math.max(cols, shape[1]);
10988
+ }
10989
+ const MISSING = Symbol("missing");
10990
+ const pick = (v, shape, i, j) => {
10991
+ const r = shape[0] === 1 ? 0 : i;
10992
+ const c = shape[1] === 1 ? 0 : j;
10993
+ return r < v.length && c < v[r].length ? v[r][c] : MISSING;
10994
+ };
10995
+ const out = [];
10996
+ for (let i = 0; i < rows; i++) {
10997
+ const row = [];
10998
+ for (let j = 0; j < cols; j++) {
10999
+ const elements = criteriaValues.map((v, k) =>
11000
+ shapes[k] ? pick(v, shapes[k], i, j) : scalarOf(v),
11001
+ );
11002
+ if (elements.includes(MISSING)) {
11003
+ row.push(FormulaError.NA);
11004
+ continue;
11005
+ }
11006
+ try {
11007
+ row.push(evaluate(elements.map((e) => Criteria.parse(e))));
11008
+ } catch (e) {
11009
+ if (e instanceof FormulaError) row.push(e);
11010
+ else throw e;
11011
+ }
11012
+ }
11013
+ out.push(row);
11014
+ }
11015
+ return out;
11016
+ },
10956
11017
  });
10957
11018
 
10958
11019
  const Address = {
@@ -20678,23 +20739,26 @@ const StatisticalFunctions = {
20678
20739
  range = ranges[0];
20679
20740
  averageRange = ranges[1];
20680
20741
 
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
- }
20742
+ const criteriaValue = H$7.accept(H$7.retrieveArg(context, criteria));
20743
+
20744
+ const averageMatching = (parsed) => {
20745
+ let sum = 0,
20746
+ cnt = 0;
20747
+ range.forEach((row, rowNum) => {
20748
+ row.forEach((value, colNum) => {
20749
+ const valueToAdd = averageRange[rowNum][colNum];
20750
+ throwIfFormulaError(valueToAdd);
20751
+ if (typeof valueToAdd !== "number") return;
20752
+ if (Criteria.matches(parsed, value)) {
20753
+ sum += valueToAdd;
20754
+ cnt++;
20755
+ }
20756
+ });
20694
20757
  });
20695
- });
20696
- if (cnt === 0) throw FormulaError.DIV0;
20697
- return sum / cnt;
20758
+ if (cnt === 0) throw FormulaError.DIV0;
20759
+ return sum / cnt;
20760
+ };
20761
+ return Criteria.lift([criteriaValue], ([parsed]) => averageMatching(parsed));
20698
20762
  },
20699
20763
 
20700
20764
  /**
@@ -20724,36 +20788,42 @@ const StatisticalFunctions = {
20724
20788
  assertSameShape(averageRange, criteriaRange);
20725
20789
  criteriaPairs.push({
20726
20790
  range: criteriaRange,
20727
- criteria: Criteria.parse(H$7.accept(H$7.retrieveArg(context, args[i + 1]))),
20791
+ criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
20728
20792
  });
20729
20793
  }
20730
20794
 
20731
- let sum = 0;
20732
- let count = 0;
20795
+ const averageMatching = (parsedCriteria) => {
20796
+ let sum = 0;
20797
+ let count = 0;
20733
20798
 
20734
- averageRange.forEach((row, rowNum) => {
20735
- row.forEach((valueToAdd, colNum) => {
20736
- throwIfFormulaError(valueToAdd);
20737
- if (typeof valueToAdd !== "number") {
20738
- return;
20739
- }
20799
+ averageRange.forEach((row, rowNum) => {
20800
+ row.forEach((valueToAdd, colNum) => {
20801
+ throwIfFormulaError(valueToAdd);
20802
+ if (typeof valueToAdd !== "number") {
20803
+ return;
20804
+ }
20740
20805
 
20741
- const meetsAllCriteria = criteriaPairs.every(({ range, criteria }) =>
20742
- Criteria.matches(criteria, range[rowNum][colNum]),
20743
- );
20806
+ const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
20807
+ Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
20808
+ );
20744
20809
 
20745
- if (meetsAllCriteria) {
20746
- sum += valueToAdd;
20747
- count++;
20748
- }
20810
+ if (meetsAllCriteria) {
20811
+ sum += valueToAdd;
20812
+ count++;
20813
+ }
20814
+ });
20749
20815
  });
20750
- });
20751
20816
 
20752
- if (count === 0) {
20753
- throw FormulaError.DIV0;
20754
- }
20817
+ if (count === 0) {
20818
+ throw FormulaError.DIV0;
20819
+ }
20755
20820
 
20756
- return sum / count;
20821
+ return sum / count;
20822
+ };
20823
+ return Criteria.lift(
20824
+ criteriaPairs.map((pair) => pair.criteria),
20825
+ averageMatching,
20826
+ );
20757
20827
  },
20758
20828
 
20759
20829
  LARGE: (array, kth) => {
@@ -20825,34 +20895,40 @@ const StatisticalFunctions = {
20825
20895
  assertSameShape(maxRange, criteriaRange);
20826
20896
  criteriaPairs.push({
20827
20897
  range: criteriaRange,
20828
- criteria: Criteria.parse(H$7.accept(H$7.retrieveArg(context, args[i + 1]))),
20898
+ criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
20829
20899
  });
20830
20900
  }
20831
20901
 
20832
- let max = Number.NEGATIVE_INFINITY;
20833
- let matchFound = false;
20902
+ const maxMatching = (parsedCriteria) => {
20903
+ let max = Number.NEGATIVE_INFINITY;
20904
+ let matchFound = false;
20834
20905
 
20835
- maxRange.forEach((row, rowNum) => {
20836
- row.forEach((valueToAdd, colNum) => {
20837
- throwIfFormulaError(valueToAdd);
20838
- if (typeof valueToAdd !== "number") {
20839
- return;
20840
- }
20906
+ maxRange.forEach((row, rowNum) => {
20907
+ row.forEach((valueToAdd, colNum) => {
20908
+ throwIfFormulaError(valueToAdd);
20909
+ if (typeof valueToAdd !== "number") {
20910
+ return;
20911
+ }
20841
20912
 
20842
- const meetsAllCriteria = criteriaPairs.every(({ range, criteria }) =>
20843
- Criteria.matches(criteria, range[rowNum][colNum]),
20844
- );
20913
+ const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
20914
+ Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
20915
+ );
20845
20916
 
20846
- if (meetsAllCriteria) {
20847
- if (valueToAdd > max) {
20848
- max = valueToAdd;
20917
+ if (meetsAllCriteria) {
20918
+ if (valueToAdd > max) {
20919
+ max = valueToAdd;
20920
+ }
20921
+ matchFound = true;
20849
20922
  }
20850
- matchFound = true;
20851
- }
20923
+ });
20852
20924
  });
20853
- });
20854
20925
 
20855
- return matchFound ? max : 0;
20926
+ return matchFound ? max : 0;
20927
+ };
20928
+ return Criteria.lift(
20929
+ criteriaPairs.map((pair) => pair.criteria),
20930
+ maxMatching,
20931
+ );
20856
20932
  },
20857
20933
 
20858
20934
  MINA: (...params) => {
@@ -20903,34 +20979,40 @@ const StatisticalFunctions = {
20903
20979
  assertSameShape(minRange, criteriaRange);
20904
20980
  criteriaPairs.push({
20905
20981
  range: criteriaRange,
20906
- criteria: Criteria.parse(H$7.accept(H$7.retrieveArg(context, args[i + 1]))),
20982
+ criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
20907
20983
  });
20908
20984
  }
20909
20985
 
20910
- let min = Number.POSITIVE_INFINITY;
20911
- let matchFound = false;
20986
+ const minMatching = (parsedCriteria) => {
20987
+ let min = Number.POSITIVE_INFINITY;
20988
+ let matchFound = false;
20912
20989
 
20913
- minRange.forEach((row, rowNum) => {
20914
- row.forEach((valueToAdd, colNum) => {
20915
- throwIfFormulaError(valueToAdd);
20916
- if (typeof valueToAdd !== "number") {
20917
- return;
20918
- }
20990
+ minRange.forEach((row, rowNum) => {
20991
+ row.forEach((valueToAdd, colNum) => {
20992
+ throwIfFormulaError(valueToAdd);
20993
+ if (typeof valueToAdd !== "number") {
20994
+ return;
20995
+ }
20919
20996
 
20920
- const meetsAllCriteria = criteriaPairs.every(({ range, criteria }) =>
20921
- Criteria.matches(criteria, range[rowNum][colNum]),
20922
- );
20997
+ const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
20998
+ Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
20999
+ );
20923
21000
 
20924
- if (meetsAllCriteria) {
20925
- if (valueToAdd < min) {
20926
- min = valueToAdd;
21001
+ if (meetsAllCriteria) {
21002
+ if (valueToAdd < min) {
21003
+ min = valueToAdd;
21004
+ }
21005
+ matchFound = true;
20927
21006
  }
20928
- matchFound = true;
20929
- }
21007
+ });
20930
21008
  });
20931
- });
20932
21009
 
20933
- return matchFound ? min : 0;
21010
+ return matchFound ? min : 0;
21011
+ };
21012
+ return Criteria.lift(
21013
+ criteriaPairs.map((pair) => pair.criteria),
21014
+ minMatching,
21015
+ );
20934
21016
  },
20935
21017
 
20936
21018
  NPV: (rate, ...values) => {
@@ -23231,26 +23313,30 @@ const MathFunctions = {
23231
23313
  range = ranges[0];
23232
23314
  sumRange = ranges[1];
23233
23315
 
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;
23316
+ const criteriaValue = H$6.accept(H$6.retrieveArg(context, criteria));
23317
+
23318
+ const sumMatching = (parsed) => {
23319
+ let sum = 0;
23320
+ const rowCount = range.length;
23321
+ for (let r = 0; r < rowCount; r++) {
23322
+ const row = range[r];
23323
+ const sumRow = sumRange[r];
23324
+ const colCount = row.length;
23325
+ for (let c = 0; c < colCount; c++) {
23326
+ const valueToAdd = sumRow[c];
23327
+ if (valueToAdd instanceof FormulaError) {
23328
+ throw valueToAdd;
23329
+ }
23330
+ if (typeof valueToAdd !== "number") continue;
23331
+ if (Criteria.matches(parsed, row[c])) {
23332
+ sum += valueToAdd;
23333
+ }
23250
23334
  }
23251
23335
  }
23252
- }
23253
- return sum;
23336
+ return sum;
23337
+ };
23338
+ // An array criterion spills one total per element (SUMPRODUCT(SUMIF(rng,{…},sum))).
23339
+ return Criteria.lift([criteriaValue], ([parsed]) => sumMatching(parsed));
23254
23340
  },
23255
23341
 
23256
23342
  /**
@@ -23285,37 +23371,43 @@ const MathFunctions = {
23285
23371
  }
23286
23372
  criteriaPairs.push({
23287
23373
  range: criteriaRange,
23288
- criteria: Criteria.parse(H$6.accept(H$6.retrieveArg(context, args[i + 1]))),
23374
+ criteria: H$6.accept(H$6.retrieveArg(context, args[i + 1])),
23289
23375
  });
23290
23376
  }
23291
23377
 
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;
23378
+ const sumMatching = (parsedCriteria) => {
23379
+ let sum = 0;
23380
+ const rowCount = sumRange.length;
23381
+ for (let r = 0; r < rowCount; r++) {
23382
+ const row = sumRange[r];
23383
+ const colCount = row.length;
23384
+ for (let c = 0; c < colCount; c++) {
23385
+ const valueToAdd = row[c];
23386
+ if (valueToAdd instanceof FormulaError) {
23387
+ throw valueToAdd;
23388
+ }
23389
+ if (typeof valueToAdd !== "number") {
23390
+ continue;
23391
+ }
23392
+ let allCriteriaMet = true;
23393
+ for (let i = 0; i < criteriaPairs.length; i++) {
23394
+ if (!Criteria.matches(parsedCriteria[i], criteriaPairs[i].range[r][c])) {
23395
+ allCriteriaMet = false;
23396
+ break;
23397
+ }
23398
+ }
23399
+ if (allCriteriaMet) {
23400
+ sum += valueToAdd;
23311
23401
  }
23312
- }
23313
- if (allCriteriaMet) {
23314
- sum += valueToAdd;
23315
23402
  }
23316
23403
  }
23317
- }
23318
- return sum;
23404
+ return sum;
23405
+ };
23406
+ // Array criteria spill one total per broadcast element.
23407
+ return Criteria.lift(
23408
+ criteriaPairs.map((pair) => pair.criteria),
23409
+ sumMatching,
23410
+ );
23319
23411
  },
23320
23412
 
23321
23413
  SUMPRODUCT: (array1, ...arrays) => {
@@ -23908,7 +24000,23 @@ const LogicalFunctions = {
23908
24000
  // never raw `undefined` (misreported as "Function IFNA is not
23909
24001
  // implemented.").
23910
24002
  const blankToZero = (v) => (v === undefined || v === null ? 0 : v);
23911
- return FormulaError.NA.equals(value.value)
24003
+ const isNa = (cell) => cell instanceof FormulaError && FormulaError.NA.equals(cell);
24004
+
24005
+ // Excel lifts IFNA element-wise over an array / multi-cell range value, like IFERROR:
24006
+ // only the #N/A elements take the fallback, every other value or error passes through
24007
+ // (`IFNA(K1:K3,0)` → {#DIV/0!;0;0}, `SUM(IFNA(rng,0))` sums the non-#N/A cells).
24008
+ const hasValueProp = value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'value');
24009
+ if (hasValueProp && value.isArray) {
24010
+ const arrayValue = value.value;
24011
+ const handleCell = (cell) => (isNa(cell) ? blankToZero(H$4.accept(valueIfNa)) : blankToZero(cell));
24012
+ if (Array.isArray(arrayValue)) {
24013
+ return arrayValue.map((row) => (Array.isArray(row) ? row.map(handleCell) : handleCell(row)));
24014
+ }
24015
+ return handleCell(arrayValue);
24016
+ }
24017
+
24018
+ const actualValue = hasValueProp ? value.value : value;
24019
+ return isNa(actualValue)
23912
24020
  ? blankToZero(H$4.accept(valueIfNa))
23913
24021
  : blankToZero(H$4.accept(value));
23914
24022
  },
@@ -25587,6 +25695,55 @@ const lookupEquals = (a, b) =>
25587
25695
  ? a.toLowerCase() === b.toLowerCase()
25588
25696
  : a === b;
25589
25697
 
25698
+ /**
25699
+ * The sheet range a lookup table argument was read from, or undefined when the table is an
25700
+ * array (an array constant, a spilled range or another function's result). The matrix the
25701
+ * host returned must fit inside that range — it is a window starting at `from` (a full-column
25702
+ * reference is trimmed to the data extent) — or no cell can be derived from a matrix index.
25703
+ */
25704
+ const rangeOf = (arg, matrix) => {
25705
+ const ref = arg?.position;
25706
+ if (!ref || ref.isSpilledRange || ref.sheetEnd != null) return undefined;
25707
+ let range;
25708
+ if (ref.from && ref.to) {
25709
+ range = ref;
25710
+ } else if (ref.row != null && ref.col != null) {
25711
+ range = {
25712
+ sheet: ref.sheet,
25713
+ from: { row: ref.row, col: ref.col },
25714
+ to: { row: ref.row, col: ref.col },
25715
+ };
25716
+ } else {
25717
+ return undefined;
25718
+ }
25719
+ const fits =
25720
+ matrix.length <= range.to.row - range.from.row + 1 &&
25721
+ (matrix[0]?.length ?? 0) <= range.to.col - range.from.col + 1;
25722
+ return fits ? range : undefined;
25723
+ };
25724
+
25725
+ /**
25726
+ * The cell VLOOKUP/HLOOKUP found. Over a sheet range it is the cell's REFERENCE (as INDEX returns
25727
+ * one), so the result carries `=B2` semantics by construction: a blank cell displays 0, is 0 in
25728
+ * arithmetic and comparison, "" in text — `=VLOOKUP(…)+0` is 0, `=VLOOKUP(…)=""` is TRUE,
25729
+ * `=VLOOKUP(…)&"x"` is "x". The engine has no blank scalar to return instead: the dispatcher reads
25730
+ * a returned `undefined` as "function not implemented" (`#NAME?`) and `null` is the skipped-argument
25731
+ * marker. Over an array there is no cell to point at, so a blank slot coerces to 0 as blank does in
25732
+ * arithmetic. A cell holding `=""` is text, not blank, and comes back as "" either way.
25733
+ */
25734
+ const matchedCell = (tableRange, tableArray, rowIdx, colIdx) => {
25735
+ if (tableRange) {
25736
+ return {
25737
+ ref: {
25738
+ sheet: tableRange.sheet,
25739
+ row: tableRange.from.row + rowIdx,
25740
+ col: tableRange.from.col + colIdx,
25741
+ },
25742
+ };
25743
+ }
25744
+ return tableArray[rowIdx][colIdx] ?? 0;
25745
+ };
25746
+
25590
25747
  /**
25591
25748
  * Exact-mode match for VLOOKUP/HLOOKUP/XLOOKUP/XMATCH: wildcards when the lookup text has
25592
25749
  * them, otherwise case-insensitive equality.
@@ -25853,6 +26010,7 @@ const ReferenceFunctions = {
25853
26010
  HLOOKUP: (lookupValue, tableArray, rowIndexNum, rangeLookup) => {
25854
26011
  // preserve type of lookupValue
25855
26012
  lookupValue = H$2.accept(lookupValue);
26013
+ const tableArg = tableArray;
25856
26014
  try {
25857
26015
  tableArray = H$2.accept(tableArray, Types.ARRAY, undefined, false);
25858
26016
  } catch (e) {
@@ -25864,6 +26022,7 @@ const ReferenceFunctions = {
25864
26022
  }
25865
26023
  throw e;
25866
26024
  }
26025
+ const tableRange = rangeOf(tableArg, tableArray);
25867
26026
  rowIndexNum = H$2.accept(rowIndexNum, Types.NUMBER);
25868
26027
  rangeLookup = H$2.accept(rangeLookup, Types.BOOLEAN, true);
25869
26028
 
@@ -25876,7 +26035,7 @@ const ReferenceFunctions = {
25876
26035
  if (rangeLookup) {
25877
26036
  const index = approximateMatchIndex(tableArray[0], lookupValue);
25878
26037
  if (index === -1) throw FormulaError.NA;
25879
- return tableArray[rowIndexNum - 1][index];
26038
+ return matchedCell(tableRange, tableArray, rowIndexNum - 1, index);
25880
26039
  }
25881
26040
  // exact lookup: case-insensitive, with wildcard support
25882
26041
  else {
@@ -25885,7 +26044,7 @@ const ReferenceFunctions = {
25885
26044
  );
25886
26045
  // the exact match is not found
25887
26046
  if (index === -1) throw FormulaError.NA;
25888
- return tableArray[rowIndexNum - 1][index];
26047
+ return matchedCell(tableRange, tableArray, rowIndexNum - 1, index);
25889
26048
  }
25890
26049
  },
25891
26050
 
@@ -26389,6 +26548,7 @@ const ReferenceFunctions = {
26389
26548
  VLOOKUP: (lookupValue, tableArray, colIndexNum, rangeLookup) => {
26390
26549
  // preserve type of lookupValue
26391
26550
  lookupValue = H$2.accept(lookupValue);
26551
+ const tableArg = tableArray;
26392
26552
  try {
26393
26553
  tableArray = H$2.accept(tableArray, Types.ARRAY, undefined, false);
26394
26554
  } catch (e) {
@@ -26400,6 +26560,7 @@ const ReferenceFunctions = {
26400
26560
  }
26401
26561
  throw e;
26402
26562
  }
26563
+ const tableRange = rangeOf(tableArg, tableArray);
26403
26564
  colIndexNum = H$2.accept(colIndexNum, Types.NUMBER);
26404
26565
  rangeLookup = H$2.accept(rangeLookup, Types.BOOLEAN, true);
26405
26566
 
@@ -26415,7 +26576,7 @@ const ReferenceFunctions = {
26415
26576
  lookupValue,
26416
26577
  );
26417
26578
  if (index === -1) throw FormulaError.NA;
26418
- return tableArray[index][colIndexNum - 1] ?? "";
26579
+ return matchedCell(tableRange, tableArray, index, colIndexNum - 1);
26419
26580
  }
26420
26581
  // exact lookup: case-insensitive, with wildcard support
26421
26582
  else {
@@ -26424,7 +26585,7 @@ const ReferenceFunctions = {
26424
26585
  );
26425
26586
  // the exact match is not found
26426
26587
  if (index === -1) throw FormulaError.NA;
26427
- return tableArray[index][colIndexNum - 1] ?? "";
26588
+ return matchedCell(tableRange, tableArray, index, colIndexNum - 1);
26428
26589
  }
26429
26590
  },
26430
26591
 
@@ -50565,6 +50726,16 @@ class Parser extends EmbeddedActionsParser {
50565
50726
  ) {
50566
50727
  return arr[0][0];
50567
50728
  }
50729
+ // Excel refuses a ragged constant (`{1,2;3}`) at entry — every row must have the width
50730
+ // of the first. A parse-level `#ERROR!`, like any other syntax error the host stores as
50731
+ // the cell value (rowsncolumns/spreadsheet#939 G-02).
50732
+ const width = arr[0].length;
50733
+ const ragged = arr.findIndex((row) => row.length !== width);
50734
+ if (ragged !== -1) {
50735
+ throw FormulaError.ERROR(
50736
+ `Array constant rows must have the same length: row 1 has ${width} element(s), row ${ragged + 1} has ${arr[ragged].length}`
50737
+ );
50738
+ }
50568
50739
  const array = this.utils.toArray(arr);
50569
50740
  return context.async
50570
50741
  ? this.utils.settleArrayItems(array, $.position)
@@ -52984,8 +53155,13 @@ const normalizeNumberResult = (value, precision, epsilon, excelPrecision) => {
52984
53155
  * @param {*} value
52985
53156
  * @return {boolean}
52986
53157
  */
53158
+ // A name's definition text: `=…` (the OOXML `<definedName>` content), or a bare `LAMBDA(…)` —
53159
+ // the shape Excel's Name Manager and the host's "Text or Formula" names store a lambda in, which
53160
+ // the call path (`Dbl(21)`) already accepts without the leading `=` (spreadsheet#939 G-07).
53161
+ const BARE_LAMBDA_TEXT = /^(_xlfn\.)?LAMBDA\s*\(/i;
52987
53162
  const isNamedFormulaText = (value) =>
52988
- typeof value === "string" && value.trimStart().startsWith("=");
53163
+ typeof value === "string" &&
53164
+ (value.trimStart().startsWith("=") || BARE_LAMBDA_TEXT.test(value.trimStart()));
52989
53165
 
52990
53166
  class FormulaParser {
52991
53167
  /**
@@ -53354,7 +53530,8 @@ class FormulaParser {
53354
53530
  if (this.namedFormulaStack.has(key)) {
53355
53531
  return FormulaError.REF;
53356
53532
  }
53357
- const source = text.trim().slice(1).trim();
53533
+ const trimmed = text.trim();
53534
+ const source = (trimmed.startsWith("=") ? trimmed.slice(1) : trimmed).trim();
53358
53535
  if (source.length === 0) {
53359
53536
  return FormulaError.NAME;
53360
53537
  }
@@ -53363,12 +53540,18 @@ class FormulaParser {
53363
53540
  this.namedFormulaParser.namedFormulaStack = this.namedFormulaStack;
53364
53541
  }
53365
53542
  this.namedFormulaStack.add(key);
53543
+ // A name bound to a LAMBDA (`Dbl` = `=LAMBDA(x,x*2)`) is the lambda VALUE wherever the
53544
+ // name is read — `MAP(A1:A3,Dbl)`, `LET(f,Dbl,f(3))` — so the definition parser must hand
53545
+ // it back instead of turning it into `#CALC!`; the referencing cell's own
53546
+ // `checkFormulaResult` still makes a bare `=Dbl` `#CALC!` (spreadsheet#939 G-07).
53547
+ this.namedFormulaParser.keepLambdaResult = true;
53366
53548
  try {
53367
53549
  return this.namedFormulaParser.parse(source, this.position, true);
53368
53550
  } catch (e) {
53369
53551
  if (e instanceof FormulaError) return e;
53370
53552
  return FormulaError.NAME;
53371
53553
  } finally {
53554
+ this.namedFormulaParser.keepLambdaResult = false;
53372
53555
  this.namedFormulaStack.delete(key);
53373
53556
  }
53374
53557
  }