@devmm/puredocs-excel-formula 1.0.8 → 1.1.0
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 +321 -106
- 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 +322 -107
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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);
|
|
@@ -29,6 +29,13 @@ var FormulaError = /* @__PURE__ */ ((FormulaError2) => {
|
|
|
29
29
|
FormulaError2[FormulaError2["Spill"] = 9] = "Spill";
|
|
30
30
|
return FormulaError2;
|
|
31
31
|
})(FormulaError || {});
|
|
32
|
+
var DECIMAL = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
33
|
+
function numericText(text) {
|
|
34
|
+
const trimmed = text.trim();
|
|
35
|
+
if (!DECIMAL.test(trimmed)) return null;
|
|
36
|
+
const n = Number(trimmed);
|
|
37
|
+
return Number.isFinite(n) ? n : null;
|
|
38
|
+
}
|
|
32
39
|
var TailCall = class {
|
|
33
40
|
constructor(lambda, args) {
|
|
34
41
|
this.lambda = lambda;
|
|
@@ -805,6 +812,16 @@ var ArrayLiteralNode = class extends FormulaNode {
|
|
|
805
812
|
}
|
|
806
813
|
};
|
|
807
814
|
_value = new WeakMap();
|
|
815
|
+
var ValueNode = class extends FormulaNode {
|
|
816
|
+
/** Mutable so one wrapper can be re-pointed across a lifted call's cells. */
|
|
817
|
+
constructor(value2) {
|
|
818
|
+
super();
|
|
819
|
+
this.value = value2;
|
|
820
|
+
}
|
|
821
|
+
evaluate() {
|
|
822
|
+
return this.value;
|
|
823
|
+
}
|
|
824
|
+
};
|
|
808
825
|
var BlankNode = class extends FormulaNode {
|
|
809
826
|
evaluate() {
|
|
810
827
|
return FormulaValue.blank;
|
|
@@ -1431,6 +1448,7 @@ var indexes = /* @__PURE__ */ new WeakMap();
|
|
|
1431
1448
|
function build(arr) {
|
|
1432
1449
|
const byText = /* @__PURE__ */ new Map();
|
|
1433
1450
|
const byNumber = /* @__PURE__ */ new Map();
|
|
1451
|
+
const byTextNumber = /* @__PURE__ */ new Map();
|
|
1434
1452
|
const classes = /* @__PURE__ */ new Map();
|
|
1435
1453
|
const addToClass = (kind, v, i2) => {
|
|
1436
1454
|
const group = classes.get(kind);
|
|
@@ -1450,6 +1468,14 @@ function build(arr) {
|
|
|
1450
1468
|
else byNumber.set(n, [i]);
|
|
1451
1469
|
} else {
|
|
1452
1470
|
addToClass(v.kind, v, i);
|
|
1471
|
+
if (v.isText) {
|
|
1472
|
+
const n = numericText(v.textValue);
|
|
1473
|
+
if (n !== null) {
|
|
1474
|
+
const atText2 = byTextNumber.get(n);
|
|
1475
|
+
if (atText2) atText2.push(i);
|
|
1476
|
+
else byTextNumber.set(n, [i]);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1453
1479
|
}
|
|
1454
1480
|
i++;
|
|
1455
1481
|
}
|
|
@@ -1458,7 +1484,15 @@ function build(arr) {
|
|
|
1458
1484
|
for (let k = 0; k < sortedNumbers.length; k++) {
|
|
1459
1485
|
cumulative[k + 1] = cumulative[k] + byNumber.get(sortedNumbers[k]).length;
|
|
1460
1486
|
}
|
|
1461
|
-
return {
|
|
1487
|
+
return {
|
|
1488
|
+
byText,
|
|
1489
|
+
byNumber,
|
|
1490
|
+
sortedNumbers,
|
|
1491
|
+
cumulative,
|
|
1492
|
+
byTextNumber,
|
|
1493
|
+
sortedTextNumbers: Float64Array.from(byTextNumber.keys()).sort(),
|
|
1494
|
+
classes: [...classes.values()]
|
|
1495
|
+
};
|
|
1462
1496
|
}
|
|
1463
1497
|
function indexOf(arr) {
|
|
1464
1498
|
let idx = indexes.get(arr);
|
|
@@ -1496,19 +1530,25 @@ function matchingPositions(criteriaRange, criteria, minSize) {
|
|
|
1496
1530
|
if (criteria.kind === "equalsNumber") {
|
|
1497
1531
|
const target = criteria.number;
|
|
1498
1532
|
const idx = indexOf(arr);
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1533
|
+
const groups = [];
|
|
1534
|
+
const collect = (byValue, sorted) => {
|
|
1535
|
+
const exact2 = byValue.get(target);
|
|
1536
|
+
if (exact2) groups.push(exact2);
|
|
1537
|
+
for (let k = lowerBound(sorted, target - EPSILON); k < sorted.length; k++) {
|
|
1538
|
+
const value2 = sorted[k];
|
|
1539
|
+
if (value2 > target + EPSILON) break;
|
|
1540
|
+
if (value2 === target) continue;
|
|
1541
|
+
if (!criteria.test(FormulaValue.number(value2))) continue;
|
|
1542
|
+
groups.push(byValue.get(value2));
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
collect(idx.byNumber, idx.sortedNumbers);
|
|
1546
|
+
collect(idx.byTextNumber, idx.sortedTextNumbers);
|
|
1509
1547
|
const blanks = criteria.test(FormulaValue.blank) ? blankPositions(idx) : void 0;
|
|
1510
|
-
if (
|
|
1511
|
-
|
|
1548
|
+
if (blanks?.length) groups.push(blanks);
|
|
1549
|
+
if (groups.length === 0) return EMPTY;
|
|
1550
|
+
if (groups.length === 1) return groups[0];
|
|
1551
|
+
const merged = groups.flat();
|
|
1512
1552
|
merged.sort((a, b) => a - b);
|
|
1513
1553
|
return merged;
|
|
1514
1554
|
}
|
|
@@ -1574,42 +1614,42 @@ function registerMath(r) {
|
|
|
1574
1614
|
r.register("PRODUCT", product, 1);
|
|
1575
1615
|
r.register("SUMPRODUCT", sumProduct, 1);
|
|
1576
1616
|
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.
|
|
1617
|
+
r.registerElementwise("ABS", abs, 1, 1);
|
|
1618
|
+
r.registerElementwise("ROUND", round, 2, 2);
|
|
1619
|
+
r.registerElementwise("ROUNDUP", roundUp, 2, 2);
|
|
1620
|
+
r.registerElementwise("ROUNDDOWN", roundDown, 2, 2);
|
|
1621
|
+
r.registerElementwise("CEILING", ceiling, 2, 2);
|
|
1622
|
+
r.registerElementwise("CEILING.MATH", ceiling, 2, 2);
|
|
1623
|
+
r.registerElementwise("FLOOR", floor, 2, 2);
|
|
1624
|
+
r.registerElementwise("FLOOR.MATH", floor, 2, 2);
|
|
1625
|
+
r.registerElementwise("INT", int_, 1, 1);
|
|
1626
|
+
r.registerElementwise("TRUNC", trunc, 1, 2);
|
|
1627
|
+
r.registerElementwise("MOD", mod, 2, 2);
|
|
1628
|
+
r.registerElementwise("QUOTIENT", quotient, 2, 2);
|
|
1629
|
+
r.registerElementwise("POWER", power, 2, 2);
|
|
1630
|
+
r.registerElementwise("SQRT", sqrt, 1, 1);
|
|
1631
|
+
r.registerElementwise("SIGN", sign, 1, 1);
|
|
1592
1632
|
r.register("PI", pi, 0, 0);
|
|
1593
1633
|
r.register("RAND", rand, 0, 0, true);
|
|
1594
1634
|
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.
|
|
1635
|
+
r.registerElementwise("LOG", log, 1, 2);
|
|
1636
|
+
r.registerElementwise("LOG10", log10, 1, 1);
|
|
1637
|
+
r.registerElementwise("LN", ln, 1, 1);
|
|
1638
|
+
r.registerElementwise("EXP", exp, 1, 1);
|
|
1639
|
+
r.registerElementwise("FACT", fact, 1, 1);
|
|
1640
|
+
r.registerElementwise("COMBIN", combin, 2, 2);
|
|
1641
|
+
r.registerElementwise("SIN", unary(Math.sin), 1, 1);
|
|
1642
|
+
r.registerElementwise("COS", unary(Math.cos), 1, 1);
|
|
1643
|
+
r.registerElementwise("TAN", unary(Math.tan), 1, 1);
|
|
1644
|
+
r.registerElementwise("ASIN", asin, 1, 1);
|
|
1645
|
+
r.registerElementwise("ACOS", acos, 1, 1);
|
|
1646
|
+
r.registerElementwise("ATAN", unary(Math.atan), 1, 1);
|
|
1647
|
+
r.registerElementwise("ATAN2", atan2, 2, 2);
|
|
1648
|
+
r.registerElementwise("SINH", unary(Math.sinh), 1, 1);
|
|
1649
|
+
r.registerElementwise("COSH", unary(Math.cosh), 1, 1);
|
|
1650
|
+
r.registerElementwise("TANH", unary(Math.tanh), 1, 1);
|
|
1651
|
+
r.registerElementwise("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
|
|
1652
|
+
r.registerElementwise("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
|
|
1613
1653
|
}
|
|
1614
1654
|
function valueAt(v, i) {
|
|
1615
1655
|
return v.isArray ? v.arrayVal.getFlat(i) : v;
|
|
@@ -2041,31 +2081,31 @@ function combin(a, c) {
|
|
|
2041
2081
|
return FormulaValue.number(Math.round(result));
|
|
2042
2082
|
}
|
|
2043
2083
|
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.
|
|
2084
|
+
r.registerElementwise("LEFT", left, 1, 2);
|
|
2085
|
+
r.registerElementwise("RIGHT", right, 1, 2);
|
|
2086
|
+
r.registerElementwise("MID", mid, 3, 3);
|
|
2087
|
+
r.registerElementwise("LEN", len, 1, 1);
|
|
2088
|
+
r.registerElementwise("UPPER", upper, 1, 1);
|
|
2089
|
+
r.registerElementwise("LOWER", lower, 1, 1);
|
|
2090
|
+
r.registerElementwise("PROPER", proper, 1, 1);
|
|
2091
|
+
r.registerElementwise("TRIM", trim, 1, 1);
|
|
2092
|
+
r.registerElementwise("CLEAN", clean, 1, 1);
|
|
2093
|
+
r.registerElementwise("SUBSTITUTE", substitute, 3, 4);
|
|
2094
|
+
r.registerElementwise("REPLACE", replace, 4, 4);
|
|
2055
2095
|
r.register("CONCATENATE", concatenate, 1);
|
|
2056
2096
|
r.register("CONCAT", concatenate, 1);
|
|
2057
2097
|
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.
|
|
2098
|
+
r.registerElementwise("TEXT", text_, 2, 2);
|
|
2099
|
+
r.registerElementwise("VALUE", value, 1, 1);
|
|
2100
|
+
r.registerElementwise("FIND", find, 2, 3);
|
|
2101
|
+
r.registerElementwise("SEARCH", search, 2, 3);
|
|
2102
|
+
r.registerElementwise("REPT", rept, 2, 2);
|
|
2103
|
+
r.registerElementwise("EXACT", exact, 2, 2);
|
|
2104
|
+
r.registerElementwise("CHAR", char_, 1, 1);
|
|
2105
|
+
r.registerElementwise("CODE", code, 1, 1);
|
|
2106
|
+
r.registerElementwise("T", t_, 1, 1);
|
|
2107
|
+
r.registerElementwise("TEXTBEFORE", textBefore, 2, 3);
|
|
2108
|
+
r.registerElementwise("TEXTAFTER", textAfter, 2, 3);
|
|
2069
2109
|
}
|
|
2070
2110
|
function str(a, c, idx) {
|
|
2071
2111
|
const r = FormulaHelper.evalString(a[idx], c);
|
|
@@ -2379,6 +2419,7 @@ function registerLogical(r) {
|
|
|
2379
2419
|
function if_(a, c) {
|
|
2380
2420
|
const cond = a[0].evaluate(c);
|
|
2381
2421
|
if (cond.isError) return cond;
|
|
2422
|
+
if (cond.isArray) return ifArray(cond.arrayVal, a, c);
|
|
2382
2423
|
const b = cond.coerceToBool();
|
|
2383
2424
|
if (b.isError) return b;
|
|
2384
2425
|
return b.booleanValue ? a[1].evaluate(c) : a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_;
|
|
@@ -2455,13 +2496,63 @@ function xor(a, c) {
|
|
|
2455
2496
|
}
|
|
2456
2497
|
return FormulaValue.boolean(trueCount % 2 === 1);
|
|
2457
2498
|
}
|
|
2458
|
-
function
|
|
2499
|
+
function ifArray(mask, a, c) {
|
|
2500
|
+
let wantTrue = false, wantFalse = false;
|
|
2501
|
+
for (const item of mask.values()) {
|
|
2502
|
+
const b = item.coerceToBool();
|
|
2503
|
+
if (b.isError) return b;
|
|
2504
|
+
if (b.booleanValue) wantTrue = true;
|
|
2505
|
+
else wantFalse = true;
|
|
2506
|
+
if (wantTrue && wantFalse) break;
|
|
2507
|
+
}
|
|
2508
|
+
const yes = wantTrue ? a[1].evaluate(c) : FormulaValue.blank;
|
|
2509
|
+
const no = wantFalse ? a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_ : FormulaValue.blank;
|
|
2510
|
+
const pick = (v, r, col) => {
|
|
2511
|
+
if (!v.isArray) return v;
|
|
2512
|
+
const arr = v.arrayVal;
|
|
2513
|
+
return r < arr.rows && col < arr.columns ? arr.get(r, col) : FormulaValue.errorNA;
|
|
2514
|
+
};
|
|
2515
|
+
const out = new ArrayValue(mask.rows, mask.columns);
|
|
2516
|
+
for (let r = 0; r < mask.rows; r++) {
|
|
2517
|
+
for (let col = 0; col < mask.columns; col++) {
|
|
2518
|
+
const b = mask.get(r, col).coerceToBool();
|
|
2519
|
+
out.set(r, col, b.booleanValue ? pick(yes, r, col) : pick(no, r, col));
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return FormulaValue.array(out);
|
|
2523
|
+
}
|
|
2524
|
+
function ifCaught(a, c, caught) {
|
|
2459
2525
|
const v = a[0].evaluate(c);
|
|
2460
|
-
|
|
2526
|
+
if (!v.isArray) return caught(v) ? a[1].evaluate(c) : v;
|
|
2527
|
+
const arr = v.arrayVal;
|
|
2528
|
+
let any = false;
|
|
2529
|
+
for (const item of arr.values()) {
|
|
2530
|
+
if (caught(item)) {
|
|
2531
|
+
any = true;
|
|
2532
|
+
break;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
if (!any) return v;
|
|
2536
|
+
const fb = a[1].evaluate(c);
|
|
2537
|
+
const fbArr = fb.isArray ? fb.arrayVal : null;
|
|
2538
|
+
const out = new ArrayValue(arr.rows, arr.columns);
|
|
2539
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
2540
|
+
for (let col = 0; col < arr.columns; col++) {
|
|
2541
|
+
const item = arr.get(r, col);
|
|
2542
|
+
if (!caught(item)) {
|
|
2543
|
+
out.set(r, col, item);
|
|
2544
|
+
continue;
|
|
2545
|
+
}
|
|
2546
|
+
out.set(r, col, fbArr ? r < fbArr.rows && col < fbArr.columns ? fbArr.get(r, col) : FormulaValue.errorNA : fb);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
return FormulaValue.array(out);
|
|
2550
|
+
}
|
|
2551
|
+
function ifError(a, c) {
|
|
2552
|
+
return ifCaught(a, c, (v) => v.isError);
|
|
2461
2553
|
}
|
|
2462
2554
|
function ifNa(a, c) {
|
|
2463
|
-
|
|
2464
|
-
return v.isError && v.errorCode === 7 /* NA */ ? a[1].evaluate(c) : v;
|
|
2555
|
+
return ifCaught(a, c, (v) => v.isError && v.errorCode === 7 /* NA */);
|
|
2465
2556
|
}
|
|
2466
2557
|
function ifs(a, c) {
|
|
2467
2558
|
for (let i = 0; i + 1 < a.length; i += 2) {
|
|
@@ -2518,21 +2609,21 @@ function fromOA(oa) {
|
|
|
2518
2609
|
function registerDate(r) {
|
|
2519
2610
|
r.register("NOW", now, 0, 0, true);
|
|
2520
2611
|
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.
|
|
2612
|
+
r.registerElementwise("DATE", date_, 3, 3);
|
|
2613
|
+
r.registerElementwise("YEAR", year, 1, 1);
|
|
2614
|
+
r.registerElementwise("MONTH", month, 1, 1);
|
|
2615
|
+
r.registerElementwise("DAY", day, 1, 1);
|
|
2616
|
+
r.registerElementwise("HOUR", hour, 1, 1);
|
|
2617
|
+
r.registerElementwise("MINUTE", minute, 1, 1);
|
|
2618
|
+
r.registerElementwise("SECOND", second, 1, 1);
|
|
2619
|
+
r.registerElementwise("DATEVALUE", dateValue, 1, 1);
|
|
2620
|
+
r.registerElementwise("DAYS", days, 2, 2);
|
|
2621
|
+
r.registerElementwise("EDATE", edate, 2, 2);
|
|
2622
|
+
r.registerElementwise("EOMONTH", eomonth, 2, 2);
|
|
2623
|
+
r.registerElementwise("WEEKDAY", weekday, 1, 2);
|
|
2624
|
+
r.registerElementwise("WEEKNUM", weeknum, 1, 2);
|
|
2534
2625
|
r.register("NETWORKDAYS", networkdays, 2, 3);
|
|
2535
|
-
r.
|
|
2626
|
+
r.registerElementwise("DATEDIF", datedif, 3, 3);
|
|
2536
2627
|
}
|
|
2537
2628
|
function evalDate(a, c, idx) {
|
|
2538
2629
|
const r = FormulaHelper.evalDouble(a[idx], c);
|
|
@@ -3345,20 +3436,20 @@ function ifsAggregate(a, c, mode2) {
|
|
|
3345
3436
|
|
|
3346
3437
|
// src/functions/info-functions.ts
|
|
3347
3438
|
function registerInfo(r) {
|
|
3348
|
-
r.
|
|
3349
|
-
r.
|
|
3350
|
-
r.
|
|
3351
|
-
r.
|
|
3352
|
-
r.
|
|
3353
|
-
r.
|
|
3354
|
-
r.
|
|
3355
|
-
r.
|
|
3439
|
+
r.registerElementwise("ISBLANK", isBlank, 1, 1);
|
|
3440
|
+
r.registerElementwise("ISNUMBER", isNumber, 1, 1);
|
|
3441
|
+
r.registerElementwise("ISTEXT", isText, 1, 1);
|
|
3442
|
+
r.registerElementwise("ISERROR", isError, 1, 1);
|
|
3443
|
+
r.registerElementwise("ISERR", isErr, 1, 1);
|
|
3444
|
+
r.registerElementwise("ISLOGICAL", isLogical, 1, 1);
|
|
3445
|
+
r.registerElementwise("ISNA", isNa, 1, 1);
|
|
3446
|
+
r.registerElementwise("ISNONTEXT", isNonText, 1, 1);
|
|
3356
3447
|
r.register("NA", na, 0, 0);
|
|
3357
|
-
r.
|
|
3358
|
-
r.
|
|
3359
|
-
r.
|
|
3360
|
-
r.
|
|
3361
|
-
r.
|
|
3448
|
+
r.registerElementwise("TYPE", type_, 1, 1);
|
|
3449
|
+
r.registerElementwise("N", n_, 1, 1);
|
|
3450
|
+
r.registerElementwise("ISODD", isOdd, 1, 1);
|
|
3451
|
+
r.registerElementwise("ISEVEN", isEven, 1, 1);
|
|
3452
|
+
r.registerElementwise("ERROR.TYPE", errorType, 1, 1);
|
|
3362
3453
|
}
|
|
3363
3454
|
function isBlank(a, c) {
|
|
3364
3455
|
return FormulaValue.boolean(a[0].evaluate(c).isBlank);
|
|
@@ -4446,7 +4537,8 @@ function toRow(a, c) {
|
|
|
4446
4537
|
}
|
|
4447
4538
|
|
|
4448
4539
|
// src/function-registry.ts
|
|
4449
|
-
var
|
|
4540
|
+
var MAX_LIFTED_CELLS = 1048576;
|
|
4541
|
+
var _default, _functions, _FunctionRegistry_static, executeElementwise_fn, _FunctionRegistry_instances, registerBuiltIns_fn;
|
|
4450
4542
|
var _FunctionRegistry = class _FunctionRegistry {
|
|
4451
4543
|
constructor() {
|
|
4452
4544
|
__privateAdd(this, _FunctionRegistry_instances);
|
|
@@ -4468,17 +4560,45 @@ var _FunctionRegistry = class _FunctionRegistry {
|
|
|
4468
4560
|
minArgs,
|
|
4469
4561
|
maxArgs,
|
|
4470
4562
|
isVolatile,
|
|
4471
|
-
handler
|
|
4563
|
+
handler,
|
|
4564
|
+
elementwise: false
|
|
4565
|
+
});
|
|
4566
|
+
}
|
|
4567
|
+
/**
|
|
4568
|
+
* Registers a **scalar** function — one whose arguments are single values —
|
|
4569
|
+
* and lifts it over arrays automatically.
|
|
4570
|
+
*
|
|
4571
|
+
* `LEFT(A2:A100, 3)` is not a special form in Excel: LEFT takes one string, and
|
|
4572
|
+
* the grid maps it over the range, giving a 99-row array. Without that lifting
|
|
4573
|
+
* the range collapses to one value, so the dynamic array built on top of it —
|
|
4574
|
+
* `FILTER(IFERROR(LEFT(A2:A100,3),""), …)` — filters a 1×1 input and returns
|
|
4575
|
+
* `#CALC!`. Lifting belongs here rather than in each handler: the ~40 scalar
|
|
4576
|
+
* text/math/date functions then get it for free and cannot each get it subtly
|
|
4577
|
+
* wrong.
|
|
4578
|
+
*
|
|
4579
|
+
* Only mark functions that are genuinely per-cell. Anything that *consumes* an
|
|
4580
|
+
* array (SUM, COUNTIF, FILTER, SORT, the lazy branch functions) must keep
|
|
4581
|
+
* `register`, or lifting would map it over the array it is meant to reduce.
|
|
4582
|
+
*/
|
|
4583
|
+
registerElementwise(name, handler, minArgs = 0, maxArgs = -1) {
|
|
4584
|
+
__privateGet(this, _functions).set(name.toUpperCase(), {
|
|
4585
|
+
name: name.toUpperCase(),
|
|
4586
|
+
minArgs,
|
|
4587
|
+
maxArgs,
|
|
4588
|
+
isVolatile: false,
|
|
4589
|
+
handler,
|
|
4590
|
+
elementwise: true
|
|
4472
4591
|
});
|
|
4473
4592
|
}
|
|
4474
4593
|
/** Executes a function by name with argument validation. */
|
|
4475
4594
|
execute(name, args, ctx) {
|
|
4595
|
+
var _a;
|
|
4476
4596
|
const def = __privateGet(this, _functions).get(name.toUpperCase());
|
|
4477
4597
|
if (!def) return FormulaValue.errorName;
|
|
4478
4598
|
if (args.length < def.minArgs) return FormulaValue.errorValue;
|
|
4479
4599
|
if (def.maxArgs >= 0 && args.length > def.maxArgs) return FormulaValue.errorValue;
|
|
4480
4600
|
try {
|
|
4481
|
-
return def.handler(args, ctx);
|
|
4601
|
+
return def.elementwise ? __privateMethod(_a = _FunctionRegistry, _FunctionRegistry_static, executeElementwise_fn).call(_a, def, args, ctx) : def.handler(args, ctx);
|
|
4482
4602
|
} catch {
|
|
4483
4603
|
return FormulaValue.errorValue;
|
|
4484
4604
|
}
|
|
@@ -4494,6 +4614,42 @@ var _FunctionRegistry = class _FunctionRegistry {
|
|
|
4494
4614
|
};
|
|
4495
4615
|
_default = new WeakMap();
|
|
4496
4616
|
_functions = new WeakMap();
|
|
4617
|
+
_FunctionRegistry_static = new WeakSet();
|
|
4618
|
+
executeElementwise_fn = function(def, args, ctx) {
|
|
4619
|
+
const values = args.map((a) => a.evaluate(ctx));
|
|
4620
|
+
let rows2 = 1, cols = 1;
|
|
4621
|
+
for (const v of values) {
|
|
4622
|
+
if (!v.isArray) continue;
|
|
4623
|
+
const a = v.arrayVal;
|
|
4624
|
+
if (a.rows > rows2) rows2 = a.rows;
|
|
4625
|
+
if (a.columns > cols) cols = a.columns;
|
|
4626
|
+
}
|
|
4627
|
+
if (rows2 === 1 && cols === 1) {
|
|
4628
|
+
return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
|
|
4629
|
+
}
|
|
4630
|
+
if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
|
|
4631
|
+
for (const v of values) {
|
|
4632
|
+
if (!v.isArray) continue;
|
|
4633
|
+
const a = v.arrayVal;
|
|
4634
|
+
if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
|
|
4635
|
+
return FormulaValue.errorValue;
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
const nodes = values.map((v) => new ValueNode(v));
|
|
4639
|
+
const out = new ArrayValue(rows2, cols);
|
|
4640
|
+
for (let r = 0; r < rows2; r++) {
|
|
4641
|
+
for (let c = 0; c < cols; c++) {
|
|
4642
|
+
for (let i = 0; i < values.length; i++) {
|
|
4643
|
+
const v = values[i];
|
|
4644
|
+
if (!v.isArray) continue;
|
|
4645
|
+
const a = v.arrayVal;
|
|
4646
|
+
nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
|
|
4647
|
+
}
|
|
4648
|
+
out.set(r, c, def.handler(nodes, ctx));
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
return FormulaValue.array(out);
|
|
4652
|
+
};
|
|
4497
4653
|
_FunctionRegistry_instances = new WeakSet();
|
|
4498
4654
|
registerBuiltIns_fn = function() {
|
|
4499
4655
|
registerMath(this);
|
|
@@ -4508,6 +4664,7 @@ registerBuiltIns_fn = function() {
|
|
|
4508
4664
|
registerAggregate(this);
|
|
4509
4665
|
registerArray(this);
|
|
4510
4666
|
};
|
|
4667
|
+
__privateAdd(_FunctionRegistry, _FunctionRegistry_static);
|
|
4511
4668
|
__privateAdd(_FunctionRegistry, _default);
|
|
4512
4669
|
var FunctionRegistry = _FunctionRegistry;
|
|
4513
4670
|
var FormulaHelper;
|
|
@@ -4573,7 +4730,8 @@ var FormulaHelper;
|
|
|
4573
4730
|
return rhsNum !== null ? numberEquality(rhsNum) : textEquality(rhs);
|
|
4574
4731
|
}
|
|
4575
4732
|
if (op === "<>") {
|
|
4576
|
-
|
|
4733
|
+
const equals = rhsNum !== null ? numberEquality(rhsNum) : textEquality(rhs);
|
|
4734
|
+
return { kind: "other", test: (v) => !equals.test(v) };
|
|
4577
4735
|
}
|
|
4578
4736
|
const wanted = op === ">" ? (c) => c > 0 : op === ">=" ? (c) => c >= 0 : op === "<" ? (c) => c < 0 : (c) => c <= 0;
|
|
4579
4737
|
const test = (v) => wanted(FormulaValue.compare(v, rhsValue));
|
|
@@ -4589,7 +4747,16 @@ var FormulaHelper;
|
|
|
4589
4747
|
FormulaHelper2.compileCriteria = compileCriteria;
|
|
4590
4748
|
function numberEquality(n) {
|
|
4591
4749
|
const target = FormulaValue.number(n);
|
|
4592
|
-
return {
|
|
4750
|
+
return {
|
|
4751
|
+
kind: "equalsNumber",
|
|
4752
|
+
number: n,
|
|
4753
|
+
test: (v) => {
|
|
4754
|
+
if (FormulaValue.areEqual(v, target)) return true;
|
|
4755
|
+
if (!v.isText) return false;
|
|
4756
|
+
const asNumber = numericText(v.textValue);
|
|
4757
|
+
return asNumber !== null && Math.abs(asNumber - n) < 1e-10;
|
|
4758
|
+
}
|
|
4759
|
+
};
|
|
4593
4760
|
}
|
|
4594
4761
|
function textEquality(s) {
|
|
4595
4762
|
const upper2 = s.toUpperCase();
|
|
@@ -5369,6 +5536,7 @@ function createWorkbookRecalcModel(workbook) {
|
|
|
5369
5536
|
getDefinedName: (name, sheet) => workbook.getDefinedName(name, sheet)
|
|
5370
5537
|
};
|
|
5371
5538
|
}
|
|
5539
|
+
var MAX_ADOPTED_SPILL_CELLS = 262144;
|
|
5372
5540
|
var normSheet = (s) => s.toUpperCase();
|
|
5373
5541
|
var normRef = (r) => r.replace(/\$/g, "").toUpperCase();
|
|
5374
5542
|
var keyOf = (sheet, ref) => `${normSheet(sheet)}!${normRef(ref)}`;
|
|
@@ -5379,7 +5547,7 @@ function parseKey(key) {
|
|
|
5379
5547
|
const { row: row2, column: column2 } = parseCellRef(ref);
|
|
5380
5548
|
return { sheet, ref, row: row2, col: column2 };
|
|
5381
5549
|
}
|
|
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;
|
|
5550
|
+
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
5551
|
var RecalcEngine = class {
|
|
5384
5552
|
constructor(model, registry = FunctionRegistry.default) {
|
|
5385
5553
|
__privateAdd(this, _RecalcEngine_instances);
|
|
@@ -5487,11 +5655,12 @@ var RecalcEngine = class {
|
|
|
5487
5655
|
const ast = parseFormulaUncached(text);
|
|
5488
5656
|
const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
|
|
5489
5657
|
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
5658
|
+
const key = keyOf(s, r);
|
|
5490
5659
|
__privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
|
|
5491
5660
|
sheet: s,
|
|
5492
5661
|
sheetName: sheet,
|
|
5493
5662
|
ref: r,
|
|
5494
|
-
key
|
|
5663
|
+
key,
|
|
5495
5664
|
row: row2,
|
|
5496
5665
|
col: column2,
|
|
5497
5666
|
formula: text,
|
|
@@ -5499,6 +5668,7 @@ var RecalcEngine = class {
|
|
|
5499
5668
|
refs,
|
|
5500
5669
|
volatile
|
|
5501
5670
|
});
|
|
5671
|
+
__privateMethod(this, _RecalcEngine_instances, adoptDeclaredSpill_fn).call(this, s, sheet, r, key, row2, column2);
|
|
5502
5672
|
registered++;
|
|
5503
5673
|
} catch (err) {
|
|
5504
5674
|
failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
|
|
@@ -5666,6 +5836,51 @@ unregister_fn = function(key) {
|
|
|
5666
5836
|
rememberSheet_fn = function(name) {
|
|
5667
5837
|
__privateGet(this, _sheetNames).set(normSheet(name), name);
|
|
5668
5838
|
};
|
|
5839
|
+
/**
|
|
5840
|
+
* Takes ownership of the spill block a *file* already declared for an anchor.
|
|
5841
|
+
*
|
|
5842
|
+
* Excel stores a dynamic array as one formula on the anchor plus the spilled
|
|
5843
|
+
* results written as plain cached values in the cells below/right, and records
|
|
5844
|
+
* the extent in the anchor's `<f t="array" ref="…">`. Without this step the
|
|
5845
|
+
* engine meets those cached values on the first recalculation, sees literal
|
|
5846
|
+
* content inside the block it wants to spill into, and reports `#SPILL!` for
|
|
5847
|
+
* every dynamic array in every real file. Registering the declared block as
|
|
5848
|
+
* this anchor's existing spill makes them what they actually are — the anchor's
|
|
5849
|
+
* own territory, free to overwrite — and reuses the ordinary shrink path, so a
|
|
5850
|
+
* result smaller than the declared block clears the leftovers.
|
|
5851
|
+
*
|
|
5852
|
+
* Cost is one Set entry per already-spilled cell, paid once at load. A declared
|
|
5853
|
+
* block far larger than any real spill (a corrupt or hand-written `ref`) is
|
|
5854
|
+
* skipped rather than materialised.
|
|
5855
|
+
*/
|
|
5856
|
+
adoptDeclaredSpill_fn = function(s, sheetName, ref, anchorKey, row2, col) {
|
|
5857
|
+
const declared = __privateGet(this, _model).getSpillRange?.(sheetName, ref);
|
|
5858
|
+
if (!declared || !declared.includes(":")) return;
|
|
5859
|
+
let rect;
|
|
5860
|
+
try {
|
|
5861
|
+
rect = parseRangeRef(declared);
|
|
5862
|
+
} catch {
|
|
5863
|
+
return;
|
|
5864
|
+
}
|
|
5865
|
+
if (rect.startRow !== row2 || rect.startColumn !== col) return;
|
|
5866
|
+
const height = rect.endRow - rect.startRow + 1;
|
|
5867
|
+
const width = rect.endColumn - rect.startColumn + 1;
|
|
5868
|
+
if (height < 1 || width < 1) return;
|
|
5869
|
+
if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
|
|
5870
|
+
if (height === 1 && width === 1) return;
|
|
5871
|
+
const members = /* @__PURE__ */ new Set();
|
|
5872
|
+
for (let r = rect.startRow; r <= rect.endRow; r++) {
|
|
5873
|
+
for (let c = rect.startColumn; c <= rect.endColumn; c++) {
|
|
5874
|
+
if (r === row2 && c === col) continue;
|
|
5875
|
+
const key = keyOf(s, cellRefFromRowCol(r, c));
|
|
5876
|
+
if (__privateGet(this, _formulas).has(key)) continue;
|
|
5877
|
+
if (__privateGet(this, _spillOwner).has(key)) continue;
|
|
5878
|
+
members.add(key);
|
|
5879
|
+
__privateGet(this, _spillOwner).set(key, anchorKey);
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
if (members.size > 0) __privateGet(this, _spills).set(anchorKey, members);
|
|
5883
|
+
};
|
|
5669
5884
|
/**
|
|
5670
5885
|
* One pass in a caller-supplied order, watching for inversions.
|
|
5671
5886
|
*
|