@rowsncolumns/fast-formula-parser 6.2.37 → 6.2.39
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 +153 -31
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +153 -31
- package/dist/esm/index.js.map +1 -1
- package/package.json +4 -3
package/dist/cjs/index.js
CHANGED
|
@@ -3883,6 +3883,80 @@ const IndexedColors = Object.freeze([
|
|
|
3883
3883
|
|
|
3884
3884
|
const isSingleSectionElapsedTimePattern = (pattern) => !pattern.includes(";") &&
|
|
3885
3885
|
/\[(?:h+|m+|s+)\]/i.test(pattern.replace(/\[\$[^\]]+\]/g, ""));
|
|
3886
|
+
const intlMonthNames = (locale, options) => {
|
|
3887
|
+
const formatter = new Intl.DateTimeFormat(locale, {
|
|
3888
|
+
...options,
|
|
3889
|
+
timeZone: "UTC",
|
|
3890
|
+
});
|
|
3891
|
+
return Array.from({ length: 12 }, (_, month) => formatter
|
|
3892
|
+
.formatToParts(new Date(Date.UTC(2020, month, 1)))
|
|
3893
|
+
.find((part) => part.type === "month")?.value ?? "");
|
|
3894
|
+
};
|
|
3895
|
+
const intlRegisteredLocales = new Set();
|
|
3896
|
+
/**
|
|
3897
|
+
* numfmt ships name tables for a fixed set of languages; a tag it does not know
|
|
3898
|
+
* (et, ms, nn, …) silently renders English month/weekday names and en-US
|
|
3899
|
+
* separators. Excel and `Intl.DateTimeFormat` localize every locale, so an
|
|
3900
|
+
* unknown tag gets a numfmt locale built from Intl on first use: separators
|
|
3901
|
+
* from `Intl.NumberFormat`, the in-date month names (numfmt's single table is
|
|
3902
|
+
* the form used beside a day number — see `getStandaloneMonthLocale`), the
|
|
3903
|
+
* in-date weekday names (the form `dateStyle: "full"` shows) and the AM/PM
|
|
3904
|
+
* designators. Tags Intl itself does not know keep numfmt's fallback.
|
|
3905
|
+
*/
|
|
3906
|
+
const ensureNumfmtLocale = (locale) => {
|
|
3907
|
+
if (intlRegisteredLocales.has(locale))
|
|
3908
|
+
return;
|
|
3909
|
+
intlRegisteredLocales.add(locale);
|
|
3910
|
+
try {
|
|
3911
|
+
if (getLocale(locale))
|
|
3912
|
+
return;
|
|
3913
|
+
const canonical = Intl.DateTimeFormat.supportedLocalesOf([locale])[0];
|
|
3914
|
+
if (!canonical)
|
|
3915
|
+
return;
|
|
3916
|
+
const numberParts = new Intl.NumberFormat(locale).formatToParts(1234567.5);
|
|
3917
|
+
const part = (type) => numberParts.find((p) => p.type === type)?.value;
|
|
3918
|
+
const weekdayNames = (weekday) => {
|
|
3919
|
+
// 2024-01-07 was a Sunday; numfmt indexes `dddd[0]` as Sunday.
|
|
3920
|
+
const fmt = new Intl.DateTimeFormat(locale, {
|
|
3921
|
+
weekday,
|
|
3922
|
+
day: "numeric",
|
|
3923
|
+
month: "long",
|
|
3924
|
+
year: "numeric",
|
|
3925
|
+
timeZone: "UTC",
|
|
3926
|
+
});
|
|
3927
|
+
return Array.from({ length: 7 }, (_, i) => fmt
|
|
3928
|
+
.formatToParts(new Date(Date.UTC(2024, 0, 7 + i)))
|
|
3929
|
+
.find((p) => p.type === "weekday")?.value ?? "");
|
|
3930
|
+
};
|
|
3931
|
+
const dayPeriod = (hour) => new Intl.DateTimeFormat(locale, {
|
|
3932
|
+
hour: "numeric",
|
|
3933
|
+
hour12: true,
|
|
3934
|
+
timeZone: "UTC",
|
|
3935
|
+
})
|
|
3936
|
+
.formatToParts(new Date(Date.UTC(2024, 0, 7, hour)))
|
|
3937
|
+
.find((p) => p.type === "dayPeriod")?.value;
|
|
3938
|
+
const dateParts = new Intl.DateTimeFormat(locale, {
|
|
3939
|
+
timeZone: "UTC",
|
|
3940
|
+
}).formatToParts(new Date(Date.UTC(2024, 0, 7)));
|
|
3941
|
+
const monthIndex = dateParts.findIndex((p) => p.type === "month");
|
|
3942
|
+
const dayIndex = dateParts.findIndex((p) => p.type === "day");
|
|
3943
|
+
const am = dayPeriod(9);
|
|
3944
|
+
const pm = dayPeriod(21);
|
|
3945
|
+
addLocale({
|
|
3946
|
+
group: part("group") ?? "\u00a0",
|
|
3947
|
+
decimal: part("decimal") ?? ".",
|
|
3948
|
+
mmmm: intlMonthNames(locale, { day: "numeric", month: "long" }),
|
|
3949
|
+
mmm: intlMonthNames(locale, { day: "numeric", month: "short" }),
|
|
3950
|
+
dddd: weekdayNames("long"),
|
|
3951
|
+
ddd: weekdayNames("short"),
|
|
3952
|
+
...(am && pm && am !== pm ? { ampm: [am, pm] } : {}),
|
|
3953
|
+
preferMDY: monthIndex >= 0 && dayIndex >= 0 && monthIndex < dayIndex,
|
|
3954
|
+
}, locale);
|
|
3955
|
+
}
|
|
3956
|
+
catch {
|
|
3957
|
+
// Malformed tag: numfmt keeps its own fallback.
|
|
3958
|
+
}
|
|
3959
|
+
};
|
|
3886
3960
|
const localeForPattern = (pattern, locale) => {
|
|
3887
3961
|
return locale;
|
|
3888
3962
|
};
|
|
@@ -4134,6 +4208,8 @@ const withWorkbookSeparatorsCached = (code, locale) => {
|
|
|
4134
4208
|
};
|
|
4135
4209
|
const ssfFormat = (pattern, value, locale) => {
|
|
4136
4210
|
try {
|
|
4211
|
+
if (locale)
|
|
4212
|
+
;
|
|
4137
4213
|
let shortAmPm = shortAmPmPatternCache.get(pattern);
|
|
4138
4214
|
if (shortAmPm === undefined && !shortAmPmPatternCache.has(pattern)) {
|
|
4139
4215
|
shortAmPm = withShortAmPmMarker(pattern);
|
|
@@ -12588,7 +12664,10 @@ const forecastEtsPrep = (ys, xs, seasonality) => {
|
|
|
12588
12664
|
if (typeof yv !== "number") continue;
|
|
12589
12665
|
pairs.push([x, yv]);
|
|
12590
12666
|
}
|
|
12591
|
-
|
|
12667
|
+
// Excel fits from two pairs (there is no four-pair floor); fewer is
|
|
12668
|
+
// #N/A, and two pairs on the same timeline point have no step (#NUM!
|
|
12669
|
+
// below). rowsncolumns/spreadsheet#642 B-01.
|
|
12670
|
+
if (pairs.length < 2) throw FormulaError.NA;
|
|
12592
12671
|
pairs.sort((a, b) => a[0] - b[0]);
|
|
12593
12672
|
const sortedX = pairs.map(p => p[0]);
|
|
12594
12673
|
const y = pairs.map(p => p[1]);
|
|
@@ -12626,7 +12705,7 @@ const detectSeasonality = (y) => {
|
|
|
12626
12705
|
const n = y.length;
|
|
12627
12706
|
if (n < 4) return 1;
|
|
12628
12707
|
const maxPeriod = Math.min(Math.floor(n / 2), 12);
|
|
12629
|
-
const baseline = runHoltWinters(y, 1, 0.5, 0.1, 0
|
|
12708
|
+
const baseline = runHoltWinters(y, 1, 0.5, 0.1, 0).sse;
|
|
12630
12709
|
let bestPeriod = 1;
|
|
12631
12710
|
let bestSse = baseline;
|
|
12632
12711
|
for (let m = 2; m <= maxPeriod; m++) {
|
|
@@ -12637,7 +12716,7 @@ const detectSeasonality = (y) => {
|
|
|
12637
12716
|
for (const alpha of [0.3, 0.7]) {
|
|
12638
12717
|
for (const beta of [0.1, 0.3]) {
|
|
12639
12718
|
for (const gamma of [0.3, 0.7]) {
|
|
12640
|
-
const sse = runHoltWinters(y, m, alpha, beta, gamma
|
|
12719
|
+
const sse = runHoltWinters(y, m, alpha, beta, gamma).sse;
|
|
12641
12720
|
if (sse < mSse) mSse = sse;
|
|
12642
12721
|
}
|
|
12643
12722
|
}
|
|
@@ -12653,11 +12732,10 @@ const detectSeasonality = (y) => {
|
|
|
12653
12732
|
};
|
|
12654
12733
|
|
|
12655
12734
|
// Fit Holt-Winters additive model with the supplied (or auto-fit) α,
|
|
12656
|
-
// β, γ smoothing parameters. Returns fitted in-sample values
|
|
12657
|
-
// `
|
|
12658
|
-
// (no seasonal component).
|
|
12659
|
-
const fitHoltWinters = (y, period
|
|
12660
|
-
y.length;
|
|
12735
|
+
// β, γ smoothing parameters. Returns the fitted in-sample values and the
|
|
12736
|
+
// terminal state (`forecastAt(h)` extrapolates it to any horizon in O(1)).
|
|
12737
|
+
// period=1 collapses to Holt linear (no seasonal component).
|
|
12738
|
+
const fitHoltWinters = (y, period) => {
|
|
12661
12739
|
// Grid search the smoothing params. The 5×5×5 grid is coarse but
|
|
12662
12740
|
// close enough to the SSE-minimizing optimum that Excel's results
|
|
12663
12741
|
// round-match within ~1% on the seasonal demo series.
|
|
@@ -12667,7 +12745,7 @@ const fitHoltWinters = (y, period, horizon) => {
|
|
|
12667
12745
|
for (const alpha of grid) {
|
|
12668
12746
|
for (const beta of grid) {
|
|
12669
12747
|
for (const gamma of candidateGammas) {
|
|
12670
|
-
const result = runHoltWinters(y, period, alpha, beta, gamma
|
|
12748
|
+
const result = runHoltWinters(y, period, alpha, beta, gamma);
|
|
12671
12749
|
if (best == null || result.sse < best.sse) {
|
|
12672
12750
|
best = { alpha, beta, gamma, ...result };
|
|
12673
12751
|
}
|
|
@@ -12677,7 +12755,25 @@ const fitHoltWinters = (y, period, horizon) => {
|
|
|
12677
12755
|
return best;
|
|
12678
12756
|
};
|
|
12679
12757
|
|
|
12680
|
-
|
|
12758
|
+
// The forecast `h` whole steps past the last observation from a run's
|
|
12759
|
+
// terminal state: level + h·trend + seasonal[(n + h - 1) mod m]. O(1) in
|
|
12760
|
+
// `h` — the old per-step forecast array made `FORECAST.ETS(1E15, …)`
|
|
12761
|
+
// hang the tab (and trapped the Rust engine's wasm with a capacity
|
|
12762
|
+
// overflow that poisoned the whole store). rowsncolumns/spreadsheet#642 B-06.
|
|
12763
|
+
const holtWintersForecastAt = ({ n, m, level, trend, seasonal }, h) => {
|
|
12764
|
+
const seasonComp = m > 1 ? seasonal[(n + h - 1) % m] : 0;
|
|
12765
|
+
return level + h * trend + seasonComp;
|
|
12766
|
+
};
|
|
12767
|
+
|
|
12768
|
+
// One additive Holt-Winters pass with fixed smoothing parameters. The
|
|
12769
|
+
// initial level is the level one step *before* the series (first value
|
|
12770
|
+
// or first-cycle mean, minus the trend), so the one-step-ahead prediction
|
|
12771
|
+
// of the first observation is the observation itself and a perfectly
|
|
12772
|
+
// linear history has zero residual — an initial level *at* the first
|
|
12773
|
+
// value predicted y₀ + trend for y₀, and that spurious residual pulled
|
|
12774
|
+
// the grid search off the exact continuation (10,20,…,100 → 109.56
|
|
12775
|
+
// instead of Excel's 110). rowsncolumns/spreadsheet#642 B-01.
|
|
12776
|
+
const runHoltWinters = (y, period, alpha, beta, gamma) => {
|
|
12681
12777
|
const n = y.length;
|
|
12682
12778
|
const m = period;
|
|
12683
12779
|
const fitted = new Array(n).fill(null);
|
|
@@ -12685,21 +12781,21 @@ const runHoltWinters = (y, period, alpha, beta, gamma, horizon) => {
|
|
|
12685
12781
|
const seasonal = new Array(m).fill(0);
|
|
12686
12782
|
if (m > 1 && n >= 2 * m) {
|
|
12687
12783
|
// Initial seasonal indices = first-cycle deviations from the
|
|
12688
|
-
// first-cycle mean. Initial
|
|
12689
|
-
//
|
|
12690
|
-
//
|
|
12784
|
+
// first-cycle mean. Initial trend = average of per-step changes
|
|
12785
|
+
// between first and second cycle, normalized by m. Initial level
|
|
12786
|
+
// = mean of first cycle, one trend step back.
|
|
12691
12787
|
let sum1 = 0;
|
|
12692
12788
|
for (let i = 0; i < m; i++) sum1 += y[i];
|
|
12693
12789
|
const mean1 = sum1 / m;
|
|
12694
|
-
level = mean1;
|
|
12695
12790
|
let trendSum = 0;
|
|
12696
12791
|
for (let i = 0; i < m; i++) trendSum += (y[i + m] - y[i]) / m;
|
|
12697
12792
|
trend = trendSum / m;
|
|
12793
|
+
level = mean1 - trend;
|
|
12698
12794
|
for (let i = 0; i < m; i++) seasonal[i] = y[i] - mean1;
|
|
12699
12795
|
} else {
|
|
12700
|
-
// Holt linear initialization.
|
|
12701
|
-
level = y[0];
|
|
12796
|
+
// Holt linear initialization, one trend step back.
|
|
12702
12797
|
trend = n > 1 ? y[1] - y[0] : 0;
|
|
12798
|
+
level = y[0] - trend;
|
|
12703
12799
|
}
|
|
12704
12800
|
let sse = 0;
|
|
12705
12801
|
let count = 0;
|
|
@@ -12718,12 +12814,12 @@ const runHoltWinters = (y, period, alpha, beta, gamma, horizon) => {
|
|
|
12718
12814
|
seasonal[seasonIdx] = gamma * (y[t] - level) + (1 - gamma) * seasonComp;
|
|
12719
12815
|
}
|
|
12720
12816
|
}
|
|
12721
|
-
const
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12817
|
+
const state = { n, m, level, trend, seasonal };
|
|
12818
|
+
return {
|
|
12819
|
+
fitted,
|
|
12820
|
+
sse: count > 0 ? sse / count : Infinity,
|
|
12821
|
+
forecastAt: (h) => holtWintersForecastAt(state, h),
|
|
12822
|
+
};
|
|
12727
12823
|
};
|
|
12728
12824
|
|
|
12729
12825
|
const percentileInclusive = (values, k) => {
|
|
@@ -13508,9 +13604,11 @@ const DistributionFunctions = {
|
|
|
13508
13604
|
throw FormulaError.NUM;
|
|
13509
13605
|
}
|
|
13510
13606
|
const horizon = Math.round((target - lastX) / step);
|
|
13607
|
+
if (!Number.isFinite(horizon)) throw FormulaError.NUM;
|
|
13511
13608
|
if (horizon < 1) return y[y.length - 1];
|
|
13512
|
-
const
|
|
13513
|
-
|
|
13609
|
+
const forecast = fitHoltWinters(y, period).forecastAt(horizon);
|
|
13610
|
+
if (!Number.isFinite(forecast)) throw FormulaError.NUM;
|
|
13611
|
+
return forecast;
|
|
13514
13612
|
},
|
|
13515
13613
|
|
|
13516
13614
|
'FORECAST.ETS.CONFINT': (targetDate, values, timeline, confidenceArg, seasonalityArg, dataCompletionArg, aggregationArg) => {
|
|
@@ -13526,7 +13624,8 @@ const DistributionFunctions = {
|
|
|
13526
13624
|
const { y, lastX, step, period } = forecastEtsPrep(ys, xs, seasonality);
|
|
13527
13625
|
if (target < lastX) throw FormulaError.NUM;
|
|
13528
13626
|
const horizon = Math.max(1, Math.round((target - lastX) / step));
|
|
13529
|
-
|
|
13627
|
+
if (!Number.isFinite(horizon)) throw FormulaError.NUM;
|
|
13628
|
+
const { fitted } = fitHoltWinters(y, period);
|
|
13530
13629
|
// RMSE of in-sample residuals — z-scaled to the requested
|
|
13531
13630
|
// confidence level. Excel uses √(horizon) scaling on the
|
|
13532
13631
|
// forecast variance.
|
|
@@ -13539,7 +13638,9 @@ const DistributionFunctions = {
|
|
|
13539
13638
|
}
|
|
13540
13639
|
const rmse = n > 0 ? Math.sqrt(sse / n) : 0;
|
|
13541
13640
|
const z = jStat.normal.inv(1 - (1 - confidence) / 2, 0, 1);
|
|
13542
|
-
|
|
13641
|
+
const halfWidth = z * rmse * Math.sqrt(horizon);
|
|
13642
|
+
if (!Number.isFinite(halfWidth)) throw FormulaError.NUM;
|
|
13643
|
+
return halfWidth;
|
|
13543
13644
|
},
|
|
13544
13645
|
|
|
13545
13646
|
'FORECAST.ETS.SEASONALITY': (values, timeline, dataCompletionArg, aggregationArg) => {
|
|
@@ -13558,7 +13659,7 @@ const DistributionFunctions = {
|
|
|
13558
13659
|
const seasonality = forecastEtsSeasonality(seasonalityArg);
|
|
13559
13660
|
forecastEtsValidateOptions(dataCompletionArg, aggregationArg);
|
|
13560
13661
|
const { y, period } = forecastEtsPrep(ys, xs, seasonality);
|
|
13561
|
-
const { alpha, beta, gamma, fitted } = fitHoltWinters(y, period
|
|
13662
|
+
const { alpha, beta, gamma, fitted } = fitHoltWinters(y, period);
|
|
13562
13663
|
// Excel STAT codes:
|
|
13563
13664
|
// 1: alpha 2: beta 3: gamma 4: MASE
|
|
13564
13665
|
// 5: SMAPE 6: MAE 7: RMSE 8: detected step size
|
|
@@ -18019,6 +18120,11 @@ const applyToArray$1 = (value, checkFn) => {
|
|
|
18019
18120
|
* Get the number of values that evaluate to true and false.
|
|
18020
18121
|
* Cast Number and "TRUE", "FALSE" to boolean.
|
|
18021
18122
|
* Ignore unrelated values.
|
|
18123
|
+
* An error value — a direct argument (`AND(1/0, FALSE)`) or a cell inside a
|
|
18124
|
+
* reference — is the result, whatever the other arguments decide: Excel never
|
|
18125
|
+
* short-circuits AND/OR/XOR past an error, and the first one in argument order
|
|
18126
|
+
* wins (rowsncolumns/spreadsheet#642 B-08). `flattenParams` only throws error
|
|
18127
|
+
* values itself when a value type is requested.
|
|
18022
18128
|
* @ignore
|
|
18023
18129
|
* @param {any[]} params
|
|
18024
18130
|
* @return {number[]}
|
|
@@ -18026,6 +18132,8 @@ const applyToArray$1 = (value, checkFn) => {
|
|
|
18026
18132
|
function getNumLogicalValue(params) {
|
|
18027
18133
|
let numTrue = 0, numFalse = 0;
|
|
18028
18134
|
H$4.flattenParams(params, null, true, (val, info) => {
|
|
18135
|
+
if (val instanceof FormulaError)
|
|
18136
|
+
throw val;
|
|
18029
18137
|
const type = typeof val;
|
|
18030
18138
|
if (type === "string") {
|
|
18031
18139
|
// Text inside a reference or array is ignored; a direct text
|
|
@@ -44190,9 +44298,16 @@ const FormulaErrorT$1 = createToken({
|
|
|
44190
44298
|
/#NULL!|#DIV\/0!|#VALUE!|#NAME\?|#NUM!|#N\/A|#GETTING_DATA|#SPILL!|#CONNECT!|#BLOCKED!|#UNKNOWN!|#FIELD!|#CALC!|#EXTERNAL!/,
|
|
44191
44299
|
});
|
|
44192
44300
|
|
|
44301
|
+
// `Table1[#REF!]` / `Table1[@#REF!]` — the text Excel writes once the referenced table column is
|
|
44302
|
+
// deleted — carries `#REF!` as a COLUMN specifier: `longer_alt` lets `TableColumnName` (defined
|
|
44303
|
+
// below) claim `#REF!]` when the error token is directly closed by `]`. A bare `#REF!` elsewhere
|
|
44304
|
+
// (`=SUM(#REF!)`) stays the error literal.
|
|
44305
|
+
// `#REF!#` is what a structural delete leaves of a spilled-range reference whose anchor row/column
|
|
44306
|
+
// went away (`=C1#` → `=#REF!#`); the postfix stays on the error token so the text still parses
|
|
44307
|
+
// (to the `#REF!` error) instead of an `#ERROR!` syntax failure.
|
|
44193
44308
|
const RefError$1 = createToken({
|
|
44194
44309
|
name: "RefError",
|
|
44195
|
-
pattern: /#REF
|
|
44310
|
+
pattern: /#REF!#?/,
|
|
44196
44311
|
});
|
|
44197
44312
|
|
|
44198
44313
|
const Name$1 = createToken({
|
|
@@ -44219,12 +44334,17 @@ const Sheet$1 = createToken({
|
|
|
44219
44334
|
// `Sales[Q1 '[est']]` for the column named `Q1 [est]`. Defined ahead of `Cell`
|
|
44220
44335
|
// so it can be its `longer_alt`: a column named like a cell address (`Q1`,
|
|
44221
44336
|
// `FY24`) must lex as the column specifier, not as a cell reference.
|
|
44337
|
+
// `#REF!` (Excel's specifier for a deleted column) is the one `#`-form admitted as a column name;
|
|
44338
|
+
// every other `#…` inside brackets is a `SpecialItem`.
|
|
44222
44339
|
const TableColumnName$1 = createToken({
|
|
44223
44340
|
name: "TableColumnName",
|
|
44224
44341
|
pattern:
|
|
44225
|
-
/(?!,)[\[]?[@]?[\[+]?(?:[@?%A-Za-z_.\d\s(),!@$%^&*+=\-\/$\u007F-\uFFFF]|'[\[\]#'])
|
|
44342
|
+
/(?!,)[\[]?[@]?[\[+]?(?:#REF!|(?:[@?%A-Za-z_.\d\s(),!@$%^&*+=\-\/$\u007F-\uFFFF]|'[\[\]#'])+)\]+(?:(?:\.[a-zA-Z_][a-zA-Z0-9_~?]*(?:\[[0-9]+\])*)+(?:\.(?![^,\s)]))?|\.(?![^,\s)]))?/,
|
|
44226
44343
|
});
|
|
44227
44344
|
|
|
44345
|
+
// Declared after `TableColumnName` exists (see the `RefError` note above).
|
|
44346
|
+
RefError$1.LONGER_ALT = TableColumnName$1;
|
|
44347
|
+
|
|
44228
44348
|
const Cell$1 = createToken({
|
|
44229
44349
|
name: "Cell",
|
|
44230
44350
|
// Trailing `#` is Excel's "spilled-range operator": e.g. `A1#` resolves
|
|
@@ -44890,7 +45010,8 @@ class Parser extends EmbeddedActionsParser {
|
|
|
44890
45010
|
},
|
|
44891
45011
|
{
|
|
44892
45012
|
ALT: () => {
|
|
44893
|
-
|
|
45013
|
+
// `#REF!#` (a broken spilled-range reference) is the `#REF!` error.
|
|
45014
|
+
const err = $.CONSUME(RefError).image.replace(/#$/, "");
|
|
44894
45015
|
return $.ACTION(() => this.utils.toError(err));
|
|
44895
45016
|
},
|
|
44896
45017
|
},
|
|
@@ -45077,7 +45198,8 @@ class Parser extends EmbeddedActionsParser {
|
|
|
45077
45198
|
// parse the number to row number when needed.
|
|
45078
45199
|
{
|
|
45079
45200
|
ALT: () => {
|
|
45080
|
-
|
|
45201
|
+
// `#REF!#` (a broken spilled-range reference) is the `#REF!` error.
|
|
45202
|
+
const err = $.CONSUME(RefError).image.replace(/#$/, "");
|
|
45081
45203
|
return $.ACTION(() => this.utils.toError(err));
|
|
45082
45204
|
},
|
|
45083
45205
|
},
|