@devmm/puredocs-excel-formula 1.0.8 → 1.0.9
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/index.cjs +266 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +267 -92
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -202,6 +202,8 @@ interface FunctionDef {
|
|
|
202
202
|
readonly maxArgs: number;
|
|
203
203
|
readonly isVolatile: boolean;
|
|
204
204
|
readonly handler: FormulaFunction;
|
|
205
|
+
/** Scalar function: an array argument maps it over the array. See {@link FunctionRegistry.registerElementwise}. */
|
|
206
|
+
readonly elementwise: boolean;
|
|
205
207
|
}
|
|
206
208
|
declare class FunctionRegistry {
|
|
207
209
|
#private;
|
|
@@ -209,6 +211,23 @@ declare class FunctionRegistry {
|
|
|
209
211
|
static get default(): FunctionRegistry;
|
|
210
212
|
/** Registers a function. Name is normalised to UPPERCASE. */
|
|
211
213
|
register(name: string, handler: FormulaFunction, minArgs?: number, maxArgs?: number, isVolatile?: boolean): void;
|
|
214
|
+
/**
|
|
215
|
+
* Registers a **scalar** function — one whose arguments are single values —
|
|
216
|
+
* and lifts it over arrays automatically.
|
|
217
|
+
*
|
|
218
|
+
* `LEFT(A2:A100, 3)` is not a special form in Excel: LEFT takes one string, and
|
|
219
|
+
* the grid maps it over the range, giving a 99-row array. Without that lifting
|
|
220
|
+
* the range collapses to one value, so the dynamic array built on top of it —
|
|
221
|
+
* `FILTER(IFERROR(LEFT(A2:A100,3),""), …)` — filters a 1×1 input and returns
|
|
222
|
+
* `#CALC!`. Lifting belongs here rather than in each handler: the ~40 scalar
|
|
223
|
+
* text/math/date functions then get it for free and cannot each get it subtly
|
|
224
|
+
* wrong.
|
|
225
|
+
*
|
|
226
|
+
* Only mark functions that are genuinely per-cell. Anything that *consumes* an
|
|
227
|
+
* array (SUM, COUNTIF, FILTER, SORT, the lazy branch functions) must keep
|
|
228
|
+
* `register`, or lifting would map it over the array it is meant to reduce.
|
|
229
|
+
*/
|
|
230
|
+
registerElementwise(name: string, handler: FormulaFunction, minArgs?: number, maxArgs?: number): void;
|
|
212
231
|
/** Executes a function by name with argument validation. */
|
|
213
232
|
execute(name: string, args: FormulaNode[], ctx: FormulaContext): FormulaValue;
|
|
214
233
|
/** Returns true if the named function is volatile (NOW, RAND, etc.). */
|
package/dist/index.d.ts
CHANGED
|
@@ -202,6 +202,8 @@ interface FunctionDef {
|
|
|
202
202
|
readonly maxArgs: number;
|
|
203
203
|
readonly isVolatile: boolean;
|
|
204
204
|
readonly handler: FormulaFunction;
|
|
205
|
+
/** Scalar function: an array argument maps it over the array. See {@link FunctionRegistry.registerElementwise}. */
|
|
206
|
+
readonly elementwise: boolean;
|
|
205
207
|
}
|
|
206
208
|
declare class FunctionRegistry {
|
|
207
209
|
#private;
|
|
@@ -209,6 +211,23 @@ declare class FunctionRegistry {
|
|
|
209
211
|
static get default(): FunctionRegistry;
|
|
210
212
|
/** Registers a function. Name is normalised to UPPERCASE. */
|
|
211
213
|
register(name: string, handler: FormulaFunction, minArgs?: number, maxArgs?: number, isVolatile?: boolean): void;
|
|
214
|
+
/**
|
|
215
|
+
* Registers a **scalar** function — one whose arguments are single values —
|
|
216
|
+
* and lifts it over arrays automatically.
|
|
217
|
+
*
|
|
218
|
+
* `LEFT(A2:A100, 3)` is not a special form in Excel: LEFT takes one string, and
|
|
219
|
+
* the grid maps it over the range, giving a 99-row array. Without that lifting
|
|
220
|
+
* the range collapses to one value, so the dynamic array built on top of it —
|
|
221
|
+
* `FILTER(IFERROR(LEFT(A2:A100,3),""), …)` — filters a 1×1 input and returns
|
|
222
|
+
* `#CALC!`. Lifting belongs here rather than in each handler: the ~40 scalar
|
|
223
|
+
* text/math/date functions then get it for free and cannot each get it subtly
|
|
224
|
+
* wrong.
|
|
225
|
+
*
|
|
226
|
+
* Only mark functions that are genuinely per-cell. Anything that *consumes* an
|
|
227
|
+
* array (SUM, COUNTIF, FILTER, SORT, the lazy branch functions) must keep
|
|
228
|
+
* `register`, or lifting would map it over the array it is meant to reduce.
|
|
229
|
+
*/
|
|
230
|
+
registerElementwise(name: string, handler: FormulaFunction, minArgs?: number, maxArgs?: number): void;
|
|
212
231
|
/** Executes a function by name with argument validation. */
|
|
213
232
|
execute(name: string, args: FormulaNode[], ctx: FormulaContext): FormulaValue;
|
|
214
233
|
/** Returns true if the named function is volatile (NOW, RAND, etc.). */
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toOADate, fromOADate, columnLetter, parseCellRef, cellRefFromRowCol, shiftIndex, shiftFormulaRefs, shiftSpan, EXCEL_MAX_ROWS, EXCEL_MAX_COLUMNS, columnNumber } from '@devmm/puredocs-excel';
|
|
1
|
+
import { toOADate, fromOADate, columnLetter, parseCellRef, cellRefFromRowCol, parseRangeRef, shiftIndex, shiftFormulaRefs, shiftSpan, EXCEL_MAX_ROWS, EXCEL_MAX_COLUMNS, columnNumber } from '@devmm/puredocs-excel';
|
|
2
2
|
|
|
3
3
|
var __typeError = (msg) => {
|
|
4
4
|
throw TypeError(msg);
|
|
@@ -805,6 +805,16 @@ var ArrayLiteralNode = class extends FormulaNode {
|
|
|
805
805
|
}
|
|
806
806
|
};
|
|
807
807
|
_value = new WeakMap();
|
|
808
|
+
var ValueNode = class extends FormulaNode {
|
|
809
|
+
/** Mutable so one wrapper can be re-pointed across a lifted call's cells. */
|
|
810
|
+
constructor(value2) {
|
|
811
|
+
super();
|
|
812
|
+
this.value = value2;
|
|
813
|
+
}
|
|
814
|
+
evaluate() {
|
|
815
|
+
return this.value;
|
|
816
|
+
}
|
|
817
|
+
};
|
|
808
818
|
var BlankNode = class extends FormulaNode {
|
|
809
819
|
evaluate() {
|
|
810
820
|
return FormulaValue.blank;
|
|
@@ -1574,42 +1584,42 @@ function registerMath(r) {
|
|
|
1574
1584
|
r.register("PRODUCT", product, 1);
|
|
1575
1585
|
r.register("SUMPRODUCT", sumProduct, 1);
|
|
1576
1586
|
r.register("SUMSQ", sumSq, 1);
|
|
1577
|
-
r.
|
|
1578
|
-
r.
|
|
1579
|
-
r.
|
|
1580
|
-
r.
|
|
1581
|
-
r.
|
|
1582
|
-
r.
|
|
1583
|
-
r.
|
|
1584
|
-
r.
|
|
1585
|
-
r.
|
|
1586
|
-
r.
|
|
1587
|
-
r.
|
|
1588
|
-
r.
|
|
1589
|
-
r.
|
|
1590
|
-
r.
|
|
1591
|
-
r.
|
|
1587
|
+
r.registerElementwise("ABS", abs, 1, 1);
|
|
1588
|
+
r.registerElementwise("ROUND", round, 2, 2);
|
|
1589
|
+
r.registerElementwise("ROUNDUP", roundUp, 2, 2);
|
|
1590
|
+
r.registerElementwise("ROUNDDOWN", roundDown, 2, 2);
|
|
1591
|
+
r.registerElementwise("CEILING", ceiling, 2, 2);
|
|
1592
|
+
r.registerElementwise("CEILING.MATH", ceiling, 2, 2);
|
|
1593
|
+
r.registerElementwise("FLOOR", floor, 2, 2);
|
|
1594
|
+
r.registerElementwise("FLOOR.MATH", floor, 2, 2);
|
|
1595
|
+
r.registerElementwise("INT", int_, 1, 1);
|
|
1596
|
+
r.registerElementwise("TRUNC", trunc, 1, 2);
|
|
1597
|
+
r.registerElementwise("MOD", mod, 2, 2);
|
|
1598
|
+
r.registerElementwise("QUOTIENT", quotient, 2, 2);
|
|
1599
|
+
r.registerElementwise("POWER", power, 2, 2);
|
|
1600
|
+
r.registerElementwise("SQRT", sqrt, 1, 1);
|
|
1601
|
+
r.registerElementwise("SIGN", sign, 1, 1);
|
|
1592
1602
|
r.register("PI", pi, 0, 0);
|
|
1593
1603
|
r.register("RAND", rand, 0, 0, true);
|
|
1594
1604
|
r.register("RANDBETWEEN", randBetween, 2, 2, true);
|
|
1595
|
-
r.
|
|
1596
|
-
r.
|
|
1597
|
-
r.
|
|
1598
|
-
r.
|
|
1599
|
-
r.
|
|
1600
|
-
r.
|
|
1601
|
-
r.
|
|
1602
|
-
r.
|
|
1603
|
-
r.
|
|
1604
|
-
r.
|
|
1605
|
-
r.
|
|
1606
|
-
r.
|
|
1607
|
-
r.
|
|
1608
|
-
r.
|
|
1609
|
-
r.
|
|
1610
|
-
r.
|
|
1611
|
-
r.
|
|
1612
|
-
r.
|
|
1605
|
+
r.registerElementwise("LOG", log, 1, 2);
|
|
1606
|
+
r.registerElementwise("LOG10", log10, 1, 1);
|
|
1607
|
+
r.registerElementwise("LN", ln, 1, 1);
|
|
1608
|
+
r.registerElementwise("EXP", exp, 1, 1);
|
|
1609
|
+
r.registerElementwise("FACT", fact, 1, 1);
|
|
1610
|
+
r.registerElementwise("COMBIN", combin, 2, 2);
|
|
1611
|
+
r.registerElementwise("SIN", unary(Math.sin), 1, 1);
|
|
1612
|
+
r.registerElementwise("COS", unary(Math.cos), 1, 1);
|
|
1613
|
+
r.registerElementwise("TAN", unary(Math.tan), 1, 1);
|
|
1614
|
+
r.registerElementwise("ASIN", asin, 1, 1);
|
|
1615
|
+
r.registerElementwise("ACOS", acos, 1, 1);
|
|
1616
|
+
r.registerElementwise("ATAN", unary(Math.atan), 1, 1);
|
|
1617
|
+
r.registerElementwise("ATAN2", atan2, 2, 2);
|
|
1618
|
+
r.registerElementwise("SINH", unary(Math.sinh), 1, 1);
|
|
1619
|
+
r.registerElementwise("COSH", unary(Math.cosh), 1, 1);
|
|
1620
|
+
r.registerElementwise("TANH", unary(Math.tanh), 1, 1);
|
|
1621
|
+
r.registerElementwise("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
|
|
1622
|
+
r.registerElementwise("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
|
|
1613
1623
|
}
|
|
1614
1624
|
function valueAt(v, i) {
|
|
1615
1625
|
return v.isArray ? v.arrayVal.getFlat(i) : v;
|
|
@@ -2041,31 +2051,31 @@ function combin(a, c) {
|
|
|
2041
2051
|
return FormulaValue.number(Math.round(result));
|
|
2042
2052
|
}
|
|
2043
2053
|
function registerText(r) {
|
|
2044
|
-
r.
|
|
2045
|
-
r.
|
|
2046
|
-
r.
|
|
2047
|
-
r.
|
|
2048
|
-
r.
|
|
2049
|
-
r.
|
|
2050
|
-
r.
|
|
2051
|
-
r.
|
|
2052
|
-
r.
|
|
2053
|
-
r.
|
|
2054
|
-
r.
|
|
2054
|
+
r.registerElementwise("LEFT", left, 1, 2);
|
|
2055
|
+
r.registerElementwise("RIGHT", right, 1, 2);
|
|
2056
|
+
r.registerElementwise("MID", mid, 3, 3);
|
|
2057
|
+
r.registerElementwise("LEN", len, 1, 1);
|
|
2058
|
+
r.registerElementwise("UPPER", upper, 1, 1);
|
|
2059
|
+
r.registerElementwise("LOWER", lower, 1, 1);
|
|
2060
|
+
r.registerElementwise("PROPER", proper, 1, 1);
|
|
2061
|
+
r.registerElementwise("TRIM", trim, 1, 1);
|
|
2062
|
+
r.registerElementwise("CLEAN", clean, 1, 1);
|
|
2063
|
+
r.registerElementwise("SUBSTITUTE", substitute, 3, 4);
|
|
2064
|
+
r.registerElementwise("REPLACE", replace, 4, 4);
|
|
2055
2065
|
r.register("CONCATENATE", concatenate, 1);
|
|
2056
2066
|
r.register("CONCAT", concatenate, 1);
|
|
2057
2067
|
r.register("TEXTJOIN", textJoin, 3);
|
|
2058
|
-
r.
|
|
2059
|
-
r.
|
|
2060
|
-
r.
|
|
2061
|
-
r.
|
|
2062
|
-
r.
|
|
2063
|
-
r.
|
|
2064
|
-
r.
|
|
2065
|
-
r.
|
|
2066
|
-
r.
|
|
2067
|
-
r.
|
|
2068
|
-
r.
|
|
2068
|
+
r.registerElementwise("TEXT", text_, 2, 2);
|
|
2069
|
+
r.registerElementwise("VALUE", value, 1, 1);
|
|
2070
|
+
r.registerElementwise("FIND", find, 2, 3);
|
|
2071
|
+
r.registerElementwise("SEARCH", search, 2, 3);
|
|
2072
|
+
r.registerElementwise("REPT", rept, 2, 2);
|
|
2073
|
+
r.registerElementwise("EXACT", exact, 2, 2);
|
|
2074
|
+
r.registerElementwise("CHAR", char_, 1, 1);
|
|
2075
|
+
r.registerElementwise("CODE", code, 1, 1);
|
|
2076
|
+
r.registerElementwise("T", t_, 1, 1);
|
|
2077
|
+
r.registerElementwise("TEXTBEFORE", textBefore, 2, 3);
|
|
2078
|
+
r.registerElementwise("TEXTAFTER", textAfter, 2, 3);
|
|
2069
2079
|
}
|
|
2070
2080
|
function str(a, c, idx) {
|
|
2071
2081
|
const r = FormulaHelper.evalString(a[idx], c);
|
|
@@ -2379,6 +2389,7 @@ function registerLogical(r) {
|
|
|
2379
2389
|
function if_(a, c) {
|
|
2380
2390
|
const cond = a[0].evaluate(c);
|
|
2381
2391
|
if (cond.isError) return cond;
|
|
2392
|
+
if (cond.isArray) return ifArray(cond.arrayVal, a, c);
|
|
2382
2393
|
const b = cond.coerceToBool();
|
|
2383
2394
|
if (b.isError) return b;
|
|
2384
2395
|
return b.booleanValue ? a[1].evaluate(c) : a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_;
|
|
@@ -2455,13 +2466,63 @@ function xor(a, c) {
|
|
|
2455
2466
|
}
|
|
2456
2467
|
return FormulaValue.boolean(trueCount % 2 === 1);
|
|
2457
2468
|
}
|
|
2458
|
-
function
|
|
2469
|
+
function ifArray(mask, a, c) {
|
|
2470
|
+
let wantTrue = false, wantFalse = false;
|
|
2471
|
+
for (const item of mask.values()) {
|
|
2472
|
+
const b = item.coerceToBool();
|
|
2473
|
+
if (b.isError) return b;
|
|
2474
|
+
if (b.booleanValue) wantTrue = true;
|
|
2475
|
+
else wantFalse = true;
|
|
2476
|
+
if (wantTrue && wantFalse) break;
|
|
2477
|
+
}
|
|
2478
|
+
const yes = wantTrue ? a[1].evaluate(c) : FormulaValue.blank;
|
|
2479
|
+
const no = wantFalse ? a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_ : FormulaValue.blank;
|
|
2480
|
+
const pick = (v, r, col) => {
|
|
2481
|
+
if (!v.isArray) return v;
|
|
2482
|
+
const arr = v.arrayVal;
|
|
2483
|
+
return r < arr.rows && col < arr.columns ? arr.get(r, col) : FormulaValue.errorNA;
|
|
2484
|
+
};
|
|
2485
|
+
const out = new ArrayValue(mask.rows, mask.columns);
|
|
2486
|
+
for (let r = 0; r < mask.rows; r++) {
|
|
2487
|
+
for (let col = 0; col < mask.columns; col++) {
|
|
2488
|
+
const b = mask.get(r, col).coerceToBool();
|
|
2489
|
+
out.set(r, col, b.booleanValue ? pick(yes, r, col) : pick(no, r, col));
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
return FormulaValue.array(out);
|
|
2493
|
+
}
|
|
2494
|
+
function ifCaught(a, c, caught) {
|
|
2459
2495
|
const v = a[0].evaluate(c);
|
|
2460
|
-
|
|
2496
|
+
if (!v.isArray) return caught(v) ? a[1].evaluate(c) : v;
|
|
2497
|
+
const arr = v.arrayVal;
|
|
2498
|
+
let any = false;
|
|
2499
|
+
for (const item of arr.values()) {
|
|
2500
|
+
if (caught(item)) {
|
|
2501
|
+
any = true;
|
|
2502
|
+
break;
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
if (!any) return v;
|
|
2506
|
+
const fb = a[1].evaluate(c);
|
|
2507
|
+
const fbArr = fb.isArray ? fb.arrayVal : null;
|
|
2508
|
+
const out = new ArrayValue(arr.rows, arr.columns);
|
|
2509
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
2510
|
+
for (let col = 0; col < arr.columns; col++) {
|
|
2511
|
+
const item = arr.get(r, col);
|
|
2512
|
+
if (!caught(item)) {
|
|
2513
|
+
out.set(r, col, item);
|
|
2514
|
+
continue;
|
|
2515
|
+
}
|
|
2516
|
+
out.set(r, col, fbArr ? r < fbArr.rows && col < fbArr.columns ? fbArr.get(r, col) : FormulaValue.errorNA : fb);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
return FormulaValue.array(out);
|
|
2520
|
+
}
|
|
2521
|
+
function ifError(a, c) {
|
|
2522
|
+
return ifCaught(a, c, (v) => v.isError);
|
|
2461
2523
|
}
|
|
2462
2524
|
function ifNa(a, c) {
|
|
2463
|
-
|
|
2464
|
-
return v.isError && v.errorCode === 7 /* NA */ ? a[1].evaluate(c) : v;
|
|
2525
|
+
return ifCaught(a, c, (v) => v.isError && v.errorCode === 7 /* NA */);
|
|
2465
2526
|
}
|
|
2466
2527
|
function ifs(a, c) {
|
|
2467
2528
|
for (let i = 0; i + 1 < a.length; i += 2) {
|
|
@@ -2518,21 +2579,21 @@ function fromOA(oa) {
|
|
|
2518
2579
|
function registerDate(r) {
|
|
2519
2580
|
r.register("NOW", now, 0, 0, true);
|
|
2520
2581
|
r.register("TODAY", today, 0, 0, true);
|
|
2521
|
-
r.
|
|
2522
|
-
r.
|
|
2523
|
-
r.
|
|
2524
|
-
r.
|
|
2525
|
-
r.
|
|
2526
|
-
r.
|
|
2527
|
-
r.
|
|
2528
|
-
r.
|
|
2529
|
-
r.
|
|
2530
|
-
r.
|
|
2531
|
-
r.
|
|
2532
|
-
r.
|
|
2533
|
-
r.
|
|
2582
|
+
r.registerElementwise("DATE", date_, 3, 3);
|
|
2583
|
+
r.registerElementwise("YEAR", year, 1, 1);
|
|
2584
|
+
r.registerElementwise("MONTH", month, 1, 1);
|
|
2585
|
+
r.registerElementwise("DAY", day, 1, 1);
|
|
2586
|
+
r.registerElementwise("HOUR", hour, 1, 1);
|
|
2587
|
+
r.registerElementwise("MINUTE", minute, 1, 1);
|
|
2588
|
+
r.registerElementwise("SECOND", second, 1, 1);
|
|
2589
|
+
r.registerElementwise("DATEVALUE", dateValue, 1, 1);
|
|
2590
|
+
r.registerElementwise("DAYS", days, 2, 2);
|
|
2591
|
+
r.registerElementwise("EDATE", edate, 2, 2);
|
|
2592
|
+
r.registerElementwise("EOMONTH", eomonth, 2, 2);
|
|
2593
|
+
r.registerElementwise("WEEKDAY", weekday, 1, 2);
|
|
2594
|
+
r.registerElementwise("WEEKNUM", weeknum, 1, 2);
|
|
2534
2595
|
r.register("NETWORKDAYS", networkdays, 2, 3);
|
|
2535
|
-
r.
|
|
2596
|
+
r.registerElementwise("DATEDIF", datedif, 3, 3);
|
|
2536
2597
|
}
|
|
2537
2598
|
function evalDate(a, c, idx) {
|
|
2538
2599
|
const r = FormulaHelper.evalDouble(a[idx], c);
|
|
@@ -3345,20 +3406,20 @@ function ifsAggregate(a, c, mode2) {
|
|
|
3345
3406
|
|
|
3346
3407
|
// src/functions/info-functions.ts
|
|
3347
3408
|
function registerInfo(r) {
|
|
3348
|
-
r.
|
|
3349
|
-
r.
|
|
3350
|
-
r.
|
|
3351
|
-
r.
|
|
3352
|
-
r.
|
|
3353
|
-
r.
|
|
3354
|
-
r.
|
|
3355
|
-
r.
|
|
3409
|
+
r.registerElementwise("ISBLANK", isBlank, 1, 1);
|
|
3410
|
+
r.registerElementwise("ISNUMBER", isNumber, 1, 1);
|
|
3411
|
+
r.registerElementwise("ISTEXT", isText, 1, 1);
|
|
3412
|
+
r.registerElementwise("ISERROR", isError, 1, 1);
|
|
3413
|
+
r.registerElementwise("ISERR", isErr, 1, 1);
|
|
3414
|
+
r.registerElementwise("ISLOGICAL", isLogical, 1, 1);
|
|
3415
|
+
r.registerElementwise("ISNA", isNa, 1, 1);
|
|
3416
|
+
r.registerElementwise("ISNONTEXT", isNonText, 1, 1);
|
|
3356
3417
|
r.register("NA", na, 0, 0);
|
|
3357
|
-
r.
|
|
3358
|
-
r.
|
|
3359
|
-
r.
|
|
3360
|
-
r.
|
|
3361
|
-
r.
|
|
3418
|
+
r.registerElementwise("TYPE", type_, 1, 1);
|
|
3419
|
+
r.registerElementwise("N", n_, 1, 1);
|
|
3420
|
+
r.registerElementwise("ISODD", isOdd, 1, 1);
|
|
3421
|
+
r.registerElementwise("ISEVEN", isEven, 1, 1);
|
|
3422
|
+
r.registerElementwise("ERROR.TYPE", errorType, 1, 1);
|
|
3362
3423
|
}
|
|
3363
3424
|
function isBlank(a, c) {
|
|
3364
3425
|
return FormulaValue.boolean(a[0].evaluate(c).isBlank);
|
|
@@ -4446,7 +4507,8 @@ function toRow(a, c) {
|
|
|
4446
4507
|
}
|
|
4447
4508
|
|
|
4448
4509
|
// src/function-registry.ts
|
|
4449
|
-
var
|
|
4510
|
+
var MAX_LIFTED_CELLS = 1048576;
|
|
4511
|
+
var _default, _functions, _FunctionRegistry_static, executeElementwise_fn, _FunctionRegistry_instances, registerBuiltIns_fn;
|
|
4450
4512
|
var _FunctionRegistry = class _FunctionRegistry {
|
|
4451
4513
|
constructor() {
|
|
4452
4514
|
__privateAdd(this, _FunctionRegistry_instances);
|
|
@@ -4468,17 +4530,45 @@ var _FunctionRegistry = class _FunctionRegistry {
|
|
|
4468
4530
|
minArgs,
|
|
4469
4531
|
maxArgs,
|
|
4470
4532
|
isVolatile,
|
|
4471
|
-
handler
|
|
4533
|
+
handler,
|
|
4534
|
+
elementwise: false
|
|
4535
|
+
});
|
|
4536
|
+
}
|
|
4537
|
+
/**
|
|
4538
|
+
* Registers a **scalar** function — one whose arguments are single values —
|
|
4539
|
+
* and lifts it over arrays automatically.
|
|
4540
|
+
*
|
|
4541
|
+
* `LEFT(A2:A100, 3)` is not a special form in Excel: LEFT takes one string, and
|
|
4542
|
+
* the grid maps it over the range, giving a 99-row array. Without that lifting
|
|
4543
|
+
* the range collapses to one value, so the dynamic array built on top of it —
|
|
4544
|
+
* `FILTER(IFERROR(LEFT(A2:A100,3),""), …)` — filters a 1×1 input and returns
|
|
4545
|
+
* `#CALC!`. Lifting belongs here rather than in each handler: the ~40 scalar
|
|
4546
|
+
* text/math/date functions then get it for free and cannot each get it subtly
|
|
4547
|
+
* wrong.
|
|
4548
|
+
*
|
|
4549
|
+
* Only mark functions that are genuinely per-cell. Anything that *consumes* an
|
|
4550
|
+
* array (SUM, COUNTIF, FILTER, SORT, the lazy branch functions) must keep
|
|
4551
|
+
* `register`, or lifting would map it over the array it is meant to reduce.
|
|
4552
|
+
*/
|
|
4553
|
+
registerElementwise(name, handler, minArgs = 0, maxArgs = -1) {
|
|
4554
|
+
__privateGet(this, _functions).set(name.toUpperCase(), {
|
|
4555
|
+
name: name.toUpperCase(),
|
|
4556
|
+
minArgs,
|
|
4557
|
+
maxArgs,
|
|
4558
|
+
isVolatile: false,
|
|
4559
|
+
handler,
|
|
4560
|
+
elementwise: true
|
|
4472
4561
|
});
|
|
4473
4562
|
}
|
|
4474
4563
|
/** Executes a function by name with argument validation. */
|
|
4475
4564
|
execute(name, args, ctx) {
|
|
4565
|
+
var _a;
|
|
4476
4566
|
const def = __privateGet(this, _functions).get(name.toUpperCase());
|
|
4477
4567
|
if (!def) return FormulaValue.errorName;
|
|
4478
4568
|
if (args.length < def.minArgs) return FormulaValue.errorValue;
|
|
4479
4569
|
if (def.maxArgs >= 0 && args.length > def.maxArgs) return FormulaValue.errorValue;
|
|
4480
4570
|
try {
|
|
4481
|
-
return def.handler(args, ctx);
|
|
4571
|
+
return def.elementwise ? __privateMethod(_a = _FunctionRegistry, _FunctionRegistry_static, executeElementwise_fn).call(_a, def, args, ctx) : def.handler(args, ctx);
|
|
4482
4572
|
} catch {
|
|
4483
4573
|
return FormulaValue.errorValue;
|
|
4484
4574
|
}
|
|
@@ -4494,6 +4584,42 @@ var _FunctionRegistry = class _FunctionRegistry {
|
|
|
4494
4584
|
};
|
|
4495
4585
|
_default = new WeakMap();
|
|
4496
4586
|
_functions = new WeakMap();
|
|
4587
|
+
_FunctionRegistry_static = new WeakSet();
|
|
4588
|
+
executeElementwise_fn = function(def, args, ctx) {
|
|
4589
|
+
const values = args.map((a) => a.evaluate(ctx));
|
|
4590
|
+
let rows2 = 1, cols = 1;
|
|
4591
|
+
for (const v of values) {
|
|
4592
|
+
if (!v.isArray) continue;
|
|
4593
|
+
const a = v.arrayVal;
|
|
4594
|
+
if (a.rows > rows2) rows2 = a.rows;
|
|
4595
|
+
if (a.columns > cols) cols = a.columns;
|
|
4596
|
+
}
|
|
4597
|
+
if (rows2 === 1 && cols === 1) {
|
|
4598
|
+
return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
|
|
4599
|
+
}
|
|
4600
|
+
if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
|
|
4601
|
+
for (const v of values) {
|
|
4602
|
+
if (!v.isArray) continue;
|
|
4603
|
+
const a = v.arrayVal;
|
|
4604
|
+
if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
|
|
4605
|
+
return FormulaValue.errorValue;
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
4608
|
+
const nodes = values.map((v) => new ValueNode(v));
|
|
4609
|
+
const out = new ArrayValue(rows2, cols);
|
|
4610
|
+
for (let r = 0; r < rows2; r++) {
|
|
4611
|
+
for (let c = 0; c < cols; c++) {
|
|
4612
|
+
for (let i = 0; i < values.length; i++) {
|
|
4613
|
+
const v = values[i];
|
|
4614
|
+
if (!v.isArray) continue;
|
|
4615
|
+
const a = v.arrayVal;
|
|
4616
|
+
nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
|
|
4617
|
+
}
|
|
4618
|
+
out.set(r, c, def.handler(nodes, ctx));
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
return FormulaValue.array(out);
|
|
4622
|
+
};
|
|
4497
4623
|
_FunctionRegistry_instances = new WeakSet();
|
|
4498
4624
|
registerBuiltIns_fn = function() {
|
|
4499
4625
|
registerMath(this);
|
|
@@ -4508,6 +4634,7 @@ registerBuiltIns_fn = function() {
|
|
|
4508
4634
|
registerAggregate(this);
|
|
4509
4635
|
registerArray(this);
|
|
4510
4636
|
};
|
|
4637
|
+
__privateAdd(_FunctionRegistry, _FunctionRegistry_static);
|
|
4511
4638
|
__privateAdd(_FunctionRegistry, _default);
|
|
4512
4639
|
var FunctionRegistry = _FunctionRegistry;
|
|
4513
4640
|
var FormulaHelper;
|
|
@@ -5369,6 +5496,7 @@ function createWorkbookRecalcModel(workbook) {
|
|
|
5369
5496
|
getDefinedName: (name, sheet) => workbook.getDefinedName(name, sheet)
|
|
5370
5497
|
};
|
|
5371
5498
|
}
|
|
5499
|
+
var MAX_ADOPTED_SPILL_CELLS = 262144;
|
|
5372
5500
|
var normSheet = (s) => s.toUpperCase();
|
|
5373
5501
|
var normRef = (r) => r.replace(/\$/g, "").toUpperCase();
|
|
5374
5502
|
var keyOf = (sheet, ref) => `${normSheet(sheet)}!${normRef(ref)}`;
|
|
@@ -5379,7 +5507,7 @@ function parseKey(key) {
|
|
|
5379
5507
|
const { row: row2, column: column2 } = parseCellRef(ref);
|
|
5380
5508
|
return { sheet, ref, row: row2, col: column2 };
|
|
5381
5509
|
}
|
|
5382
|
-
var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _dependents, _volatiles, _sheetLikes, _workbookLike, _rangeCache, _RecalcEngine_instances, writeCell_fn, register_fn, unregister_fn, rememberSheet_fn, recalcInOrder_fn, applyShift_fn, shiftPrecedents_fn, shiftSpillBookkeeping_fn, recalcFrom_fn, runFixpoint_fn, addVolatiles_fn, dependentClosure_fn, forEachDirectDependent_fn, evaluatePass_fn, writeResult_fn, isOccupied_fn, clearSpill_fn, evaluateFormulaValue_fn, sheetLike_fn, buildSheetLike_fn, buildWorkbookLike_fn;
|
|
5510
|
+
var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _dependents, _volatiles, _sheetLikes, _workbookLike, _rangeCache, _RecalcEngine_instances, writeCell_fn, register_fn, unregister_fn, rememberSheet_fn, adoptDeclaredSpill_fn, recalcInOrder_fn, applyShift_fn, shiftPrecedents_fn, shiftSpillBookkeeping_fn, recalcFrom_fn, runFixpoint_fn, addVolatiles_fn, dependentClosure_fn, forEachDirectDependent_fn, evaluatePass_fn, writeResult_fn, isOccupied_fn, clearSpill_fn, evaluateFormulaValue_fn, sheetLike_fn, buildSheetLike_fn, buildWorkbookLike_fn;
|
|
5383
5511
|
var RecalcEngine = class {
|
|
5384
5512
|
constructor(model, registry = FunctionRegistry.default) {
|
|
5385
5513
|
__privateAdd(this, _RecalcEngine_instances);
|
|
@@ -5487,11 +5615,12 @@ var RecalcEngine = class {
|
|
|
5487
5615
|
const ast = parseFormulaUncached(text);
|
|
5488
5616
|
const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
|
|
5489
5617
|
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
5618
|
+
const key = keyOf(s, r);
|
|
5490
5619
|
__privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
|
|
5491
5620
|
sheet: s,
|
|
5492
5621
|
sheetName: sheet,
|
|
5493
5622
|
ref: r,
|
|
5494
|
-
key
|
|
5623
|
+
key,
|
|
5495
5624
|
row: row2,
|
|
5496
5625
|
col: column2,
|
|
5497
5626
|
formula: text,
|
|
@@ -5499,6 +5628,7 @@ var RecalcEngine = class {
|
|
|
5499
5628
|
refs,
|
|
5500
5629
|
volatile
|
|
5501
5630
|
});
|
|
5631
|
+
__privateMethod(this, _RecalcEngine_instances, adoptDeclaredSpill_fn).call(this, s, sheet, r, key, row2, column2);
|
|
5502
5632
|
registered++;
|
|
5503
5633
|
} catch (err) {
|
|
5504
5634
|
failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
|
|
@@ -5666,6 +5796,51 @@ unregister_fn = function(key) {
|
|
|
5666
5796
|
rememberSheet_fn = function(name) {
|
|
5667
5797
|
__privateGet(this, _sheetNames).set(normSheet(name), name);
|
|
5668
5798
|
};
|
|
5799
|
+
/**
|
|
5800
|
+
* Takes ownership of the spill block a *file* already declared for an anchor.
|
|
5801
|
+
*
|
|
5802
|
+
* Excel stores a dynamic array as one formula on the anchor plus the spilled
|
|
5803
|
+
* results written as plain cached values in the cells below/right, and records
|
|
5804
|
+
* the extent in the anchor's `<f t="array" ref="…">`. Without this step the
|
|
5805
|
+
* engine meets those cached values on the first recalculation, sees literal
|
|
5806
|
+
* content inside the block it wants to spill into, and reports `#SPILL!` for
|
|
5807
|
+
* every dynamic array in every real file. Registering the declared block as
|
|
5808
|
+
* this anchor's existing spill makes them what they actually are — the anchor's
|
|
5809
|
+
* own territory, free to overwrite — and reuses the ordinary shrink path, so a
|
|
5810
|
+
* result smaller than the declared block clears the leftovers.
|
|
5811
|
+
*
|
|
5812
|
+
* Cost is one Set entry per already-spilled cell, paid once at load. A declared
|
|
5813
|
+
* block far larger than any real spill (a corrupt or hand-written `ref`) is
|
|
5814
|
+
* skipped rather than materialised.
|
|
5815
|
+
*/
|
|
5816
|
+
adoptDeclaredSpill_fn = function(s, sheetName, ref, anchorKey, row2, col) {
|
|
5817
|
+
const declared = __privateGet(this, _model).getSpillRange?.(sheetName, ref);
|
|
5818
|
+
if (!declared || !declared.includes(":")) return;
|
|
5819
|
+
let rect;
|
|
5820
|
+
try {
|
|
5821
|
+
rect = parseRangeRef(declared);
|
|
5822
|
+
} catch {
|
|
5823
|
+
return;
|
|
5824
|
+
}
|
|
5825
|
+
if (rect.startRow !== row2 || rect.startColumn !== col) return;
|
|
5826
|
+
const height = rect.endRow - rect.startRow + 1;
|
|
5827
|
+
const width = rect.endColumn - rect.startColumn + 1;
|
|
5828
|
+
if (height < 1 || width < 1) return;
|
|
5829
|
+
if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
|
|
5830
|
+
if (height === 1 && width === 1) return;
|
|
5831
|
+
const members = /* @__PURE__ */ new Set();
|
|
5832
|
+
for (let r = rect.startRow; r <= rect.endRow; r++) {
|
|
5833
|
+
for (let c = rect.startColumn; c <= rect.endColumn; c++) {
|
|
5834
|
+
if (r === row2 && c === col) continue;
|
|
5835
|
+
const key = keyOf(s, cellRefFromRowCol(r, c));
|
|
5836
|
+
if (__privateGet(this, _formulas).has(key)) continue;
|
|
5837
|
+
if (__privateGet(this, _spillOwner).has(key)) continue;
|
|
5838
|
+
members.add(key);
|
|
5839
|
+
__privateGet(this, _spillOwner).set(key, anchorKey);
|
|
5840
|
+
}
|
|
5841
|
+
}
|
|
5842
|
+
if (members.size > 0) __privateGet(this, _spills).set(anchorKey, members);
|
|
5843
|
+
};
|
|
5669
5844
|
/**
|
|
5670
5845
|
* One pass in a caller-supplied order, watching for inversions.
|
|
5671
5846
|
*
|