@rowsncolumns/fast-formula-parser 6.2.36 → 6.2.37
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 +540 -93
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +540 -94
- package/dist/esm/index.js.map +1 -1
- package/index.d.ts +12 -0
- package/package.json +3 -3
package/dist/cjs/index.js
CHANGED
|
@@ -3343,6 +3343,15 @@ function formatValue (value, parseData, opts) {
|
|
|
3343
3343
|
return part ? runPart(value, part, opts, l10n) : opts.overflow;
|
|
3344
3344
|
}
|
|
3345
3345
|
|
|
3346
|
+
function isDate (partitions) {
|
|
3347
|
+
return !!(
|
|
3348
|
+
(partitions[0] && partitions[0].date) ||
|
|
3349
|
+
(partitions[1] && partitions[1].date) ||
|
|
3350
|
+
(partitions[2] && partitions[2].date) ||
|
|
3351
|
+
(partitions[3] && partitions[3].date)
|
|
3352
|
+
);
|
|
3353
|
+
}
|
|
3354
|
+
|
|
3346
3355
|
const tokenHandlers = [
|
|
3347
3356
|
[ TOKEN_GENERAL, /^General/i, 0 ],
|
|
3348
3357
|
[ TOKEN_HASH, /^#/, 0 ],
|
|
@@ -3780,6 +3789,22 @@ function format (pattern, value, options = {}) {
|
|
|
3780
3789
|
return formatValue(val, data, opts);
|
|
3781
3790
|
}
|
|
3782
3791
|
|
|
3792
|
+
// FIXME: what is a a section?...
|
|
3793
|
+
/**
|
|
3794
|
+
* Determine if a given format pattern is a date pattern.
|
|
3795
|
+
*
|
|
3796
|
+
* The pattern is considered a date pattern if any of its sections contain a
|
|
3797
|
+
* date symbol (such as `Y` or `H`). Each section is restricted to be
|
|
3798
|
+
* _either_ a number or date format.
|
|
3799
|
+
*
|
|
3800
|
+
* @param {string} pattern - A format pattern in the ECMA-376 number format.
|
|
3801
|
+
* @returns {boolean} True if the specified pattern is date pattern, False otherwise.
|
|
3802
|
+
*/
|
|
3803
|
+
function isDateFormat (pattern) {
|
|
3804
|
+
const data = prepareFormatterData(pattern, false);
|
|
3805
|
+
return isDate(data.partitions);
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3783
3808
|
// Excel indexed colors mapping used for legacy color references
|
|
3784
3809
|
const IndexedColors = Object.freeze([
|
|
3785
3810
|
// 0-7
|
|
@@ -3965,6 +3990,148 @@ const DEFAULT_SEPARATORS = { decimal: ".", group: "," };
|
|
|
3965
3990
|
const separatorsFor = (locale) => {
|
|
3966
3991
|
return DEFAULT_SEPARATORS;
|
|
3967
3992
|
};
|
|
3993
|
+
/**
|
|
3994
|
+
* Rewrite the `[$symbol-LCID]` tokens of one section through `rewrite`,
|
|
3995
|
+
* which receives the bracket content (`€-407`) and returns the new content,
|
|
3996
|
+
* or an empty string to drop the token. Quoted literals, escapes and other
|
|
3997
|
+
* bracket directives are left as written.
|
|
3998
|
+
*/
|
|
3999
|
+
const rewriteSectionLocaleTokens = (section, rewrite) => {
|
|
4000
|
+
let out = "";
|
|
4001
|
+
let inQuotes = false;
|
|
4002
|
+
let inBrackets = false;
|
|
4003
|
+
for (let i = 0; i < section.length; i++) {
|
|
4004
|
+
const char = section[i];
|
|
4005
|
+
if (inQuotes) {
|
|
4006
|
+
inQuotes = char !== '"';
|
|
4007
|
+
}
|
|
4008
|
+
else if (inBrackets) {
|
|
4009
|
+
inBrackets = char !== "]";
|
|
4010
|
+
}
|
|
4011
|
+
else if (char === '"') {
|
|
4012
|
+
inQuotes = true;
|
|
4013
|
+
}
|
|
4014
|
+
else if (char === "[") {
|
|
4015
|
+
const end = section[i + 1] === "$" ? section.indexOf("]", i) : -1;
|
|
4016
|
+
if (end > 0) {
|
|
4017
|
+
const token = rewrite(section.slice(i + 1, end));
|
|
4018
|
+
if (token)
|
|
4019
|
+
out += `[${token}]`;
|
|
4020
|
+
i = end;
|
|
4021
|
+
continue;
|
|
4022
|
+
}
|
|
4023
|
+
inBrackets = true;
|
|
4024
|
+
}
|
|
4025
|
+
else if (char === "\\" || char === "_" || char === "*") {
|
|
4026
|
+
out += char + (section[i + 1] ?? "");
|
|
4027
|
+
i++;
|
|
4028
|
+
continue;
|
|
4029
|
+
}
|
|
4030
|
+
out += char;
|
|
4031
|
+
}
|
|
4032
|
+
return out;
|
|
4033
|
+
};
|
|
4034
|
+
/** `[$€-407]` → `[$€]`; a bare `[$-407]` goes away. */
|
|
4035
|
+
const currencySymbolOnly = (token) => {
|
|
4036
|
+
const dash = token.indexOf("-");
|
|
4037
|
+
const symbol = dash < 0 ? token : token.slice(0, dash);
|
|
4038
|
+
return symbol.length > 1 ? symbol : "";
|
|
4039
|
+
};
|
|
4040
|
+
const separatorLocales = new Map();
|
|
4041
|
+
/**
|
|
4042
|
+
* Companion numfmt locale carrying `locale`'s calendar names and AM/PM with
|
|
4043
|
+
* the workbook's decimal / group separators, so a date section pinned to
|
|
4044
|
+
* `[$-407]` renders German names but a workbook decimal mark in `ss.00`.
|
|
4045
|
+
* Returns `locale` itself when its separators already match.
|
|
4046
|
+
*/
|
|
4047
|
+
const getWorkbookSeparatorLocale = (locale, separators) => {
|
|
4048
|
+
const key = `${locale}\u0000${separators.decimal}\u0000${separators.group}`;
|
|
4049
|
+
const cached = separatorLocales.get(key);
|
|
4050
|
+
if (cached !== undefined)
|
|
4051
|
+
return cached;
|
|
4052
|
+
let overlay = locale;
|
|
4053
|
+
try {
|
|
4054
|
+
const base = getLocale(locale);
|
|
4055
|
+
if (base &&
|
|
4056
|
+
(base.decimal !== separators.decimal || base.group !== separators.group)) {
|
|
4057
|
+
// numfmt locale ids are `[a-z\d]+` with one optional `_` part.
|
|
4058
|
+
const hex = (text) => [...text].map((c) => c.codePointAt(0).toString(16)).join("x");
|
|
4059
|
+
overlay = `sep${hex(separators.decimal)}x${hex(separators.group)}_${locale.replace(/[^a-z\d]/gi, "")}`;
|
|
4060
|
+
addLocale({ ...base, decimal: separators.decimal, group: separators.group }, overlay);
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
catch {
|
|
4064
|
+
overlay = locale;
|
|
4065
|
+
}
|
|
4066
|
+
separatorLocales.set(key, overlay);
|
|
4067
|
+
return overlay;
|
|
4068
|
+
};
|
|
4069
|
+
/**
|
|
4070
|
+
* Re-point a date section's `[$symbol-LCID]` token at the companion locale
|
|
4071
|
+
* with the workbook's separators (`$-407` → `$-sep2ex2c_407`). An LCID numfmt
|
|
4072
|
+
* does not know or one carrying calendar bits stays as written.
|
|
4073
|
+
*/
|
|
4074
|
+
const workbookSeparatorLocaleToken = (separators) => (token) => {
|
|
4075
|
+
const dash = token.indexOf("-");
|
|
4076
|
+
if (dash < 0)
|
|
4077
|
+
return token;
|
|
4078
|
+
const code = token.slice(dash + 1);
|
|
4079
|
+
const wincode = Number.parseInt(code, 16);
|
|
4080
|
+
if (Number.isFinite(wincode) && wincode & 0xff0000)
|
|
4081
|
+
return token;
|
|
4082
|
+
if (!getLocale(code))
|
|
4083
|
+
return token;
|
|
4084
|
+
return `${token.slice(0, dash + 1)}${getWorkbookSeparatorLocale(code, separators)}`;
|
|
4085
|
+
};
|
|
4086
|
+
const isDateSection = (section) => {
|
|
4087
|
+
try {
|
|
4088
|
+
return isDateFormat(section);
|
|
4089
|
+
}
|
|
4090
|
+
catch {
|
|
4091
|
+
return false;
|
|
4092
|
+
}
|
|
4093
|
+
};
|
|
4094
|
+
/**
|
|
4095
|
+
* Excel's `[$symbol-LCID]` token selects the currency symbol and the
|
|
4096
|
+
* language of day/month names and AM/PM; digit grouping and the decimal
|
|
4097
|
+
* separator follow the workbook locale (`[$€-407] #,##0.00` is
|
|
4098
|
+
* `€ 1,234.50` on a US host). numfmt applies the token's locale to the whole
|
|
4099
|
+
* section, separators included, so the locale part is dropped from every
|
|
4100
|
+
* section that is not a date (number, scientific, fraction, text, …); a
|
|
4101
|
+
* date section keeps the token for its names, re-pointed at a companion
|
|
4102
|
+
* locale that carries the workbook's separators for fractional seconds.
|
|
4103
|
+
* Returns `undefined` when nothing had to change.
|
|
4104
|
+
*/
|
|
4105
|
+
const withWorkbookSeparators = (pattern, locale) => {
|
|
4106
|
+
if (!pattern.includes("[$"))
|
|
4107
|
+
return undefined;
|
|
4108
|
+
const sections = splitFormatSections(pattern);
|
|
4109
|
+
let changed = false;
|
|
4110
|
+
let dateTokens;
|
|
4111
|
+
const rewritten = sections.map((section) => {
|
|
4112
|
+
if (!section.includes("[$"))
|
|
4113
|
+
return section;
|
|
4114
|
+
let rewrite = currencySymbolOnly;
|
|
4115
|
+
if (isDateSection(section)) {
|
|
4116
|
+
dateTokens ??= workbookSeparatorLocaleToken(separatorsFor());
|
|
4117
|
+
rewrite = dateTokens;
|
|
4118
|
+
}
|
|
4119
|
+
const stripped = rewriteSectionLocaleTokens(section, rewrite);
|
|
4120
|
+
changed ||= stripped !== section;
|
|
4121
|
+
return stripped;
|
|
4122
|
+
});
|
|
4123
|
+
return changed ? rewritten.join(";") : undefined;
|
|
4124
|
+
};
|
|
4125
|
+
const workbookSeparatorPatternCache = new Map();
|
|
4126
|
+
const withWorkbookSeparatorsCached = (code, locale) => {
|
|
4127
|
+
const key = `${""}\u0000${code}`;
|
|
4128
|
+
let rewritten = workbookSeparatorPatternCache.get(key);
|
|
4129
|
+
if (rewritten === undefined) {
|
|
4130
|
+
rewritten = withWorkbookSeparators(code) ?? code;
|
|
4131
|
+
workbookSeparatorPatternCache.set(key, rewritten);
|
|
4132
|
+
}
|
|
4133
|
+
return rewritten;
|
|
4134
|
+
};
|
|
3968
4135
|
const ssfFormat = (pattern, value, locale) => {
|
|
3969
4136
|
try {
|
|
3970
4137
|
let shortAmPm = shortAmPmPatternCache.get(pattern);
|
|
@@ -3972,7 +4139,7 @@ const ssfFormat = (pattern, value, locale) => {
|
|
|
3972
4139
|
shortAmPm = withShortAmPmMarker(pattern);
|
|
3973
4140
|
shortAmPmPatternCache.set(pattern, shortAmPm);
|
|
3974
4141
|
}
|
|
3975
|
-
const code = shortAmPm ?? pattern;
|
|
4142
|
+
const code = withWorkbookSeparatorsCached(shortAmPm ?? pattern, locale);
|
|
3976
4143
|
let patternLocale = localeForPattern(pattern, locale);
|
|
3977
4144
|
if (shortAmPm !== undefined && patternLocale) {
|
|
3978
4145
|
patternLocale = getShortAmPmLocale(patternLocale);
|
|
@@ -4675,10 +4842,12 @@ class FormulaHelpersClass {
|
|
|
4675
4842
|
!isRaw && Object.prototype.hasOwnProperty.call(param, "value");
|
|
4676
4843
|
if (hasValueProp) {
|
|
4677
4844
|
if (param.value == null) {
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4845
|
+
// A blank cell is not an omitted argument (only omission takes
|
|
4846
|
+
// `defValue`): Excel reads it as "" in a text slot, 0 in a number
|
|
4847
|
+
// slot and FALSE in a logical slot.
|
|
4848
|
+
if (type === Types.STRING) return "";
|
|
4849
|
+
if (type === Types.NUMBER || type === Types.NUMBER_NO_BOOLEAN) return 0;
|
|
4850
|
+
if (type === Types.BOOLEAN) return false;
|
|
4682
4851
|
param = param.value;
|
|
4683
4852
|
} else {
|
|
4684
4853
|
param = param.value;
|
|
@@ -4758,6 +4927,7 @@ class FormulaHelpersClass {
|
|
|
4758
4927
|
* @param range2
|
|
4759
4928
|
*/
|
|
4760
4929
|
retrieveRanges(context, range1, range2) {
|
|
4930
|
+
if (range1 === undefined) throw FormulaError.ARG_MISSING([Types.ARRAY]);
|
|
4761
4931
|
// process args
|
|
4762
4932
|
range2 = Address.extend(range1, range2);
|
|
4763
4933
|
|
|
@@ -4774,6 +4944,9 @@ class FormulaHelpersClass {
|
|
|
4774
4944
|
}
|
|
4775
4945
|
|
|
4776
4946
|
retrieveArg(context, arg) {
|
|
4947
|
+
// `null` is an explicitly skipped argument (`SUMIF(A1:A5,,B1:B5)`);
|
|
4948
|
+
// `undefined` is one the call never supplied — too few arguments.
|
|
4949
|
+
if (arg === undefined) throw FormulaError.ARG_MISSING([Types.ARRAY]);
|
|
4777
4950
|
if (arg === null) return { value: 0, isArray: false, omitted: true };
|
|
4778
4951
|
const res = context.utils.extractRefValue(arg);
|
|
4779
4952
|
return { value: res.val, isArray: res.isArray, ref: arg.ref };
|
|
@@ -6343,33 +6516,48 @@ const TextFunctions = {
|
|
|
6343
6516
|
return text.slice(0, m.index) + expanded + text.slice(m.index + m[0].length);
|
|
6344
6517
|
},
|
|
6345
6518
|
|
|
6519
|
+
/**
|
|
6520
|
+
* JOIN(delimiter, value_or_array1, [value_or_array2, …]) — Google Sheets.
|
|
6521
|
+
* Ranges / arrays contribute every element; blanks and empty text are
|
|
6522
|
+
* skipped; values spell as TEXTJOIN spells them (TRUE, 15-digit numbers) and
|
|
6523
|
+
* an error among the values propagates. The rnc engine mirrors these rules.
|
|
6524
|
+
*/
|
|
6346
6525
|
JOIN: (delimiter, ...values) => {
|
|
6347
6526
|
delimiter = H$a.accept(delimiter, Types.STRING);
|
|
6348
6527
|
const result = [];
|
|
6349
6528
|
|
|
6350
|
-
H$a.flattenParams(values,
|
|
6351
|
-
if (item
|
|
6352
|
-
|
|
6353
|
-
|
|
6529
|
+
H$a.flattenParams(values, Types.STRING, true, (item) => {
|
|
6530
|
+
if (item === undefined || item === null) return;
|
|
6531
|
+
const str = H$a.accept(item, Types.STRING);
|
|
6532
|
+
if (str !== "") result.push(str);
|
|
6354
6533
|
});
|
|
6355
6534
|
|
|
6356
6535
|
return result.join(delimiter);
|
|
6357
6536
|
},
|
|
6358
6537
|
|
|
6538
|
+
/**
|
|
6539
|
+
* SPLIT(text, delimiter, [split_by_each=TRUE], [remove_empty_text=TRUE]) —
|
|
6540
|
+
* Google Sheets. Pieces stay text (no number coercion); an empty delimiter
|
|
6541
|
+
* is #VALUE!; when every piece is removed the result is empty text. The
|
|
6542
|
+
* rnc engine mirrors these rules.
|
|
6543
|
+
*/
|
|
6359
6544
|
SPLIT: (text, delimiter, splitByEach, removeEmpty) => {
|
|
6360
6545
|
text = H$a.accept(text, Types.STRING);
|
|
6361
6546
|
delimiter = H$a.accept(delimiter, Types.STRING);
|
|
6362
6547
|
splitByEach = H$a.accept(splitByEach, Types.BOOLEAN, true);
|
|
6363
6548
|
removeEmpty = H$a.accept(removeEmpty, Types.BOOLEAN, true);
|
|
6549
|
+
if (delimiter === "") throw FormulaError.VALUE;
|
|
6364
6550
|
|
|
6365
6551
|
let parts;
|
|
6366
6552
|
if (splitByEach) {
|
|
6367
|
-
//
|
|
6368
|
-
|
|
6369
|
-
const
|
|
6370
|
-
|
|
6371
|
-
)
|
|
6372
|
-
|
|
6553
|
+
// Every character of the delimiter is a separator — a literal character,
|
|
6554
|
+
// never a RegExp class ("d" must not become \d, "b" must not become \b).
|
|
6555
|
+
const separators = new Set(delimiter);
|
|
6556
|
+
parts = [""];
|
|
6557
|
+
for (const ch of text) {
|
|
6558
|
+
if (separators.has(ch)) parts.push("");
|
|
6559
|
+
else parts[parts.length - 1] += ch;
|
|
6560
|
+
}
|
|
6373
6561
|
} else {
|
|
6374
6562
|
// Split by the entire delimiter string
|
|
6375
6563
|
parts = text.split(delimiter);
|
|
@@ -6378,6 +6566,7 @@ const TextFunctions = {
|
|
|
6378
6566
|
if (removeEmpty) {
|
|
6379
6567
|
parts = parts.filter((part) => part !== "");
|
|
6380
6568
|
}
|
|
6569
|
+
if (parts.length === 0) return "";
|
|
6381
6570
|
|
|
6382
6571
|
// Return as a single row array
|
|
6383
6572
|
return [parts];
|
|
@@ -12353,15 +12542,48 @@ const collectNumericValues = (array) => {
|
|
|
12353
12542
|
// FORECAST.ETS helpers — Holt-Winters AAA implementation.
|
|
12354
12543
|
// ---------------------------------------------------------------------
|
|
12355
12544
|
|
|
12545
|
+
// Excel's `seasonality` argument: 1 (the default) auto-detects the
|
|
12546
|
+
// period, 0 fits without a seasonal component, 2..8760 forces the
|
|
12547
|
+
// period; anything else is #NUM!. Fractions are truncated.
|
|
12548
|
+
const ETS_MAX_SEASONALITY = 8760;
|
|
12549
|
+
const forecastEtsSeasonality = (seasonalityArg) => {
|
|
12550
|
+
if (seasonalityArg == null) return 1;
|
|
12551
|
+
const value = H$8.accept(seasonalityArg, Types.NUMBER, 1);
|
|
12552
|
+
if (value == null) return 1;
|
|
12553
|
+
const seasonality = Math.floor(value);
|
|
12554
|
+
if (seasonality < 0 || seasonality > ETS_MAX_SEASONALITY) throw FormulaError.NUM;
|
|
12555
|
+
return seasonality;
|
|
12556
|
+
};
|
|
12557
|
+
|
|
12558
|
+
// `data_completion` must be 0 or 1 and `aggregation` 1..7 (AVERAGE,
|
|
12559
|
+
// COUNT, COUNTA, MAX, MEDIAN, MIN, SUM), otherwise #NUM!. Neither
|
|
12560
|
+
// changes the fit: the timeline is used as given (no gap filling,
|
|
12561
|
+
// duplicates are not aggregated).
|
|
12562
|
+
const forecastEtsValidateOptions = (dataCompletionArg, aggregationArg) => {
|
|
12563
|
+
if (dataCompletionArg != null) {
|
|
12564
|
+
const completion = Math.floor(H$8.accept(dataCompletionArg, Types.NUMBER, 1));
|
|
12565
|
+
if (completion !== 0 && completion !== 1) throw FormulaError.NUM;
|
|
12566
|
+
}
|
|
12567
|
+
if (aggregationArg != null) {
|
|
12568
|
+
const aggregation = Math.floor(H$8.accept(aggregationArg, Types.NUMBER, 1));
|
|
12569
|
+
if (aggregation < 1 || aggregation > 7) throw FormulaError.NUM;
|
|
12570
|
+
}
|
|
12571
|
+
};
|
|
12572
|
+
|
|
12356
12573
|
// Pair up timeline + values, sort by timeline, infer the constant
|
|
12357
12574
|
// step (Excel requires a uniformly-spaced timeline), and resolve
|
|
12358
|
-
// the seasonality parameter (auto-
|
|
12575
|
+
// the seasonality parameter (Excel spelling: 1 auto-detects, 0 is no
|
|
12576
|
+
// seasonality, n > 1 is the period).
|
|
12359
12577
|
const forecastEtsPrep = (ys, xs, seasonality) => {
|
|
12360
12578
|
if (xs.length !== ys.length) throw FormulaError.NA;
|
|
12361
12579
|
const pairs = [];
|
|
12362
12580
|
for (let i = 0; i < xs.length; i++) {
|
|
12363
12581
|
const x = xs[i];
|
|
12364
12582
|
const yv = ys[i];
|
|
12583
|
+
// An error anywhere in the series is the result (Excel and the Rust
|
|
12584
|
+
// port's `series_number`); other non-numbers (text, blanks) are skipped.
|
|
12585
|
+
if (x instanceof FormulaError) throw x;
|
|
12586
|
+
if (yv instanceof FormulaError) throw yv;
|
|
12365
12587
|
if (typeof x !== "number") continue;
|
|
12366
12588
|
if (typeof yv !== "number") continue;
|
|
12367
12589
|
pairs.push([x, yv]);
|
|
@@ -12382,12 +12604,12 @@ const forecastEtsPrep = (ys, xs, seasonality) => {
|
|
|
12382
12604
|
diffs.sort((a, b) => a - b);
|
|
12383
12605
|
const step = diffs[Math.floor(diffs.length / 2)];
|
|
12384
12606
|
let period;
|
|
12385
|
-
if (seasonality ===
|
|
12607
|
+
if (seasonality === 0) {
|
|
12386
12608
|
period = 1; // No seasonality (linear Holt).
|
|
12387
|
-
} else if (seasonality
|
|
12388
|
-
period = seasonality;
|
|
12389
|
-
} else {
|
|
12609
|
+
} else if (seasonality === 1) {
|
|
12390
12610
|
period = detectSeasonality(y);
|
|
12611
|
+
} else {
|
|
12612
|
+
period = seasonality;
|
|
12391
12613
|
}
|
|
12392
12614
|
return { y, lastX: sortedX[sortedX.length - 1], step, period };
|
|
12393
12615
|
};
|
|
@@ -13267,20 +13489,18 @@ const DistributionFunctions = {
|
|
|
13267
13489
|
// trend. We use the same idea — scan candidate periods 2..N/2,
|
|
13268
13490
|
// capped at 12 (Excel's default cap for monthly data), and pick
|
|
13269
13491
|
// the one with the lowest in-sample SSE for the fitted Holt-Winters
|
|
13270
|
-
// model.
|
|
13271
|
-
// linear).
|
|
13492
|
+
// model. `seasonality` follows Excel: 1 (default) auto-detects, 0
|
|
13493
|
+
// forces "no seasonality" (Holt linear), n > 1 is the period. The
|
|
13494
|
+
// Rust engine (formualizer `stats/forecast_ets.rs`) is a port of this
|
|
13495
|
+
// code, so both engines agree to 1e-9 on the same inputs.
|
|
13272
13496
|
// -------------------------------------------------------------------
|
|
13273
13497
|
|
|
13274
13498
|
'FORECAST.ETS': (targetDate, values, timeline, seasonalityArg, dataCompletionArg, aggregationArg) => {
|
|
13275
13499
|
const target = H$8.accept(targetDate, Types.NUMBER);
|
|
13276
13500
|
const ys = H$8.accept(values, Types.ARRAY, undefined, true, true);
|
|
13277
13501
|
const xs = H$8.accept(timeline, Types.ARRAY, undefined, true, true);
|
|
13278
|
-
const seasonality = seasonalityArg
|
|
13279
|
-
|
|
13280
|
-
: Math.floor(H$8.accept(seasonalityArg, Types.NUMBER, 1));
|
|
13281
|
-
// dataCompletion + aggregation honored only at the prep step —
|
|
13282
|
-
// gap-filling uses the previous value (matches Excel's
|
|
13283
|
-
// dataCompletion=1 default).
|
|
13502
|
+
const seasonality = forecastEtsSeasonality(seasonalityArg);
|
|
13503
|
+
forecastEtsValidateOptions(dataCompletionArg, aggregationArg);
|
|
13284
13504
|
const { y, lastX, step, period } = forecastEtsPrep(ys, xs, seasonality);
|
|
13285
13505
|
if (target < lastX) {
|
|
13286
13506
|
// Excel returns #NUM! for in-history targets in FORECAST.ETS
|
|
@@ -13293,7 +13513,7 @@ const DistributionFunctions = {
|
|
|
13293
13513
|
return forecast[horizon - 1];
|
|
13294
13514
|
},
|
|
13295
13515
|
|
|
13296
|
-
'FORECAST.ETS.CONFINT': (targetDate, values, timeline, confidenceArg, seasonalityArg) => {
|
|
13516
|
+
'FORECAST.ETS.CONFINT': (targetDate, values, timeline, confidenceArg, seasonalityArg, dataCompletionArg, aggregationArg) => {
|
|
13297
13517
|
const target = H$8.accept(targetDate, Types.NUMBER);
|
|
13298
13518
|
const ys = H$8.accept(values, Types.ARRAY, undefined, true, true);
|
|
13299
13519
|
const xs = H$8.accept(timeline, Types.ARRAY, undefined, true, true);
|
|
@@ -13301,9 +13521,8 @@ const DistributionFunctions = {
|
|
|
13301
13521
|
? 0.95
|
|
13302
13522
|
: H$8.accept(confidenceArg, Types.NUMBER, 0.95);
|
|
13303
13523
|
if (confidence <= 0 || confidence >= 1) throw FormulaError.NUM;
|
|
13304
|
-
const seasonality = seasonalityArg
|
|
13305
|
-
|
|
13306
|
-
: Math.floor(H$8.accept(seasonalityArg, Types.NUMBER, 1));
|
|
13524
|
+
const seasonality = forecastEtsSeasonality(seasonalityArg);
|
|
13525
|
+
forecastEtsValidateOptions(dataCompletionArg, aggregationArg);
|
|
13307
13526
|
const { y, lastX, step, period } = forecastEtsPrep(ys, xs, seasonality);
|
|
13308
13527
|
if (target < lastX) throw FormulaError.NUM;
|
|
13309
13528
|
const horizon = Math.max(1, Math.round((target - lastX) / step));
|
|
@@ -13323,21 +13542,21 @@ const DistributionFunctions = {
|
|
|
13323
13542
|
return z * rmse * Math.sqrt(horizon);
|
|
13324
13543
|
},
|
|
13325
13544
|
|
|
13326
|
-
'FORECAST.ETS.SEASONALITY': (values, timeline) => {
|
|
13545
|
+
'FORECAST.ETS.SEASONALITY': (values, timeline, dataCompletionArg, aggregationArg) => {
|
|
13327
13546
|
const ys = H$8.accept(values, Types.ARRAY, undefined, true, true);
|
|
13328
13547
|
const xs = H$8.accept(timeline, Types.ARRAY, undefined, true, true);
|
|
13329
|
-
|
|
13330
|
-
|
|
13548
|
+
forecastEtsValidateOptions(dataCompletionArg, aggregationArg);
|
|
13549
|
+
const { period } = forecastEtsPrep(ys, xs, 1);
|
|
13550
|
+
return period;
|
|
13331
13551
|
},
|
|
13332
13552
|
|
|
13333
|
-
'FORECAST.ETS.STAT': (values, timeline, statTypeArg, seasonalityArg) => {
|
|
13553
|
+
'FORECAST.ETS.STAT': (values, timeline, statTypeArg, seasonalityArg, dataCompletionArg, aggregationArg) => {
|
|
13334
13554
|
const ys = H$8.accept(values, Types.ARRAY, undefined, true, true);
|
|
13335
13555
|
const xs = H$8.accept(timeline, Types.ARRAY, undefined, true, true);
|
|
13336
13556
|
const statType = Math.floor(H$8.accept(statTypeArg, Types.NUMBER));
|
|
13337
13557
|
if (statType < 1 || statType > 8) throw FormulaError.NUM;
|
|
13338
|
-
const seasonality = seasonalityArg
|
|
13339
|
-
|
|
13340
|
-
: Math.floor(H$8.accept(seasonalityArg, Types.NUMBER, 1));
|
|
13558
|
+
const seasonality = forecastEtsSeasonality(seasonalityArg);
|
|
13559
|
+
forecastEtsValidateOptions(dataCompletionArg, aggregationArg);
|
|
13341
13560
|
const { y, period } = forecastEtsPrep(ys, xs, seasonality);
|
|
13342
13561
|
const { alpha, beta, gamma, fitted } = fitHoltWinters(y, period, 0);
|
|
13343
13562
|
// Excel STAT codes:
|
|
@@ -14424,6 +14643,9 @@ const DistributionFunctions = {
|
|
|
14424
14643
|
const prediction = truncated.reduce((sum, value, idx) => sum + value * slopes[idx], 0) + intercept;
|
|
14425
14644
|
return [prediction];
|
|
14426
14645
|
});
|
|
14646
|
+
// A single new_x is a plain number in Excel (and in the Rust
|
|
14647
|
+
// engine); a 1x1 array would anchor an array formula on the cell.
|
|
14648
|
+
if (result.length === 1) return result[0][0];
|
|
14427
14649
|
return result;
|
|
14428
14650
|
},
|
|
14429
14651
|
|
|
@@ -15406,7 +15628,7 @@ const StatisticalFunctions = {
|
|
|
15406
15628
|
COUNTIF: (range, criteria) => {
|
|
15407
15629
|
// do not flatten the array
|
|
15408
15630
|
range = H$7.accept(range, Types.ARRAY, undefined, false, true);
|
|
15409
|
-
const isCriteriaArray = criteria
|
|
15631
|
+
const isCriteriaArray = criteria?.isArray;
|
|
15410
15632
|
criteria = H$7.accept(criteria);
|
|
15411
15633
|
|
|
15412
15634
|
const countMatching = (parsed) => {
|
|
@@ -16721,7 +16943,15 @@ const MathFunctions = {
|
|
|
16721
16943
|
FLOOR: (number, significance) => {
|
|
16722
16944
|
number = H$6.accept(number, Types.NUMBER);
|
|
16723
16945
|
significance = H$6.accept(significance, Types.NUMBER, 1);
|
|
16724
|
-
|
|
16946
|
+
// Excel: a zero significance is #DIV/0! for FLOOR of a non-zero number
|
|
16947
|
+
// (FLOOR(0,0) is 0; CEILING, FLOOR.MATH and FLOOR.PRECISE return 0); a
|
|
16948
|
+
// positive number with a negative significance is #NUM!; a negative
|
|
16949
|
+
// number rounds away from zero unless the significance is negative too
|
|
16950
|
+
// (FLOOR(-2.5,2) = -4, FLOOR(-2.5,-2) = -2).
|
|
16951
|
+
if (significance === 0) {
|
|
16952
|
+
if (number === 0) return 0;
|
|
16953
|
+
throw FormulaError.DIV0;
|
|
16954
|
+
}
|
|
16725
16955
|
if (number > 0 && significance < 0) throw FormulaError.NUM;
|
|
16726
16956
|
const absSignificance = Math.abs(significance);
|
|
16727
16957
|
const quotient = excelQuotient(Math.abs(number), absSignificance);
|
|
@@ -16746,6 +16976,7 @@ const MathFunctions = {
|
|
|
16746
16976
|
// mode can be 0 or any other number, 0 means away from zero
|
|
16747
16977
|
// the official documentation seems wrong
|
|
16748
16978
|
mode = H$6.accept(mode, Types.NUMBER, 0);
|
|
16979
|
+
if (significance === 0) return 0;
|
|
16749
16980
|
// The Mode argument does not affect positive numbers.
|
|
16750
16981
|
if (mode === 0 || number >= 0) {
|
|
16751
16982
|
// away from zero
|
|
@@ -16758,7 +16989,8 @@ const MathFunctions = {
|
|
|
16758
16989
|
"FLOOR.PRECISE": (number, significance) => {
|
|
16759
16990
|
number = H$6.accept(number, Types.NUMBER);
|
|
16760
16991
|
significance = H$6.accept(significance, Types.NUMBER, 1);
|
|
16761
|
-
//
|
|
16992
|
+
// A zero significance is 0 (Excel); otherwise the mathematical floor.
|
|
16993
|
+
if (significance === 0) return 0;
|
|
16762
16994
|
return MathFunctions.FLOOR(number, Math.abs(significance));
|
|
16763
16995
|
},
|
|
16764
16996
|
|
|
@@ -17264,6 +17496,22 @@ const MathFunctions = {
|
|
|
17264
17496
|
return result;
|
|
17265
17497
|
},
|
|
17266
17498
|
|
|
17499
|
+
/**
|
|
17500
|
+
* PERCENTOF(data_subset, data_all) — SUM(data_subset) / SUM(data_all) under
|
|
17501
|
+
* SUM's rules (text / logical / blank cells inside ranges are skipped, error
|
|
17502
|
+
* values propagate, a non-numeric scalar is #VALUE!); a zero whole is
|
|
17503
|
+
* #DIV/0!. Excel 365's helper for GROUPBY / PIVOTBY percent-of-total.
|
|
17504
|
+
*/
|
|
17505
|
+
PERCENTOF: (dataSubset, dataAll) => {
|
|
17506
|
+
if (H$6.isOmitted(dataSubset) || H$6.isOmitted(dataAll)) {
|
|
17507
|
+
throw FormulaError.ARG_MISSING([Types.NUMBER]);
|
|
17508
|
+
}
|
|
17509
|
+
const subset = MathFunctions.SUM(dataSubset);
|
|
17510
|
+
const all = MathFunctions.SUM(dataAll);
|
|
17511
|
+
if (all === 0) throw FormulaError.DIV0;
|
|
17512
|
+
return subset / all;
|
|
17513
|
+
},
|
|
17514
|
+
|
|
17267
17515
|
/**
|
|
17268
17516
|
* This functions requires instance of {@link FormulaParser}.
|
|
17269
17517
|
*/
|
|
@@ -19850,6 +20098,10 @@ const ReferenceFunctions = {
|
|
|
19850
20098
|
// Retrieve actual data to determine effective column count
|
|
19851
20099
|
const { val: data } = context.utils.extractRefValue(obj, context.position);
|
|
19852
20100
|
const colCount = Array.isArray(data) && Array.isArray(data[0]) ? data[0].length : 1;
|
|
20101
|
+
// One column (`COLUMN(C:C)`, `COLUMN(B2:B5)`) is a plain number, as
|
|
20102
|
+
// Excel shows it and as the Rust engine returns it; a 1x1 array would
|
|
20103
|
+
// make the host anchor an array formula on the cell.
|
|
20104
|
+
if (colCount <= 1) return fromCol;
|
|
19853
20105
|
const result = [];
|
|
19854
20106
|
for (let i = 0; i < colCount; i++) {
|
|
19855
20107
|
result.push(fromCol + i);
|
|
@@ -19866,7 +20118,11 @@ const ReferenceFunctions = {
|
|
|
19866
20118
|
if (obj == null) {
|
|
19867
20119
|
throw Error("COLUMNS requires one argument");
|
|
19868
20120
|
}
|
|
19869
|
-
if (
|
|
20121
|
+
if (obj instanceof FormulaError) throw obj;
|
|
20122
|
+
// An array (`{1,2;3,4}`, `SEQUENCE(3,2)`, `RANDARRAY(3,2)`, a SORT/FILTER
|
|
20123
|
+
// result) reports its width; a scalar is a 1×1 array (`COLUMNS(5)` = 1).
|
|
20124
|
+
if (Array.isArray(obj)) return obj[0]?.length ?? 0;
|
|
20125
|
+
if (typeof obj != "object") return 1;
|
|
19870
20126
|
if (H$2.isCellRef(obj)) {
|
|
19871
20127
|
return 1;
|
|
19872
20128
|
} else if (H$2.isRangeRef(obj)) {
|
|
@@ -19917,6 +20173,7 @@ const ReferenceFunctions = {
|
|
|
19917
20173
|
|
|
19918
20174
|
// Special
|
|
19919
20175
|
INDEX: (context, ranges, rowNum, colNum, areaNum) => {
|
|
20176
|
+
if (ranges === undefined) throw FormulaError.ARG_MISSING([Types.ARRAY]);
|
|
19920
20177
|
// retrieve values
|
|
19921
20178
|
const isColNumMissing = colNum == null;
|
|
19922
20179
|
const isRowNumMissing = rowNum == null;
|
|
@@ -20299,6 +20556,8 @@ const ReferenceFunctions = {
|
|
|
20299
20556
|
// Retrieve actual data to determine effective row count
|
|
20300
20557
|
const { val: data } = context.utils.extractRefValue(obj, context.position);
|
|
20301
20558
|
const rowCount = Array.isArray(data) ? data.length : 1;
|
|
20559
|
+
// One row (`ROW(3:3)`, `ROW(B2:C2)`) is a plain number; see COLUMN.
|
|
20560
|
+
if (rowCount <= 1) return fromRow;
|
|
20302
20561
|
const result = [];
|
|
20303
20562
|
for (let i = 0; i < rowCount; i++) {
|
|
20304
20563
|
result.push([fromRow + i]); // Vertical array (column vector)
|
|
@@ -20315,7 +20574,11 @@ const ReferenceFunctions = {
|
|
|
20315
20574
|
if (obj == null) {
|
|
20316
20575
|
throw Error("ROWS requires one argument");
|
|
20317
20576
|
}
|
|
20318
|
-
if (
|
|
20577
|
+
if (obj instanceof FormulaError) throw obj;
|
|
20578
|
+
// An array (`{1,2;3,4}`, `SEQUENCE(3,2)`, `RANDARRAY(3,2)`, a SORT/FILTER
|
|
20579
|
+
// result) reports its height; a scalar is a 1×1 array (`ROWS(5)` = 1).
|
|
20580
|
+
if (Array.isArray(obj)) return obj.length;
|
|
20581
|
+
if (typeof obj != "object") return 1;
|
|
20319
20582
|
if (H$2.isCellRef(obj)) {
|
|
20320
20583
|
return 1;
|
|
20321
20584
|
} else if (H$2.isRangeRef(obj)) {
|
|
@@ -21117,6 +21380,44 @@ const ReferenceFunctions = {
|
|
|
21117
21380
|
);
|
|
21118
21381
|
},
|
|
21119
21382
|
|
|
21383
|
+
/**
|
|
21384
|
+
* TRIMRANGE(range, [trim_rows], [trim_cols]) — drops the blank rows /
|
|
21385
|
+
* columns at the edges of a range or array: 0 keeps every row / column,
|
|
21386
|
+
* 1 trims leading blanks, 2 trailing blanks, 3 (default) both. Only truly
|
|
21387
|
+
* empty cells are blank (a "" text result or a 0 keeps its row); trimming
|
|
21388
|
+
* everything away is `#REF!`, as in Excel. The function form of Excel's
|
|
21389
|
+
* `A1.:.E10` / `A1:.E10` / `A1.:E10` trim-ref operators.
|
|
21390
|
+
*/
|
|
21391
|
+
TRIMRANGE: (range, trim_rows, trim_cols) => {
|
|
21392
|
+
if (H$2.isOmitted(range)) throw FormulaError.ARG_MISSING([Types.ARRAY]);
|
|
21393
|
+
const mode = (arg) => {
|
|
21394
|
+
if (H$2.isOmitted(arg)) return 3;
|
|
21395
|
+
const n = Math.trunc(H$2.accept(arg, Types.NUMBER, 3));
|
|
21396
|
+
if (n < 0 || n > 3) throw FormulaError.VALUE;
|
|
21397
|
+
return n;
|
|
21398
|
+
};
|
|
21399
|
+
const trimRows = mode(trim_rows);
|
|
21400
|
+
const trimCols = mode(trim_cols);
|
|
21401
|
+
const m = toMatrix(range);
|
|
21402
|
+
const height = m.length;
|
|
21403
|
+
const width = m[0]?.length ?? 0;
|
|
21404
|
+
const isBlank = (v) => v == null;
|
|
21405
|
+
const blankRow = (r) => m[r].every(isBlank);
|
|
21406
|
+
const blankCol = (c) => m.every((row) => isBlank(row[c]));
|
|
21407
|
+
let rowStart = 0;
|
|
21408
|
+
let rowEnd = height;
|
|
21409
|
+
if (trimRows & 1) while (rowStart < rowEnd && blankRow(rowStart)) rowStart++;
|
|
21410
|
+
if (trimRows & 2) while (rowEnd > rowStart && blankRow(rowEnd - 1)) rowEnd--;
|
|
21411
|
+
let colStart = 0;
|
|
21412
|
+
let colEnd = width;
|
|
21413
|
+
if (trimCols & 1) while (colStart < colEnd && blankCol(colStart)) colStart++;
|
|
21414
|
+
if (trimCols & 2) while (colEnd > colStart && blankCol(colEnd - 1)) colEnd--;
|
|
21415
|
+
if (rowStart >= rowEnd || colStart >= colEnd) throw FormulaError.REF;
|
|
21416
|
+
return m
|
|
21417
|
+
.slice(rowStart, rowEnd)
|
|
21418
|
+
.map((row) => row.slice(colStart, colEnd));
|
|
21419
|
+
},
|
|
21420
|
+
|
|
21120
21421
|
/**
|
|
21121
21422
|
* EXPAND(array, rows, [columns], [pad_with]) — grows an array to
|
|
21122
21423
|
* `rows`×`columns`, filling new cells with `pad_with` (default `#N/A`).
|
|
@@ -21223,21 +21524,33 @@ const ReferenceFunctions = {
|
|
|
21223
21524
|
return result;
|
|
21224
21525
|
},
|
|
21225
21526
|
|
|
21527
|
+
/**
|
|
21528
|
+
* SORTN(range, [n], [display_ties_mode], [sort_column], [is_ascending]) —
|
|
21529
|
+
* Google Sheets. SORT on one key column followed by the first n rows;
|
|
21530
|
+
* display_ties_mode is accepted and ignored. A negative n is #VALUE!. The
|
|
21531
|
+
* rnc engine mirrors these rules.
|
|
21532
|
+
*/
|
|
21226
21533
|
SORTN: (context, range, n, displayTies, sortColumn, isAscending) => {
|
|
21227
|
-
range = H$2.accept(range, Types.ARRAY);
|
|
21534
|
+
range = H$2.accept(range, Types.ARRAY, undefined, false, true);
|
|
21228
21535
|
n = H$2.accept(n, Types.NUMBER, range.length);
|
|
21229
21536
|
displayTies = H$2.accept(displayTies, Types.NUMBER, 0);
|
|
21230
21537
|
sortColumn = H$2.accept(sortColumn, Types.NUMBER, 1);
|
|
21231
21538
|
isAscending = H$2.accept(isAscending, Types.BOOLEAN, true);
|
|
21232
|
-
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21237
|
-
|
|
21238
|
-
|
|
21239
|
-
|
|
21240
|
-
|
|
21539
|
+
if (n < 0) throw FormulaError.VALUE;
|
|
21540
|
+
if (!Array.isArray(range) || range.length === 0) return range;
|
|
21541
|
+
// Same stable row sort as SORT's row path (SORT itself re-parses its
|
|
21542
|
+
// arguments as parser params, so it cannot be called with the already
|
|
21543
|
+
// accepted values — doing so made every SORTN call #VALUE!).
|
|
21544
|
+
const width = range[0]?.length ?? 0;
|
|
21545
|
+
if (sortColumn < 1 || sortColumn > width) throw FormulaError.VALUE;
|
|
21546
|
+
const key = sortColumn - 1;
|
|
21547
|
+
const compareValues = makeComparator(context?.locale);
|
|
21548
|
+
const indexed = range.map((row, idx) => ({ row, idx }));
|
|
21549
|
+
indexed.sort((a, b) => {
|
|
21550
|
+
const comparison = compareValues(a.row[key], b.row[key]);
|
|
21551
|
+
return (isAscending ? comparison : -comparison) || a.idx - b.idx;
|
|
21552
|
+
});
|
|
21553
|
+
return indexed.slice(0, n).map((entry) => entry.row);
|
|
21241
21554
|
},
|
|
21242
21555
|
|
|
21243
21556
|
/**
|
|
@@ -25350,6 +25663,10 @@ const FinancialFunctions = {
|
|
|
25350
25663
|
// identifiers. Importers keep the cached result Excel wrote and exporters write
|
|
25351
25664
|
// the formula back unchanged, so a workbook round-trips intact; only a
|
|
25352
25665
|
// recalculation on this engine surfaces the #N/A.
|
|
25666
|
+
//
|
|
25667
|
+
// QUERY is the Google Sheets counterpart: it needs a query-language interpreter
|
|
25668
|
+
// neither engine has, and degrades the same way (the rnc engine registers the
|
|
25669
|
+
// same stub) so a Sheets-authored workbook evaluates identically on both.
|
|
25353
25670
|
const unavailable = () => {
|
|
25354
25671
|
throw FormulaError.NA;
|
|
25355
25672
|
};
|
|
@@ -25369,6 +25686,7 @@ const ExternalFunctions = {
|
|
|
25369
25686
|
EUROCONVERT: unavailable,
|
|
25370
25687
|
TRANSLATE: unavailable,
|
|
25371
25688
|
DETECTLANGUAGE: unavailable,
|
|
25689
|
+
QUERY: unavailable,
|
|
25372
25690
|
};
|
|
25373
25691
|
|
|
25374
25692
|
var api$3 = {};
|
|
@@ -44517,7 +44835,10 @@ class Parser extends EmbeddedActionsParser {
|
|
|
44517
44835
|
) {
|
|
44518
44836
|
return arr[0][0];
|
|
44519
44837
|
}
|
|
44520
|
-
|
|
44838
|
+
const array = this.utils.toArray(arr);
|
|
44839
|
+
return context.async
|
|
44840
|
+
? this.utils.settleArrayItems(array, $.position)
|
|
44841
|
+
: array;
|
|
44521
44842
|
});
|
|
44522
44843
|
});
|
|
44523
44844
|
|
|
@@ -44538,9 +44859,13 @@ class Parser extends EmbeddedActionsParser {
|
|
|
44538
44859
|
const prefix = $.OPTION(() => $.SUBRULE($.plusMinusOp));
|
|
44539
44860
|
const image = $.CONSUME(Number$1).image;
|
|
44540
44861
|
const number = $.ACTION(() => this.utils.toNumber(image));
|
|
44862
|
+
// A signed literal is a plain number: apply the sign directly
|
|
44863
|
+
// rather than through `applyPrefix`, whose async branch would
|
|
44864
|
+
// hand the array a pending Promise (`{-100,60,60}` summed to 120
|
|
44865
|
+
// under `parseAsync`).
|
|
44541
44866
|
if (prefix)
|
|
44542
44867
|
return $.ACTION(() =>
|
|
44543
|
-
this.utils.
|
|
44868
|
+
this.utils.applyLiteralPrefix(prefix, number)
|
|
44544
44869
|
);
|
|
44545
44870
|
return number;
|
|
44546
44871
|
},
|
|
@@ -45177,18 +45502,21 @@ const expandLet = (source) => {
|
|
|
45177
45502
|
|
|
45178
45503
|
|
|
45179
45504
|
const LAMBDA_INTERNAL = "__RNC_LAMBDA__";
|
|
45180
|
-
/** Source spliced in for
|
|
45505
|
+
/** Source spliced in for a parameter the caller left out. */
|
|
45181
45506
|
const OMITTED_INTERNAL = "__RNC_OMITTED__";
|
|
45182
45507
|
const OMITTED_CALL = `${OMITTED_INTERNAL}()`;
|
|
45183
45508
|
/**
|
|
45184
45509
|
* Runtime call of a lambda value: `__RNC_CALL__(lambda, arg1, …)`. Used
|
|
45185
|
-
* when `LAMBDA(...)(...)` cannot be inlined (
|
|
45186
|
-
* evaluator reports `#VALUE!` like Excel instead of a
|
|
45510
|
+
* when `LAMBDA(...)(...)` cannot be inlined (more arguments than
|
|
45511
|
+
* parameters) so the evaluator reports `#VALUE!` like Excel instead of a
|
|
45512
|
+
* parse error.
|
|
45187
45513
|
*/
|
|
45188
45514
|
const CALL_INTERNAL = "__RNC_CALL__";
|
|
45189
45515
|
|
|
45190
|
-
// `[name]` — Excel's optional-parameter
|
|
45516
|
+
// `[name]` — Excel's optional-parameter spelling. The lexer sees it as a
|
|
45191
45517
|
// table-column token; only a bare identifier inside the brackets counts.
|
|
45518
|
+
// The brackets are documentation only: Excel lets a call leave out any
|
|
45519
|
+
// trailing parameter, bracketed or not (ISOMITTED reports it either way).
|
|
45192
45520
|
const OPTIONAL_PARAM = /^\[\s*([A-Za-z_][A-Za-z0-9_.]*)\s*\]$/;
|
|
45193
45521
|
|
|
45194
45522
|
/**
|
|
@@ -45206,8 +45534,6 @@ const paramFromSpan = (tokens, span) => {
|
|
|
45206
45534
|
return null;
|
|
45207
45535
|
};
|
|
45208
45536
|
|
|
45209
|
-
const requiredCount = (params) => params.filter((p) => !p.optional).length;
|
|
45210
|
-
|
|
45211
45537
|
/** Excel writes optional params back as `[name]`; keep that in the value form. */
|
|
45212
45538
|
const paramsToSource = (params) =>
|
|
45213
45539
|
params.map((p) => (p.optional ? `[${p.name}]` : p.name)).join(",");
|
|
@@ -45217,7 +45543,9 @@ const paramsToSource = (params) =>
|
|
|
45217
45543
|
* IIFE inlining: when `LAMBDA(...)(...)` appears, substitute the
|
|
45218
45544
|
* params with the call's arg sources and return the substituted body.
|
|
45219
45545
|
* Returns null if the structure doesn't match (e.g. LAMBDA without
|
|
45220
|
-
* immediate invocation
|
|
45546
|
+
* immediate invocation). A call with more arguments than parameters is
|
|
45547
|
+
* handed to the runtime, which reports `#VALUE!`; a call with fewer binds
|
|
45548
|
+
* the missing parameters to the omitted marker.
|
|
45221
45549
|
*/
|
|
45222
45550
|
const inlineIIFE = (source, tokens, lambdaOpenIdx, lambdaCloseIdx) => {
|
|
45223
45551
|
// Next token after the lambda's close paren must be a bare OpenParen
|
|
@@ -45245,13 +45573,10 @@ const inlineIIFE = (source, tokens, lambdaOpenIdx, lambdaCloseIdx) => {
|
|
|
45245
45573
|
callCloseIdx === callOpenIdx + 1
|
|
45246
45574
|
? []
|
|
45247
45575
|
: splitArgs(tokens, callOpenIdx + 1, callCloseIdx);
|
|
45248
|
-
if (
|
|
45249
|
-
|
|
45250
|
-
|
|
45251
|
-
|
|
45252
|
-
// Arity mismatch — hand the call to the runtime, which applies the
|
|
45253
|
-
// lambda value and reports #VALUE! (Excel's answer) instead of the
|
|
45254
|
-
// parser choking on `(...)(...)`.
|
|
45576
|
+
if (callArgSpans.length > params.length) {
|
|
45577
|
+
// Too many arguments — hand the call to the runtime, which applies
|
|
45578
|
+
// the lambda value and reports #VALUE! (Excel's answer) instead of
|
|
45579
|
+
// the parser choking on `(...)(...)`.
|
|
45255
45580
|
const lambdaValue = transformLambdaToValue(
|
|
45256
45581
|
source,
|
|
45257
45582
|
tokens,
|
|
@@ -45269,9 +45594,9 @@ const inlineIIFE = (source, tokens, lambdaOpenIdx, lambdaCloseIdx) => {
|
|
|
45269
45594
|
};
|
|
45270
45595
|
}
|
|
45271
45596
|
const bindings = params.map(({ name }, i) => {
|
|
45272
|
-
// Raw value — substituteNamesInBody decides the wrap.
|
|
45273
|
-
//
|
|
45274
|
-
//
|
|
45597
|
+
// Raw value — substituteNamesInBody decides the wrap. A parameter the
|
|
45598
|
+
// call left out binds to the omitted marker so ISOMITTED can see it
|
|
45599
|
+
// (and any other use of it is #VALUE!).
|
|
45275
45600
|
const value =
|
|
45276
45601
|
i < callArgSpans.length
|
|
45277
45602
|
? sourceSlice(source, tokens, callArgSpans[i]).trim()
|
|
@@ -45396,14 +45721,15 @@ const isLambdaValue = (v) =>
|
|
|
45396
45721
|
v != null && typeof v === "object" && v.__isLambda === true;
|
|
45397
45722
|
|
|
45398
45723
|
/**
|
|
45399
|
-
* Marker bound to
|
|
45724
|
+
* Marker bound to a parameter the caller left out (Excel allows a call
|
|
45725
|
+
* with fewer arguments than parameters, `[param]` or not). Only
|
|
45400
45726
|
* `ISOMITTED` looks at it; anywhere else it is an invalid operand.
|
|
45401
45727
|
*/
|
|
45402
45728
|
const OMITTED = Object.freeze({ __isOmitted: true });
|
|
45403
45729
|
const isOmittedValue = (v) =>
|
|
45404
45730
|
v != null && typeof v === "object" && v.__isOmitted === true;
|
|
45405
45731
|
|
|
45406
|
-
/** `[name]` → optional
|
|
45732
|
+
/** `[name]` → parameter documented as optional, `name` → plain parameter. */
|
|
45407
45733
|
const parseLambdaParams = (paramsStr) =>
|
|
45408
45734
|
paramsStr
|
|
45409
45735
|
.split(",")
|
|
@@ -45414,9 +45740,43 @@ const parseLambdaParams = (paramsStr) =>
|
|
|
45414
45740
|
return { name: optional ? raw.slice(1, -1).trim() : raw, optional };
|
|
45415
45741
|
});
|
|
45416
45742
|
|
|
45743
|
+
/**
|
|
45744
|
+
* An omitted marker that escapes `ISOMITTED` — a body that returns the
|
|
45745
|
+
* omitted parameter itself, or spreads it into an array — is `#VALUE!`
|
|
45746
|
+
* in the result, per cell inside an array. Arrays are only copied when
|
|
45747
|
+
* one of their cells actually holds the marker.
|
|
45748
|
+
*/
|
|
45749
|
+
const rejectOmittedResult = (result) => {
|
|
45750
|
+
if (isOmittedValue(result)) return FormulaError.VALUE;
|
|
45751
|
+
if (!Array.isArray(result)) return result;
|
|
45752
|
+
let out = null;
|
|
45753
|
+
for (let i = 0; i < result.length; i++) {
|
|
45754
|
+
const cell = rejectOmittedResult(result[i]);
|
|
45755
|
+
if (cell !== result[i]) {
|
|
45756
|
+
if (out === null) out = result.slice();
|
|
45757
|
+
out[i] = cell;
|
|
45758
|
+
}
|
|
45759
|
+
}
|
|
45760
|
+
return out ?? result;
|
|
45761
|
+
};
|
|
45762
|
+
|
|
45417
45763
|
const requiredParamCount = (lambda) =>
|
|
45418
45764
|
lambda.params.filter((_, i) => !(lambda.optional?.[i] ?? false)).length;
|
|
45419
45765
|
|
|
45766
|
+
/**
|
|
45767
|
+
* Excel's "Incorrect Parameters" `#VALUE!` for MAP / REDUCE / SCAN / BYROW /
|
|
45768
|
+
* BYCOL / MAKEARRAY / GROUPBY / PIVOTBY: the helper drives the lambda with
|
|
45769
|
+
* a fixed number of arguments and the lambda must declare a parameter for
|
|
45770
|
+
* each of them. Only a `[name]` parameter may stay unsupplied — the
|
|
45771
|
+
* "fewer arguments than parameters" allowance is for direct calls, not
|
|
45772
|
+
* helpers. Returns the error for the whole helper call, or null when the
|
|
45773
|
+
* counts fit.
|
|
45774
|
+
*/
|
|
45775
|
+
const hofArityError = (lambda, supplied) =>
|
|
45776
|
+
supplied < requiredParamCount(lambda) || supplied > lambda.params.length
|
|
45777
|
+
? FormulaError.VALUE
|
|
45778
|
+
: null;
|
|
45779
|
+
|
|
45420
45780
|
const formatLiteral = (value) => {
|
|
45421
45781
|
if (value === null || value === undefined) return "0";
|
|
45422
45782
|
if (value instanceof FormulaError) {
|
|
@@ -45459,12 +45819,11 @@ const formatLiteral = (value) => {
|
|
|
45459
45819
|
*/
|
|
45460
45820
|
const applyLambda = async (context, lambda, args) => {
|
|
45461
45821
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
45462
|
-
|
|
45463
|
-
|
|
45464
|
-
|
|
45465
|
-
|
|
45466
|
-
|
|
45467
|
-
}
|
|
45822
|
+
// Excel rejects surplus arguments but accepts fewer than declared: the
|
|
45823
|
+
// missing parameters are omitted (ISOMITTED → TRUE, any other use →
|
|
45824
|
+
// #VALUE!). Helpers check their own parameter count with
|
|
45825
|
+
// `hofArityError` before getting here.
|
|
45826
|
+
if (args.length > lambda.params.length) return FormulaError.VALUE;
|
|
45468
45827
|
|
|
45469
45828
|
context.__lambdaDepth = (context.__lambdaDepth ?? 0) + 1;
|
|
45470
45829
|
if (context.__lambdaDepth > MAX_LAMBDA_DEPTH) {
|
|
@@ -45477,14 +45836,14 @@ const applyLambda = async (context, lambda, args) => {
|
|
|
45477
45836
|
try {
|
|
45478
45837
|
const bindings = lambda.params.map((name, i) => ({
|
|
45479
45838
|
name,
|
|
45480
|
-
//
|
|
45839
|
+
// A parameter the call left out binds to the omitted marker so
|
|
45481
45840
|
// ISOMITTED inside the body can report it.
|
|
45482
45841
|
value:
|
|
45483
45842
|
i < args.length ? `(${formatLiteral(args[i])})` : `(${OMITTED_CALL})`,
|
|
45484
45843
|
}));
|
|
45485
45844
|
const substituted = substituteNamesInBody(lambda.body, bindings);
|
|
45486
45845
|
const result = await context.parseAsync(substituted, context.position, true);
|
|
45487
|
-
return result;
|
|
45846
|
+
return rejectOmittedResult(result);
|
|
45488
45847
|
} catch (e) {
|
|
45489
45848
|
if (e instanceof FormulaError) return e;
|
|
45490
45849
|
throw e;
|
|
@@ -45545,35 +45904,58 @@ const LambdaFunctions = {
|
|
|
45545
45904
|
},
|
|
45546
45905
|
|
|
45547
45906
|
/**
|
|
45548
|
-
* Internal: the value spliced in for
|
|
45549
|
-
*
|
|
45907
|
+
* Internal: the value spliced in for a parameter the caller left out
|
|
45908
|
+
* (see the LAMBDA preprocessor / applyLambda).
|
|
45550
45909
|
*/
|
|
45551
45910
|
__RNC_OMITTED__: () => OMITTED,
|
|
45552
45911
|
|
|
45553
45912
|
/**
|
|
45554
45913
|
* Internal: `__RNC_CALL__(lambda, arg1, …)` — the preprocessor emits
|
|
45555
|
-
* this for an inline `LAMBDA(...)(...)`
|
|
45556
|
-
*
|
|
45914
|
+
* this for an inline `LAMBDA(...)(...)` with more arguments than
|
|
45915
|
+
* parameters, so the arity error surfaces as #VALUE!.
|
|
45557
45916
|
*/
|
|
45558
45917
|
__RNC_CALL__: async (context, lambdaArg, ...args) =>
|
|
45559
45918
|
applyLambda(context, unwrap(lambdaArg), args.map(unwrap)),
|
|
45560
45919
|
|
|
45561
45920
|
/**
|
|
45562
|
-
* ISOMITTED(argument) — TRUE when `argument` is
|
|
45563
|
-
*
|
|
45921
|
+
* ISOMITTED(argument) — TRUE when `argument` is a LAMBDA parameter the
|
|
45922
|
+
* caller did not supply (bracketed `[p]` or not), FALSE otherwise.
|
|
45564
45923
|
*/
|
|
45565
45924
|
ISOMITTED: (arg) => isOmittedValue(unwrap(arg)),
|
|
45566
45925
|
|
|
45926
|
+
/**
|
|
45927
|
+
* A well-formed `LET(name, value, ..., body)` never reaches the function
|
|
45928
|
+
* table — the LET preprocessor inlines it. One that does is malformed:
|
|
45929
|
+
* too few arguments is `#N/A` (the too-few-arguments contract shared with
|
|
45930
|
+
* the Rust engine), an even count or a non-name binding is `#VALUE!`.
|
|
45931
|
+
*/
|
|
45932
|
+
LET: (...args) => {
|
|
45933
|
+
if (args.length < 3) return FormulaError.NA;
|
|
45934
|
+
return FormulaError.VALUE;
|
|
45935
|
+
},
|
|
45936
|
+
|
|
45937
|
+
/**
|
|
45938
|
+
* Same for `LAMBDA(...)`: the preprocessor rewrites every LAMBDA with a
|
|
45939
|
+
* body, so only `LAMBDA()` (too few arguments, `#N/A`) and a body-less
|
|
45940
|
+
* `LAMBDA(x)` (a lambda value as the cell result, `#CALC!`) get here.
|
|
45941
|
+
*/
|
|
45942
|
+
LAMBDA: (...args) => {
|
|
45943
|
+
if (args.length === 0) return FormulaError.NA;
|
|
45944
|
+
return FormulaError.CALC;
|
|
45945
|
+
},
|
|
45946
|
+
|
|
45567
45947
|
/**
|
|
45568
45948
|
* MAP(array1, [array2, ...], lambda) — applies lambda to each set
|
|
45569
45949
|
* of corresponding elements. Returns a same-shape 2D array.
|
|
45570
45950
|
*/
|
|
45571
45951
|
MAP: async (context, ...args) => {
|
|
45572
|
-
if (args.length < 2) return FormulaError.
|
|
45952
|
+
if (args.length < 2) return FormulaError.NA;
|
|
45573
45953
|
const lambda = unwrap(args[args.length - 1]);
|
|
45574
45954
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
45575
45955
|
const arrays = args.slice(0, -1).map((a) => as2D(unwrap(a)));
|
|
45576
45956
|
if (arrays.length === 0) return FormulaError.VALUE;
|
|
45957
|
+
const arity = hofArityError(lambda, arrays.length);
|
|
45958
|
+
if (arity) return arity;
|
|
45577
45959
|
const rows = arrays[0].length;
|
|
45578
45960
|
const cols = arrays[0][0]?.length ?? 0;
|
|
45579
45961
|
const out = [];
|
|
@@ -45595,6 +45977,8 @@ const LambdaFunctions = {
|
|
|
45595
45977
|
REDUCE: async (context, initialArg, arrayArg, lambdaArg) => {
|
|
45596
45978
|
const lambda = unwrap(lambdaArg);
|
|
45597
45979
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
45980
|
+
const arity = hofArityError(lambda, 2);
|
|
45981
|
+
if (arity) return arity;
|
|
45598
45982
|
let acc = unwrap(initialArg);
|
|
45599
45983
|
const values = flatten2D(as2D(unwrap(arrayArg)));
|
|
45600
45984
|
for (const v of values) {
|
|
@@ -45612,6 +45996,8 @@ const LambdaFunctions = {
|
|
|
45612
45996
|
SCAN: async (context, initialArg, arrayArg, lambdaArg) => {
|
|
45613
45997
|
const lambda = unwrap(lambdaArg);
|
|
45614
45998
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
45999
|
+
const arity = hofArityError(lambda, 2);
|
|
46000
|
+
if (arity) return arity;
|
|
45615
46001
|
let acc = unwrap(initialArg);
|
|
45616
46002
|
const arr = as2D(unwrap(arrayArg));
|
|
45617
46003
|
const out = [];
|
|
@@ -45633,8 +46019,11 @@ const LambdaFunctions = {
|
|
|
45633
46019
|
* array), returns a column vector of results.
|
|
45634
46020
|
*/
|
|
45635
46021
|
BYROW: async (context, arrayArg, lambdaArg) => {
|
|
46022
|
+
if (lambdaArg === undefined) return FormulaError.NA;
|
|
45636
46023
|
const lambda = unwrap(lambdaArg);
|
|
45637
46024
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
46025
|
+
const arity = hofArityError(lambda, 1);
|
|
46026
|
+
if (arity) return arity;
|
|
45638
46027
|
const arr = as2D(unwrap(arrayArg));
|
|
45639
46028
|
const out = [];
|
|
45640
46029
|
for (const row of arr) {
|
|
@@ -45650,8 +46039,11 @@ const LambdaFunctions = {
|
|
|
45650
46039
|
* 1D array), returns a row vector of results.
|
|
45651
46040
|
*/
|
|
45652
46041
|
BYCOL: async (context, arrayArg, lambdaArg) => {
|
|
46042
|
+
if (lambdaArg === undefined) return FormulaError.NA;
|
|
45653
46043
|
const lambda = unwrap(lambdaArg);
|
|
45654
46044
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
46045
|
+
const arity = hofArityError(lambda, 1);
|
|
46046
|
+
if (arity) return arity;
|
|
45655
46047
|
const arr = as2D(unwrap(arrayArg));
|
|
45656
46048
|
const cols = arr[0]?.length ?? 0;
|
|
45657
46049
|
const out = [];
|
|
@@ -45669,8 +46061,11 @@ const LambdaFunctions = {
|
|
|
45669
46061
|
* lambda(r, c) for each cell. r, c are 1-indexed per Excel.
|
|
45670
46062
|
*/
|
|
45671
46063
|
MAKEARRAY: async (context, rowsArg, colsArg, lambdaArg) => {
|
|
46064
|
+
if (lambdaArg === undefined) return FormulaError.NA;
|
|
45672
46065
|
const lambda = unwrap(lambdaArg);
|
|
45673
46066
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
46067
|
+
const arity = hofArityError(lambda, 2);
|
|
46068
|
+
if (arity) return arity;
|
|
45674
46069
|
const rows = Math.floor(
|
|
45675
46070
|
FormulaHelpers.accept(rowsArg, Types.NUMBER, 0),
|
|
45676
46071
|
);
|
|
@@ -45719,6 +46114,8 @@ const LambdaFunctions = {
|
|
|
45719
46114
|
const valueFields = as2D(unwrap(args[1]));
|
|
45720
46115
|
const lambda = unwrap(args[2]);
|
|
45721
46116
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
46117
|
+
const arity = hofArityError(lambda, 1);
|
|
46118
|
+
if (arity) return arity;
|
|
45722
46119
|
const fieldHeaders = args[3] != null
|
|
45723
46120
|
? Math.floor(FormulaHelpers.accept(args[3], Types.NUMBER, 0))
|
|
45724
46121
|
: 0;
|
|
@@ -45767,6 +46164,8 @@ const LambdaFunctions = {
|
|
|
45767
46164
|
const valueFields = as2D(unwrap(args[2]));
|
|
45768
46165
|
const lambda = unwrap(args[3]);
|
|
45769
46166
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
46167
|
+
const arity = hofArityError(lambda, 1);
|
|
46168
|
+
if (arity) return arity;
|
|
45770
46169
|
|
|
45771
46170
|
const numOpt = (i, d) =>
|
|
45772
46171
|
args[i] != null
|
|
@@ -46223,6 +46622,42 @@ let Utils$1 = class Utils {
|
|
|
46223
46622
|
return Prefix.unaryOp(prefixes, val, isArray);
|
|
46224
46623
|
}
|
|
46225
46624
|
|
|
46625
|
+
/**
|
|
46626
|
+
* Sign a numeric literal (`-1`, `+2` inside an array constant). Nothing
|
|
46627
|
+
* to dereference or await here, so this stays synchronous in both parse
|
|
46628
|
+
* modes.
|
|
46629
|
+
* @param {string} prefix - `+` or `-`
|
|
46630
|
+
* @param {number} number
|
|
46631
|
+
* @return {number}
|
|
46632
|
+
*/
|
|
46633
|
+
applyLiteralPrefix(prefix, number) {
|
|
46634
|
+
return this._applyPrefix([prefix], number, false);
|
|
46635
|
+
}
|
|
46636
|
+
|
|
46637
|
+
/**
|
|
46638
|
+
* Under `parseAsync` an array-constant item built from a sub-expression
|
|
46639
|
+
* (`{SUM(1,-2),3}`) is a pending Promise; an array must only ever carry
|
|
46640
|
+
* values, so settle them. A settled item may itself be a reference
|
|
46641
|
+
* (`{INDEX(A1:C5,1,1),2}`), which the sync arm of `constantArray`
|
|
46642
|
+
* dereferences before the Promise ever existed — do the same here so both
|
|
46643
|
+
* parse modes agree. Returns the array itself when nothing is pending.
|
|
46644
|
+
* @param {Array.<Array>} array
|
|
46645
|
+
* @param {{row: number, col: number, sheet: string}} position
|
|
46646
|
+
* @return {Array.<Array>|Promise.<Array.<Array>>}
|
|
46647
|
+
*/
|
|
46648
|
+
settleArrayItems(array, position) {
|
|
46649
|
+
const isPending = (item) =>
|
|
46650
|
+
item != null && typeof item.then === "function";
|
|
46651
|
+
if (!array.some((row) => row.some(isPending))) return array;
|
|
46652
|
+
const deref = (item) =>
|
|
46653
|
+
item && item.ref ? this.extractRefValue(item, position).val : item;
|
|
46654
|
+
return Promise.all(
|
|
46655
|
+
array.map((row) =>
|
|
46656
|
+
Promise.all(row).then((items) => items.map(deref))
|
|
46657
|
+
)
|
|
46658
|
+
);
|
|
46659
|
+
}
|
|
46660
|
+
|
|
46226
46661
|
async applyPrefixAsync(prefixes, value, position) {
|
|
46227
46662
|
const { val, isArray } = this.extractRefValue(await value, position);
|
|
46228
46663
|
return this._applyPrefix(prefixes, val, isArray);
|
|
@@ -46417,15 +46852,21 @@ let Utils$1 = class Utils {
|
|
|
46417
46852
|
}
|
|
46418
46853
|
}
|
|
46419
46854
|
|
|
46420
|
-
applyIntersect(refs) {
|
|
46855
|
+
applyIntersect(refs, position) {
|
|
46421
46856
|
// console.log('applyIntersect', refs);
|
|
46422
46857
|
if (this.isFormulaError(refs[0])) return refs[0];
|
|
46423
46858
|
if (!refs[0].ref) throw Error(`Expecting a reference, but got ${refs[0]}.`);
|
|
46424
46859
|
// a intersection will keep track of references, value won't be retrieved here.
|
|
46425
46860
|
let maxRow, maxCol, minRow, minCol, sheet, res; // index start from 1
|
|
46861
|
+
// An unqualified operand (`B:B`, sheet undefined) lives on the formula's sheet: it must
|
|
46862
|
+
// intersect an operand that names that sheet explicitly (`Sheet1!A1:C3`, or a defined name
|
|
46863
|
+
// — the host resolves names WITH their sheet). Only a genuinely different sheet is #NULL!.
|
|
46864
|
+
const currentSheet = (position ?? this.context.position)?.sheet;
|
|
46865
|
+
const sheetOf = (ref) => ref.sheet ?? currentSheet;
|
|
46426
46866
|
// first time setup
|
|
46427
46867
|
const ref = refs.shift().ref;
|
|
46428
46868
|
sheet = ref.sheet;
|
|
46869
|
+
const sheetKey = sheetOf(ref);
|
|
46429
46870
|
if (!ref.from) {
|
|
46430
46871
|
// check whole row/col reference
|
|
46431
46872
|
if (ref.row === undefined || ref.col === undefined) {
|
|
@@ -46459,10 +46900,11 @@ let Utils$1 = class Utils {
|
|
|
46459
46900
|
ref.row < minRow ||
|
|
46460
46901
|
ref.col > maxCol ||
|
|
46461
46902
|
ref.col < minCol ||
|
|
46462
|
-
|
|
46903
|
+
sheetKey !== sheetOf(ref)
|
|
46463
46904
|
) {
|
|
46464
46905
|
err = FormulaError.NULL;
|
|
46465
46906
|
}
|
|
46907
|
+
sheet ??= ref.sheet;
|
|
46466
46908
|
maxRow = minRow = ref.row;
|
|
46467
46909
|
maxCol = minCol = ref.col;
|
|
46468
46910
|
} else {
|
|
@@ -46476,10 +46918,11 @@ let Utils$1 = class Utils {
|
|
|
46476
46918
|
refMaxRow < minRow ||
|
|
46477
46919
|
refMinCol > maxCol ||
|
|
46478
46920
|
refMaxCol < minCol ||
|
|
46479
|
-
|
|
46921
|
+
sheetKey !== sheetOf(ref)
|
|
46480
46922
|
) {
|
|
46481
46923
|
err = FormulaError.NULL;
|
|
46482
46924
|
}
|
|
46925
|
+
sheet ??= ref.sheet;
|
|
46483
46926
|
// update
|
|
46484
46927
|
maxRow = Math.min(maxRow, refMaxRow);
|
|
46485
46928
|
minRow = Math.max(minRow, refMinRow);
|
|
@@ -47480,6 +47923,9 @@ class FormulaParser {
|
|
|
47480
47923
|
if (!this.keepLambdaResult && unwrapLambdaValue(result)) {
|
|
47481
47924
|
return FormulaError.CALC;
|
|
47482
47925
|
}
|
|
47926
|
+
// An inlined `LAMBDA(x,y,y)(1)` evaluates straight to the omitted
|
|
47927
|
+
// marker; only ISOMITTED may consume it, so as a result it is #VALUE!.
|
|
47928
|
+
result = rejectOmittedResult(result);
|
|
47483
47929
|
const type = typeof result;
|
|
47484
47930
|
// number
|
|
47485
47931
|
if (type === "number") {
|
|
@@ -48700,6 +49146,7 @@ exports.WildCard = WildCard;
|
|
|
48700
49146
|
exports.allTokens = allTokens;
|
|
48701
49147
|
exports.default = FormulaParser;
|
|
48702
49148
|
exports.implementedFunctionNames = implementedFunctionNames;
|
|
49149
|
+
exports.isNestedAggregateFormula = isNestedAggregateFormula;
|
|
48703
49150
|
exports.lex = lex;
|
|
48704
49151
|
exports.noop = noop;
|
|
48705
49152
|
exports.numberToText = numberToText;
|