@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/esm/index.js
CHANGED
|
@@ -3339,6 +3339,15 @@ function formatValue (value, parseData, opts) {
|
|
|
3339
3339
|
return part ? runPart(value, part, opts, l10n) : opts.overflow;
|
|
3340
3340
|
}
|
|
3341
3341
|
|
|
3342
|
+
function isDate (partitions) {
|
|
3343
|
+
return !!(
|
|
3344
|
+
(partitions[0] && partitions[0].date) ||
|
|
3345
|
+
(partitions[1] && partitions[1].date) ||
|
|
3346
|
+
(partitions[2] && partitions[2].date) ||
|
|
3347
|
+
(partitions[3] && partitions[3].date)
|
|
3348
|
+
);
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3342
3351
|
const tokenHandlers = [
|
|
3343
3352
|
[ TOKEN_GENERAL, /^General/i, 0 ],
|
|
3344
3353
|
[ TOKEN_HASH, /^#/, 0 ],
|
|
@@ -3776,6 +3785,22 @@ function format (pattern, value, options = {}) {
|
|
|
3776
3785
|
return formatValue(val, data, opts);
|
|
3777
3786
|
}
|
|
3778
3787
|
|
|
3788
|
+
// FIXME: what is a a section?...
|
|
3789
|
+
/**
|
|
3790
|
+
* Determine if a given format pattern is a date pattern.
|
|
3791
|
+
*
|
|
3792
|
+
* The pattern is considered a date pattern if any of its sections contain a
|
|
3793
|
+
* date symbol (such as `Y` or `H`). Each section is restricted to be
|
|
3794
|
+
* _either_ a number or date format.
|
|
3795
|
+
*
|
|
3796
|
+
* @param {string} pattern - A format pattern in the ECMA-376 number format.
|
|
3797
|
+
* @returns {boolean} True if the specified pattern is date pattern, False otherwise.
|
|
3798
|
+
*/
|
|
3799
|
+
function isDateFormat (pattern) {
|
|
3800
|
+
const data = prepareFormatterData(pattern, false);
|
|
3801
|
+
return isDate(data.partitions);
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3779
3804
|
// Excel indexed colors mapping used for legacy color references
|
|
3780
3805
|
const IndexedColors = Object.freeze([
|
|
3781
3806
|
// 0-7
|
|
@@ -3961,6 +3986,148 @@ const DEFAULT_SEPARATORS = { decimal: ".", group: "," };
|
|
|
3961
3986
|
const separatorsFor = (locale) => {
|
|
3962
3987
|
return DEFAULT_SEPARATORS;
|
|
3963
3988
|
};
|
|
3989
|
+
/**
|
|
3990
|
+
* Rewrite the `[$symbol-LCID]` tokens of one section through `rewrite`,
|
|
3991
|
+
* which receives the bracket content (`€-407`) and returns the new content,
|
|
3992
|
+
* or an empty string to drop the token. Quoted literals, escapes and other
|
|
3993
|
+
* bracket directives are left as written.
|
|
3994
|
+
*/
|
|
3995
|
+
const rewriteSectionLocaleTokens = (section, rewrite) => {
|
|
3996
|
+
let out = "";
|
|
3997
|
+
let inQuotes = false;
|
|
3998
|
+
let inBrackets = false;
|
|
3999
|
+
for (let i = 0; i < section.length; i++) {
|
|
4000
|
+
const char = section[i];
|
|
4001
|
+
if (inQuotes) {
|
|
4002
|
+
inQuotes = char !== '"';
|
|
4003
|
+
}
|
|
4004
|
+
else if (inBrackets) {
|
|
4005
|
+
inBrackets = char !== "]";
|
|
4006
|
+
}
|
|
4007
|
+
else if (char === '"') {
|
|
4008
|
+
inQuotes = true;
|
|
4009
|
+
}
|
|
4010
|
+
else if (char === "[") {
|
|
4011
|
+
const end = section[i + 1] === "$" ? section.indexOf("]", i) : -1;
|
|
4012
|
+
if (end > 0) {
|
|
4013
|
+
const token = rewrite(section.slice(i + 1, end));
|
|
4014
|
+
if (token)
|
|
4015
|
+
out += `[${token}]`;
|
|
4016
|
+
i = end;
|
|
4017
|
+
continue;
|
|
4018
|
+
}
|
|
4019
|
+
inBrackets = true;
|
|
4020
|
+
}
|
|
4021
|
+
else if (char === "\\" || char === "_" || char === "*") {
|
|
4022
|
+
out += char + (section[i + 1] ?? "");
|
|
4023
|
+
i++;
|
|
4024
|
+
continue;
|
|
4025
|
+
}
|
|
4026
|
+
out += char;
|
|
4027
|
+
}
|
|
4028
|
+
return out;
|
|
4029
|
+
};
|
|
4030
|
+
/** `[$€-407]` → `[$€]`; a bare `[$-407]` goes away. */
|
|
4031
|
+
const currencySymbolOnly = (token) => {
|
|
4032
|
+
const dash = token.indexOf("-");
|
|
4033
|
+
const symbol = dash < 0 ? token : token.slice(0, dash);
|
|
4034
|
+
return symbol.length > 1 ? symbol : "";
|
|
4035
|
+
};
|
|
4036
|
+
const separatorLocales = new Map();
|
|
4037
|
+
/**
|
|
4038
|
+
* Companion numfmt locale carrying `locale`'s calendar names and AM/PM with
|
|
4039
|
+
* the workbook's decimal / group separators, so a date section pinned to
|
|
4040
|
+
* `[$-407]` renders German names but a workbook decimal mark in `ss.00`.
|
|
4041
|
+
* Returns `locale` itself when its separators already match.
|
|
4042
|
+
*/
|
|
4043
|
+
const getWorkbookSeparatorLocale = (locale, separators) => {
|
|
4044
|
+
const key = `${locale}\u0000${separators.decimal}\u0000${separators.group}`;
|
|
4045
|
+
const cached = separatorLocales.get(key);
|
|
4046
|
+
if (cached !== undefined)
|
|
4047
|
+
return cached;
|
|
4048
|
+
let overlay = locale;
|
|
4049
|
+
try {
|
|
4050
|
+
const base = getLocale(locale);
|
|
4051
|
+
if (base &&
|
|
4052
|
+
(base.decimal !== separators.decimal || base.group !== separators.group)) {
|
|
4053
|
+
// numfmt locale ids are `[a-z\d]+` with one optional `_` part.
|
|
4054
|
+
const hex = (text) => [...text].map((c) => c.codePointAt(0).toString(16)).join("x");
|
|
4055
|
+
overlay = `sep${hex(separators.decimal)}x${hex(separators.group)}_${locale.replace(/[^a-z\d]/gi, "")}`;
|
|
4056
|
+
addLocale({ ...base, decimal: separators.decimal, group: separators.group }, overlay);
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
catch {
|
|
4060
|
+
overlay = locale;
|
|
4061
|
+
}
|
|
4062
|
+
separatorLocales.set(key, overlay);
|
|
4063
|
+
return overlay;
|
|
4064
|
+
};
|
|
4065
|
+
/**
|
|
4066
|
+
* Re-point a date section's `[$symbol-LCID]` token at the companion locale
|
|
4067
|
+
* with the workbook's separators (`$-407` → `$-sep2ex2c_407`). An LCID numfmt
|
|
4068
|
+
* does not know or one carrying calendar bits stays as written.
|
|
4069
|
+
*/
|
|
4070
|
+
const workbookSeparatorLocaleToken = (separators) => (token) => {
|
|
4071
|
+
const dash = token.indexOf("-");
|
|
4072
|
+
if (dash < 0)
|
|
4073
|
+
return token;
|
|
4074
|
+
const code = token.slice(dash + 1);
|
|
4075
|
+
const wincode = Number.parseInt(code, 16);
|
|
4076
|
+
if (Number.isFinite(wincode) && wincode & 0xff0000)
|
|
4077
|
+
return token;
|
|
4078
|
+
if (!getLocale(code))
|
|
4079
|
+
return token;
|
|
4080
|
+
return `${token.slice(0, dash + 1)}${getWorkbookSeparatorLocale(code, separators)}`;
|
|
4081
|
+
};
|
|
4082
|
+
const isDateSection = (section) => {
|
|
4083
|
+
try {
|
|
4084
|
+
return isDateFormat(section);
|
|
4085
|
+
}
|
|
4086
|
+
catch {
|
|
4087
|
+
return false;
|
|
4088
|
+
}
|
|
4089
|
+
};
|
|
4090
|
+
/**
|
|
4091
|
+
* Excel's `[$symbol-LCID]` token selects the currency symbol and the
|
|
4092
|
+
* language of day/month names and AM/PM; digit grouping and the decimal
|
|
4093
|
+
* separator follow the workbook locale (`[$€-407] #,##0.00` is
|
|
4094
|
+
* `€ 1,234.50` on a US host). numfmt applies the token's locale to the whole
|
|
4095
|
+
* section, separators included, so the locale part is dropped from every
|
|
4096
|
+
* section that is not a date (number, scientific, fraction, text, …); a
|
|
4097
|
+
* date section keeps the token for its names, re-pointed at a companion
|
|
4098
|
+
* locale that carries the workbook's separators for fractional seconds.
|
|
4099
|
+
* Returns `undefined` when nothing had to change.
|
|
4100
|
+
*/
|
|
4101
|
+
const withWorkbookSeparators = (pattern, locale) => {
|
|
4102
|
+
if (!pattern.includes("[$"))
|
|
4103
|
+
return undefined;
|
|
4104
|
+
const sections = splitFormatSections(pattern);
|
|
4105
|
+
let changed = false;
|
|
4106
|
+
let dateTokens;
|
|
4107
|
+
const rewritten = sections.map((section) => {
|
|
4108
|
+
if (!section.includes("[$"))
|
|
4109
|
+
return section;
|
|
4110
|
+
let rewrite = currencySymbolOnly;
|
|
4111
|
+
if (isDateSection(section)) {
|
|
4112
|
+
dateTokens ??= workbookSeparatorLocaleToken(separatorsFor());
|
|
4113
|
+
rewrite = dateTokens;
|
|
4114
|
+
}
|
|
4115
|
+
const stripped = rewriteSectionLocaleTokens(section, rewrite);
|
|
4116
|
+
changed ||= stripped !== section;
|
|
4117
|
+
return stripped;
|
|
4118
|
+
});
|
|
4119
|
+
return changed ? rewritten.join(";") : undefined;
|
|
4120
|
+
};
|
|
4121
|
+
const workbookSeparatorPatternCache = new Map();
|
|
4122
|
+
const withWorkbookSeparatorsCached = (code, locale) => {
|
|
4123
|
+
const key = `${""}\u0000${code}`;
|
|
4124
|
+
let rewritten = workbookSeparatorPatternCache.get(key);
|
|
4125
|
+
if (rewritten === undefined) {
|
|
4126
|
+
rewritten = withWorkbookSeparators(code) ?? code;
|
|
4127
|
+
workbookSeparatorPatternCache.set(key, rewritten);
|
|
4128
|
+
}
|
|
4129
|
+
return rewritten;
|
|
4130
|
+
};
|
|
3964
4131
|
const ssfFormat = (pattern, value, locale) => {
|
|
3965
4132
|
try {
|
|
3966
4133
|
let shortAmPm = shortAmPmPatternCache.get(pattern);
|
|
@@ -3968,7 +4135,7 @@ const ssfFormat = (pattern, value, locale) => {
|
|
|
3968
4135
|
shortAmPm = withShortAmPmMarker(pattern);
|
|
3969
4136
|
shortAmPmPatternCache.set(pattern, shortAmPm);
|
|
3970
4137
|
}
|
|
3971
|
-
const code = shortAmPm ?? pattern;
|
|
4138
|
+
const code = withWorkbookSeparatorsCached(shortAmPm ?? pattern, locale);
|
|
3972
4139
|
let patternLocale = localeForPattern(pattern, locale);
|
|
3973
4140
|
if (shortAmPm !== undefined && patternLocale) {
|
|
3974
4141
|
patternLocale = getShortAmPmLocale(patternLocale);
|
|
@@ -4671,10 +4838,12 @@ class FormulaHelpersClass {
|
|
|
4671
4838
|
!isRaw && Object.prototype.hasOwnProperty.call(param, "value");
|
|
4672
4839
|
if (hasValueProp) {
|
|
4673
4840
|
if (param.value == null) {
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4841
|
+
// A blank cell is not an omitted argument (only omission takes
|
|
4842
|
+
// `defValue`): Excel reads it as "" in a text slot, 0 in a number
|
|
4843
|
+
// slot and FALSE in a logical slot.
|
|
4844
|
+
if (type === Types.STRING) return "";
|
|
4845
|
+
if (type === Types.NUMBER || type === Types.NUMBER_NO_BOOLEAN) return 0;
|
|
4846
|
+
if (type === Types.BOOLEAN) return false;
|
|
4678
4847
|
param = param.value;
|
|
4679
4848
|
} else {
|
|
4680
4849
|
param = param.value;
|
|
@@ -4754,6 +4923,7 @@ class FormulaHelpersClass {
|
|
|
4754
4923
|
* @param range2
|
|
4755
4924
|
*/
|
|
4756
4925
|
retrieveRanges(context, range1, range2) {
|
|
4926
|
+
if (range1 === undefined) throw FormulaError.ARG_MISSING([Types.ARRAY]);
|
|
4757
4927
|
// process args
|
|
4758
4928
|
range2 = Address.extend(range1, range2);
|
|
4759
4929
|
|
|
@@ -4770,6 +4940,9 @@ class FormulaHelpersClass {
|
|
|
4770
4940
|
}
|
|
4771
4941
|
|
|
4772
4942
|
retrieveArg(context, arg) {
|
|
4943
|
+
// `null` is an explicitly skipped argument (`SUMIF(A1:A5,,B1:B5)`);
|
|
4944
|
+
// `undefined` is one the call never supplied — too few arguments.
|
|
4945
|
+
if (arg === undefined) throw FormulaError.ARG_MISSING([Types.ARRAY]);
|
|
4773
4946
|
if (arg === null) return { value: 0, isArray: false, omitted: true };
|
|
4774
4947
|
const res = context.utils.extractRefValue(arg);
|
|
4775
4948
|
return { value: res.val, isArray: res.isArray, ref: arg.ref };
|
|
@@ -6339,33 +6512,48 @@ const TextFunctions = {
|
|
|
6339
6512
|
return text.slice(0, m.index) + expanded + text.slice(m.index + m[0].length);
|
|
6340
6513
|
},
|
|
6341
6514
|
|
|
6515
|
+
/**
|
|
6516
|
+
* JOIN(delimiter, value_or_array1, [value_or_array2, …]) — Google Sheets.
|
|
6517
|
+
* Ranges / arrays contribute every element; blanks and empty text are
|
|
6518
|
+
* skipped; values spell as TEXTJOIN spells them (TRUE, 15-digit numbers) and
|
|
6519
|
+
* an error among the values propagates. The rnc engine mirrors these rules.
|
|
6520
|
+
*/
|
|
6342
6521
|
JOIN: (delimiter, ...values) => {
|
|
6343
6522
|
delimiter = H$a.accept(delimiter, Types.STRING);
|
|
6344
6523
|
const result = [];
|
|
6345
6524
|
|
|
6346
|
-
H$a.flattenParams(values,
|
|
6347
|
-
if (item
|
|
6348
|
-
|
|
6349
|
-
|
|
6525
|
+
H$a.flattenParams(values, Types.STRING, true, (item) => {
|
|
6526
|
+
if (item === undefined || item === null) return;
|
|
6527
|
+
const str = H$a.accept(item, Types.STRING);
|
|
6528
|
+
if (str !== "") result.push(str);
|
|
6350
6529
|
});
|
|
6351
6530
|
|
|
6352
6531
|
return result.join(delimiter);
|
|
6353
6532
|
},
|
|
6354
6533
|
|
|
6534
|
+
/**
|
|
6535
|
+
* SPLIT(text, delimiter, [split_by_each=TRUE], [remove_empty_text=TRUE]) —
|
|
6536
|
+
* Google Sheets. Pieces stay text (no number coercion); an empty delimiter
|
|
6537
|
+
* is #VALUE!; when every piece is removed the result is empty text. The
|
|
6538
|
+
* rnc engine mirrors these rules.
|
|
6539
|
+
*/
|
|
6355
6540
|
SPLIT: (text, delimiter, splitByEach, removeEmpty) => {
|
|
6356
6541
|
text = H$a.accept(text, Types.STRING);
|
|
6357
6542
|
delimiter = H$a.accept(delimiter, Types.STRING);
|
|
6358
6543
|
splitByEach = H$a.accept(splitByEach, Types.BOOLEAN, true);
|
|
6359
6544
|
removeEmpty = H$a.accept(removeEmpty, Types.BOOLEAN, true);
|
|
6545
|
+
if (delimiter === "") throw FormulaError.VALUE;
|
|
6360
6546
|
|
|
6361
6547
|
let parts;
|
|
6362
6548
|
if (splitByEach) {
|
|
6363
|
-
//
|
|
6364
|
-
|
|
6365
|
-
const
|
|
6366
|
-
|
|
6367
|
-
)
|
|
6368
|
-
|
|
6549
|
+
// Every character of the delimiter is a separator — a literal character,
|
|
6550
|
+
// never a RegExp class ("d" must not become \d, "b" must not become \b).
|
|
6551
|
+
const separators = new Set(delimiter);
|
|
6552
|
+
parts = [""];
|
|
6553
|
+
for (const ch of text) {
|
|
6554
|
+
if (separators.has(ch)) parts.push("");
|
|
6555
|
+
else parts[parts.length - 1] += ch;
|
|
6556
|
+
}
|
|
6369
6557
|
} else {
|
|
6370
6558
|
// Split by the entire delimiter string
|
|
6371
6559
|
parts = text.split(delimiter);
|
|
@@ -6374,6 +6562,7 @@ const TextFunctions = {
|
|
|
6374
6562
|
if (removeEmpty) {
|
|
6375
6563
|
parts = parts.filter((part) => part !== "");
|
|
6376
6564
|
}
|
|
6565
|
+
if (parts.length === 0) return "";
|
|
6377
6566
|
|
|
6378
6567
|
// Return as a single row array
|
|
6379
6568
|
return [parts];
|
|
@@ -12349,15 +12538,48 @@ const collectNumericValues = (array) => {
|
|
|
12349
12538
|
// FORECAST.ETS helpers — Holt-Winters AAA implementation.
|
|
12350
12539
|
// ---------------------------------------------------------------------
|
|
12351
12540
|
|
|
12541
|
+
// Excel's `seasonality` argument: 1 (the default) auto-detects the
|
|
12542
|
+
// period, 0 fits without a seasonal component, 2..8760 forces the
|
|
12543
|
+
// period; anything else is #NUM!. Fractions are truncated.
|
|
12544
|
+
const ETS_MAX_SEASONALITY = 8760;
|
|
12545
|
+
const forecastEtsSeasonality = (seasonalityArg) => {
|
|
12546
|
+
if (seasonalityArg == null) return 1;
|
|
12547
|
+
const value = H$8.accept(seasonalityArg, Types.NUMBER, 1);
|
|
12548
|
+
if (value == null) return 1;
|
|
12549
|
+
const seasonality = Math.floor(value);
|
|
12550
|
+
if (seasonality < 0 || seasonality > ETS_MAX_SEASONALITY) throw FormulaError.NUM;
|
|
12551
|
+
return seasonality;
|
|
12552
|
+
};
|
|
12553
|
+
|
|
12554
|
+
// `data_completion` must be 0 or 1 and `aggregation` 1..7 (AVERAGE,
|
|
12555
|
+
// COUNT, COUNTA, MAX, MEDIAN, MIN, SUM), otherwise #NUM!. Neither
|
|
12556
|
+
// changes the fit: the timeline is used as given (no gap filling,
|
|
12557
|
+
// duplicates are not aggregated).
|
|
12558
|
+
const forecastEtsValidateOptions = (dataCompletionArg, aggregationArg) => {
|
|
12559
|
+
if (dataCompletionArg != null) {
|
|
12560
|
+
const completion = Math.floor(H$8.accept(dataCompletionArg, Types.NUMBER, 1));
|
|
12561
|
+
if (completion !== 0 && completion !== 1) throw FormulaError.NUM;
|
|
12562
|
+
}
|
|
12563
|
+
if (aggregationArg != null) {
|
|
12564
|
+
const aggregation = Math.floor(H$8.accept(aggregationArg, Types.NUMBER, 1));
|
|
12565
|
+
if (aggregation < 1 || aggregation > 7) throw FormulaError.NUM;
|
|
12566
|
+
}
|
|
12567
|
+
};
|
|
12568
|
+
|
|
12352
12569
|
// Pair up timeline + values, sort by timeline, infer the constant
|
|
12353
12570
|
// step (Excel requires a uniformly-spaced timeline), and resolve
|
|
12354
|
-
// the seasonality parameter (auto-
|
|
12571
|
+
// the seasonality parameter (Excel spelling: 1 auto-detects, 0 is no
|
|
12572
|
+
// seasonality, n > 1 is the period).
|
|
12355
12573
|
const forecastEtsPrep = (ys, xs, seasonality) => {
|
|
12356
12574
|
if (xs.length !== ys.length) throw FormulaError.NA;
|
|
12357
12575
|
const pairs = [];
|
|
12358
12576
|
for (let i = 0; i < xs.length; i++) {
|
|
12359
12577
|
const x = xs[i];
|
|
12360
12578
|
const yv = ys[i];
|
|
12579
|
+
// An error anywhere in the series is the result (Excel and the Rust
|
|
12580
|
+
// port's `series_number`); other non-numbers (text, blanks) are skipped.
|
|
12581
|
+
if (x instanceof FormulaError) throw x;
|
|
12582
|
+
if (yv instanceof FormulaError) throw yv;
|
|
12361
12583
|
if (typeof x !== "number") continue;
|
|
12362
12584
|
if (typeof yv !== "number") continue;
|
|
12363
12585
|
pairs.push([x, yv]);
|
|
@@ -12378,12 +12600,12 @@ const forecastEtsPrep = (ys, xs, seasonality) => {
|
|
|
12378
12600
|
diffs.sort((a, b) => a - b);
|
|
12379
12601
|
const step = diffs[Math.floor(diffs.length / 2)];
|
|
12380
12602
|
let period;
|
|
12381
|
-
if (seasonality ===
|
|
12603
|
+
if (seasonality === 0) {
|
|
12382
12604
|
period = 1; // No seasonality (linear Holt).
|
|
12383
|
-
} else if (seasonality
|
|
12384
|
-
period = seasonality;
|
|
12385
|
-
} else {
|
|
12605
|
+
} else if (seasonality === 1) {
|
|
12386
12606
|
period = detectSeasonality(y);
|
|
12607
|
+
} else {
|
|
12608
|
+
period = seasonality;
|
|
12387
12609
|
}
|
|
12388
12610
|
return { y, lastX: sortedX[sortedX.length - 1], step, period };
|
|
12389
12611
|
};
|
|
@@ -13263,20 +13485,18 @@ const DistributionFunctions = {
|
|
|
13263
13485
|
// trend. We use the same idea — scan candidate periods 2..N/2,
|
|
13264
13486
|
// capped at 12 (Excel's default cap for monthly data), and pick
|
|
13265
13487
|
// the one with the lowest in-sample SSE for the fitted Holt-Winters
|
|
13266
|
-
// model.
|
|
13267
|
-
// linear).
|
|
13488
|
+
// model. `seasonality` follows Excel: 1 (default) auto-detects, 0
|
|
13489
|
+
// forces "no seasonality" (Holt linear), n > 1 is the period. The
|
|
13490
|
+
// Rust engine (formualizer `stats/forecast_ets.rs`) is a port of this
|
|
13491
|
+
// code, so both engines agree to 1e-9 on the same inputs.
|
|
13268
13492
|
// -------------------------------------------------------------------
|
|
13269
13493
|
|
|
13270
13494
|
'FORECAST.ETS': (targetDate, values, timeline, seasonalityArg, dataCompletionArg, aggregationArg) => {
|
|
13271
13495
|
const target = H$8.accept(targetDate, Types.NUMBER);
|
|
13272
13496
|
const ys = H$8.accept(values, Types.ARRAY, undefined, true, true);
|
|
13273
13497
|
const xs = H$8.accept(timeline, Types.ARRAY, undefined, true, true);
|
|
13274
|
-
const seasonality = seasonalityArg
|
|
13275
|
-
|
|
13276
|
-
: Math.floor(H$8.accept(seasonalityArg, Types.NUMBER, 1));
|
|
13277
|
-
// dataCompletion + aggregation honored only at the prep step —
|
|
13278
|
-
// gap-filling uses the previous value (matches Excel's
|
|
13279
|
-
// dataCompletion=1 default).
|
|
13498
|
+
const seasonality = forecastEtsSeasonality(seasonalityArg);
|
|
13499
|
+
forecastEtsValidateOptions(dataCompletionArg, aggregationArg);
|
|
13280
13500
|
const { y, lastX, step, period } = forecastEtsPrep(ys, xs, seasonality);
|
|
13281
13501
|
if (target < lastX) {
|
|
13282
13502
|
// Excel returns #NUM! for in-history targets in FORECAST.ETS
|
|
@@ -13289,7 +13509,7 @@ const DistributionFunctions = {
|
|
|
13289
13509
|
return forecast[horizon - 1];
|
|
13290
13510
|
},
|
|
13291
13511
|
|
|
13292
|
-
'FORECAST.ETS.CONFINT': (targetDate, values, timeline, confidenceArg, seasonalityArg) => {
|
|
13512
|
+
'FORECAST.ETS.CONFINT': (targetDate, values, timeline, confidenceArg, seasonalityArg, dataCompletionArg, aggregationArg) => {
|
|
13293
13513
|
const target = H$8.accept(targetDate, Types.NUMBER);
|
|
13294
13514
|
const ys = H$8.accept(values, Types.ARRAY, undefined, true, true);
|
|
13295
13515
|
const xs = H$8.accept(timeline, Types.ARRAY, undefined, true, true);
|
|
@@ -13297,9 +13517,8 @@ const DistributionFunctions = {
|
|
|
13297
13517
|
? 0.95
|
|
13298
13518
|
: H$8.accept(confidenceArg, Types.NUMBER, 0.95);
|
|
13299
13519
|
if (confidence <= 0 || confidence >= 1) throw FormulaError.NUM;
|
|
13300
|
-
const seasonality = seasonalityArg
|
|
13301
|
-
|
|
13302
|
-
: Math.floor(H$8.accept(seasonalityArg, Types.NUMBER, 1));
|
|
13520
|
+
const seasonality = forecastEtsSeasonality(seasonalityArg);
|
|
13521
|
+
forecastEtsValidateOptions(dataCompletionArg, aggregationArg);
|
|
13303
13522
|
const { y, lastX, step, period } = forecastEtsPrep(ys, xs, seasonality);
|
|
13304
13523
|
if (target < lastX) throw FormulaError.NUM;
|
|
13305
13524
|
const horizon = Math.max(1, Math.round((target - lastX) / step));
|
|
@@ -13319,21 +13538,21 @@ const DistributionFunctions = {
|
|
|
13319
13538
|
return z * rmse * Math.sqrt(horizon);
|
|
13320
13539
|
},
|
|
13321
13540
|
|
|
13322
|
-
'FORECAST.ETS.SEASONALITY': (values, timeline) => {
|
|
13541
|
+
'FORECAST.ETS.SEASONALITY': (values, timeline, dataCompletionArg, aggregationArg) => {
|
|
13323
13542
|
const ys = H$8.accept(values, Types.ARRAY, undefined, true, true);
|
|
13324
13543
|
const xs = H$8.accept(timeline, Types.ARRAY, undefined, true, true);
|
|
13325
|
-
|
|
13326
|
-
|
|
13544
|
+
forecastEtsValidateOptions(dataCompletionArg, aggregationArg);
|
|
13545
|
+
const { period } = forecastEtsPrep(ys, xs, 1);
|
|
13546
|
+
return period;
|
|
13327
13547
|
},
|
|
13328
13548
|
|
|
13329
|
-
'FORECAST.ETS.STAT': (values, timeline, statTypeArg, seasonalityArg) => {
|
|
13549
|
+
'FORECAST.ETS.STAT': (values, timeline, statTypeArg, seasonalityArg, dataCompletionArg, aggregationArg) => {
|
|
13330
13550
|
const ys = H$8.accept(values, Types.ARRAY, undefined, true, true);
|
|
13331
13551
|
const xs = H$8.accept(timeline, Types.ARRAY, undefined, true, true);
|
|
13332
13552
|
const statType = Math.floor(H$8.accept(statTypeArg, Types.NUMBER));
|
|
13333
13553
|
if (statType < 1 || statType > 8) throw FormulaError.NUM;
|
|
13334
|
-
const seasonality = seasonalityArg
|
|
13335
|
-
|
|
13336
|
-
: Math.floor(H$8.accept(seasonalityArg, Types.NUMBER, 1));
|
|
13554
|
+
const seasonality = forecastEtsSeasonality(seasonalityArg);
|
|
13555
|
+
forecastEtsValidateOptions(dataCompletionArg, aggregationArg);
|
|
13337
13556
|
const { y, period } = forecastEtsPrep(ys, xs, seasonality);
|
|
13338
13557
|
const { alpha, beta, gamma, fitted } = fitHoltWinters(y, period, 0);
|
|
13339
13558
|
// Excel STAT codes:
|
|
@@ -14420,6 +14639,9 @@ const DistributionFunctions = {
|
|
|
14420
14639
|
const prediction = truncated.reduce((sum, value, idx) => sum + value * slopes[idx], 0) + intercept;
|
|
14421
14640
|
return [prediction];
|
|
14422
14641
|
});
|
|
14642
|
+
// A single new_x is a plain number in Excel (and in the Rust
|
|
14643
|
+
// engine); a 1x1 array would anchor an array formula on the cell.
|
|
14644
|
+
if (result.length === 1) return result[0][0];
|
|
14423
14645
|
return result;
|
|
14424
14646
|
},
|
|
14425
14647
|
|
|
@@ -15402,7 +15624,7 @@ const StatisticalFunctions = {
|
|
|
15402
15624
|
COUNTIF: (range, criteria) => {
|
|
15403
15625
|
// do not flatten the array
|
|
15404
15626
|
range = H$7.accept(range, Types.ARRAY, undefined, false, true);
|
|
15405
|
-
const isCriteriaArray = criteria
|
|
15627
|
+
const isCriteriaArray = criteria?.isArray;
|
|
15406
15628
|
criteria = H$7.accept(criteria);
|
|
15407
15629
|
|
|
15408
15630
|
const countMatching = (parsed) => {
|
|
@@ -16717,7 +16939,15 @@ const MathFunctions = {
|
|
|
16717
16939
|
FLOOR: (number, significance) => {
|
|
16718
16940
|
number = H$6.accept(number, Types.NUMBER);
|
|
16719
16941
|
significance = H$6.accept(significance, Types.NUMBER, 1);
|
|
16720
|
-
|
|
16942
|
+
// Excel: a zero significance is #DIV/0! for FLOOR of a non-zero number
|
|
16943
|
+
// (FLOOR(0,0) is 0; CEILING, FLOOR.MATH and FLOOR.PRECISE return 0); a
|
|
16944
|
+
// positive number with a negative significance is #NUM!; a negative
|
|
16945
|
+
// number rounds away from zero unless the significance is negative too
|
|
16946
|
+
// (FLOOR(-2.5,2) = -4, FLOOR(-2.5,-2) = -2).
|
|
16947
|
+
if (significance === 0) {
|
|
16948
|
+
if (number === 0) return 0;
|
|
16949
|
+
throw FormulaError.DIV0;
|
|
16950
|
+
}
|
|
16721
16951
|
if (number > 0 && significance < 0) throw FormulaError.NUM;
|
|
16722
16952
|
const absSignificance = Math.abs(significance);
|
|
16723
16953
|
const quotient = excelQuotient(Math.abs(number), absSignificance);
|
|
@@ -16742,6 +16972,7 @@ const MathFunctions = {
|
|
|
16742
16972
|
// mode can be 0 or any other number, 0 means away from zero
|
|
16743
16973
|
// the official documentation seems wrong
|
|
16744
16974
|
mode = H$6.accept(mode, Types.NUMBER, 0);
|
|
16975
|
+
if (significance === 0) return 0;
|
|
16745
16976
|
// The Mode argument does not affect positive numbers.
|
|
16746
16977
|
if (mode === 0 || number >= 0) {
|
|
16747
16978
|
// away from zero
|
|
@@ -16754,7 +16985,8 @@ const MathFunctions = {
|
|
|
16754
16985
|
"FLOOR.PRECISE": (number, significance) => {
|
|
16755
16986
|
number = H$6.accept(number, Types.NUMBER);
|
|
16756
16987
|
significance = H$6.accept(significance, Types.NUMBER, 1);
|
|
16757
|
-
//
|
|
16988
|
+
// A zero significance is 0 (Excel); otherwise the mathematical floor.
|
|
16989
|
+
if (significance === 0) return 0;
|
|
16758
16990
|
return MathFunctions.FLOOR(number, Math.abs(significance));
|
|
16759
16991
|
},
|
|
16760
16992
|
|
|
@@ -17260,6 +17492,22 @@ const MathFunctions = {
|
|
|
17260
17492
|
return result;
|
|
17261
17493
|
},
|
|
17262
17494
|
|
|
17495
|
+
/**
|
|
17496
|
+
* PERCENTOF(data_subset, data_all) — SUM(data_subset) / SUM(data_all) under
|
|
17497
|
+
* SUM's rules (text / logical / blank cells inside ranges are skipped, error
|
|
17498
|
+
* values propagate, a non-numeric scalar is #VALUE!); a zero whole is
|
|
17499
|
+
* #DIV/0!. Excel 365's helper for GROUPBY / PIVOTBY percent-of-total.
|
|
17500
|
+
*/
|
|
17501
|
+
PERCENTOF: (dataSubset, dataAll) => {
|
|
17502
|
+
if (H$6.isOmitted(dataSubset) || H$6.isOmitted(dataAll)) {
|
|
17503
|
+
throw FormulaError.ARG_MISSING([Types.NUMBER]);
|
|
17504
|
+
}
|
|
17505
|
+
const subset = MathFunctions.SUM(dataSubset);
|
|
17506
|
+
const all = MathFunctions.SUM(dataAll);
|
|
17507
|
+
if (all === 0) throw FormulaError.DIV0;
|
|
17508
|
+
return subset / all;
|
|
17509
|
+
},
|
|
17510
|
+
|
|
17263
17511
|
/**
|
|
17264
17512
|
* This functions requires instance of {@link FormulaParser}.
|
|
17265
17513
|
*/
|
|
@@ -19846,6 +20094,10 @@ const ReferenceFunctions = {
|
|
|
19846
20094
|
// Retrieve actual data to determine effective column count
|
|
19847
20095
|
const { val: data } = context.utils.extractRefValue(obj, context.position);
|
|
19848
20096
|
const colCount = Array.isArray(data) && Array.isArray(data[0]) ? data[0].length : 1;
|
|
20097
|
+
// One column (`COLUMN(C:C)`, `COLUMN(B2:B5)`) is a plain number, as
|
|
20098
|
+
// Excel shows it and as the Rust engine returns it; a 1x1 array would
|
|
20099
|
+
// make the host anchor an array formula on the cell.
|
|
20100
|
+
if (colCount <= 1) return fromCol;
|
|
19849
20101
|
const result = [];
|
|
19850
20102
|
for (let i = 0; i < colCount; i++) {
|
|
19851
20103
|
result.push(fromCol + i);
|
|
@@ -19862,7 +20114,11 @@ const ReferenceFunctions = {
|
|
|
19862
20114
|
if (obj == null) {
|
|
19863
20115
|
throw Error("COLUMNS requires one argument");
|
|
19864
20116
|
}
|
|
19865
|
-
if (
|
|
20117
|
+
if (obj instanceof FormulaError) throw obj;
|
|
20118
|
+
// An array (`{1,2;3,4}`, `SEQUENCE(3,2)`, `RANDARRAY(3,2)`, a SORT/FILTER
|
|
20119
|
+
// result) reports its width; a scalar is a 1×1 array (`COLUMNS(5)` = 1).
|
|
20120
|
+
if (Array.isArray(obj)) return obj[0]?.length ?? 0;
|
|
20121
|
+
if (typeof obj != "object") return 1;
|
|
19866
20122
|
if (H$2.isCellRef(obj)) {
|
|
19867
20123
|
return 1;
|
|
19868
20124
|
} else if (H$2.isRangeRef(obj)) {
|
|
@@ -19913,6 +20169,7 @@ const ReferenceFunctions = {
|
|
|
19913
20169
|
|
|
19914
20170
|
// Special
|
|
19915
20171
|
INDEX: (context, ranges, rowNum, colNum, areaNum) => {
|
|
20172
|
+
if (ranges === undefined) throw FormulaError.ARG_MISSING([Types.ARRAY]);
|
|
19916
20173
|
// retrieve values
|
|
19917
20174
|
const isColNumMissing = colNum == null;
|
|
19918
20175
|
const isRowNumMissing = rowNum == null;
|
|
@@ -20295,6 +20552,8 @@ const ReferenceFunctions = {
|
|
|
20295
20552
|
// Retrieve actual data to determine effective row count
|
|
20296
20553
|
const { val: data } = context.utils.extractRefValue(obj, context.position);
|
|
20297
20554
|
const rowCount = Array.isArray(data) ? data.length : 1;
|
|
20555
|
+
// One row (`ROW(3:3)`, `ROW(B2:C2)`) is a plain number; see COLUMN.
|
|
20556
|
+
if (rowCount <= 1) return fromRow;
|
|
20298
20557
|
const result = [];
|
|
20299
20558
|
for (let i = 0; i < rowCount; i++) {
|
|
20300
20559
|
result.push([fromRow + i]); // Vertical array (column vector)
|
|
@@ -20311,7 +20570,11 @@ const ReferenceFunctions = {
|
|
|
20311
20570
|
if (obj == null) {
|
|
20312
20571
|
throw Error("ROWS requires one argument");
|
|
20313
20572
|
}
|
|
20314
|
-
if (
|
|
20573
|
+
if (obj instanceof FormulaError) throw obj;
|
|
20574
|
+
// An array (`{1,2;3,4}`, `SEQUENCE(3,2)`, `RANDARRAY(3,2)`, a SORT/FILTER
|
|
20575
|
+
// result) reports its height; a scalar is a 1×1 array (`ROWS(5)` = 1).
|
|
20576
|
+
if (Array.isArray(obj)) return obj.length;
|
|
20577
|
+
if (typeof obj != "object") return 1;
|
|
20315
20578
|
if (H$2.isCellRef(obj)) {
|
|
20316
20579
|
return 1;
|
|
20317
20580
|
} else if (H$2.isRangeRef(obj)) {
|
|
@@ -21113,6 +21376,44 @@ const ReferenceFunctions = {
|
|
|
21113
21376
|
);
|
|
21114
21377
|
},
|
|
21115
21378
|
|
|
21379
|
+
/**
|
|
21380
|
+
* TRIMRANGE(range, [trim_rows], [trim_cols]) — drops the blank rows /
|
|
21381
|
+
* columns at the edges of a range or array: 0 keeps every row / column,
|
|
21382
|
+
* 1 trims leading blanks, 2 trailing blanks, 3 (default) both. Only truly
|
|
21383
|
+
* empty cells are blank (a "" text result or a 0 keeps its row); trimming
|
|
21384
|
+
* everything away is `#REF!`, as in Excel. The function form of Excel's
|
|
21385
|
+
* `A1.:.E10` / `A1:.E10` / `A1.:E10` trim-ref operators.
|
|
21386
|
+
*/
|
|
21387
|
+
TRIMRANGE: (range, trim_rows, trim_cols) => {
|
|
21388
|
+
if (H$2.isOmitted(range)) throw FormulaError.ARG_MISSING([Types.ARRAY]);
|
|
21389
|
+
const mode = (arg) => {
|
|
21390
|
+
if (H$2.isOmitted(arg)) return 3;
|
|
21391
|
+
const n = Math.trunc(H$2.accept(arg, Types.NUMBER, 3));
|
|
21392
|
+
if (n < 0 || n > 3) throw FormulaError.VALUE;
|
|
21393
|
+
return n;
|
|
21394
|
+
};
|
|
21395
|
+
const trimRows = mode(trim_rows);
|
|
21396
|
+
const trimCols = mode(trim_cols);
|
|
21397
|
+
const m = toMatrix(range);
|
|
21398
|
+
const height = m.length;
|
|
21399
|
+
const width = m[0]?.length ?? 0;
|
|
21400
|
+
const isBlank = (v) => v == null;
|
|
21401
|
+
const blankRow = (r) => m[r].every(isBlank);
|
|
21402
|
+
const blankCol = (c) => m.every((row) => isBlank(row[c]));
|
|
21403
|
+
let rowStart = 0;
|
|
21404
|
+
let rowEnd = height;
|
|
21405
|
+
if (trimRows & 1) while (rowStart < rowEnd && blankRow(rowStart)) rowStart++;
|
|
21406
|
+
if (trimRows & 2) while (rowEnd > rowStart && blankRow(rowEnd - 1)) rowEnd--;
|
|
21407
|
+
let colStart = 0;
|
|
21408
|
+
let colEnd = width;
|
|
21409
|
+
if (trimCols & 1) while (colStart < colEnd && blankCol(colStart)) colStart++;
|
|
21410
|
+
if (trimCols & 2) while (colEnd > colStart && blankCol(colEnd - 1)) colEnd--;
|
|
21411
|
+
if (rowStart >= rowEnd || colStart >= colEnd) throw FormulaError.REF;
|
|
21412
|
+
return m
|
|
21413
|
+
.slice(rowStart, rowEnd)
|
|
21414
|
+
.map((row) => row.slice(colStart, colEnd));
|
|
21415
|
+
},
|
|
21416
|
+
|
|
21116
21417
|
/**
|
|
21117
21418
|
* EXPAND(array, rows, [columns], [pad_with]) — grows an array to
|
|
21118
21419
|
* `rows`×`columns`, filling new cells with `pad_with` (default `#N/A`).
|
|
@@ -21219,21 +21520,33 @@ const ReferenceFunctions = {
|
|
|
21219
21520
|
return result;
|
|
21220
21521
|
},
|
|
21221
21522
|
|
|
21523
|
+
/**
|
|
21524
|
+
* SORTN(range, [n], [display_ties_mode], [sort_column], [is_ascending]) —
|
|
21525
|
+
* Google Sheets. SORT on one key column followed by the first n rows;
|
|
21526
|
+
* display_ties_mode is accepted and ignored. A negative n is #VALUE!. The
|
|
21527
|
+
* rnc engine mirrors these rules.
|
|
21528
|
+
*/
|
|
21222
21529
|
SORTN: (context, range, n, displayTies, sortColumn, isAscending) => {
|
|
21223
|
-
range = H$2.accept(range, Types.ARRAY);
|
|
21530
|
+
range = H$2.accept(range, Types.ARRAY, undefined, false, true);
|
|
21224
21531
|
n = H$2.accept(n, Types.NUMBER, range.length);
|
|
21225
21532
|
displayTies = H$2.accept(displayTies, Types.NUMBER, 0);
|
|
21226
21533
|
sortColumn = H$2.accept(sortColumn, Types.NUMBER, 1);
|
|
21227
21534
|
isAscending = H$2.accept(isAscending, Types.BOOLEAN, true);
|
|
21228
|
-
|
|
21229
|
-
|
|
21230
|
-
|
|
21231
|
-
|
|
21232
|
-
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21535
|
+
if (n < 0) throw FormulaError.VALUE;
|
|
21536
|
+
if (!Array.isArray(range) || range.length === 0) return range;
|
|
21537
|
+
// Same stable row sort as SORT's row path (SORT itself re-parses its
|
|
21538
|
+
// arguments as parser params, so it cannot be called with the already
|
|
21539
|
+
// accepted values — doing so made every SORTN call #VALUE!).
|
|
21540
|
+
const width = range[0]?.length ?? 0;
|
|
21541
|
+
if (sortColumn < 1 || sortColumn > width) throw FormulaError.VALUE;
|
|
21542
|
+
const key = sortColumn - 1;
|
|
21543
|
+
const compareValues = makeComparator(context?.locale);
|
|
21544
|
+
const indexed = range.map((row, idx) => ({ row, idx }));
|
|
21545
|
+
indexed.sort((a, b) => {
|
|
21546
|
+
const comparison = compareValues(a.row[key], b.row[key]);
|
|
21547
|
+
return (isAscending ? comparison : -comparison) || a.idx - b.idx;
|
|
21548
|
+
});
|
|
21549
|
+
return indexed.slice(0, n).map((entry) => entry.row);
|
|
21237
21550
|
},
|
|
21238
21551
|
|
|
21239
21552
|
/**
|
|
@@ -25346,6 +25659,10 @@ const FinancialFunctions = {
|
|
|
25346
25659
|
// identifiers. Importers keep the cached result Excel wrote and exporters write
|
|
25347
25660
|
// the formula back unchanged, so a workbook round-trips intact; only a
|
|
25348
25661
|
// recalculation on this engine surfaces the #N/A.
|
|
25662
|
+
//
|
|
25663
|
+
// QUERY is the Google Sheets counterpart: it needs a query-language interpreter
|
|
25664
|
+
// neither engine has, and degrades the same way (the rnc engine registers the
|
|
25665
|
+
// same stub) so a Sheets-authored workbook evaluates identically on both.
|
|
25349
25666
|
const unavailable = () => {
|
|
25350
25667
|
throw FormulaError.NA;
|
|
25351
25668
|
};
|
|
@@ -25365,6 +25682,7 @@ const ExternalFunctions = {
|
|
|
25365
25682
|
EUROCONVERT: unavailable,
|
|
25366
25683
|
TRANSLATE: unavailable,
|
|
25367
25684
|
DETECTLANGUAGE: unavailable,
|
|
25685
|
+
QUERY: unavailable,
|
|
25368
25686
|
};
|
|
25369
25687
|
|
|
25370
25688
|
var api$3 = {};
|
|
@@ -44513,7 +44831,10 @@ class Parser extends EmbeddedActionsParser {
|
|
|
44513
44831
|
) {
|
|
44514
44832
|
return arr[0][0];
|
|
44515
44833
|
}
|
|
44516
|
-
|
|
44834
|
+
const array = this.utils.toArray(arr);
|
|
44835
|
+
return context.async
|
|
44836
|
+
? this.utils.settleArrayItems(array, $.position)
|
|
44837
|
+
: array;
|
|
44517
44838
|
});
|
|
44518
44839
|
});
|
|
44519
44840
|
|
|
@@ -44534,9 +44855,13 @@ class Parser extends EmbeddedActionsParser {
|
|
|
44534
44855
|
const prefix = $.OPTION(() => $.SUBRULE($.plusMinusOp));
|
|
44535
44856
|
const image = $.CONSUME(Number$1).image;
|
|
44536
44857
|
const number = $.ACTION(() => this.utils.toNumber(image));
|
|
44858
|
+
// A signed literal is a plain number: apply the sign directly
|
|
44859
|
+
// rather than through `applyPrefix`, whose async branch would
|
|
44860
|
+
// hand the array a pending Promise (`{-100,60,60}` summed to 120
|
|
44861
|
+
// under `parseAsync`).
|
|
44537
44862
|
if (prefix)
|
|
44538
44863
|
return $.ACTION(() =>
|
|
44539
|
-
this.utils.
|
|
44864
|
+
this.utils.applyLiteralPrefix(prefix, number)
|
|
44540
44865
|
);
|
|
44541
44866
|
return number;
|
|
44542
44867
|
},
|
|
@@ -45173,18 +45498,21 @@ const expandLet = (source) => {
|
|
|
45173
45498
|
|
|
45174
45499
|
|
|
45175
45500
|
const LAMBDA_INTERNAL = "__RNC_LAMBDA__";
|
|
45176
|
-
/** Source spliced in for
|
|
45501
|
+
/** Source spliced in for a parameter the caller left out. */
|
|
45177
45502
|
const OMITTED_INTERNAL = "__RNC_OMITTED__";
|
|
45178
45503
|
const OMITTED_CALL = `${OMITTED_INTERNAL}()`;
|
|
45179
45504
|
/**
|
|
45180
45505
|
* Runtime call of a lambda value: `__RNC_CALL__(lambda, arg1, …)`. Used
|
|
45181
|
-
* when `LAMBDA(...)(...)` cannot be inlined (
|
|
45182
|
-
* evaluator reports `#VALUE!` like Excel instead of a
|
|
45506
|
+
* when `LAMBDA(...)(...)` cannot be inlined (more arguments than
|
|
45507
|
+
* parameters) so the evaluator reports `#VALUE!` like Excel instead of a
|
|
45508
|
+
* parse error.
|
|
45183
45509
|
*/
|
|
45184
45510
|
const CALL_INTERNAL = "__RNC_CALL__";
|
|
45185
45511
|
|
|
45186
|
-
// `[name]` — Excel's optional-parameter
|
|
45512
|
+
// `[name]` — Excel's optional-parameter spelling. The lexer sees it as a
|
|
45187
45513
|
// table-column token; only a bare identifier inside the brackets counts.
|
|
45514
|
+
// The brackets are documentation only: Excel lets a call leave out any
|
|
45515
|
+
// trailing parameter, bracketed or not (ISOMITTED reports it either way).
|
|
45188
45516
|
const OPTIONAL_PARAM = /^\[\s*([A-Za-z_][A-Za-z0-9_.]*)\s*\]$/;
|
|
45189
45517
|
|
|
45190
45518
|
/**
|
|
@@ -45202,8 +45530,6 @@ const paramFromSpan = (tokens, span) => {
|
|
|
45202
45530
|
return null;
|
|
45203
45531
|
};
|
|
45204
45532
|
|
|
45205
|
-
const requiredCount = (params) => params.filter((p) => !p.optional).length;
|
|
45206
|
-
|
|
45207
45533
|
/** Excel writes optional params back as `[name]`; keep that in the value form. */
|
|
45208
45534
|
const paramsToSource = (params) =>
|
|
45209
45535
|
params.map((p) => (p.optional ? `[${p.name}]` : p.name)).join(",");
|
|
@@ -45213,7 +45539,9 @@ const paramsToSource = (params) =>
|
|
|
45213
45539
|
* IIFE inlining: when `LAMBDA(...)(...)` appears, substitute the
|
|
45214
45540
|
* params with the call's arg sources and return the substituted body.
|
|
45215
45541
|
* Returns null if the structure doesn't match (e.g. LAMBDA without
|
|
45216
|
-
* immediate invocation
|
|
45542
|
+
* immediate invocation). A call with more arguments than parameters is
|
|
45543
|
+
* handed to the runtime, which reports `#VALUE!`; a call with fewer binds
|
|
45544
|
+
* the missing parameters to the omitted marker.
|
|
45217
45545
|
*/
|
|
45218
45546
|
const inlineIIFE = (source, tokens, lambdaOpenIdx, lambdaCloseIdx) => {
|
|
45219
45547
|
// Next token after the lambda's close paren must be a bare OpenParen
|
|
@@ -45241,13 +45569,10 @@ const inlineIIFE = (source, tokens, lambdaOpenIdx, lambdaCloseIdx) => {
|
|
|
45241
45569
|
callCloseIdx === callOpenIdx + 1
|
|
45242
45570
|
? []
|
|
45243
45571
|
: splitArgs(tokens, callOpenIdx + 1, callCloseIdx);
|
|
45244
|
-
if (
|
|
45245
|
-
|
|
45246
|
-
|
|
45247
|
-
|
|
45248
|
-
// Arity mismatch — hand the call to the runtime, which applies the
|
|
45249
|
-
// lambda value and reports #VALUE! (Excel's answer) instead of the
|
|
45250
|
-
// parser choking on `(...)(...)`.
|
|
45572
|
+
if (callArgSpans.length > params.length) {
|
|
45573
|
+
// Too many arguments — hand the call to the runtime, which applies
|
|
45574
|
+
// the lambda value and reports #VALUE! (Excel's answer) instead of
|
|
45575
|
+
// the parser choking on `(...)(...)`.
|
|
45251
45576
|
const lambdaValue = transformLambdaToValue(
|
|
45252
45577
|
source,
|
|
45253
45578
|
tokens,
|
|
@@ -45265,9 +45590,9 @@ const inlineIIFE = (source, tokens, lambdaOpenIdx, lambdaCloseIdx) => {
|
|
|
45265
45590
|
};
|
|
45266
45591
|
}
|
|
45267
45592
|
const bindings = params.map(({ name }, i) => {
|
|
45268
|
-
// Raw value — substituteNamesInBody decides the wrap.
|
|
45269
|
-
//
|
|
45270
|
-
//
|
|
45593
|
+
// Raw value — substituteNamesInBody decides the wrap. A parameter the
|
|
45594
|
+
// call left out binds to the omitted marker so ISOMITTED can see it
|
|
45595
|
+
// (and any other use of it is #VALUE!).
|
|
45271
45596
|
const value =
|
|
45272
45597
|
i < callArgSpans.length
|
|
45273
45598
|
? sourceSlice(source, tokens, callArgSpans[i]).trim()
|
|
@@ -45392,14 +45717,15 @@ const isLambdaValue = (v) =>
|
|
|
45392
45717
|
v != null && typeof v === "object" && v.__isLambda === true;
|
|
45393
45718
|
|
|
45394
45719
|
/**
|
|
45395
|
-
* Marker bound to
|
|
45720
|
+
* Marker bound to a parameter the caller left out (Excel allows a call
|
|
45721
|
+
* with fewer arguments than parameters, `[param]` or not). Only
|
|
45396
45722
|
* `ISOMITTED` looks at it; anywhere else it is an invalid operand.
|
|
45397
45723
|
*/
|
|
45398
45724
|
const OMITTED = Object.freeze({ __isOmitted: true });
|
|
45399
45725
|
const isOmittedValue = (v) =>
|
|
45400
45726
|
v != null && typeof v === "object" && v.__isOmitted === true;
|
|
45401
45727
|
|
|
45402
|
-
/** `[name]` → optional
|
|
45728
|
+
/** `[name]` → parameter documented as optional, `name` → plain parameter. */
|
|
45403
45729
|
const parseLambdaParams = (paramsStr) =>
|
|
45404
45730
|
paramsStr
|
|
45405
45731
|
.split(",")
|
|
@@ -45410,9 +45736,43 @@ const parseLambdaParams = (paramsStr) =>
|
|
|
45410
45736
|
return { name: optional ? raw.slice(1, -1).trim() : raw, optional };
|
|
45411
45737
|
});
|
|
45412
45738
|
|
|
45739
|
+
/**
|
|
45740
|
+
* An omitted marker that escapes `ISOMITTED` — a body that returns the
|
|
45741
|
+
* omitted parameter itself, or spreads it into an array — is `#VALUE!`
|
|
45742
|
+
* in the result, per cell inside an array. Arrays are only copied when
|
|
45743
|
+
* one of their cells actually holds the marker.
|
|
45744
|
+
*/
|
|
45745
|
+
const rejectOmittedResult = (result) => {
|
|
45746
|
+
if (isOmittedValue(result)) return FormulaError.VALUE;
|
|
45747
|
+
if (!Array.isArray(result)) return result;
|
|
45748
|
+
let out = null;
|
|
45749
|
+
for (let i = 0; i < result.length; i++) {
|
|
45750
|
+
const cell = rejectOmittedResult(result[i]);
|
|
45751
|
+
if (cell !== result[i]) {
|
|
45752
|
+
if (out === null) out = result.slice();
|
|
45753
|
+
out[i] = cell;
|
|
45754
|
+
}
|
|
45755
|
+
}
|
|
45756
|
+
return out ?? result;
|
|
45757
|
+
};
|
|
45758
|
+
|
|
45413
45759
|
const requiredParamCount = (lambda) =>
|
|
45414
45760
|
lambda.params.filter((_, i) => !(lambda.optional?.[i] ?? false)).length;
|
|
45415
45761
|
|
|
45762
|
+
/**
|
|
45763
|
+
* Excel's "Incorrect Parameters" `#VALUE!` for MAP / REDUCE / SCAN / BYROW /
|
|
45764
|
+
* BYCOL / MAKEARRAY / GROUPBY / PIVOTBY: the helper drives the lambda with
|
|
45765
|
+
* a fixed number of arguments and the lambda must declare a parameter for
|
|
45766
|
+
* each of them. Only a `[name]` parameter may stay unsupplied — the
|
|
45767
|
+
* "fewer arguments than parameters" allowance is for direct calls, not
|
|
45768
|
+
* helpers. Returns the error for the whole helper call, or null when the
|
|
45769
|
+
* counts fit.
|
|
45770
|
+
*/
|
|
45771
|
+
const hofArityError = (lambda, supplied) =>
|
|
45772
|
+
supplied < requiredParamCount(lambda) || supplied > lambda.params.length
|
|
45773
|
+
? FormulaError.VALUE
|
|
45774
|
+
: null;
|
|
45775
|
+
|
|
45416
45776
|
const formatLiteral = (value) => {
|
|
45417
45777
|
if (value === null || value === undefined) return "0";
|
|
45418
45778
|
if (value instanceof FormulaError) {
|
|
@@ -45455,12 +45815,11 @@ const formatLiteral = (value) => {
|
|
|
45455
45815
|
*/
|
|
45456
45816
|
const applyLambda = async (context, lambda, args) => {
|
|
45457
45817
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
45458
|
-
|
|
45459
|
-
|
|
45460
|
-
|
|
45461
|
-
|
|
45462
|
-
|
|
45463
|
-
}
|
|
45818
|
+
// Excel rejects surplus arguments but accepts fewer than declared: the
|
|
45819
|
+
// missing parameters are omitted (ISOMITTED → TRUE, any other use →
|
|
45820
|
+
// #VALUE!). Helpers check their own parameter count with
|
|
45821
|
+
// `hofArityError` before getting here.
|
|
45822
|
+
if (args.length > lambda.params.length) return FormulaError.VALUE;
|
|
45464
45823
|
|
|
45465
45824
|
context.__lambdaDepth = (context.__lambdaDepth ?? 0) + 1;
|
|
45466
45825
|
if (context.__lambdaDepth > MAX_LAMBDA_DEPTH) {
|
|
@@ -45473,14 +45832,14 @@ const applyLambda = async (context, lambda, args) => {
|
|
|
45473
45832
|
try {
|
|
45474
45833
|
const bindings = lambda.params.map((name, i) => ({
|
|
45475
45834
|
name,
|
|
45476
|
-
//
|
|
45835
|
+
// A parameter the call left out binds to the omitted marker so
|
|
45477
45836
|
// ISOMITTED inside the body can report it.
|
|
45478
45837
|
value:
|
|
45479
45838
|
i < args.length ? `(${formatLiteral(args[i])})` : `(${OMITTED_CALL})`,
|
|
45480
45839
|
}));
|
|
45481
45840
|
const substituted = substituteNamesInBody(lambda.body, bindings);
|
|
45482
45841
|
const result = await context.parseAsync(substituted, context.position, true);
|
|
45483
|
-
return result;
|
|
45842
|
+
return rejectOmittedResult(result);
|
|
45484
45843
|
} catch (e) {
|
|
45485
45844
|
if (e instanceof FormulaError) return e;
|
|
45486
45845
|
throw e;
|
|
@@ -45541,35 +45900,58 @@ const LambdaFunctions = {
|
|
|
45541
45900
|
},
|
|
45542
45901
|
|
|
45543
45902
|
/**
|
|
45544
|
-
* Internal: the value spliced in for
|
|
45545
|
-
*
|
|
45903
|
+
* Internal: the value spliced in for a parameter the caller left out
|
|
45904
|
+
* (see the LAMBDA preprocessor / applyLambda).
|
|
45546
45905
|
*/
|
|
45547
45906
|
__RNC_OMITTED__: () => OMITTED,
|
|
45548
45907
|
|
|
45549
45908
|
/**
|
|
45550
45909
|
* Internal: `__RNC_CALL__(lambda, arg1, …)` — the preprocessor emits
|
|
45551
|
-
* this for an inline `LAMBDA(...)(...)`
|
|
45552
|
-
*
|
|
45910
|
+
* this for an inline `LAMBDA(...)(...)` with more arguments than
|
|
45911
|
+
* parameters, so the arity error surfaces as #VALUE!.
|
|
45553
45912
|
*/
|
|
45554
45913
|
__RNC_CALL__: async (context, lambdaArg, ...args) =>
|
|
45555
45914
|
applyLambda(context, unwrap(lambdaArg), args.map(unwrap)),
|
|
45556
45915
|
|
|
45557
45916
|
/**
|
|
45558
|
-
* ISOMITTED(argument) — TRUE when `argument` is
|
|
45559
|
-
*
|
|
45917
|
+
* ISOMITTED(argument) — TRUE when `argument` is a LAMBDA parameter the
|
|
45918
|
+
* caller did not supply (bracketed `[p]` or not), FALSE otherwise.
|
|
45560
45919
|
*/
|
|
45561
45920
|
ISOMITTED: (arg) => isOmittedValue(unwrap(arg)),
|
|
45562
45921
|
|
|
45922
|
+
/**
|
|
45923
|
+
* A well-formed `LET(name, value, ..., body)` never reaches the function
|
|
45924
|
+
* table — the LET preprocessor inlines it. One that does is malformed:
|
|
45925
|
+
* too few arguments is `#N/A` (the too-few-arguments contract shared with
|
|
45926
|
+
* the Rust engine), an even count or a non-name binding is `#VALUE!`.
|
|
45927
|
+
*/
|
|
45928
|
+
LET: (...args) => {
|
|
45929
|
+
if (args.length < 3) return FormulaError.NA;
|
|
45930
|
+
return FormulaError.VALUE;
|
|
45931
|
+
},
|
|
45932
|
+
|
|
45933
|
+
/**
|
|
45934
|
+
* Same for `LAMBDA(...)`: the preprocessor rewrites every LAMBDA with a
|
|
45935
|
+
* body, so only `LAMBDA()` (too few arguments, `#N/A`) and a body-less
|
|
45936
|
+
* `LAMBDA(x)` (a lambda value as the cell result, `#CALC!`) get here.
|
|
45937
|
+
*/
|
|
45938
|
+
LAMBDA: (...args) => {
|
|
45939
|
+
if (args.length === 0) return FormulaError.NA;
|
|
45940
|
+
return FormulaError.CALC;
|
|
45941
|
+
},
|
|
45942
|
+
|
|
45563
45943
|
/**
|
|
45564
45944
|
* MAP(array1, [array2, ...], lambda) — applies lambda to each set
|
|
45565
45945
|
* of corresponding elements. Returns a same-shape 2D array.
|
|
45566
45946
|
*/
|
|
45567
45947
|
MAP: async (context, ...args) => {
|
|
45568
|
-
if (args.length < 2) return FormulaError.
|
|
45948
|
+
if (args.length < 2) return FormulaError.NA;
|
|
45569
45949
|
const lambda = unwrap(args[args.length - 1]);
|
|
45570
45950
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
45571
45951
|
const arrays = args.slice(0, -1).map((a) => as2D(unwrap(a)));
|
|
45572
45952
|
if (arrays.length === 0) return FormulaError.VALUE;
|
|
45953
|
+
const arity = hofArityError(lambda, arrays.length);
|
|
45954
|
+
if (arity) return arity;
|
|
45573
45955
|
const rows = arrays[0].length;
|
|
45574
45956
|
const cols = arrays[0][0]?.length ?? 0;
|
|
45575
45957
|
const out = [];
|
|
@@ -45591,6 +45973,8 @@ const LambdaFunctions = {
|
|
|
45591
45973
|
REDUCE: async (context, initialArg, arrayArg, lambdaArg) => {
|
|
45592
45974
|
const lambda = unwrap(lambdaArg);
|
|
45593
45975
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
45976
|
+
const arity = hofArityError(lambda, 2);
|
|
45977
|
+
if (arity) return arity;
|
|
45594
45978
|
let acc = unwrap(initialArg);
|
|
45595
45979
|
const values = flatten2D(as2D(unwrap(arrayArg)));
|
|
45596
45980
|
for (const v of values) {
|
|
@@ -45608,6 +45992,8 @@ const LambdaFunctions = {
|
|
|
45608
45992
|
SCAN: async (context, initialArg, arrayArg, lambdaArg) => {
|
|
45609
45993
|
const lambda = unwrap(lambdaArg);
|
|
45610
45994
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
45995
|
+
const arity = hofArityError(lambda, 2);
|
|
45996
|
+
if (arity) return arity;
|
|
45611
45997
|
let acc = unwrap(initialArg);
|
|
45612
45998
|
const arr = as2D(unwrap(arrayArg));
|
|
45613
45999
|
const out = [];
|
|
@@ -45629,8 +46015,11 @@ const LambdaFunctions = {
|
|
|
45629
46015
|
* array), returns a column vector of results.
|
|
45630
46016
|
*/
|
|
45631
46017
|
BYROW: async (context, arrayArg, lambdaArg) => {
|
|
46018
|
+
if (lambdaArg === undefined) return FormulaError.NA;
|
|
45632
46019
|
const lambda = unwrap(lambdaArg);
|
|
45633
46020
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
46021
|
+
const arity = hofArityError(lambda, 1);
|
|
46022
|
+
if (arity) return arity;
|
|
45634
46023
|
const arr = as2D(unwrap(arrayArg));
|
|
45635
46024
|
const out = [];
|
|
45636
46025
|
for (const row of arr) {
|
|
@@ -45646,8 +46035,11 @@ const LambdaFunctions = {
|
|
|
45646
46035
|
* 1D array), returns a row vector of results.
|
|
45647
46036
|
*/
|
|
45648
46037
|
BYCOL: async (context, arrayArg, lambdaArg) => {
|
|
46038
|
+
if (lambdaArg === undefined) return FormulaError.NA;
|
|
45649
46039
|
const lambda = unwrap(lambdaArg);
|
|
45650
46040
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
46041
|
+
const arity = hofArityError(lambda, 1);
|
|
46042
|
+
if (arity) return arity;
|
|
45651
46043
|
const arr = as2D(unwrap(arrayArg));
|
|
45652
46044
|
const cols = arr[0]?.length ?? 0;
|
|
45653
46045
|
const out = [];
|
|
@@ -45665,8 +46057,11 @@ const LambdaFunctions = {
|
|
|
45665
46057
|
* lambda(r, c) for each cell. r, c are 1-indexed per Excel.
|
|
45666
46058
|
*/
|
|
45667
46059
|
MAKEARRAY: async (context, rowsArg, colsArg, lambdaArg) => {
|
|
46060
|
+
if (lambdaArg === undefined) return FormulaError.NA;
|
|
45668
46061
|
const lambda = unwrap(lambdaArg);
|
|
45669
46062
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
46063
|
+
const arity = hofArityError(lambda, 2);
|
|
46064
|
+
if (arity) return arity;
|
|
45670
46065
|
const rows = Math.floor(
|
|
45671
46066
|
FormulaHelpers.accept(rowsArg, Types.NUMBER, 0),
|
|
45672
46067
|
);
|
|
@@ -45715,6 +46110,8 @@ const LambdaFunctions = {
|
|
|
45715
46110
|
const valueFields = as2D(unwrap(args[1]));
|
|
45716
46111
|
const lambda = unwrap(args[2]);
|
|
45717
46112
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
46113
|
+
const arity = hofArityError(lambda, 1);
|
|
46114
|
+
if (arity) return arity;
|
|
45718
46115
|
const fieldHeaders = args[3] != null
|
|
45719
46116
|
? Math.floor(FormulaHelpers.accept(args[3], Types.NUMBER, 0))
|
|
45720
46117
|
: 0;
|
|
@@ -45763,6 +46160,8 @@ const LambdaFunctions = {
|
|
|
45763
46160
|
const valueFields = as2D(unwrap(args[2]));
|
|
45764
46161
|
const lambda = unwrap(args[3]);
|
|
45765
46162
|
if (!isLambdaValue(lambda)) return FormulaError.VALUE;
|
|
46163
|
+
const arity = hofArityError(lambda, 1);
|
|
46164
|
+
if (arity) return arity;
|
|
45766
46165
|
|
|
45767
46166
|
const numOpt = (i, d) =>
|
|
45768
46167
|
args[i] != null
|
|
@@ -46219,6 +46618,42 @@ let Utils$1 = class Utils {
|
|
|
46219
46618
|
return Prefix.unaryOp(prefixes, val, isArray);
|
|
46220
46619
|
}
|
|
46221
46620
|
|
|
46621
|
+
/**
|
|
46622
|
+
* Sign a numeric literal (`-1`, `+2` inside an array constant). Nothing
|
|
46623
|
+
* to dereference or await here, so this stays synchronous in both parse
|
|
46624
|
+
* modes.
|
|
46625
|
+
* @param {string} prefix - `+` or `-`
|
|
46626
|
+
* @param {number} number
|
|
46627
|
+
* @return {number}
|
|
46628
|
+
*/
|
|
46629
|
+
applyLiteralPrefix(prefix, number) {
|
|
46630
|
+
return this._applyPrefix([prefix], number, false);
|
|
46631
|
+
}
|
|
46632
|
+
|
|
46633
|
+
/**
|
|
46634
|
+
* Under `parseAsync` an array-constant item built from a sub-expression
|
|
46635
|
+
* (`{SUM(1,-2),3}`) is a pending Promise; an array must only ever carry
|
|
46636
|
+
* values, so settle them. A settled item may itself be a reference
|
|
46637
|
+
* (`{INDEX(A1:C5,1,1),2}`), which the sync arm of `constantArray`
|
|
46638
|
+
* dereferences before the Promise ever existed — do the same here so both
|
|
46639
|
+
* parse modes agree. Returns the array itself when nothing is pending.
|
|
46640
|
+
* @param {Array.<Array>} array
|
|
46641
|
+
* @param {{row: number, col: number, sheet: string}} position
|
|
46642
|
+
* @return {Array.<Array>|Promise.<Array.<Array>>}
|
|
46643
|
+
*/
|
|
46644
|
+
settleArrayItems(array, position) {
|
|
46645
|
+
const isPending = (item) =>
|
|
46646
|
+
item != null && typeof item.then === "function";
|
|
46647
|
+
if (!array.some((row) => row.some(isPending))) return array;
|
|
46648
|
+
const deref = (item) =>
|
|
46649
|
+
item && item.ref ? this.extractRefValue(item, position).val : item;
|
|
46650
|
+
return Promise.all(
|
|
46651
|
+
array.map((row) =>
|
|
46652
|
+
Promise.all(row).then((items) => items.map(deref))
|
|
46653
|
+
)
|
|
46654
|
+
);
|
|
46655
|
+
}
|
|
46656
|
+
|
|
46222
46657
|
async applyPrefixAsync(prefixes, value, position) {
|
|
46223
46658
|
const { val, isArray } = this.extractRefValue(await value, position);
|
|
46224
46659
|
return this._applyPrefix(prefixes, val, isArray);
|
|
@@ -46413,15 +46848,21 @@ let Utils$1 = class Utils {
|
|
|
46413
46848
|
}
|
|
46414
46849
|
}
|
|
46415
46850
|
|
|
46416
|
-
applyIntersect(refs) {
|
|
46851
|
+
applyIntersect(refs, position) {
|
|
46417
46852
|
// console.log('applyIntersect', refs);
|
|
46418
46853
|
if (this.isFormulaError(refs[0])) return refs[0];
|
|
46419
46854
|
if (!refs[0].ref) throw Error(`Expecting a reference, but got ${refs[0]}.`);
|
|
46420
46855
|
// a intersection will keep track of references, value won't be retrieved here.
|
|
46421
46856
|
let maxRow, maxCol, minRow, minCol, sheet, res; // index start from 1
|
|
46857
|
+
// An unqualified operand (`B:B`, sheet undefined) lives on the formula's sheet: it must
|
|
46858
|
+
// intersect an operand that names that sheet explicitly (`Sheet1!A1:C3`, or a defined name
|
|
46859
|
+
// — the host resolves names WITH their sheet). Only a genuinely different sheet is #NULL!.
|
|
46860
|
+
const currentSheet = (position ?? this.context.position)?.sheet;
|
|
46861
|
+
const sheetOf = (ref) => ref.sheet ?? currentSheet;
|
|
46422
46862
|
// first time setup
|
|
46423
46863
|
const ref = refs.shift().ref;
|
|
46424
46864
|
sheet = ref.sheet;
|
|
46865
|
+
const sheetKey = sheetOf(ref);
|
|
46425
46866
|
if (!ref.from) {
|
|
46426
46867
|
// check whole row/col reference
|
|
46427
46868
|
if (ref.row === undefined || ref.col === undefined) {
|
|
@@ -46455,10 +46896,11 @@ let Utils$1 = class Utils {
|
|
|
46455
46896
|
ref.row < minRow ||
|
|
46456
46897
|
ref.col > maxCol ||
|
|
46457
46898
|
ref.col < minCol ||
|
|
46458
|
-
|
|
46899
|
+
sheetKey !== sheetOf(ref)
|
|
46459
46900
|
) {
|
|
46460
46901
|
err = FormulaError.NULL;
|
|
46461
46902
|
}
|
|
46903
|
+
sheet ??= ref.sheet;
|
|
46462
46904
|
maxRow = minRow = ref.row;
|
|
46463
46905
|
maxCol = minCol = ref.col;
|
|
46464
46906
|
} else {
|
|
@@ -46472,10 +46914,11 @@ let Utils$1 = class Utils {
|
|
|
46472
46914
|
refMaxRow < minRow ||
|
|
46473
46915
|
refMinCol > maxCol ||
|
|
46474
46916
|
refMaxCol < minCol ||
|
|
46475
|
-
|
|
46917
|
+
sheetKey !== sheetOf(ref)
|
|
46476
46918
|
) {
|
|
46477
46919
|
err = FormulaError.NULL;
|
|
46478
46920
|
}
|
|
46921
|
+
sheet ??= ref.sheet;
|
|
46479
46922
|
// update
|
|
46480
46923
|
maxRow = Math.min(maxRow, refMaxRow);
|
|
46481
46924
|
minRow = Math.max(minRow, refMinRow);
|
|
@@ -47476,6 +47919,9 @@ class FormulaParser {
|
|
|
47476
47919
|
if (!this.keepLambdaResult && unwrapLambdaValue(result)) {
|
|
47477
47920
|
return FormulaError.CALC;
|
|
47478
47921
|
}
|
|
47922
|
+
// An inlined `LAMBDA(x,y,y)(1)` evaluates straight to the omitted
|
|
47923
|
+
// marker; only ISOMITTED may consume it, so as a result it is #VALUE!.
|
|
47924
|
+
result = rejectOmittedResult(result);
|
|
47479
47925
|
const type = typeof result;
|
|
47480
47926
|
// number
|
|
47481
47927
|
if (type === "number") {
|
|
@@ -48681,5 +49127,5 @@ class DepParser {
|
|
|
48681
49127
|
// Export a Set of implemented function names for O(1) lookup
|
|
48682
49128
|
const implementedFunctionNames = FormulaParser.getImplementedFunctionNames();
|
|
48683
49129
|
|
|
48684
|
-
export { Address, Criteria, DepParser, Factorials, FormulaError, FormulaHelpers, MAX_COLUMN, MAX_ROW, ReversedTypes, Types, UnknownChar, WildCard, allTokens, FormulaParser as default, implementedFunctionNames, lex, noop, numberToText, parseNumericText, tokenVocabulary$1 as tokenVocabulary, tolerantLex };
|
|
49130
|
+
export { Address, Criteria, DepParser, Factorials, FormulaError, FormulaHelpers, MAX_COLUMN, MAX_ROW, ReversedTypes, Types, UnknownChar, WildCard, allTokens, FormulaParser as default, implementedFunctionNames, isNestedAggregateFormula, lex, noop, numberToText, parseNumericText, tokenVocabulary$1 as tokenVocabulary, tolerantLex };
|
|
48685
49131
|
//# sourceMappingURL=index.js.map
|