@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/esm/index.js
CHANGED
|
@@ -3879,6 +3879,80 @@ const IndexedColors = Object.freeze([
|
|
|
3879
3879
|
|
|
3880
3880
|
const isSingleSectionElapsedTimePattern = (pattern) => !pattern.includes(";") &&
|
|
3881
3881
|
/\[(?:h+|m+|s+)\]/i.test(pattern.replace(/\[\$[^\]]+\]/g, ""));
|
|
3882
|
+
const intlMonthNames = (locale, options) => {
|
|
3883
|
+
const formatter = new Intl.DateTimeFormat(locale, {
|
|
3884
|
+
...options,
|
|
3885
|
+
timeZone: "UTC",
|
|
3886
|
+
});
|
|
3887
|
+
return Array.from({ length: 12 }, (_, month) => formatter
|
|
3888
|
+
.formatToParts(new Date(Date.UTC(2020, month, 1)))
|
|
3889
|
+
.find((part) => part.type === "month")?.value ?? "");
|
|
3890
|
+
};
|
|
3891
|
+
const intlRegisteredLocales = new Set();
|
|
3892
|
+
/**
|
|
3893
|
+
* numfmt ships name tables for a fixed set of languages; a tag it does not know
|
|
3894
|
+
* (et, ms, nn, …) silently renders English month/weekday names and en-US
|
|
3895
|
+
* separators. Excel and `Intl.DateTimeFormat` localize every locale, so an
|
|
3896
|
+
* unknown tag gets a numfmt locale built from Intl on first use: separators
|
|
3897
|
+
* from `Intl.NumberFormat`, the in-date month names (numfmt's single table is
|
|
3898
|
+
* the form used beside a day number — see `getStandaloneMonthLocale`), the
|
|
3899
|
+
* in-date weekday names (the form `dateStyle: "full"` shows) and the AM/PM
|
|
3900
|
+
* designators. Tags Intl itself does not know keep numfmt's fallback.
|
|
3901
|
+
*/
|
|
3902
|
+
const ensureNumfmtLocale = (locale) => {
|
|
3903
|
+
if (intlRegisteredLocales.has(locale))
|
|
3904
|
+
return;
|
|
3905
|
+
intlRegisteredLocales.add(locale);
|
|
3906
|
+
try {
|
|
3907
|
+
if (getLocale(locale))
|
|
3908
|
+
return;
|
|
3909
|
+
const canonical = Intl.DateTimeFormat.supportedLocalesOf([locale])[0];
|
|
3910
|
+
if (!canonical)
|
|
3911
|
+
return;
|
|
3912
|
+
const numberParts = new Intl.NumberFormat(locale).formatToParts(1234567.5);
|
|
3913
|
+
const part = (type) => numberParts.find((p) => p.type === type)?.value;
|
|
3914
|
+
const weekdayNames = (weekday) => {
|
|
3915
|
+
// 2024-01-07 was a Sunday; numfmt indexes `dddd[0]` as Sunday.
|
|
3916
|
+
const fmt = new Intl.DateTimeFormat(locale, {
|
|
3917
|
+
weekday,
|
|
3918
|
+
day: "numeric",
|
|
3919
|
+
month: "long",
|
|
3920
|
+
year: "numeric",
|
|
3921
|
+
timeZone: "UTC",
|
|
3922
|
+
});
|
|
3923
|
+
return Array.from({ length: 7 }, (_, i) => fmt
|
|
3924
|
+
.formatToParts(new Date(Date.UTC(2024, 0, 7 + i)))
|
|
3925
|
+
.find((p) => p.type === "weekday")?.value ?? "");
|
|
3926
|
+
};
|
|
3927
|
+
const dayPeriod = (hour) => new Intl.DateTimeFormat(locale, {
|
|
3928
|
+
hour: "numeric",
|
|
3929
|
+
hour12: true,
|
|
3930
|
+
timeZone: "UTC",
|
|
3931
|
+
})
|
|
3932
|
+
.formatToParts(new Date(Date.UTC(2024, 0, 7, hour)))
|
|
3933
|
+
.find((p) => p.type === "dayPeriod")?.value;
|
|
3934
|
+
const dateParts = new Intl.DateTimeFormat(locale, {
|
|
3935
|
+
timeZone: "UTC",
|
|
3936
|
+
}).formatToParts(new Date(Date.UTC(2024, 0, 7)));
|
|
3937
|
+
const monthIndex = dateParts.findIndex((p) => p.type === "month");
|
|
3938
|
+
const dayIndex = dateParts.findIndex((p) => p.type === "day");
|
|
3939
|
+
const am = dayPeriod(9);
|
|
3940
|
+
const pm = dayPeriod(21);
|
|
3941
|
+
addLocale({
|
|
3942
|
+
group: part("group") ?? "\u00a0",
|
|
3943
|
+
decimal: part("decimal") ?? ".",
|
|
3944
|
+
mmmm: intlMonthNames(locale, { day: "numeric", month: "long" }),
|
|
3945
|
+
mmm: intlMonthNames(locale, { day: "numeric", month: "short" }),
|
|
3946
|
+
dddd: weekdayNames("long"),
|
|
3947
|
+
ddd: weekdayNames("short"),
|
|
3948
|
+
...(am && pm && am !== pm ? { ampm: [am, pm] } : {}),
|
|
3949
|
+
preferMDY: monthIndex >= 0 && dayIndex >= 0 && monthIndex < dayIndex,
|
|
3950
|
+
}, locale);
|
|
3951
|
+
}
|
|
3952
|
+
catch {
|
|
3953
|
+
// Malformed tag: numfmt keeps its own fallback.
|
|
3954
|
+
}
|
|
3955
|
+
};
|
|
3882
3956
|
const localeForPattern = (pattern, locale) => {
|
|
3883
3957
|
return locale;
|
|
3884
3958
|
};
|
|
@@ -4130,6 +4204,8 @@ const withWorkbookSeparatorsCached = (code, locale) => {
|
|
|
4130
4204
|
};
|
|
4131
4205
|
const ssfFormat = (pattern, value, locale) => {
|
|
4132
4206
|
try {
|
|
4207
|
+
if (locale)
|
|
4208
|
+
;
|
|
4133
4209
|
let shortAmPm = shortAmPmPatternCache.get(pattern);
|
|
4134
4210
|
if (shortAmPm === undefined && !shortAmPmPatternCache.has(pattern)) {
|
|
4135
4211
|
shortAmPm = withShortAmPmMarker(pattern);
|
|
@@ -12584,7 +12660,10 @@ const forecastEtsPrep = (ys, xs, seasonality) => {
|
|
|
12584
12660
|
if (typeof yv !== "number") continue;
|
|
12585
12661
|
pairs.push([x, yv]);
|
|
12586
12662
|
}
|
|
12587
|
-
|
|
12663
|
+
// Excel fits from two pairs (there is no four-pair floor); fewer is
|
|
12664
|
+
// #N/A, and two pairs on the same timeline point have no step (#NUM!
|
|
12665
|
+
// below). rowsncolumns/spreadsheet#642 B-01.
|
|
12666
|
+
if (pairs.length < 2) throw FormulaError.NA;
|
|
12588
12667
|
pairs.sort((a, b) => a[0] - b[0]);
|
|
12589
12668
|
const sortedX = pairs.map(p => p[0]);
|
|
12590
12669
|
const y = pairs.map(p => p[1]);
|
|
@@ -12622,7 +12701,7 @@ const detectSeasonality = (y) => {
|
|
|
12622
12701
|
const n = y.length;
|
|
12623
12702
|
if (n < 4) return 1;
|
|
12624
12703
|
const maxPeriod = Math.min(Math.floor(n / 2), 12);
|
|
12625
|
-
const baseline = runHoltWinters(y, 1, 0.5, 0.1, 0
|
|
12704
|
+
const baseline = runHoltWinters(y, 1, 0.5, 0.1, 0).sse;
|
|
12626
12705
|
let bestPeriod = 1;
|
|
12627
12706
|
let bestSse = baseline;
|
|
12628
12707
|
for (let m = 2; m <= maxPeriod; m++) {
|
|
@@ -12633,7 +12712,7 @@ const detectSeasonality = (y) => {
|
|
|
12633
12712
|
for (const alpha of [0.3, 0.7]) {
|
|
12634
12713
|
for (const beta of [0.1, 0.3]) {
|
|
12635
12714
|
for (const gamma of [0.3, 0.7]) {
|
|
12636
|
-
const sse = runHoltWinters(y, m, alpha, beta, gamma
|
|
12715
|
+
const sse = runHoltWinters(y, m, alpha, beta, gamma).sse;
|
|
12637
12716
|
if (sse < mSse) mSse = sse;
|
|
12638
12717
|
}
|
|
12639
12718
|
}
|
|
@@ -12649,11 +12728,10 @@ const detectSeasonality = (y) => {
|
|
|
12649
12728
|
};
|
|
12650
12729
|
|
|
12651
12730
|
// Fit Holt-Winters additive model with the supplied (or auto-fit) α,
|
|
12652
|
-
// β, γ smoothing parameters. Returns fitted in-sample values
|
|
12653
|
-
// `
|
|
12654
|
-
// (no seasonal component).
|
|
12655
|
-
const fitHoltWinters = (y, period
|
|
12656
|
-
y.length;
|
|
12731
|
+
// β, γ smoothing parameters. Returns the fitted in-sample values and the
|
|
12732
|
+
// terminal state (`forecastAt(h)` extrapolates it to any horizon in O(1)).
|
|
12733
|
+
// period=1 collapses to Holt linear (no seasonal component).
|
|
12734
|
+
const fitHoltWinters = (y, period) => {
|
|
12657
12735
|
// Grid search the smoothing params. The 5×5×5 grid is coarse but
|
|
12658
12736
|
// close enough to the SSE-minimizing optimum that Excel's results
|
|
12659
12737
|
// round-match within ~1% on the seasonal demo series.
|
|
@@ -12663,7 +12741,7 @@ const fitHoltWinters = (y, period, horizon) => {
|
|
|
12663
12741
|
for (const alpha of grid) {
|
|
12664
12742
|
for (const beta of grid) {
|
|
12665
12743
|
for (const gamma of candidateGammas) {
|
|
12666
|
-
const result = runHoltWinters(y, period, alpha, beta, gamma
|
|
12744
|
+
const result = runHoltWinters(y, period, alpha, beta, gamma);
|
|
12667
12745
|
if (best == null || result.sse < best.sse) {
|
|
12668
12746
|
best = { alpha, beta, gamma, ...result };
|
|
12669
12747
|
}
|
|
@@ -12673,7 +12751,25 @@ const fitHoltWinters = (y, period, horizon) => {
|
|
|
12673
12751
|
return best;
|
|
12674
12752
|
};
|
|
12675
12753
|
|
|
12676
|
-
|
|
12754
|
+
// The forecast `h` whole steps past the last observation from a run's
|
|
12755
|
+
// terminal state: level + h·trend + seasonal[(n + h - 1) mod m]. O(1) in
|
|
12756
|
+
// `h` — the old per-step forecast array made `FORECAST.ETS(1E15, …)`
|
|
12757
|
+
// hang the tab (and trapped the Rust engine's wasm with a capacity
|
|
12758
|
+
// overflow that poisoned the whole store). rowsncolumns/spreadsheet#642 B-06.
|
|
12759
|
+
const holtWintersForecastAt = ({ n, m, level, trend, seasonal }, h) => {
|
|
12760
|
+
const seasonComp = m > 1 ? seasonal[(n + h - 1) % m] : 0;
|
|
12761
|
+
return level + h * trend + seasonComp;
|
|
12762
|
+
};
|
|
12763
|
+
|
|
12764
|
+
// One additive Holt-Winters pass with fixed smoothing parameters. The
|
|
12765
|
+
// initial level is the level one step *before* the series (first value
|
|
12766
|
+
// or first-cycle mean, minus the trend), so the one-step-ahead prediction
|
|
12767
|
+
// of the first observation is the observation itself and a perfectly
|
|
12768
|
+
// linear history has zero residual — an initial level *at* the first
|
|
12769
|
+
// value predicted y₀ + trend for y₀, and that spurious residual pulled
|
|
12770
|
+
// the grid search off the exact continuation (10,20,…,100 → 109.56
|
|
12771
|
+
// instead of Excel's 110). rowsncolumns/spreadsheet#642 B-01.
|
|
12772
|
+
const runHoltWinters = (y, period, alpha, beta, gamma) => {
|
|
12677
12773
|
const n = y.length;
|
|
12678
12774
|
const m = period;
|
|
12679
12775
|
const fitted = new Array(n).fill(null);
|
|
@@ -12681,21 +12777,21 @@ const runHoltWinters = (y, period, alpha, beta, gamma, horizon) => {
|
|
|
12681
12777
|
const seasonal = new Array(m).fill(0);
|
|
12682
12778
|
if (m > 1 && n >= 2 * m) {
|
|
12683
12779
|
// Initial seasonal indices = first-cycle deviations from the
|
|
12684
|
-
// first-cycle mean. Initial
|
|
12685
|
-
//
|
|
12686
|
-
//
|
|
12780
|
+
// first-cycle mean. Initial trend = average of per-step changes
|
|
12781
|
+
// between first and second cycle, normalized by m. Initial level
|
|
12782
|
+
// = mean of first cycle, one trend step back.
|
|
12687
12783
|
let sum1 = 0;
|
|
12688
12784
|
for (let i = 0; i < m; i++) sum1 += y[i];
|
|
12689
12785
|
const mean1 = sum1 / m;
|
|
12690
|
-
level = mean1;
|
|
12691
12786
|
let trendSum = 0;
|
|
12692
12787
|
for (let i = 0; i < m; i++) trendSum += (y[i + m] - y[i]) / m;
|
|
12693
12788
|
trend = trendSum / m;
|
|
12789
|
+
level = mean1 - trend;
|
|
12694
12790
|
for (let i = 0; i < m; i++) seasonal[i] = y[i] - mean1;
|
|
12695
12791
|
} else {
|
|
12696
|
-
// Holt linear initialization.
|
|
12697
|
-
level = y[0];
|
|
12792
|
+
// Holt linear initialization, one trend step back.
|
|
12698
12793
|
trend = n > 1 ? y[1] - y[0] : 0;
|
|
12794
|
+
level = y[0] - trend;
|
|
12699
12795
|
}
|
|
12700
12796
|
let sse = 0;
|
|
12701
12797
|
let count = 0;
|
|
@@ -12714,12 +12810,12 @@ const runHoltWinters = (y, period, alpha, beta, gamma, horizon) => {
|
|
|
12714
12810
|
seasonal[seasonIdx] = gamma * (y[t] - level) + (1 - gamma) * seasonComp;
|
|
12715
12811
|
}
|
|
12716
12812
|
}
|
|
12717
|
-
const
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12813
|
+
const state = { n, m, level, trend, seasonal };
|
|
12814
|
+
return {
|
|
12815
|
+
fitted,
|
|
12816
|
+
sse: count > 0 ? sse / count : Infinity,
|
|
12817
|
+
forecastAt: (h) => holtWintersForecastAt(state, h),
|
|
12818
|
+
};
|
|
12723
12819
|
};
|
|
12724
12820
|
|
|
12725
12821
|
const percentileInclusive = (values, k) => {
|
|
@@ -13504,9 +13600,11 @@ const DistributionFunctions = {
|
|
|
13504
13600
|
throw FormulaError.NUM;
|
|
13505
13601
|
}
|
|
13506
13602
|
const horizon = Math.round((target - lastX) / step);
|
|
13603
|
+
if (!Number.isFinite(horizon)) throw FormulaError.NUM;
|
|
13507
13604
|
if (horizon < 1) return y[y.length - 1];
|
|
13508
|
-
const
|
|
13509
|
-
|
|
13605
|
+
const forecast = fitHoltWinters(y, period).forecastAt(horizon);
|
|
13606
|
+
if (!Number.isFinite(forecast)) throw FormulaError.NUM;
|
|
13607
|
+
return forecast;
|
|
13510
13608
|
},
|
|
13511
13609
|
|
|
13512
13610
|
'FORECAST.ETS.CONFINT': (targetDate, values, timeline, confidenceArg, seasonalityArg, dataCompletionArg, aggregationArg) => {
|
|
@@ -13522,7 +13620,8 @@ const DistributionFunctions = {
|
|
|
13522
13620
|
const { y, lastX, step, period } = forecastEtsPrep(ys, xs, seasonality);
|
|
13523
13621
|
if (target < lastX) throw FormulaError.NUM;
|
|
13524
13622
|
const horizon = Math.max(1, Math.round((target - lastX) / step));
|
|
13525
|
-
|
|
13623
|
+
if (!Number.isFinite(horizon)) throw FormulaError.NUM;
|
|
13624
|
+
const { fitted } = fitHoltWinters(y, period);
|
|
13526
13625
|
// RMSE of in-sample residuals — z-scaled to the requested
|
|
13527
13626
|
// confidence level. Excel uses √(horizon) scaling on the
|
|
13528
13627
|
// forecast variance.
|
|
@@ -13535,7 +13634,9 @@ const DistributionFunctions = {
|
|
|
13535
13634
|
}
|
|
13536
13635
|
const rmse = n > 0 ? Math.sqrt(sse / n) : 0;
|
|
13537
13636
|
const z = jStat.normal.inv(1 - (1 - confidence) / 2, 0, 1);
|
|
13538
|
-
|
|
13637
|
+
const halfWidth = z * rmse * Math.sqrt(horizon);
|
|
13638
|
+
if (!Number.isFinite(halfWidth)) throw FormulaError.NUM;
|
|
13639
|
+
return halfWidth;
|
|
13539
13640
|
},
|
|
13540
13641
|
|
|
13541
13642
|
'FORECAST.ETS.SEASONALITY': (values, timeline, dataCompletionArg, aggregationArg) => {
|
|
@@ -13554,7 +13655,7 @@ const DistributionFunctions = {
|
|
|
13554
13655
|
const seasonality = forecastEtsSeasonality(seasonalityArg);
|
|
13555
13656
|
forecastEtsValidateOptions(dataCompletionArg, aggregationArg);
|
|
13556
13657
|
const { y, period } = forecastEtsPrep(ys, xs, seasonality);
|
|
13557
|
-
const { alpha, beta, gamma, fitted } = fitHoltWinters(y, period
|
|
13658
|
+
const { alpha, beta, gamma, fitted } = fitHoltWinters(y, period);
|
|
13558
13659
|
// Excel STAT codes:
|
|
13559
13660
|
// 1: alpha 2: beta 3: gamma 4: MASE
|
|
13560
13661
|
// 5: SMAPE 6: MAE 7: RMSE 8: detected step size
|
|
@@ -18015,6 +18116,11 @@ const applyToArray$1 = (value, checkFn) => {
|
|
|
18015
18116
|
* Get the number of values that evaluate to true and false.
|
|
18016
18117
|
* Cast Number and "TRUE", "FALSE" to boolean.
|
|
18017
18118
|
* Ignore unrelated values.
|
|
18119
|
+
* An error value — a direct argument (`AND(1/0, FALSE)`) or a cell inside a
|
|
18120
|
+
* reference — is the result, whatever the other arguments decide: Excel never
|
|
18121
|
+
* short-circuits AND/OR/XOR past an error, and the first one in argument order
|
|
18122
|
+
* wins (rowsncolumns/spreadsheet#642 B-08). `flattenParams` only throws error
|
|
18123
|
+
* values itself when a value type is requested.
|
|
18018
18124
|
* @ignore
|
|
18019
18125
|
* @param {any[]} params
|
|
18020
18126
|
* @return {number[]}
|
|
@@ -18022,6 +18128,8 @@ const applyToArray$1 = (value, checkFn) => {
|
|
|
18022
18128
|
function getNumLogicalValue(params) {
|
|
18023
18129
|
let numTrue = 0, numFalse = 0;
|
|
18024
18130
|
H$4.flattenParams(params, null, true, (val, info) => {
|
|
18131
|
+
if (val instanceof FormulaError)
|
|
18132
|
+
throw val;
|
|
18025
18133
|
const type = typeof val;
|
|
18026
18134
|
if (type === "string") {
|
|
18027
18135
|
// Text inside a reference or array is ignored; a direct text
|
|
@@ -44186,9 +44294,16 @@ const FormulaErrorT$1 = createToken({
|
|
|
44186
44294
|
/#NULL!|#DIV\/0!|#VALUE!|#NAME\?|#NUM!|#N\/A|#GETTING_DATA|#SPILL!|#CONNECT!|#BLOCKED!|#UNKNOWN!|#FIELD!|#CALC!|#EXTERNAL!/,
|
|
44187
44295
|
});
|
|
44188
44296
|
|
|
44297
|
+
// `Table1[#REF!]` / `Table1[@#REF!]` — the text Excel writes once the referenced table column is
|
|
44298
|
+
// deleted — carries `#REF!` as a COLUMN specifier: `longer_alt` lets `TableColumnName` (defined
|
|
44299
|
+
// below) claim `#REF!]` when the error token is directly closed by `]`. A bare `#REF!` elsewhere
|
|
44300
|
+
// (`=SUM(#REF!)`) stays the error literal.
|
|
44301
|
+
// `#REF!#` is what a structural delete leaves of a spilled-range reference whose anchor row/column
|
|
44302
|
+
// went away (`=C1#` → `=#REF!#`); the postfix stays on the error token so the text still parses
|
|
44303
|
+
// (to the `#REF!` error) instead of an `#ERROR!` syntax failure.
|
|
44189
44304
|
const RefError$1 = createToken({
|
|
44190
44305
|
name: "RefError",
|
|
44191
|
-
pattern: /#REF
|
|
44306
|
+
pattern: /#REF!#?/,
|
|
44192
44307
|
});
|
|
44193
44308
|
|
|
44194
44309
|
const Name$1 = createToken({
|
|
@@ -44215,12 +44330,17 @@ const Sheet$1 = createToken({
|
|
|
44215
44330
|
// `Sales[Q1 '[est']]` for the column named `Q1 [est]`. Defined ahead of `Cell`
|
|
44216
44331
|
// so it can be its `longer_alt`: a column named like a cell address (`Q1`,
|
|
44217
44332
|
// `FY24`) must lex as the column specifier, not as a cell reference.
|
|
44333
|
+
// `#REF!` (Excel's specifier for a deleted column) is the one `#`-form admitted as a column name;
|
|
44334
|
+
// every other `#…` inside brackets is a `SpecialItem`.
|
|
44218
44335
|
const TableColumnName$1 = createToken({
|
|
44219
44336
|
name: "TableColumnName",
|
|
44220
44337
|
pattern:
|
|
44221
|
-
/(?!,)[\[]?[@]?[\[+]?(?:[@?%A-Za-z_.\d\s(),!@$%^&*+=\-\/$\u007F-\uFFFF]|'[\[\]#'])
|
|
44338
|
+
/(?!,)[\[]?[@]?[\[+]?(?:#REF!|(?:[@?%A-Za-z_.\d\s(),!@$%^&*+=\-\/$\u007F-\uFFFF]|'[\[\]#'])+)\]+(?:(?:\.[a-zA-Z_][a-zA-Z0-9_~?]*(?:\[[0-9]+\])*)+(?:\.(?![^,\s)]))?|\.(?![^,\s)]))?/,
|
|
44222
44339
|
});
|
|
44223
44340
|
|
|
44341
|
+
// Declared after `TableColumnName` exists (see the `RefError` note above).
|
|
44342
|
+
RefError$1.LONGER_ALT = TableColumnName$1;
|
|
44343
|
+
|
|
44224
44344
|
const Cell$1 = createToken({
|
|
44225
44345
|
name: "Cell",
|
|
44226
44346
|
// Trailing `#` is Excel's "spilled-range operator": e.g. `A1#` resolves
|
|
@@ -44886,7 +45006,8 @@ class Parser extends EmbeddedActionsParser {
|
|
|
44886
45006
|
},
|
|
44887
45007
|
{
|
|
44888
45008
|
ALT: () => {
|
|
44889
|
-
|
|
45009
|
+
// `#REF!#` (a broken spilled-range reference) is the `#REF!` error.
|
|
45010
|
+
const err = $.CONSUME(RefError).image.replace(/#$/, "");
|
|
44890
45011
|
return $.ACTION(() => this.utils.toError(err));
|
|
44891
45012
|
},
|
|
44892
45013
|
},
|
|
@@ -45073,7 +45194,8 @@ class Parser extends EmbeddedActionsParser {
|
|
|
45073
45194
|
// parse the number to row number when needed.
|
|
45074
45195
|
{
|
|
45075
45196
|
ALT: () => {
|
|
45076
|
-
|
|
45197
|
+
// `#REF!#` (a broken spilled-range reference) is the `#REF!` error.
|
|
45198
|
+
const err = $.CONSUME(RefError).image.replace(/#$/, "");
|
|
45077
45199
|
return $.ACTION(() => this.utils.toError(err));
|
|
45078
45200
|
},
|
|
45079
45201
|
},
|