@devmm/puredocs-excel-formula 1.0.0 → 1.0.1
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 +1635 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +234 -7
- package/dist/index.d.ts +234 -7
- package/dist/index.js +1627 -48
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toOADate, fromOADate, parseCellRef, cellRefFromRowCol } from '@devmm/puredocs-excel';
|
|
1
|
+
import { toOADate, fromOADate, columnLetter, parseCellRef, cellRefFromRowCol } from '@devmm/puredocs-excel';
|
|
2
2
|
|
|
3
3
|
var __typeError = (msg) => {
|
|
4
4
|
throw TypeError(msg);
|
|
@@ -29,15 +29,21 @@ var FormulaError = /* @__PURE__ */ ((FormulaError2) => {
|
|
|
29
29
|
FormulaError2[FormulaError2["Spill"] = 9] = "Spill";
|
|
30
30
|
return FormulaError2;
|
|
31
31
|
})(FormulaError || {});
|
|
32
|
+
var TailCall = class {
|
|
33
|
+
constructor(lambda, args) {
|
|
34
|
+
this.lambda = lambda;
|
|
35
|
+
this.args = args;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
32
38
|
var _num, _obj;
|
|
33
39
|
var _FormulaValue = class _FormulaValue {
|
|
34
|
-
constructor(kind,
|
|
40
|
+
constructor(kind, num5 = 0, obj, errorCode = 0 /* None */) {
|
|
35
41
|
/** Numeric payload (also used for boolean: 1=true, 0=false). */
|
|
36
42
|
__privateAdd(this, _num);
|
|
37
|
-
/** String or
|
|
43
|
+
/** String, ArrayValue, or LambdaLike payload. */
|
|
38
44
|
__privateAdd(this, _obj);
|
|
39
45
|
this.kind = kind;
|
|
40
|
-
__privateSet(this, _num,
|
|
46
|
+
__privateSet(this, _num, num5);
|
|
41
47
|
__privateSet(this, _obj, obj);
|
|
42
48
|
this.errorCode = errorCode;
|
|
43
49
|
}
|
|
@@ -77,6 +83,9 @@ var _FormulaValue = class _FormulaValue {
|
|
|
77
83
|
static array(a) {
|
|
78
84
|
return new _FormulaValue("array", 0, a);
|
|
79
85
|
}
|
|
86
|
+
static lambda(fn) {
|
|
87
|
+
return new _FormulaValue("lambda", 0, fn);
|
|
88
|
+
}
|
|
80
89
|
// ── Type checks ──────────────────────────────────────────────────────────
|
|
81
90
|
get isBlank() {
|
|
82
91
|
return this.kind === "blank";
|
|
@@ -96,6 +105,9 @@ var _FormulaValue = class _FormulaValue {
|
|
|
96
105
|
get isArray() {
|
|
97
106
|
return this.kind === "array";
|
|
98
107
|
}
|
|
108
|
+
get isLambda() {
|
|
109
|
+
return this.kind === "lambda";
|
|
110
|
+
}
|
|
99
111
|
get isNumeric() {
|
|
100
112
|
return this.kind === "number" || this.kind === "boolean" || this.kind === "blank";
|
|
101
113
|
}
|
|
@@ -112,6 +124,9 @@ var _FormulaValue = class _FormulaValue {
|
|
|
112
124
|
get arrayVal() {
|
|
113
125
|
return __privateGet(this, _obj) ?? ArrayValue.empty;
|
|
114
126
|
}
|
|
127
|
+
get lambdaVal() {
|
|
128
|
+
return __privateGet(this, _obj);
|
|
129
|
+
}
|
|
115
130
|
// ── Coercion (Excel semantics, no exceptions) ─────────────────────────────
|
|
116
131
|
coerceToNumber() {
|
|
117
132
|
switch (this.kind) {
|
|
@@ -179,6 +194,8 @@ var _FormulaValue = class _FormulaValue {
|
|
|
179
194
|
return "";
|
|
180
195
|
case "error":
|
|
181
196
|
return _FormulaValue.errorToString(this.errorCode);
|
|
197
|
+
case "lambda":
|
|
198
|
+
return _FormulaValue.errorToString(8 /* Calc */);
|
|
182
199
|
default:
|
|
183
200
|
return "";
|
|
184
201
|
}
|
|
@@ -276,6 +293,8 @@ var _FormulaValue = class _FormulaValue {
|
|
|
276
293
|
return _FormulaValue.errorToString(this.errorCode);
|
|
277
294
|
case "array":
|
|
278
295
|
return this.arrayVal.toObjectArray();
|
|
296
|
+
case "lambda":
|
|
297
|
+
return _FormulaValue.errorToString(8 /* Calc */);
|
|
279
298
|
default:
|
|
280
299
|
return null;
|
|
281
300
|
}
|
|
@@ -424,10 +443,11 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
|
|
|
424
443
|
TokenType2[TokenType2["GreaterThanOrEqual"] = 21] = "GreaterThanOrEqual";
|
|
425
444
|
TokenType2[TokenType2["SheetReference"] = 22] = "SheetReference";
|
|
426
445
|
TokenType2[TokenType2["NamedRange"] = 23] = "NamedRange";
|
|
427
|
-
TokenType2[TokenType2["
|
|
428
|
-
TokenType2[TokenType2["
|
|
429
|
-
TokenType2[TokenType2["
|
|
430
|
-
TokenType2[TokenType2["
|
|
446
|
+
TokenType2[TokenType2["SpillReference"] = 24] = "SpillReference";
|
|
447
|
+
TokenType2[TokenType2["Exclamation"] = 25] = "Exclamation";
|
|
448
|
+
TokenType2[TokenType2["AtSign"] = 26] = "AtSign";
|
|
449
|
+
TokenType2[TokenType2["ErrorLiteral"] = 27] = "ErrorLiteral";
|
|
450
|
+
TokenType2[TokenType2["EOF"] = 28] = "EOF";
|
|
431
451
|
return TokenType2;
|
|
432
452
|
})(TokenType || {});
|
|
433
453
|
function makeToken(type, value2, position) {
|
|
@@ -470,7 +490,7 @@ var FormulaLexer = class {
|
|
|
470
490
|
const token = __privateMethod(this, _FormulaLexer_instances, readNextToken_fn).call(this);
|
|
471
491
|
if (token) tokens.push(token);
|
|
472
492
|
}
|
|
473
|
-
tokens.push(makeToken(
|
|
493
|
+
tokens.push(makeToken(28 /* EOF */, "", __privateGet(this, _pos)));
|
|
474
494
|
return tokens;
|
|
475
495
|
}
|
|
476
496
|
};
|
|
@@ -524,7 +544,7 @@ readErrorLiteral_fn = function() {
|
|
|
524
544
|
if (!KNOWN_ERRORS.has(upper2)) {
|
|
525
545
|
throw new FormulaException(`Unknown error literal "${raw}" at position ${start}.`);
|
|
526
546
|
}
|
|
527
|
-
return makeToken(
|
|
547
|
+
return makeToken(27 /* ErrorLiteral */, upper2, start);
|
|
528
548
|
};
|
|
529
549
|
readQuotedSheetRef_fn = function() {
|
|
530
550
|
const start = __privateWrapper(this, _pos)._++;
|
|
@@ -603,7 +623,13 @@ readIdentifier_fn = function() {
|
|
|
603
623
|
return makeToken(5 /* Function */, upper2, start);
|
|
604
624
|
}
|
|
605
625
|
const stripped = raw.replace(/\$/g, "");
|
|
606
|
-
if (isCellRef(stripped))
|
|
626
|
+
if (isCellRef(stripped)) {
|
|
627
|
+
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "#") {
|
|
628
|
+
__privateWrapper(this, _pos)._++;
|
|
629
|
+
return makeToken(24 /* SpillReference */, stripped, start);
|
|
630
|
+
}
|
|
631
|
+
return makeToken(3 /* CellReference */, stripped, start);
|
|
632
|
+
}
|
|
607
633
|
return makeToken(23 /* NamedRange */, raw, start);
|
|
608
634
|
};
|
|
609
635
|
readCellRefChars_fn = function() {
|
|
@@ -646,9 +672,9 @@ readOperator_fn = function() {
|
|
|
646
672
|
case "=":
|
|
647
673
|
return makeToken(16 /* Equal */, "=", start);
|
|
648
674
|
case "@":
|
|
649
|
-
return makeToken(
|
|
675
|
+
return makeToken(26 /* AtSign */, "@", start);
|
|
650
676
|
case "!":
|
|
651
|
-
return makeToken(
|
|
677
|
+
return makeToken(25 /* Exclamation */, "!", start);
|
|
652
678
|
case "<":
|
|
653
679
|
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === ">") {
|
|
654
680
|
__privateWrapper(this, _pos)._++;
|
|
@@ -685,6 +711,15 @@ function isCellRef(s) {
|
|
|
685
711
|
|
|
686
712
|
// src/formula-node.ts
|
|
687
713
|
var FormulaNode = class {
|
|
714
|
+
/**
|
|
715
|
+
* Evaluates this node in tail position. Returns a TailCall when the node's
|
|
716
|
+
* value is a call to a lambda in tail position (so the caller's trampoline can
|
|
717
|
+
* iterate without growing the stack); otherwise the final value. The default
|
|
718
|
+
* is a plain evaluate — only control-flow and call nodes override it.
|
|
719
|
+
*/
|
|
720
|
+
evaluateTail(ctx) {
|
|
721
|
+
return this.evaluate(ctx);
|
|
722
|
+
}
|
|
688
723
|
};
|
|
689
724
|
var NumberNode = class extends FormulaNode {
|
|
690
725
|
constructor(value2) {
|
|
@@ -776,6 +811,15 @@ var NamedRangeNode = class extends FormulaNode {
|
|
|
776
811
|
return ctx.resolveNamedRange(this.name);
|
|
777
812
|
}
|
|
778
813
|
};
|
|
814
|
+
var SpillReferenceNode = class extends FormulaNode {
|
|
815
|
+
constructor(anchorRef) {
|
|
816
|
+
super();
|
|
817
|
+
this.anchorRef = anchorRef;
|
|
818
|
+
}
|
|
819
|
+
evaluate(ctx) {
|
|
820
|
+
return ctx.resolveSpillRange(this.anchorRef);
|
|
821
|
+
}
|
|
822
|
+
};
|
|
779
823
|
var BinaryOperator = /* @__PURE__ */ ((BinaryOperator2) => {
|
|
780
824
|
BinaryOperator2[BinaryOperator2["Add"] = 0] = "Add";
|
|
781
825
|
BinaryOperator2[BinaryOperator2["Subtract"] = 1] = "Subtract";
|
|
@@ -899,10 +943,85 @@ var FunctionCallNode = class extends FormulaNode {
|
|
|
899
943
|
evaluate(ctx) {
|
|
900
944
|
return ctx.evaluateFunction(this.functionName, this.args);
|
|
901
945
|
}
|
|
946
|
+
evaluateTail(ctx) {
|
|
947
|
+
const name = this.functionName;
|
|
948
|
+
const a = this.args;
|
|
949
|
+
switch (name) {
|
|
950
|
+
case "IF": {
|
|
951
|
+
const cond = a[0].evaluate(ctx);
|
|
952
|
+
if (cond.isError) return cond;
|
|
953
|
+
const b = cond.coerceToBool();
|
|
954
|
+
if (b.isError) return b;
|
|
955
|
+
if (b.booleanValue) return a[1].evaluateTail(ctx);
|
|
956
|
+
return a.length > 2 ? a[2].evaluateTail(ctx) : FormulaValue.false_;
|
|
957
|
+
}
|
|
958
|
+
case "IFS": {
|
|
959
|
+
for (let i = 0; i + 1 < a.length; i += 2) {
|
|
960
|
+
const cond = a[i].evaluate(ctx);
|
|
961
|
+
if (cond.isError) return cond;
|
|
962
|
+
const b = cond.coerceToBool();
|
|
963
|
+
if (b.isError) return b;
|
|
964
|
+
if (b.booleanValue) return a[i + 1].evaluateTail(ctx);
|
|
965
|
+
}
|
|
966
|
+
return FormulaValue.errorNA;
|
|
967
|
+
}
|
|
968
|
+
case "IFERROR": {
|
|
969
|
+
const v = a[0].evaluate(ctx);
|
|
970
|
+
return v.isError ? a[1].evaluateTail(ctx) : v;
|
|
971
|
+
}
|
|
972
|
+
case "IFNA": {
|
|
973
|
+
const v = a[0].evaluate(ctx);
|
|
974
|
+
return v.isError && v.errorCode === 7 /* NA */ ? a[1].evaluateTail(ctx) : v;
|
|
975
|
+
}
|
|
976
|
+
case "LET": {
|
|
977
|
+
if (a.length < 3 || a.length % 2 === 0) break;
|
|
978
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
979
|
+
ctx.pushScope(bindings);
|
|
980
|
+
try {
|
|
981
|
+
for (let i = 0; i < a.length - 1; i += 2) {
|
|
982
|
+
const nn = a[i];
|
|
983
|
+
if (!(nn instanceof NamedRangeNode)) return FormulaValue.errorValue;
|
|
984
|
+
const val = a[i + 1].evaluate(ctx);
|
|
985
|
+
if (val.isError) return val;
|
|
986
|
+
bindings.set(nn.name.toUpperCase(), val);
|
|
987
|
+
}
|
|
988
|
+
return a[a.length - 1].evaluateTail(ctx);
|
|
989
|
+
} finally {
|
|
990
|
+
ctx.popScope();
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (!ctx.hasFunction(name)) {
|
|
995
|
+
const named = ctx.resolveNamedRange(name);
|
|
996
|
+
if (named.isLambda) {
|
|
997
|
+
return new TailCall(named.lambdaVal, a.map((arg2) => arg2.evaluate(ctx)));
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return this.evaluate(ctx);
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
var CallNode = class extends FormulaNode {
|
|
1004
|
+
constructor(callee, args) {
|
|
1005
|
+
super();
|
|
1006
|
+
this.callee = callee;
|
|
1007
|
+
this.args = args;
|
|
1008
|
+
}
|
|
1009
|
+
evaluate(ctx) {
|
|
1010
|
+
const fn = this.callee.evaluate(ctx);
|
|
1011
|
+
if (fn.isError) return fn;
|
|
1012
|
+
if (!fn.isLambda) return FormulaValue.errorValue;
|
|
1013
|
+
return fn.lambdaVal.call(this.args.map((a) => a.evaluate(ctx)));
|
|
1014
|
+
}
|
|
1015
|
+
evaluateTail(ctx) {
|
|
1016
|
+
const fn = this.callee.evaluate(ctx);
|
|
1017
|
+
if (fn.isError) return fn;
|
|
1018
|
+
if (!fn.isLambda) return FormulaValue.errorValue;
|
|
1019
|
+
return new TailCall(fn.lambdaVal, this.args.map((a) => a.evaluate(ctx)));
|
|
1020
|
+
}
|
|
902
1021
|
};
|
|
903
1022
|
|
|
904
1023
|
// src/formula-parser.ts
|
|
905
|
-
var _tokens, _pos2, _FormulaParser_instances, current_get, advance_fn, expect_fn, parseComparison_fn, parseConcatenation_fn, parseAddSub_fn, parseMulDiv_fn, parseUnary_fn, parsePower_fn, parsePercent_fn, parsePrimary_fn, parseFunction_fn;
|
|
1024
|
+
var _tokens, _pos2, _FormulaParser_instances, current_get, advance_fn, expect_fn, parseComparison_fn, parseConcatenation_fn, parseAddSub_fn, parseMulDiv_fn, parseUnary_fn, parsePower_fn, parsePercent_fn, parsePostfix_fn, parsePrimary_fn, parseFunction_fn, parseArgList_fn;
|
|
906
1025
|
var FormulaParser = class {
|
|
907
1026
|
constructor(tokens) {
|
|
908
1027
|
__privateAdd(this, _FormulaParser_instances);
|
|
@@ -913,7 +1032,7 @@ var FormulaParser = class {
|
|
|
913
1032
|
parse() {
|
|
914
1033
|
__privateSet(this, _pos2, 0);
|
|
915
1034
|
const node = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
|
|
916
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type !==
|
|
1035
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type !== 28 /* EOF */) {
|
|
917
1036
|
throw new FormulaException(
|
|
918
1037
|
`Unexpected token '${__privateGet(this, _FormulaParser_instances, current_get).value}' at position ${__privateGet(this, _FormulaParser_instances, current_get).position}.`
|
|
919
1038
|
);
|
|
@@ -925,7 +1044,7 @@ _tokens = new WeakMap();
|
|
|
925
1044
|
_pos2 = new WeakMap();
|
|
926
1045
|
_FormulaParser_instances = new WeakSet();
|
|
927
1046
|
current_get = function() {
|
|
928
|
-
return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type:
|
|
1047
|
+
return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 28 /* EOF */, value: "", position: -1 };
|
|
929
1048
|
};
|
|
930
1049
|
advance_fn = function() {
|
|
931
1050
|
const t = __privateGet(this, _FormulaParser_instances, current_get);
|
|
@@ -990,7 +1109,7 @@ parseUnary_fn = function() {
|
|
|
990
1109
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
991
1110
|
return new UnaryOpNode(1 /* Plus */, __privateMethod(this, _FormulaParser_instances, parsePower_fn).call(this));
|
|
992
1111
|
}
|
|
993
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type ===
|
|
1112
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 26 /* AtSign */) {
|
|
994
1113
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
995
1114
|
return new ImplicitIntersectionNode(__privateMethod(this, _FormulaParser_instances, parsePower_fn).call(this));
|
|
996
1115
|
}
|
|
@@ -1005,13 +1124,22 @@ parsePower_fn = function() {
|
|
|
1005
1124
|
return left2;
|
|
1006
1125
|
};
|
|
1007
1126
|
parsePercent_fn = function() {
|
|
1008
|
-
let node = __privateMethod(this, _FormulaParser_instances,
|
|
1127
|
+
let node = __privateMethod(this, _FormulaParser_instances, parsePostfix_fn).call(this);
|
|
1009
1128
|
while (__privateGet(this, _FormulaParser_instances, current_get).type === 14 /* Percent */) {
|
|
1010
1129
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1011
1130
|
node = new UnaryOpNode(2 /* Percent */, node);
|
|
1012
1131
|
}
|
|
1013
1132
|
return node;
|
|
1014
1133
|
};
|
|
1134
|
+
/** Handles postfix call syntax on an expression, e.g. LAMBDA(x,x+1)(5). */
|
|
1135
|
+
parsePostfix_fn = function() {
|
|
1136
|
+
let node = __privateMethod(this, _FormulaParser_instances, parsePrimary_fn).call(this);
|
|
1137
|
+
while (__privateGet(this, _FormulaParser_instances, current_get).type === 6 /* LeftParen */) {
|
|
1138
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1139
|
+
node = new CallNode(node, __privateMethod(this, _FormulaParser_instances, parseArgList_fn).call(this));
|
|
1140
|
+
}
|
|
1141
|
+
return node;
|
|
1142
|
+
};
|
|
1015
1143
|
parsePrimary_fn = function() {
|
|
1016
1144
|
const token = __privateGet(this, _FormulaParser_instances, current_get);
|
|
1017
1145
|
switch (token.type) {
|
|
@@ -1027,7 +1155,7 @@ parsePrimary_fn = function() {
|
|
|
1027
1155
|
case 2 /* Boolean */:
|
|
1028
1156
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1029
1157
|
return new BooleanNode(token.value === "TRUE");
|
|
1030
|
-
case
|
|
1158
|
+
case 27 /* ErrorLiteral */:
|
|
1031
1159
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1032
1160
|
return new ErrorNode(FormulaValue.error(FormulaValue.errorFromString(token.value)));
|
|
1033
1161
|
case 3 /* CellReference */: {
|
|
@@ -1054,6 +1182,9 @@ parsePrimary_fn = function() {
|
|
|
1054
1182
|
case 23 /* NamedRange */:
|
|
1055
1183
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1056
1184
|
return new NamedRangeNode(token.value);
|
|
1185
|
+
case 24 /* SpillReference */:
|
|
1186
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1187
|
+
return new SpillReferenceNode(token.value);
|
|
1057
1188
|
case 5 /* Function */:
|
|
1058
1189
|
return __privateMethod(this, _FormulaParser_instances, parseFunction_fn).call(this);
|
|
1059
1190
|
case 6 /* LeftParen */: {
|
|
@@ -1071,6 +1202,10 @@ parsePrimary_fn = function() {
|
|
|
1071
1202
|
parseFunction_fn = function() {
|
|
1072
1203
|
const nameToken = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1073
1204
|
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 6 /* LeftParen */);
|
|
1205
|
+
return new FunctionCallNode(nameToken.value.toUpperCase(), __privateMethod(this, _FormulaParser_instances, parseArgList_fn).call(this));
|
|
1206
|
+
};
|
|
1207
|
+
/** Parses a comma-separated argument list up to and including the ')'. */
|
|
1208
|
+
parseArgList_fn = function() {
|
|
1074
1209
|
const args = [];
|
|
1075
1210
|
if (__privateGet(this, _FormulaParser_instances, current_get).type !== 7 /* RightParen */) {
|
|
1076
1211
|
if (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */) {
|
|
@@ -1088,7 +1223,7 @@ parseFunction_fn = function() {
|
|
|
1088
1223
|
}
|
|
1089
1224
|
}
|
|
1090
1225
|
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 7 /* RightParen */);
|
|
1091
|
-
return
|
|
1226
|
+
return args;
|
|
1092
1227
|
};
|
|
1093
1228
|
function comparisonOp(type) {
|
|
1094
1229
|
switch (type) {
|
|
@@ -1119,10 +1254,14 @@ function registerMath(r) {
|
|
|
1119
1254
|
r.register("COUNTA", countA, 1);
|
|
1120
1255
|
r.register("COUNTBLANK", countBlank, 1, 1);
|
|
1121
1256
|
r.register("COUNTIF", countIf, 2, 2);
|
|
1257
|
+
r.register("COUNTIFS", countIfs, 2);
|
|
1122
1258
|
r.register("SUMIF", sumIf, 2, 3);
|
|
1259
|
+
r.register("SUMIFS", sumIfs, 3);
|
|
1123
1260
|
r.register("AVERAGEIF", averageIf, 2, 3);
|
|
1261
|
+
r.register("AVERAGEIFS", averageIfs, 3);
|
|
1124
1262
|
r.register("PRODUCT", product, 1);
|
|
1125
1263
|
r.register("SUMPRODUCT", sumProduct, 1);
|
|
1264
|
+
r.register("SUMSQ", sumSq, 1);
|
|
1126
1265
|
r.register("ABS", abs, 1, 1);
|
|
1127
1266
|
r.register("ROUND", round, 2, 2);
|
|
1128
1267
|
r.register("ROUNDUP", roundUp, 2, 2);
|
|
@@ -1147,6 +1286,114 @@ function registerMath(r) {
|
|
|
1147
1286
|
r.register("EXP", exp, 1, 1);
|
|
1148
1287
|
r.register("FACT", fact, 1, 1);
|
|
1149
1288
|
r.register("COMBIN", combin, 2, 2);
|
|
1289
|
+
r.register("SIN", unary(Math.sin), 1, 1);
|
|
1290
|
+
r.register("COS", unary(Math.cos), 1, 1);
|
|
1291
|
+
r.register("TAN", unary(Math.tan), 1, 1);
|
|
1292
|
+
r.register("ASIN", asin, 1, 1);
|
|
1293
|
+
r.register("ACOS", acos, 1, 1);
|
|
1294
|
+
r.register("ATAN", unary(Math.atan), 1, 1);
|
|
1295
|
+
r.register("ATAN2", atan2, 2, 2);
|
|
1296
|
+
r.register("SINH", unary(Math.sinh), 1, 1);
|
|
1297
|
+
r.register("COSH", unary(Math.cosh), 1, 1);
|
|
1298
|
+
r.register("TANH", unary(Math.tanh), 1, 1);
|
|
1299
|
+
r.register("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
|
|
1300
|
+
r.register("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
|
|
1301
|
+
}
|
|
1302
|
+
function valueAt(v, i) {
|
|
1303
|
+
return v.isArray ? v.arrayVal.getFlat(i) : v;
|
|
1304
|
+
}
|
|
1305
|
+
function lengthOf(v) {
|
|
1306
|
+
return v.isArray ? v.arrayVal.length : 1;
|
|
1307
|
+
}
|
|
1308
|
+
function computeIfsMask(a, c, startIdx, len2) {
|
|
1309
|
+
const mask = new Uint8Array(len2).fill(1);
|
|
1310
|
+
for (let p = startIdx; p + 1 < a.length; p += 2) {
|
|
1311
|
+
const cr = a[p].evaluate(c);
|
|
1312
|
+
if (cr.isError) return { ok: false, error: cr };
|
|
1313
|
+
const crit = a[p + 1].evaluate(c);
|
|
1314
|
+
if (crit.isError) return { ok: false, error: crit };
|
|
1315
|
+
const critText = crit.asText();
|
|
1316
|
+
for (let i = 0; i < len2; i++) {
|
|
1317
|
+
if (!mask[i]) continue;
|
|
1318
|
+
if (!FormulaHelper.matchesCriteria(valueAt(cr, i), critText)) mask[i] = 0;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return { ok: true, mask };
|
|
1322
|
+
}
|
|
1323
|
+
function sumIfs(a, c) {
|
|
1324
|
+
if (a.length < 3 || a.length % 2 === 0) return FormulaValue.errorValue;
|
|
1325
|
+
const sumRange = a[0].evaluate(c);
|
|
1326
|
+
if (sumRange.isError) return sumRange;
|
|
1327
|
+
const len2 = lengthOf(sumRange);
|
|
1328
|
+
const m = computeIfsMask(a, c, 1, len2);
|
|
1329
|
+
if (!m.ok) return m.error;
|
|
1330
|
+
let total = 0;
|
|
1331
|
+
for (let i = 0; i < len2; i++) {
|
|
1332
|
+
if (!m.mask[i]) continue;
|
|
1333
|
+
const r = valueAt(sumRange, i).tryAsDouble();
|
|
1334
|
+
if (r.ok) total += r.value;
|
|
1335
|
+
}
|
|
1336
|
+
return FormulaValue.number(total);
|
|
1337
|
+
}
|
|
1338
|
+
function countIfs(a, c) {
|
|
1339
|
+
if (a.length < 2 || a.length % 2 !== 0) return FormulaValue.errorValue;
|
|
1340
|
+
const first = a[0].evaluate(c);
|
|
1341
|
+
if (first.isError) return first;
|
|
1342
|
+
const len2 = lengthOf(first);
|
|
1343
|
+
const m = computeIfsMask(a, c, 0, len2);
|
|
1344
|
+
if (!m.ok) return m.error;
|
|
1345
|
+
let n = 0;
|
|
1346
|
+
for (let i = 0; i < len2; i++) if (m.mask[i]) n++;
|
|
1347
|
+
return FormulaValue.number(n);
|
|
1348
|
+
}
|
|
1349
|
+
function averageIfs(a, c) {
|
|
1350
|
+
if (a.length < 3 || a.length % 2 === 0) return FormulaValue.errorValue;
|
|
1351
|
+
const avgRange = a[0].evaluate(c);
|
|
1352
|
+
if (avgRange.isError) return avgRange;
|
|
1353
|
+
const len2 = lengthOf(avgRange);
|
|
1354
|
+
const m = computeIfsMask(a, c, 1, len2);
|
|
1355
|
+
if (!m.ok) return m.error;
|
|
1356
|
+
let total = 0, cnt = 0;
|
|
1357
|
+
for (let i = 0; i < len2; i++) {
|
|
1358
|
+
if (!m.mask[i]) continue;
|
|
1359
|
+
const r = valueAt(avgRange, i).tryAsDouble();
|
|
1360
|
+
if (r.ok) {
|
|
1361
|
+
total += r.value;
|
|
1362
|
+
cnt++;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return cnt === 0 ? FormulaValue.errorDiv0 : FormulaValue.number(total / cnt);
|
|
1366
|
+
}
|
|
1367
|
+
function sumSq(a, c) {
|
|
1368
|
+
const r = FormulaHelper.collectNumbers(a, c);
|
|
1369
|
+
if (!r.ok) return r.error;
|
|
1370
|
+
return FormulaValue.number(r.values.reduce((s, n) => s + n * n, 0));
|
|
1371
|
+
}
|
|
1372
|
+
function unary(fn) {
|
|
1373
|
+
return (a, c) => {
|
|
1374
|
+
const r = FormulaHelper.evalDouble(a[0], c);
|
|
1375
|
+
return r.ok ? FormulaValue.number(fn(r.value)) : r.error;
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
function asin(a, c) {
|
|
1379
|
+
const r = FormulaHelper.evalDouble(a[0], c);
|
|
1380
|
+
if (!r.ok) return r.error;
|
|
1381
|
+
if (r.value < -1 || r.value > 1) return FormulaValue.errorNum;
|
|
1382
|
+
return FormulaValue.number(Math.asin(r.value));
|
|
1383
|
+
}
|
|
1384
|
+
function acos(a, c) {
|
|
1385
|
+
const r = FormulaHelper.evalDouble(a[0], c);
|
|
1386
|
+
if (!r.ok) return r.error;
|
|
1387
|
+
if (r.value < -1 || r.value > 1) return FormulaValue.errorNum;
|
|
1388
|
+
return FormulaValue.number(Math.acos(r.value));
|
|
1389
|
+
}
|
|
1390
|
+
function atan2(a, c) {
|
|
1391
|
+
const x = FormulaHelper.evalDouble(a[0], c);
|
|
1392
|
+
if (!x.ok) return x.error;
|
|
1393
|
+
const y = FormulaHelper.evalDouble(a[1], c);
|
|
1394
|
+
if (!y.ok) return y.error;
|
|
1395
|
+
if (x.value === 0 && y.value === 0) return FormulaValue.errorDiv0;
|
|
1396
|
+
return FormulaValue.number(Math.atan2(y.value, x.value));
|
|
1150
1397
|
}
|
|
1151
1398
|
function sum(a, c) {
|
|
1152
1399
|
const r = FormulaHelper.collectNumbers(a, c);
|
|
@@ -1265,7 +1512,7 @@ function product(a, c) {
|
|
|
1265
1512
|
return FormulaValue.number(r.values.reduce((p, n) => p * n, 1));
|
|
1266
1513
|
}
|
|
1267
1514
|
function sumProduct(a, c) {
|
|
1268
|
-
const arrays = a.map((
|
|
1515
|
+
const arrays = a.map((arg2) => arg2.evaluate(c));
|
|
1269
1516
|
for (const v of arrays) if (v.isError) return v;
|
|
1270
1517
|
const first = arrays[0];
|
|
1271
1518
|
const len2 = first.isArray ? first.arrayVal.length : 1;
|
|
@@ -1466,6 +1713,8 @@ function registerText(r) {
|
|
|
1466
1713
|
r.register("CHAR", char_, 1, 1);
|
|
1467
1714
|
r.register("CODE", code, 1, 1);
|
|
1468
1715
|
r.register("T", t_, 1, 1);
|
|
1716
|
+
r.register("TEXTBEFORE", textBefore, 2, 3);
|
|
1717
|
+
r.register("TEXTAFTER", textAfter, 2, 3);
|
|
1469
1718
|
}
|
|
1470
1719
|
function str(a, c, idx) {
|
|
1471
1720
|
const r = FormulaHelper.evalString(a[idx], c);
|
|
@@ -1594,8 +1843,8 @@ function replace(a, c) {
|
|
|
1594
1843
|
}
|
|
1595
1844
|
function concatenate(a, c) {
|
|
1596
1845
|
let result = "";
|
|
1597
|
-
for (const
|
|
1598
|
-
const v =
|
|
1846
|
+
for (const arg2 of a) {
|
|
1847
|
+
const v = arg2.evaluate(c);
|
|
1599
1848
|
if (v.isError) return v;
|
|
1600
1849
|
result += v.asText();
|
|
1601
1850
|
}
|
|
@@ -1722,6 +1971,45 @@ function t_(a, c) {
|
|
|
1722
1971
|
const v = a[0].evaluate(c);
|
|
1723
1972
|
return v.isText ? v : FormulaValue.emptyString;
|
|
1724
1973
|
}
|
|
1974
|
+
function textBefore(a, c) {
|
|
1975
|
+
return textSplitAt(a, c, "before");
|
|
1976
|
+
}
|
|
1977
|
+
function textAfter(a, c) {
|
|
1978
|
+
return textSplitAt(a, c, "after");
|
|
1979
|
+
}
|
|
1980
|
+
function textSplitAt(a, c, mode2) {
|
|
1981
|
+
const s = str(a, c, 0);
|
|
1982
|
+
if (!s.ok) return s.e;
|
|
1983
|
+
const d = str(a, c, 1);
|
|
1984
|
+
if (!d.ok) return d.e;
|
|
1985
|
+
let instance = 1;
|
|
1986
|
+
if (a.length > 2) {
|
|
1987
|
+
const r = num(a, c, 2);
|
|
1988
|
+
if (!r.ok) return r.e;
|
|
1989
|
+
instance = Math.trunc(r.n);
|
|
1990
|
+
}
|
|
1991
|
+
if (instance === 0) return FormulaValue.errorValue;
|
|
1992
|
+
const text = s.s;
|
|
1993
|
+
const delim = d.s;
|
|
1994
|
+
if (delim === "") {
|
|
1995
|
+
return FormulaValue.text(mode2 === "before" ? "" : text);
|
|
1996
|
+
}
|
|
1997
|
+
const positions = [];
|
|
1998
|
+
let from = 0;
|
|
1999
|
+
for (; ; ) {
|
|
2000
|
+
const idx = text.indexOf(delim, from);
|
|
2001
|
+
if (idx === -1) break;
|
|
2002
|
+
positions.push(idx);
|
|
2003
|
+
from = idx + delim.length;
|
|
2004
|
+
}
|
|
2005
|
+
if (positions.length === 0) return FormulaValue.errorNA;
|
|
2006
|
+
const nth = instance > 0 ? instance - 1 : positions.length + instance;
|
|
2007
|
+
if (nth < 0 || nth >= positions.length) return FormulaValue.errorNA;
|
|
2008
|
+
const pos = positions[nth];
|
|
2009
|
+
return FormulaValue.text(
|
|
2010
|
+
mode2 === "before" ? text.slice(0, pos) : text.slice(pos + delim.length)
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
1725
2013
|
|
|
1726
2014
|
// src/functions/logical-functions.ts
|
|
1727
2015
|
function registerLogical(r) {
|
|
@@ -1746,8 +2034,8 @@ function if_(a, c) {
|
|
|
1746
2034
|
}
|
|
1747
2035
|
function and(a, c) {
|
|
1748
2036
|
let hasValue = false;
|
|
1749
|
-
for (const
|
|
1750
|
-
const v =
|
|
2037
|
+
for (const arg2 of a) {
|
|
2038
|
+
const v = arg2.evaluate(c);
|
|
1751
2039
|
if (v.isError) return v;
|
|
1752
2040
|
if (v.isArray) {
|
|
1753
2041
|
for (const item of v.arrayVal.values()) {
|
|
@@ -1773,8 +2061,8 @@ function and(a, c) {
|
|
|
1773
2061
|
}
|
|
1774
2062
|
function or(a, c) {
|
|
1775
2063
|
let hasValue = false;
|
|
1776
|
-
for (const
|
|
1777
|
-
const v =
|
|
2064
|
+
for (const arg2 of a) {
|
|
2065
|
+
const v = arg2.evaluate(c);
|
|
1778
2066
|
if (v.isError) return v;
|
|
1779
2067
|
if (v.isArray) {
|
|
1780
2068
|
for (const item of v.arrayVal.values()) {
|
|
@@ -1807,8 +2095,8 @@ function not(a, c) {
|
|
|
1807
2095
|
}
|
|
1808
2096
|
function xor(a, c) {
|
|
1809
2097
|
let trueCount = 0;
|
|
1810
|
-
for (const
|
|
1811
|
-
const v =
|
|
2098
|
+
for (const arg2 of a) {
|
|
2099
|
+
const v = arg2.evaluate(c);
|
|
1812
2100
|
if (v.isError) return v;
|
|
1813
2101
|
const bv = v.coerceToBool();
|
|
1814
2102
|
if (bv.isError) return bv;
|
|
@@ -2068,14 +2356,17 @@ function datedif(a, c) {
|
|
|
2068
2356
|
function registerLookup(r) {
|
|
2069
2357
|
r.register("VLOOKUP", vlookup, 3, 4);
|
|
2070
2358
|
r.register("HLOOKUP", hlookup, 3, 4);
|
|
2359
|
+
r.register("LOOKUP", lookup, 2, 3);
|
|
2071
2360
|
r.register("INDEX", index_, 2, 3);
|
|
2072
2361
|
r.register("MATCH", match, 2, 3);
|
|
2073
2362
|
r.register("CHOOSE", choose, 2);
|
|
2363
|
+
r.register("ADDRESS", address, 2, 5);
|
|
2074
2364
|
r.register("ROW", row, 0, 1);
|
|
2075
2365
|
r.register("COLUMN", column, 0, 1);
|
|
2076
2366
|
r.register("ROWS", rows, 1, 1);
|
|
2077
2367
|
r.register("COLUMNS", columns, 1, 1);
|
|
2078
2368
|
r.register("OFFSET", offset, 3, 5, true);
|
|
2369
|
+
r.register("INDIRECT", indirect, 1, 2, true);
|
|
2079
2370
|
}
|
|
2080
2371
|
function num2(a, c, i) {
|
|
2081
2372
|
const r = FormulaHelper.evalDouble(a[i], c);
|
|
@@ -2204,6 +2495,86 @@ function choose(a, c) {
|
|
|
2204
2495
|
if (idx < 1 || idx >= a.length) return FormulaValue.errorValue;
|
|
2205
2496
|
return a[idx].evaluate(c);
|
|
2206
2497
|
}
|
|
2498
|
+
function approxMatchFlat(arr, lookupVal) {
|
|
2499
|
+
let best = -1;
|
|
2500
|
+
for (let i = 0; i < arr.length; i++) {
|
|
2501
|
+
if (FormulaValue.compare(arr.getFlat(i), lookupVal) <= 0) best = i;
|
|
2502
|
+
else break;
|
|
2503
|
+
}
|
|
2504
|
+
return best;
|
|
2505
|
+
}
|
|
2506
|
+
function lookup(a, c) {
|
|
2507
|
+
const lookupVal = a[0].evaluate(c);
|
|
2508
|
+
if (lookupVal.isError) return lookupVal;
|
|
2509
|
+
const vecVal = a[1].evaluate(c);
|
|
2510
|
+
if (vecVal.isError) return vecVal;
|
|
2511
|
+
if (a.length > 2) {
|
|
2512
|
+
const resVal = a[2].evaluate(c);
|
|
2513
|
+
if (resVal.isError) return resVal;
|
|
2514
|
+
const vecArr = vecVal.isArray ? vecVal.arrayVal : ArrayValue.fromScalar(vecVal);
|
|
2515
|
+
const idx = approxMatchFlat(vecArr, lookupVal);
|
|
2516
|
+
if (idx < 0) return FormulaValue.errorNA;
|
|
2517
|
+
if (resVal.isArray) return resVal.arrayVal.getFlat(idx);
|
|
2518
|
+
return idx === 0 ? resVal : FormulaValue.errorNA;
|
|
2519
|
+
}
|
|
2520
|
+
if (!vecVal.isArray) {
|
|
2521
|
+
return FormulaValue.areEqual(vecVal, lookupVal) ? vecVal : FormulaValue.errorNA;
|
|
2522
|
+
}
|
|
2523
|
+
const arr = vecVal.arrayVal;
|
|
2524
|
+
if (arr.columns >= arr.rows) {
|
|
2525
|
+
let best2 = -1;
|
|
2526
|
+
for (let col = 0; col < arr.columns; col++) {
|
|
2527
|
+
if (FormulaValue.compare(arr.get(0, col), lookupVal) <= 0) best2 = col;
|
|
2528
|
+
else break;
|
|
2529
|
+
}
|
|
2530
|
+
return best2 >= 0 ? arr.get(arr.rows - 1, best2) : FormulaValue.errorNA;
|
|
2531
|
+
}
|
|
2532
|
+
let best = -1;
|
|
2533
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
2534
|
+
if (FormulaValue.compare(arr.get(r, 0), lookupVal) <= 0) best = r;
|
|
2535
|
+
else break;
|
|
2536
|
+
}
|
|
2537
|
+
return best >= 0 ? arr.get(best, arr.columns - 1) : FormulaValue.errorNA;
|
|
2538
|
+
}
|
|
2539
|
+
function address(a, c) {
|
|
2540
|
+
const rn = num2(a, c, 0);
|
|
2541
|
+
if (!rn.ok) return rn.e;
|
|
2542
|
+
const cn = num2(a, c, 1);
|
|
2543
|
+
if (!cn.ok) return cn.e;
|
|
2544
|
+
const row2 = Math.round(rn.n);
|
|
2545
|
+
const col = Math.round(cn.n);
|
|
2546
|
+
if (row2 < 1 || col < 1) return FormulaValue.errorValue;
|
|
2547
|
+
let absNum = 1;
|
|
2548
|
+
if (a.length > 2) {
|
|
2549
|
+
const r = num2(a, c, 2);
|
|
2550
|
+
if (!r.ok) return r.e;
|
|
2551
|
+
absNum = Math.round(r.n);
|
|
2552
|
+
}
|
|
2553
|
+
if (absNum < 1 || absNum > 4) return FormulaValue.errorValue;
|
|
2554
|
+
let a1 = true;
|
|
2555
|
+
if (a.length > 3) {
|
|
2556
|
+
const v = a[3].evaluate(c);
|
|
2557
|
+
if (v.isError) return v;
|
|
2558
|
+
a1 = v.isBlank ? true : v.coerceToBool().booleanValue;
|
|
2559
|
+
}
|
|
2560
|
+
let sheetPrefix = "";
|
|
2561
|
+
if (a.length > 4) {
|
|
2562
|
+
const s = FormulaHelper.evalString(a[4], c);
|
|
2563
|
+
if (!s.ok) return s.error;
|
|
2564
|
+
if (s.value !== "") sheetPrefix = `${s.value}!`;
|
|
2565
|
+
}
|
|
2566
|
+
const rowAbs = absNum === 1 || absNum === 2;
|
|
2567
|
+
const colAbs = absNum === 1 || absNum === 3;
|
|
2568
|
+
let ref;
|
|
2569
|
+
if (a1) {
|
|
2570
|
+
ref = `${colAbs ? "$" : ""}${columnLetter(col)}${rowAbs ? "$" : ""}${row2}`;
|
|
2571
|
+
} else {
|
|
2572
|
+
const rPart = rowAbs ? `R${row2}` : `R[${row2}]`;
|
|
2573
|
+
const cPart = colAbs ? `C${col}` : `C[${col}]`;
|
|
2574
|
+
ref = `${rPart}${cPart}`;
|
|
2575
|
+
}
|
|
2576
|
+
return FormulaValue.text(`${sheetPrefix}${ref}`);
|
|
2577
|
+
}
|
|
2207
2578
|
function row(a, c) {
|
|
2208
2579
|
if (a.length === 0) return FormulaValue.number(c.formulaRow || 1);
|
|
2209
2580
|
const node = a[0];
|
|
@@ -2242,6 +2613,61 @@ function columns(a, c) {
|
|
|
2242
2613
|
const v = a[0].evaluate(c);
|
|
2243
2614
|
return v.isArray ? FormulaValue.number(v.arrayVal.columns) : FormulaValue.one;
|
|
2244
2615
|
}
|
|
2616
|
+
function indirect(a, c) {
|
|
2617
|
+
const s = FormulaHelper.evalString(a[0], c);
|
|
2618
|
+
if (!s.ok) return s.error;
|
|
2619
|
+
let a1 = true;
|
|
2620
|
+
if (a.length > 1) {
|
|
2621
|
+
const v = a[1].evaluate(c);
|
|
2622
|
+
if (v.isError) return v;
|
|
2623
|
+
a1 = v.isBlank ? true : v.coerceToBool().booleanValue;
|
|
2624
|
+
}
|
|
2625
|
+
const text = s.value.trim();
|
|
2626
|
+
if (text === "") return FormulaValue.errorRef;
|
|
2627
|
+
let sheet;
|
|
2628
|
+
let body = text;
|
|
2629
|
+
const bang = text.lastIndexOf("!");
|
|
2630
|
+
if (bang >= 0) {
|
|
2631
|
+
sheet = text.slice(0, bang).replace(/^'|'$/g, "");
|
|
2632
|
+
body = text.slice(bang + 1);
|
|
2633
|
+
}
|
|
2634
|
+
const colon = body.indexOf(":");
|
|
2635
|
+
try {
|
|
2636
|
+
if (colon >= 0) {
|
|
2637
|
+
const start = toA1(body.slice(0, colon), a1, c);
|
|
2638
|
+
const end = toA1(body.slice(colon + 1), a1, c);
|
|
2639
|
+
if (start === null || end === null) return FormulaValue.errorRef;
|
|
2640
|
+
return sheet !== void 0 ? c.getSheetRangeValues(sheet, start, end) : c.getRangeValues(start, end);
|
|
2641
|
+
}
|
|
2642
|
+
const cell = toA1(body, a1, c);
|
|
2643
|
+
if (cell === null) return FormulaValue.errorRef;
|
|
2644
|
+
return sheet !== void 0 ? c.getSheetCellValue(sheet, cell) : c.getCellValue(cell);
|
|
2645
|
+
} catch {
|
|
2646
|
+
return FormulaValue.errorRef;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
function toA1(ref, a1, c) {
|
|
2650
|
+
const r = ref.trim();
|
|
2651
|
+
if (a1) {
|
|
2652
|
+
try {
|
|
2653
|
+
const { row: row3, column: column2 } = parseCellRef(r);
|
|
2654
|
+
return cellRefFromRowCol(row3, column2);
|
|
2655
|
+
} catch {
|
|
2656
|
+
return null;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
const m = /^R(\[-?\d+\]|\d+)?C(\[-?\d+\]|\d+)?$/i.exec(r);
|
|
2660
|
+
if (!m) return null;
|
|
2661
|
+
const axis = (part, base) => {
|
|
2662
|
+
if (part === void 0) return base;
|
|
2663
|
+
if (part.startsWith("[")) return base + parseInt(part.slice(1, -1), 10);
|
|
2664
|
+
return parseInt(part, 10);
|
|
2665
|
+
};
|
|
2666
|
+
const row2 = axis(m[1], c.formulaRow || 1);
|
|
2667
|
+
const col = axis(m[2], c.formulaCol || 1);
|
|
2668
|
+
if (row2 === null || col === null || row2 < 1 || col < 1) return null;
|
|
2669
|
+
return cellRefFromRowCol(row2, col);
|
|
2670
|
+
}
|
|
2245
2671
|
function offset(a, c) {
|
|
2246
2672
|
const baseNode = a[0];
|
|
2247
2673
|
let baseRef;
|
|
@@ -2298,8 +2724,8 @@ function registerStatistical(r) {
|
|
|
2298
2724
|
}
|
|
2299
2725
|
function variance(nums, population) {
|
|
2300
2726
|
const mean = nums.reduce((s, n) => s + n, 0) / nums.length;
|
|
2301
|
-
const
|
|
2302
|
-
return
|
|
2727
|
+
const sumSq2 = nums.reduce((s, n) => s + (n - mean) ** 2, 0);
|
|
2728
|
+
return sumSq2 / (population ? nums.length : nums.length - 1);
|
|
2303
2729
|
}
|
|
2304
2730
|
function numsFromArray(v) {
|
|
2305
2731
|
if (v.isArray) {
|
|
@@ -2532,6 +2958,627 @@ function errorType(a, c) {
|
|
|
2532
2958
|
return FormulaValue.number(map[v.errorCode] ?? 7);
|
|
2533
2959
|
}
|
|
2534
2960
|
|
|
2961
|
+
// src/functions/finance-functions.ts
|
|
2962
|
+
function registerFinance(r) {
|
|
2963
|
+
r.register("PMT", pmt, 3, 5);
|
|
2964
|
+
r.register("FV", fv, 3, 5);
|
|
2965
|
+
r.register("PV", pv, 3, 5);
|
|
2966
|
+
r.register("NPV", npv, 2);
|
|
2967
|
+
r.register("IRR", irr, 1, 2);
|
|
2968
|
+
}
|
|
2969
|
+
function arg(a, c, idx, dflt) {
|
|
2970
|
+
if (idx >= a.length) return { ok: true, n: dflt };
|
|
2971
|
+
const r = FormulaHelper.evalDouble(a[idx], c);
|
|
2972
|
+
return r.ok ? { ok: true, n: r.value } : { ok: false, e: r.error };
|
|
2973
|
+
}
|
|
2974
|
+
function pmt(a, c) {
|
|
2975
|
+
const rate = arg(a, c, 0, 0);
|
|
2976
|
+
if (!rate.ok) return rate.e;
|
|
2977
|
+
const nper = arg(a, c, 1, 0);
|
|
2978
|
+
if (!nper.ok) return nper.e;
|
|
2979
|
+
const pvv = arg(a, c, 2, 0);
|
|
2980
|
+
if (!pvv.ok) return pvv.e;
|
|
2981
|
+
const fvv = arg(a, c, 3, 0);
|
|
2982
|
+
if (!fvv.ok) return fvv.e;
|
|
2983
|
+
const type = arg(a, c, 4, 0);
|
|
2984
|
+
if (!type.ok) return type.e;
|
|
2985
|
+
const r = rate.n, n = nper.n, present = pvv.n, future = fvv.n, t = type.n;
|
|
2986
|
+
if (n === 0) return FormulaValue.errorNum;
|
|
2987
|
+
if (r === 0) return FormulaValue.number(-(present + future) / n);
|
|
2988
|
+
const pow = Math.pow(1 + r, n);
|
|
2989
|
+
let payment = (-future - present * pow) * r / (pow - 1);
|
|
2990
|
+
if (t === 1) payment /= 1 + r;
|
|
2991
|
+
return FormulaValue.number(payment);
|
|
2992
|
+
}
|
|
2993
|
+
function fv(a, c) {
|
|
2994
|
+
const rate = arg(a, c, 0, 0);
|
|
2995
|
+
if (!rate.ok) return rate.e;
|
|
2996
|
+
const nper = arg(a, c, 1, 0);
|
|
2997
|
+
if (!nper.ok) return nper.e;
|
|
2998
|
+
const pmtv = arg(a, c, 2, 0);
|
|
2999
|
+
if (!pmtv.ok) return pmtv.e;
|
|
3000
|
+
const pvv = arg(a, c, 3, 0);
|
|
3001
|
+
if (!pvv.ok) return pvv.e;
|
|
3002
|
+
const type = arg(a, c, 4, 0);
|
|
3003
|
+
if (!type.ok) return type.e;
|
|
3004
|
+
const r = rate.n, n = nper.n, payment = pmtv.n, present = pvv.n, t = type.n;
|
|
3005
|
+
if (r === 0) return FormulaValue.number(-(present + payment * n));
|
|
3006
|
+
const pow = Math.pow(1 + r, n);
|
|
3007
|
+
return FormulaValue.number(-(present * pow + payment * (1 + r * t) * (pow - 1) / r));
|
|
3008
|
+
}
|
|
3009
|
+
function pv(a, c) {
|
|
3010
|
+
const rate = arg(a, c, 0, 0);
|
|
3011
|
+
if (!rate.ok) return rate.e;
|
|
3012
|
+
const nper = arg(a, c, 1, 0);
|
|
3013
|
+
if (!nper.ok) return nper.e;
|
|
3014
|
+
const pmtv = arg(a, c, 2, 0);
|
|
3015
|
+
if (!pmtv.ok) return pmtv.e;
|
|
3016
|
+
const fvv = arg(a, c, 3, 0);
|
|
3017
|
+
if (!fvv.ok) return fvv.e;
|
|
3018
|
+
const type = arg(a, c, 4, 0);
|
|
3019
|
+
if (!type.ok) return type.e;
|
|
3020
|
+
const r = rate.n, n = nper.n, payment = pmtv.n, future = fvv.n, t = type.n;
|
|
3021
|
+
if (r === 0) return FormulaValue.number(-(future + payment * n));
|
|
3022
|
+
const pow = Math.pow(1 + r, n);
|
|
3023
|
+
return FormulaValue.number(-(future + payment * (1 + r * t) * (pow - 1) / r) / pow);
|
|
3024
|
+
}
|
|
3025
|
+
function npv(a, c) {
|
|
3026
|
+
const rate = FormulaHelper.evalDouble(a[0], c);
|
|
3027
|
+
if (!rate.ok) return rate.error;
|
|
3028
|
+
if (rate.value === -1) return FormulaValue.errorDiv0;
|
|
3029
|
+
const flows = FormulaHelper.collectNumbers(a.slice(1), c);
|
|
3030
|
+
if (!flows.ok) return flows.error;
|
|
3031
|
+
let total = 0;
|
|
3032
|
+
for (let i = 0; i < flows.values.length; i++) {
|
|
3033
|
+
total += flows.values[i] / Math.pow(1 + rate.value, i + 1);
|
|
3034
|
+
}
|
|
3035
|
+
return FormulaValue.number(total);
|
|
3036
|
+
}
|
|
3037
|
+
function irr(a, c) {
|
|
3038
|
+
const flowsRes = FormulaHelper.collectNumbers([a[0]], c);
|
|
3039
|
+
if (!flowsRes.ok) return flowsRes.error;
|
|
3040
|
+
const flows = flowsRes.values;
|
|
3041
|
+
if (flows.length < 2) return FormulaValue.errorNum;
|
|
3042
|
+
let guess = 0.1;
|
|
3043
|
+
if (a.length > 1) {
|
|
3044
|
+
const g = FormulaHelper.evalDouble(a[1], c);
|
|
3045
|
+
if (!g.ok) return g.error;
|
|
3046
|
+
guess = g.value;
|
|
3047
|
+
}
|
|
3048
|
+
const npvAt = (rate2) => {
|
|
3049
|
+
let sum2 = 0;
|
|
3050
|
+
for (let i = 0; i < flows.length; i++) sum2 += flows[i] / Math.pow(1 + rate2, i);
|
|
3051
|
+
return sum2;
|
|
3052
|
+
};
|
|
3053
|
+
let rate = guess;
|
|
3054
|
+
for (let iter = 0; iter < 100; iter++) {
|
|
3055
|
+
const f = npvAt(rate);
|
|
3056
|
+
if (Math.abs(f) < 1e-7) return FormulaValue.number(rate);
|
|
3057
|
+
const h = 1e-6;
|
|
3058
|
+
const deriv = (npvAt(rate + h) - f) / h;
|
|
3059
|
+
if (deriv === 0) break;
|
|
3060
|
+
const next = rate - f / deriv;
|
|
3061
|
+
if (!isFinite(next) || next <= -1) break;
|
|
3062
|
+
if (Math.abs(next - rate) < 1e-9) {
|
|
3063
|
+
rate = next;
|
|
3064
|
+
break;
|
|
3065
|
+
}
|
|
3066
|
+
rate = next;
|
|
3067
|
+
}
|
|
3068
|
+
if (Math.abs(npvAt(rate)) < 1e-6) return FormulaValue.number(rate);
|
|
3069
|
+
return FormulaValue.errorNum;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
// src/functions/meta-functions.ts
|
|
3073
|
+
var MAX_TAIL_ITERATIONS = 1e7;
|
|
3074
|
+
function registerMeta(r) {
|
|
3075
|
+
r.register("LET", let_, 3);
|
|
3076
|
+
r.register("LAMBDA", lambda_, 1);
|
|
3077
|
+
r.register("MAP", map_, 2);
|
|
3078
|
+
r.register("REDUCE", reduce_, 3, 3);
|
|
3079
|
+
r.register("SCAN", scan_, 3, 3);
|
|
3080
|
+
r.register("BYROW", byRow, 2, 2);
|
|
3081
|
+
r.register("BYCOL", byCol, 2, 2);
|
|
3082
|
+
}
|
|
3083
|
+
var asArray = (v) => v.isArray ? v.arrayVal : ArrayValue.fromScalar(v);
|
|
3084
|
+
function let_(a, c) {
|
|
3085
|
+
if (a.length < 3 || a.length % 2 === 0) return FormulaValue.errorValue;
|
|
3086
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3087
|
+
c.pushScope(bindings);
|
|
3088
|
+
try {
|
|
3089
|
+
for (let i = 0; i < a.length - 1; i += 2) {
|
|
3090
|
+
const nameNode = a[i];
|
|
3091
|
+
if (!(nameNode instanceof NamedRangeNode)) return FormulaValue.errorValue;
|
|
3092
|
+
const value2 = a[i + 1].evaluate(c);
|
|
3093
|
+
if (value2.isError) return value2;
|
|
3094
|
+
bindings.set(nameNode.name.toUpperCase(), value2);
|
|
3095
|
+
}
|
|
3096
|
+
return a[a.length - 1].evaluate(c);
|
|
3097
|
+
} finally {
|
|
3098
|
+
c.popScope();
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
function lambda_(a, c) {
|
|
3102
|
+
const paramNodes = a.slice(0, -1);
|
|
3103
|
+
const body = a[a.length - 1];
|
|
3104
|
+
const params = [];
|
|
3105
|
+
for (const p of paramNodes) {
|
|
3106
|
+
if (!(p instanceof NamedRangeNode)) return FormulaValue.errorValue;
|
|
3107
|
+
params.push(p.name.toUpperCase());
|
|
3108
|
+
}
|
|
3109
|
+
const ctx = c;
|
|
3110
|
+
const lambda = {
|
|
3111
|
+
arity: params.length,
|
|
3112
|
+
// One tail-aware evaluation of the body: binds params, then evaluates in
|
|
3113
|
+
// tail mode so a self/other tail call surfaces as a TailCall.
|
|
3114
|
+
step(args) {
|
|
3115
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3116
|
+
params.forEach((p, i) => bindings.set(p, args[i] ?? FormulaValue.blank));
|
|
3117
|
+
ctx.pushScope(bindings);
|
|
3118
|
+
try {
|
|
3119
|
+
return body.evaluateTail(ctx);
|
|
3120
|
+
} finally {
|
|
3121
|
+
ctx.popScope();
|
|
3122
|
+
}
|
|
3123
|
+
},
|
|
3124
|
+
// Run to completion. Tail calls iterate here (O(1) stack); only non-tail
|
|
3125
|
+
// recursion re-enters call() and is bounded by the recursion guard.
|
|
3126
|
+
call(args) {
|
|
3127
|
+
const guard = ctx.recursionGuard;
|
|
3128
|
+
if (guard) {
|
|
3129
|
+
if (guard.depth >= guard.max) return FormulaValue.errorNum;
|
|
3130
|
+
guard.depth++;
|
|
3131
|
+
}
|
|
3132
|
+
try {
|
|
3133
|
+
let current = lambda;
|
|
3134
|
+
let currentArgs = args;
|
|
3135
|
+
for (let iter = 0; ; iter++) {
|
|
3136
|
+
if (iter > MAX_TAIL_ITERATIONS) return FormulaValue.errorNum;
|
|
3137
|
+
const r = current.step ? current.step(currentArgs) : current.call(currentArgs);
|
|
3138
|
+
if (r instanceof TailCall) {
|
|
3139
|
+
current = r.lambda;
|
|
3140
|
+
currentArgs = r.args;
|
|
3141
|
+
continue;
|
|
3142
|
+
}
|
|
3143
|
+
return r;
|
|
3144
|
+
}
|
|
3145
|
+
} finally {
|
|
3146
|
+
if (guard) guard.depth--;
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
};
|
|
3150
|
+
return FormulaValue.lambda(lambda);
|
|
3151
|
+
}
|
|
3152
|
+
function lambdaArg(node, c) {
|
|
3153
|
+
const v = node.evaluate(c);
|
|
3154
|
+
if (v.isError) return { ok: false, e: v };
|
|
3155
|
+
if (!v.isLambda) return { ok: false, e: FormulaValue.errorValue };
|
|
3156
|
+
return { ok: true, fn: v.lambdaVal };
|
|
3157
|
+
}
|
|
3158
|
+
function map_(a, c) {
|
|
3159
|
+
const fn = lambdaArg(a[a.length - 1], c);
|
|
3160
|
+
if (!fn.ok) return fn.e;
|
|
3161
|
+
const arrays = a.slice(0, -1).map((n) => asArray(n.evaluate(c)));
|
|
3162
|
+
if (arrays.length === 0) return FormulaValue.errorValue;
|
|
3163
|
+
const rows2 = arrays[0].rows, cols = arrays[0].columns;
|
|
3164
|
+
const out = new ArrayValue(rows2, cols);
|
|
3165
|
+
for (let r = 0; r < rows2; r++) {
|
|
3166
|
+
for (let col = 0; col < cols; col++) {
|
|
3167
|
+
out.set(r, col, fn.fn.call(arrays.map((ar) => ar.get(r, col))));
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
return FormulaValue.array(out);
|
|
3171
|
+
}
|
|
3172
|
+
function reduce_(a, c) {
|
|
3173
|
+
let acc = a[0].evaluate(c);
|
|
3174
|
+
if (acc.isError) return acc;
|
|
3175
|
+
const arr = asArray(a[1].evaluate(c));
|
|
3176
|
+
const fn = lambdaArg(a[2], c);
|
|
3177
|
+
if (!fn.ok) return fn.e;
|
|
3178
|
+
for (const v of arr.values()) {
|
|
3179
|
+
acc = fn.fn.call([acc, v]);
|
|
3180
|
+
if (acc.isError) return acc;
|
|
3181
|
+
}
|
|
3182
|
+
return acc;
|
|
3183
|
+
}
|
|
3184
|
+
function scan_(a, c) {
|
|
3185
|
+
let acc = a[0].evaluate(c);
|
|
3186
|
+
if (acc.isError) return acc;
|
|
3187
|
+
const arr = asArray(a[1].evaluate(c));
|
|
3188
|
+
const fn = lambdaArg(a[2], c);
|
|
3189
|
+
if (!fn.ok) return fn.e;
|
|
3190
|
+
const out = new ArrayValue(arr.rows, arr.columns);
|
|
3191
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
3192
|
+
for (let col = 0; col < arr.columns; col++) {
|
|
3193
|
+
acc = fn.fn.call([acc, arr.get(r, col)]);
|
|
3194
|
+
out.set(r, col, acc);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
return FormulaValue.array(out);
|
|
3198
|
+
}
|
|
3199
|
+
function byRow(a, c) {
|
|
3200
|
+
const arr = asArray(a[0].evaluate(c));
|
|
3201
|
+
const fn = lambdaArg(a[1], c);
|
|
3202
|
+
if (!fn.ok) return fn.e;
|
|
3203
|
+
const out = new ArrayValue(arr.rows, 1);
|
|
3204
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
3205
|
+
const row2 = new ArrayValue(1, arr.columns);
|
|
3206
|
+
for (let col = 0; col < arr.columns; col++) row2.set(0, col, arr.get(r, col));
|
|
3207
|
+
out.set(r, 0, fn.fn.call([FormulaValue.array(row2)]));
|
|
3208
|
+
}
|
|
3209
|
+
return FormulaValue.array(out);
|
|
3210
|
+
}
|
|
3211
|
+
function byCol(a, c) {
|
|
3212
|
+
const arr = asArray(a[0].evaluate(c));
|
|
3213
|
+
const fn = lambdaArg(a[1], c);
|
|
3214
|
+
if (!fn.ok) return fn.e;
|
|
3215
|
+
const out = new ArrayValue(1, arr.columns);
|
|
3216
|
+
for (let col = 0; col < arr.columns; col++) {
|
|
3217
|
+
const column2 = new ArrayValue(arr.rows, 1);
|
|
3218
|
+
for (let r = 0; r < arr.rows; r++) column2.set(r, 0, arr.get(r, col));
|
|
3219
|
+
out.set(0, col, fn.fn.call([FormulaValue.array(column2)]));
|
|
3220
|
+
}
|
|
3221
|
+
return FormulaValue.array(out);
|
|
3222
|
+
}
|
|
3223
|
+
function registerAggregate(r) {
|
|
3224
|
+
r.register("SUBTOTAL", subtotal, 2);
|
|
3225
|
+
r.register("AGGREGATE", aggregate, 3);
|
|
3226
|
+
}
|
|
3227
|
+
function collectValues(a, c, startIdx, opts) {
|
|
3228
|
+
const out = [];
|
|
3229
|
+
const ws = c.worksheet;
|
|
3230
|
+
for (let i = startIdx; i < a.length; i++) {
|
|
3231
|
+
const node = a[i];
|
|
3232
|
+
if (node instanceof RangeReferenceNode) {
|
|
3233
|
+
const b = c.getRangeBounds(node.startRef, node.endRef);
|
|
3234
|
+
for (let row2 = b.startRow; row2 <= b.endRow; row2++) {
|
|
3235
|
+
if (opts.excludeHidden && ws.isRowHidden?.(row2)) continue;
|
|
3236
|
+
for (let col = b.startCol; col <= b.endCol; col++) {
|
|
3237
|
+
const ref = cellRefFromRowCol(row2, col);
|
|
3238
|
+
if (opts.nestedPattern) {
|
|
3239
|
+
const f = ws.getCellFormula?.(ref);
|
|
3240
|
+
if (f && opts.nestedPattern.test(f)) continue;
|
|
3241
|
+
}
|
|
3242
|
+
const v = c.getCellValue(ref);
|
|
3243
|
+
if (v.isError) {
|
|
3244
|
+
if (opts.ignoreErrors) continue;
|
|
3245
|
+
return { ok: false, error: v };
|
|
3246
|
+
}
|
|
3247
|
+
out.push(v);
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
} else {
|
|
3251
|
+
const v = node.evaluate(c);
|
|
3252
|
+
if (v.isError) {
|
|
3253
|
+
if (opts.ignoreErrors) continue;
|
|
3254
|
+
return { ok: false, error: v };
|
|
3255
|
+
}
|
|
3256
|
+
if (v.isArray) {
|
|
3257
|
+
for (const item of v.arrayVal.values()) {
|
|
3258
|
+
if (item.isError) {
|
|
3259
|
+
if (opts.ignoreErrors) continue;
|
|
3260
|
+
return { ok: false, error: item };
|
|
3261
|
+
}
|
|
3262
|
+
out.push(item);
|
|
3263
|
+
}
|
|
3264
|
+
} else {
|
|
3265
|
+
out.push(v);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
return { ok: true, values: out };
|
|
3270
|
+
}
|
|
3271
|
+
function applyAggregate(fn, values, extra) {
|
|
3272
|
+
const nums = [];
|
|
3273
|
+
let countA2 = 0;
|
|
3274
|
+
for (const v of values) {
|
|
3275
|
+
if (!v.isBlank) countA2++;
|
|
3276
|
+
const r = v.tryAsDouble();
|
|
3277
|
+
if (r.ok && !v.isText && !v.isBlank) nums.push(r.value);
|
|
3278
|
+
}
|
|
3279
|
+
const sum2 = () => nums.reduce((s, n) => s + n, 0);
|
|
3280
|
+
const avg = () => nums.length ? sum2() / nums.length : NaN;
|
|
3281
|
+
const variance2 = (sample) => {
|
|
3282
|
+
const n = nums.length;
|
|
3283
|
+
if (sample ? n < 2 : n < 1) return NaN;
|
|
3284
|
+
const m = avg();
|
|
3285
|
+
const ss = nums.reduce((s, x) => s + (x - m) ** 2, 0);
|
|
3286
|
+
return ss / (sample ? n - 1 : n);
|
|
3287
|
+
};
|
|
3288
|
+
const sorted = () => [...nums].sort((x, y) => x - y);
|
|
3289
|
+
switch (fn) {
|
|
3290
|
+
case 1:
|
|
3291
|
+
return isNaN(avg()) ? FormulaValue.errorDiv0 : FormulaValue.number(avg());
|
|
3292
|
+
case 2:
|
|
3293
|
+
return FormulaValue.number(nums.length);
|
|
3294
|
+
// COUNT
|
|
3295
|
+
case 3:
|
|
3296
|
+
return FormulaValue.number(countA2);
|
|
3297
|
+
// COUNTA
|
|
3298
|
+
case 4:
|
|
3299
|
+
return nums.length ? FormulaValue.number(Math.max(...nums)) : FormulaValue.zero;
|
|
3300
|
+
case 5:
|
|
3301
|
+
return nums.length ? FormulaValue.number(Math.min(...nums)) : FormulaValue.zero;
|
|
3302
|
+
case 6:
|
|
3303
|
+
return FormulaValue.number(nums.reduce((p, n) => p * n, 1));
|
|
3304
|
+
// PRODUCT
|
|
3305
|
+
case 7: {
|
|
3306
|
+
const v = variance2(true);
|
|
3307
|
+
return isNaN(v) ? FormulaValue.errorDiv0 : FormulaValue.number(Math.sqrt(v));
|
|
3308
|
+
}
|
|
3309
|
+
// STDEV.S
|
|
3310
|
+
case 8: {
|
|
3311
|
+
const v = variance2(false);
|
|
3312
|
+
return isNaN(v) ? FormulaValue.errorDiv0 : FormulaValue.number(Math.sqrt(v));
|
|
3313
|
+
}
|
|
3314
|
+
// STDEV.P
|
|
3315
|
+
case 9:
|
|
3316
|
+
return FormulaValue.number(sum2());
|
|
3317
|
+
// SUM
|
|
3318
|
+
case 10: {
|
|
3319
|
+
const v = variance2(true);
|
|
3320
|
+
return isNaN(v) ? FormulaValue.errorDiv0 : FormulaValue.number(v);
|
|
3321
|
+
}
|
|
3322
|
+
// VAR.S
|
|
3323
|
+
case 11: {
|
|
3324
|
+
const v = variance2(false);
|
|
3325
|
+
return isNaN(v) ? FormulaValue.errorDiv0 : FormulaValue.number(v);
|
|
3326
|
+
}
|
|
3327
|
+
// VAR.P
|
|
3328
|
+
// AGGREGATE-only selectors:
|
|
3329
|
+
case 12: {
|
|
3330
|
+
const s = sorted();
|
|
3331
|
+
const m = s.length;
|
|
3332
|
+
if (!m) return FormulaValue.errorNum;
|
|
3333
|
+
const mid2 = Math.floor(m / 2);
|
|
3334
|
+
return FormulaValue.number(m % 2 ? s[mid2] : (s[mid2 - 1] + s[mid2]) / 2);
|
|
3335
|
+
}
|
|
3336
|
+
// MEDIAN
|
|
3337
|
+
case 14: {
|
|
3338
|
+
const s = sorted();
|
|
3339
|
+
const k = Math.round(extra ?? 0);
|
|
3340
|
+
return k >= 1 && k <= s.length ? FormulaValue.number(s[s.length - k]) : FormulaValue.errorNum;
|
|
3341
|
+
}
|
|
3342
|
+
case 15: {
|
|
3343
|
+
const s = sorted();
|
|
3344
|
+
const k = Math.round(extra ?? 0);
|
|
3345
|
+
return k >= 1 && k <= s.length ? FormulaValue.number(s[k - 1]) : FormulaValue.errorNum;
|
|
3346
|
+
}
|
|
3347
|
+
default:
|
|
3348
|
+
return FormulaValue.errorValue;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
function subtotal(a, c) {
|
|
3352
|
+
const fnVal = a[0].evaluate(c);
|
|
3353
|
+
if (fnVal.isError) return fnVal;
|
|
3354
|
+
const raw = fnVal.tryAsDouble();
|
|
3355
|
+
if (!raw.ok) return FormulaValue.errorValue;
|
|
3356
|
+
const code2 = Math.round(raw.value);
|
|
3357
|
+
const excludeHidden = code2 >= 101 && code2 <= 111;
|
|
3358
|
+
const fn = excludeHidden ? code2 - 100 : code2;
|
|
3359
|
+
if (fn < 1 || fn > 11) return FormulaValue.errorValue;
|
|
3360
|
+
const collected = collectValues(a, c, 1, {
|
|
3361
|
+
excludeHidden,
|
|
3362
|
+
nestedPattern: /^\s*SUBTOTAL\s*\(/i,
|
|
3363
|
+
ignoreErrors: false
|
|
3364
|
+
});
|
|
3365
|
+
if (!collected.ok) return collected.error;
|
|
3366
|
+
return applyAggregate(fn, collected.values);
|
|
3367
|
+
}
|
|
3368
|
+
function aggregate(a, c) {
|
|
3369
|
+
const fnVal = a[0].evaluate(c);
|
|
3370
|
+
if (fnVal.isError) return fnVal;
|
|
3371
|
+
const optVal = a[1].evaluate(c);
|
|
3372
|
+
if (optVal.isError) return optVal;
|
|
3373
|
+
const fnR = fnVal.tryAsDouble();
|
|
3374
|
+
const optR = optVal.tryAsDouble();
|
|
3375
|
+
if (!fnR.ok || !optR.ok) return FormulaValue.errorValue;
|
|
3376
|
+
const fn = Math.round(fnR.value);
|
|
3377
|
+
const option = Math.round(optR.value);
|
|
3378
|
+
if (fn < 1 || fn > 19) return FormulaValue.errorValue;
|
|
3379
|
+
const excludeHidden = option === 1 || option === 3 || option === 5 || option === 7;
|
|
3380
|
+
const ignoreErrors = option === 2 || option === 3 || option === 6 || option === 7;
|
|
3381
|
+
const ignoreNested = option <= 3 || option === 5 || option === 7;
|
|
3382
|
+
let extra;
|
|
3383
|
+
let endIdx = a.length;
|
|
3384
|
+
if (fn === 14 || fn === 15) {
|
|
3385
|
+
const kVal = a[a.length - 1].evaluate(c);
|
|
3386
|
+
const kR = kVal.tryAsDouble();
|
|
3387
|
+
if (!kR.ok) return FormulaValue.errorValue;
|
|
3388
|
+
extra = kR.value;
|
|
3389
|
+
endIdx = a.length - 1;
|
|
3390
|
+
}
|
|
3391
|
+
const collected = collectValues(a.slice(0, endIdx), c, 2, {
|
|
3392
|
+
excludeHidden,
|
|
3393
|
+
nestedPattern: ignoreNested ? /^\s*(SUBTOTAL|AGGREGATE)\s*\(/i : null,
|
|
3394
|
+
ignoreErrors
|
|
3395
|
+
});
|
|
3396
|
+
if (!collected.ok) return collected.error;
|
|
3397
|
+
return applyAggregate(fn, collected.values, extra);
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
// src/functions/array-functions.ts
|
|
3401
|
+
function registerArray(r) {
|
|
3402
|
+
r.register("TRANSPOSE", transpose, 1, 1);
|
|
3403
|
+
r.register("SEQUENCE", sequence, 1, 4);
|
|
3404
|
+
r.register("UNIQUE", unique, 1, 3);
|
|
3405
|
+
r.register("SORT", sort, 1, 4);
|
|
3406
|
+
r.register("FILTER", filter, 2, 3);
|
|
3407
|
+
r.register("MMULT", mmult, 2, 2);
|
|
3408
|
+
r.register("TEXTSPLIT", textSplit, 2, 6);
|
|
3409
|
+
}
|
|
3410
|
+
var asArray2 = (v) => v.isArray ? v.arrayVal : ArrayValue.fromScalar(v);
|
|
3411
|
+
function fromRows(rows2) {
|
|
3412
|
+
const nr = rows2.length;
|
|
3413
|
+
const nc = nr > 0 ? rows2[0].length : 0;
|
|
3414
|
+
const arr = new ArrayValue(nr, nc);
|
|
3415
|
+
for (let r = 0; r < nr; r++) for (let c = 0; c < nc; c++) arr.set(r, c, rows2[r][c]);
|
|
3416
|
+
return FormulaValue.array(arr);
|
|
3417
|
+
}
|
|
3418
|
+
function toRows(arr) {
|
|
3419
|
+
const rows2 = [];
|
|
3420
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
3421
|
+
const row2 = [];
|
|
3422
|
+
for (let c = 0; c < arr.columns; c++) row2.push(arr.get(r, c));
|
|
3423
|
+
rows2.push(row2);
|
|
3424
|
+
}
|
|
3425
|
+
return rows2;
|
|
3426
|
+
}
|
|
3427
|
+
var num4 = (a, c, i, dflt) => {
|
|
3428
|
+
if (i >= a.length) return { ok: true, n: dflt };
|
|
3429
|
+
const r = FormulaHelper.evalDouble(a[i], c);
|
|
3430
|
+
return r.ok ? { ok: true, n: r.value } : { ok: false, e: r.error };
|
|
3431
|
+
};
|
|
3432
|
+
function transpose(a, c) {
|
|
3433
|
+
const v = a[0].evaluate(c);
|
|
3434
|
+
if (v.isError) return v;
|
|
3435
|
+
return FormulaValue.array(asArray2(v).transpose());
|
|
3436
|
+
}
|
|
3437
|
+
function sequence(a, c) {
|
|
3438
|
+
const rows2 = num4(a, c, 0, 1);
|
|
3439
|
+
if (!rows2.ok) return rows2.e;
|
|
3440
|
+
const cols = num4(a, c, 1, 1);
|
|
3441
|
+
if (!cols.ok) return cols.e;
|
|
3442
|
+
const start = num4(a, c, 2, 1);
|
|
3443
|
+
if (!start.ok) return start.e;
|
|
3444
|
+
const step = num4(a, c, 3, 1);
|
|
3445
|
+
if (!step.ok) return step.e;
|
|
3446
|
+
const R = Math.trunc(rows2.n), C = Math.trunc(cols.n);
|
|
3447
|
+
if (R < 1 || C < 1) return FormulaValue.errorValue;
|
|
3448
|
+
const arr = new ArrayValue(R, C);
|
|
3449
|
+
for (let r = 0; r < R; r++) {
|
|
3450
|
+
for (let col = 0; col < C; col++) {
|
|
3451
|
+
arr.set(r, col, FormulaValue.number(start.n + (r * C + col) * step.n));
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
return FormulaValue.array(arr);
|
|
3455
|
+
}
|
|
3456
|
+
function rowsEqual(x, y) {
|
|
3457
|
+
if (x.length !== y.length) return false;
|
|
3458
|
+
for (let i = 0; i < x.length; i++) if (!FormulaValue.areEqual(x[i], y[i])) return false;
|
|
3459
|
+
return true;
|
|
3460
|
+
}
|
|
3461
|
+
function unique(a, c) {
|
|
3462
|
+
const v = a[0].evaluate(c);
|
|
3463
|
+
if (v.isError) return v;
|
|
3464
|
+
const byCol2 = a.length > 1 ? a[1].evaluate(c).coerceToBool().booleanValue : false;
|
|
3465
|
+
const exactlyOnce = a.length > 2 ? a[2].evaluate(c).coerceToBool().booleanValue : false;
|
|
3466
|
+
let rows2 = toRows(byCol2 ? asArray2(v).transpose() : asArray2(v));
|
|
3467
|
+
const counts = rows2.map((r) => rows2.filter((o) => rowsEqual(o, r)).length);
|
|
3468
|
+
const seen = [];
|
|
3469
|
+
const result = [];
|
|
3470
|
+
rows2.forEach((r, i) => {
|
|
3471
|
+
if (seen.some((o) => rowsEqual(o, r))) return;
|
|
3472
|
+
seen.push(r);
|
|
3473
|
+
if (exactlyOnce && counts[i] > 1) return;
|
|
3474
|
+
result.push(r);
|
|
3475
|
+
});
|
|
3476
|
+
if (result.length === 0) return FormulaValue.error(8 /* Calc */);
|
|
3477
|
+
const out = fromRows(result);
|
|
3478
|
+
return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
|
|
3479
|
+
}
|
|
3480
|
+
function sort(a, c) {
|
|
3481
|
+
const v = a[0].evaluate(c);
|
|
3482
|
+
if (v.isError) return v;
|
|
3483
|
+
const sortIndex = num4(a, c, 1, 1);
|
|
3484
|
+
if (!sortIndex.ok) return sortIndex.e;
|
|
3485
|
+
const sortOrder = num4(a, c, 2, 1);
|
|
3486
|
+
if (!sortOrder.ok) return sortOrder.e;
|
|
3487
|
+
const byCol2 = a.length > 3 ? a[3].evaluate(c).coerceToBool().booleanValue : false;
|
|
3488
|
+
const base = byCol2 ? asArray2(v).transpose() : asArray2(v);
|
|
3489
|
+
const rows2 = toRows(base);
|
|
3490
|
+
const idx = Math.trunc(sortIndex.n) - 1;
|
|
3491
|
+
const dir = sortOrder.n < 0 ? -1 : 1;
|
|
3492
|
+
if (rows2.length > 0 && (idx < 0 || idx >= rows2[0].length)) return FormulaValue.errorValue;
|
|
3493
|
+
const sorted = rows2.map((r, i) => ({ r, i })).sort((x, y) => {
|
|
3494
|
+
const cmp = FormulaValue.compare(x.r[idx], y.r[idx]) * dir;
|
|
3495
|
+
return cmp !== 0 ? cmp : x.i - y.i;
|
|
3496
|
+
}).map((o) => o.r);
|
|
3497
|
+
const out = fromRows(sorted);
|
|
3498
|
+
return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
|
|
3499
|
+
}
|
|
3500
|
+
function filter(a, c) {
|
|
3501
|
+
const v = a[0].evaluate(c);
|
|
3502
|
+
if (v.isError) return v;
|
|
3503
|
+
const inc = a[1].evaluate(c);
|
|
3504
|
+
if (inc.isError) return inc;
|
|
3505
|
+
const arr = asArray2(v);
|
|
3506
|
+
const incArr = asArray2(inc);
|
|
3507
|
+
const perRow = incArr.rows === arr.rows && incArr.columns === 1;
|
|
3508
|
+
const perCol = incArr.columns === arr.columns && incArr.rows === 1;
|
|
3509
|
+
if (!perRow && !perCol) return FormulaValue.errorValue;
|
|
3510
|
+
const keptRows = [];
|
|
3511
|
+
if (perRow) {
|
|
3512
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
3513
|
+
if (!incArr.get(r, 0).coerceToBool().booleanValue) continue;
|
|
3514
|
+
const row2 = [];
|
|
3515
|
+
for (let col = 0; col < arr.columns; col++) row2.push(arr.get(r, col));
|
|
3516
|
+
keptRows.push(row2);
|
|
3517
|
+
}
|
|
3518
|
+
} else {
|
|
3519
|
+
const keepCols = [];
|
|
3520
|
+
for (let col = 0; col < arr.columns; col++) {
|
|
3521
|
+
if (incArr.get(0, col).coerceToBool().booleanValue) keepCols.push(col);
|
|
3522
|
+
}
|
|
3523
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
3524
|
+
keptRows.push(keepCols.map((col) => arr.get(r, col)));
|
|
3525
|
+
}
|
|
3526
|
+
if (keepCols.length === 0) keptRows.length = 0;
|
|
3527
|
+
}
|
|
3528
|
+
if (keptRows.length === 0 || keptRows[0] && keptRows[0].length === 0) {
|
|
3529
|
+
return a.length > 2 ? a[2].evaluate(c) : FormulaValue.error(8 /* Calc */);
|
|
3530
|
+
}
|
|
3531
|
+
return fromRows(keptRows);
|
|
3532
|
+
}
|
|
3533
|
+
function mmult(a, c) {
|
|
3534
|
+
const av = a[0].evaluate(c);
|
|
3535
|
+
if (av.isError) return av;
|
|
3536
|
+
const bv = a[1].evaluate(c);
|
|
3537
|
+
if (bv.isError) return bv;
|
|
3538
|
+
const A = asArray2(av), B = asArray2(bv);
|
|
3539
|
+
if (A.columns !== B.rows) return FormulaValue.errorValue;
|
|
3540
|
+
const out = new ArrayValue(A.rows, B.columns);
|
|
3541
|
+
for (let i = 0; i < A.rows; i++) {
|
|
3542
|
+
for (let j = 0; j < B.columns; j++) {
|
|
3543
|
+
let sum2 = 0;
|
|
3544
|
+
for (let k = 0; k < A.columns; k++) {
|
|
3545
|
+
const x = A.get(i, k).tryAsDouble();
|
|
3546
|
+
const y = B.get(k, j).tryAsDouble();
|
|
3547
|
+
if (!x.ok || !y.ok) return FormulaValue.errorValue;
|
|
3548
|
+
sum2 += x.value * y.value;
|
|
3549
|
+
}
|
|
3550
|
+
out.set(i, j, FormulaValue.number(sum2));
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
return FormulaValue.array(out);
|
|
3554
|
+
}
|
|
3555
|
+
function textSplit(a, c) {
|
|
3556
|
+
const t = FormulaHelper.evalString(a[0], c);
|
|
3557
|
+
if (!t.ok) return t.error;
|
|
3558
|
+
const colD = FormulaHelper.evalString(a[1], c);
|
|
3559
|
+
if (!colD.ok) return colD.error;
|
|
3560
|
+
let rowD = "";
|
|
3561
|
+
if (a.length > 2) {
|
|
3562
|
+
const rd = a[2].evaluate(c);
|
|
3563
|
+
if (rd.isError) return rd;
|
|
3564
|
+
if (!rd.isBlank) {
|
|
3565
|
+
const r = FormulaHelper.evalString(a[2], c);
|
|
3566
|
+
if (!r.ok) return r.error;
|
|
3567
|
+
rowD = r.value;
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
const split = (s, d) => d === "" ? [s] : s.split(d);
|
|
3571
|
+
const lines = rowD ? split(t.value, rowD) : [t.value];
|
|
3572
|
+
const grid = lines.map((line) => split(line, colD.value));
|
|
3573
|
+
const width = grid.reduce((m, r) => Math.max(m, r.length), 0);
|
|
3574
|
+
const rows2 = grid.map((r) => {
|
|
3575
|
+
const row2 = [];
|
|
3576
|
+
for (let i = 0; i < width; i++) row2.push(FormulaValue.text(r[i] ?? ""));
|
|
3577
|
+
return row2;
|
|
3578
|
+
});
|
|
3579
|
+
return fromRows(rows2);
|
|
3580
|
+
}
|
|
3581
|
+
|
|
2535
3582
|
// src/function-registry.ts
|
|
2536
3583
|
var _default, _functions, _FunctionRegistry_instances, registerBuiltIns_fn;
|
|
2537
3584
|
var _FunctionRegistry = class _FunctionRegistry {
|
|
@@ -2590,6 +3637,10 @@ registerBuiltIns_fn = function() {
|
|
|
2590
3637
|
registerLookup(this);
|
|
2591
3638
|
registerStatistical(this);
|
|
2592
3639
|
registerInfo(this);
|
|
3640
|
+
registerFinance(this);
|
|
3641
|
+
registerMeta(this);
|
|
3642
|
+
registerAggregate(this);
|
|
3643
|
+
registerArray(this);
|
|
2593
3644
|
};
|
|
2594
3645
|
__privateAdd(_FunctionRegistry, _default);
|
|
2595
3646
|
var FunctionRegistry = _FunctionRegistry;
|
|
@@ -2611,8 +3662,8 @@ var FormulaHelper;
|
|
|
2611
3662
|
FormulaHelper2.evalString = evalString;
|
|
2612
3663
|
function collectNumbers(args, ctx) {
|
|
2613
3664
|
const nums = [];
|
|
2614
|
-
for (const
|
|
2615
|
-
const v =
|
|
3665
|
+
for (const arg2 of args) {
|
|
3666
|
+
const v = arg2.evaluate(ctx);
|
|
2616
3667
|
if (v.isError) return { ok: false, error: v };
|
|
2617
3668
|
if (v.isArray) {
|
|
2618
3669
|
for (const item of v.arrayVal.values()) {
|
|
@@ -2632,8 +3683,8 @@ var FormulaHelper;
|
|
|
2632
3683
|
FormulaHelper2.collectNumbers = collectNumbers;
|
|
2633
3684
|
function flattenArgs(args, ctx) {
|
|
2634
3685
|
const result = [];
|
|
2635
|
-
for (const
|
|
2636
|
-
const v =
|
|
3686
|
+
for (const arg2 of args) {
|
|
3687
|
+
const v = arg2.evaluate(ctx);
|
|
2637
3688
|
if (v.isArray) {
|
|
2638
3689
|
for (const item of v.arrayVal.values()) result.push(item);
|
|
2639
3690
|
} else {
|
|
@@ -2671,9 +3722,9 @@ var FormulaHelper;
|
|
|
2671
3722
|
const pattern = wildcardToRegex(criteria);
|
|
2672
3723
|
return pattern.test(value2.asText());
|
|
2673
3724
|
}
|
|
2674
|
-
const
|
|
2675
|
-
if (!isNaN(
|
|
2676
|
-
return FormulaValue.areEqual(value2, FormulaValue.number(
|
|
3725
|
+
const num5 = parseFloat(criteria);
|
|
3726
|
+
if (!isNaN(num5)) {
|
|
3727
|
+
return FormulaValue.areEqual(value2, FormulaValue.number(num5));
|
|
2677
3728
|
}
|
|
2678
3729
|
return value2.asText().toUpperCase() === criteria.toUpperCase();
|
|
2679
3730
|
}
|
|
@@ -2685,18 +3736,33 @@ var FormulaHelper;
|
|
|
2685
3736
|
})(FormulaHelper || (FormulaHelper = {}));
|
|
2686
3737
|
|
|
2687
3738
|
// src/formula-context.ts
|
|
2688
|
-
var _sheet, _functions2, _evaluating;
|
|
3739
|
+
var _sheet, _functions2, _evaluating, _scopes, _FormulaContext_instances, lookupScope_fn;
|
|
2689
3740
|
var _FormulaContext = class _FormulaContext {
|
|
2690
3741
|
constructor(sheet, formulaRow = 0, formulaCol = 0, evaluating, registry) {
|
|
3742
|
+
__privateAdd(this, _FormulaContext_instances);
|
|
2691
3743
|
__privateAdd(this, _sheet);
|
|
2692
3744
|
__privateAdd(this, _functions2);
|
|
2693
3745
|
__privateAdd(this, _evaluating);
|
|
3746
|
+
/**
|
|
3747
|
+
* Lexical scope stack for LET/LAMBDA local names. Each frame maps an
|
|
3748
|
+
* upper-cased local name to its value. Names resolve innermost-first and take
|
|
3749
|
+
* precedence over workbook defined names.
|
|
3750
|
+
*/
|
|
3751
|
+
__privateAdd(this, _scopes, []);
|
|
2694
3752
|
__privateSet(this, _sheet, sheet);
|
|
2695
3753
|
this.formulaRow = formulaRow;
|
|
2696
3754
|
this.formulaCol = formulaCol;
|
|
2697
3755
|
__privateSet(this, _evaluating, evaluating ?? /* @__PURE__ */ new Set());
|
|
2698
3756
|
__privateSet(this, _functions2, registry ?? FunctionRegistry.default);
|
|
2699
3757
|
}
|
|
3758
|
+
/** Pushes a new binding frame (used by LET/LAMBDA). */
|
|
3759
|
+
pushScope(bindings) {
|
|
3760
|
+
__privateGet(this, _scopes).push(bindings);
|
|
3761
|
+
}
|
|
3762
|
+
/** Pops the innermost binding frame. */
|
|
3763
|
+
popScope() {
|
|
3764
|
+
__privateGet(this, _scopes).pop();
|
|
3765
|
+
}
|
|
2700
3766
|
get worksheet() {
|
|
2701
3767
|
return __privateGet(this, _sheet);
|
|
2702
3768
|
}
|
|
@@ -2748,8 +3814,21 @@ var _FormulaContext = class _FormulaContext {
|
|
|
2748
3814
|
return FormulaValue.errorRef;
|
|
2749
3815
|
}
|
|
2750
3816
|
}
|
|
2751
|
-
resolveNamedRange(
|
|
2752
|
-
|
|
3817
|
+
resolveNamedRange(name) {
|
|
3818
|
+
const scoped = __privateMethod(this, _FormulaContext_instances, lookupScope_fn).call(this, name);
|
|
3819
|
+
if (scoped !== void 0) return scoped;
|
|
3820
|
+
return this.nameResolver ? this.nameResolver(name) : FormulaValue.errorName;
|
|
3821
|
+
}
|
|
3822
|
+
/**
|
|
3823
|
+
* Resolves a spill-range reference (A1#) to the array spilling from the anchor
|
|
3824
|
+
* cell. Requires the worksheet to expose getSpillRange; otherwise #REF!.
|
|
3825
|
+
*/
|
|
3826
|
+
resolveSpillRange(anchorRef) {
|
|
3827
|
+
const spillRef = __privateGet(this, _sheet).getSpillRange?.(anchorRef);
|
|
3828
|
+
if (!spillRef) return FormulaValue.errorRef;
|
|
3829
|
+
const colon = spillRef.indexOf(":");
|
|
3830
|
+
if (colon < 0) return this.getCellValue(spillRef);
|
|
3831
|
+
return this.getRangeValues(spillRef.slice(0, colon), spillRef.slice(colon + 1));
|
|
2753
3832
|
}
|
|
2754
3833
|
getRangeBounds(startRef, endRef) {
|
|
2755
3834
|
const s = parseCellRef(startRef);
|
|
@@ -2757,13 +3836,34 @@ var _FormulaContext = class _FormulaContext {
|
|
|
2757
3836
|
return { startRow: s.row, startCol: s.column, endRow: e.row, endCol: e.column };
|
|
2758
3837
|
}
|
|
2759
3838
|
// ── Function dispatch ─────────────────────────────────────────────────────
|
|
3839
|
+
/** True if a built-in function with this name is registered. */
|
|
3840
|
+
hasFunction(name) {
|
|
3841
|
+
return __privateGet(this, _functions2).has(name);
|
|
3842
|
+
}
|
|
2760
3843
|
evaluateFunction(name, args) {
|
|
3844
|
+
if (!__privateGet(this, _functions2).has(name)) {
|
|
3845
|
+
const named = this.resolveNamedRange(name);
|
|
3846
|
+
if (named.isLambda) {
|
|
3847
|
+
return named.lambdaVal.call(args.map((a) => a.evaluate(this)));
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
2761
3850
|
return __privateGet(this, _functions2).execute(name, args, this);
|
|
2762
3851
|
}
|
|
2763
3852
|
};
|
|
2764
3853
|
_sheet = new WeakMap();
|
|
2765
3854
|
_functions2 = new WeakMap();
|
|
2766
3855
|
_evaluating = new WeakMap();
|
|
3856
|
+
_scopes = new WeakMap();
|
|
3857
|
+
_FormulaContext_instances = new WeakSet();
|
|
3858
|
+
lookupScope_fn = function(name) {
|
|
3859
|
+
if (__privateGet(this, _scopes).length === 0) return void 0;
|
|
3860
|
+
const key = name.toUpperCase();
|
|
3861
|
+
for (let i = __privateGet(this, _scopes).length - 1; i >= 0; i--) {
|
|
3862
|
+
const v = __privateGet(this, _scopes)[i].get(key);
|
|
3863
|
+
if (v !== void 0) return v;
|
|
3864
|
+
}
|
|
3865
|
+
return void 0;
|
|
3866
|
+
};
|
|
2767
3867
|
var FormulaContext = _FormulaContext;
|
|
2768
3868
|
|
|
2769
3869
|
// src/lru-cache.ts
|
|
@@ -2870,16 +3970,63 @@ function parseFormula(formula) {
|
|
|
2870
3970
|
astCache.set(stripped, ast);
|
|
2871
3971
|
return ast;
|
|
2872
3972
|
}
|
|
3973
|
+
var DEFAULT_LAMBDA_DEPTH = 1024;
|
|
3974
|
+
function buildContext(sheet, opts) {
|
|
3975
|
+
const evaluating = opts.evaluating ?? /* @__PURE__ */ new Set();
|
|
3976
|
+
const ctx = new FormulaContext(
|
|
3977
|
+
sheet,
|
|
3978
|
+
opts.formulaRow ?? 0,
|
|
3979
|
+
opts.formulaCol ?? 0,
|
|
3980
|
+
evaluating
|
|
3981
|
+
);
|
|
3982
|
+
ctx.recursionGuard = opts.recursionGuard ?? { depth: 0, max: DEFAULT_LAMBDA_DEPTH };
|
|
3983
|
+
const wb = opts.workbook;
|
|
3984
|
+
if (wb) {
|
|
3985
|
+
ctx.workbook = wb;
|
|
3986
|
+
if (typeof wb.getDefinedName === "function") {
|
|
3987
|
+
const resolving = opts.resolvingNames ?? /* @__PURE__ */ new Set();
|
|
3988
|
+
const resolved = opts.resolvedNames ?? /* @__PURE__ */ new Map();
|
|
3989
|
+
const guard = ctx.recursionGuard;
|
|
3990
|
+
ctx.nameResolver = (name) => resolveName(name, sheet, wb, evaluating, resolving, resolved, guard);
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
return ctx;
|
|
3994
|
+
}
|
|
3995
|
+
function resolveName(name, sheet, workbook, evaluating, resolving, resolved, recursionGuard) {
|
|
3996
|
+
const key = `${sheet.name}\0${name.toUpperCase()}`;
|
|
3997
|
+
const cached = resolved.get(key);
|
|
3998
|
+
if (cached !== void 0) return cached;
|
|
3999
|
+
if (resolving.has(key)) return FormulaValue.errorRef;
|
|
4000
|
+
const refersTo = workbook.getDefinedName(name, sheet.name);
|
|
4001
|
+
if (refersTo == null || refersTo === "") return FormulaValue.errorName;
|
|
4002
|
+
resolving.add(key);
|
|
4003
|
+
try {
|
|
4004
|
+
const ast = parseFormula(refersTo);
|
|
4005
|
+
const ctx = buildContext(sheet, {
|
|
4006
|
+
workbook,
|
|
4007
|
+
evaluating,
|
|
4008
|
+
resolvingNames: resolving,
|
|
4009
|
+
resolvedNames: resolved,
|
|
4010
|
+
recursionGuard
|
|
4011
|
+
});
|
|
4012
|
+
const value2 = ast.evaluate(ctx);
|
|
4013
|
+
if (value2.isLambda) resolved.set(key, value2);
|
|
4014
|
+
return value2;
|
|
4015
|
+
} catch {
|
|
4016
|
+
return FormulaValue.errorName;
|
|
4017
|
+
} finally {
|
|
4018
|
+
resolving.delete(key);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
2873
4021
|
function evaluateFormula(formula, sheet, options) {
|
|
2874
4022
|
if (!formula) return FormulaValue.blank;
|
|
2875
4023
|
try {
|
|
2876
4024
|
const ast = parseFormula(formula);
|
|
2877
|
-
const ctx =
|
|
2878
|
-
|
|
2879
|
-
options?.formulaRow
|
|
2880
|
-
options?.formulaCol
|
|
2881
|
-
);
|
|
2882
|
-
if (options?.workbook) ctx.workbook = options.workbook;
|
|
4025
|
+
const ctx = buildContext(sheet, {
|
|
4026
|
+
workbook: options?.workbook,
|
|
4027
|
+
formulaRow: options?.formulaRow,
|
|
4028
|
+
formulaCol: options?.formulaCol
|
|
4029
|
+
});
|
|
2883
4030
|
return ast.evaluate(ctx);
|
|
2884
4031
|
} catch {
|
|
2885
4032
|
return FormulaValue.errorValue;
|
|
@@ -2899,7 +4046,439 @@ function getAstCacheStats() {
|
|
|
2899
4046
|
function setAstCacheCapacity(capacity) {
|
|
2900
4047
|
astCache.resize(capacity);
|
|
2901
4048
|
}
|
|
4049
|
+
function extractReferences(ast, formulaSheet, registry = FunctionRegistry.default, resolveName2) {
|
|
4050
|
+
const refs = [];
|
|
4051
|
+
let volatile = false;
|
|
4052
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
4053
|
+
const cell = (sheet, ref) => {
|
|
4054
|
+
const { row: row2, column: column2 } = parseCellRef(ref);
|
|
4055
|
+
return { sheet: sheet.toUpperCase(), startRow: row2, startCol: column2, endRow: row2, endCol: column2 };
|
|
4056
|
+
};
|
|
4057
|
+
const range = (sheet, startRef, endRef) => {
|
|
4058
|
+
const s = parseCellRef(startRef);
|
|
4059
|
+
const e = parseCellRef(endRef);
|
|
4060
|
+
return {
|
|
4061
|
+
sheet: sheet.toUpperCase(),
|
|
4062
|
+
startRow: Math.min(s.row, e.row),
|
|
4063
|
+
startCol: Math.min(s.column, e.column),
|
|
4064
|
+
endRow: Math.max(s.row, e.row),
|
|
4065
|
+
endCol: Math.max(s.column, e.column)
|
|
4066
|
+
};
|
|
4067
|
+
};
|
|
4068
|
+
const walk = (node) => {
|
|
4069
|
+
if (node instanceof CellReferenceNode) {
|
|
4070
|
+
refs.push(cell(formulaSheet, node.reference));
|
|
4071
|
+
} else if (node instanceof RangeReferenceNode) {
|
|
4072
|
+
refs.push(range(formulaSheet, node.startRef, node.endRef));
|
|
4073
|
+
} else if (node instanceof SheetCellReferenceNode) {
|
|
4074
|
+
refs.push(cell(node.sheetName, node.cellReference));
|
|
4075
|
+
} else if (node instanceof SheetRangeReferenceNode) {
|
|
4076
|
+
refs.push(range(node.sheetName, node.startRef, node.endRef));
|
|
4077
|
+
} else if (node instanceof NamedRangeNode) {
|
|
4078
|
+
expandName(node.name);
|
|
4079
|
+
} else if (node instanceof SpillReferenceNode) {
|
|
4080
|
+
refs.push(cell(formulaSheet, node.anchorRef));
|
|
4081
|
+
} else if (node instanceof FunctionCallNode) {
|
|
4082
|
+
if (registry.isVolatile(node.functionName)) volatile = true;
|
|
4083
|
+
for (const arg2 of node.args) walk(arg2);
|
|
4084
|
+
} else if (node instanceof BinaryOpNode) {
|
|
4085
|
+
walk(node.left);
|
|
4086
|
+
walk(node.right);
|
|
4087
|
+
} else if (node instanceof UnaryOpNode) {
|
|
4088
|
+
walk(node.operand);
|
|
4089
|
+
} else if (node instanceof ImplicitIntersectionNode) {
|
|
4090
|
+
walk(node.inner);
|
|
4091
|
+
}
|
|
4092
|
+
};
|
|
4093
|
+
const expandName = (name) => {
|
|
4094
|
+
if (!resolveName2) return;
|
|
4095
|
+
const key = name.toUpperCase();
|
|
4096
|
+
if (seenNames.has(key)) return;
|
|
4097
|
+
seenNames.add(key);
|
|
4098
|
+
const refersTo = resolveName2(name, formulaSheet);
|
|
4099
|
+
if (!refersTo) return;
|
|
4100
|
+
try {
|
|
4101
|
+
walk(parseFormula(refersTo));
|
|
4102
|
+
} catch {
|
|
4103
|
+
}
|
|
4104
|
+
};
|
|
4105
|
+
walk(ast);
|
|
4106
|
+
return { refs, volatile };
|
|
4107
|
+
}
|
|
4108
|
+
function refContains(ref, sheet, row2, col) {
|
|
4109
|
+
return ref.sheet === sheet && row2 >= ref.startRow && row2 <= ref.endRow && col >= ref.startCol && col <= ref.endCol;
|
|
4110
|
+
}
|
|
4111
|
+
|
|
4112
|
+
// src/recalc-engine.ts
|
|
4113
|
+
function createWorkbookRecalcModel(workbook) {
|
|
4114
|
+
return {
|
|
4115
|
+
getCellValue: (sheet, ref) => workbook.getWorksheet(sheet).getCellValue(ref),
|
|
4116
|
+
setCellValue: (sheet, ref, value2) => {
|
|
4117
|
+
workbook.getWorksheet(sheet).getCell(ref).setComputedValue(value2);
|
|
4118
|
+
},
|
|
4119
|
+
setCellFormula: (sheet, ref, formula) => {
|
|
4120
|
+
workbook.getWorksheet(sheet).getCell(ref).setFormula(formula);
|
|
4121
|
+
},
|
|
4122
|
+
clearCell: (sheet, ref) => {
|
|
4123
|
+
workbook.getWorksheet(sheet).getCell(ref).clear();
|
|
4124
|
+
},
|
|
4125
|
+
setCellArrayRef: (sheet, ref, spillRef) => {
|
|
4126
|
+
workbook.getWorksheet(sheet).getCell(ref).setArrayRef(spillRef);
|
|
4127
|
+
},
|
|
4128
|
+
getCellFormula: (sheet, ref) => workbook.getWorksheet(sheet).getCellFormula?.(ref) ?? null,
|
|
4129
|
+
getSpillRange: (sheet, ref) => workbook.getWorksheet(sheet).getSpillRange?.(ref) ?? null,
|
|
4130
|
+
isRowHidden: (sheet, row2) => workbook.getWorksheet(sheet).isRowHidden?.(row2) ?? false,
|
|
4131
|
+
getDefinedName: (name, sheet) => workbook.getDefinedName(name, sheet)
|
|
4132
|
+
};
|
|
4133
|
+
}
|
|
4134
|
+
var normSheet = (s) => s.toUpperCase();
|
|
4135
|
+
var normRef = (r) => r.replace(/\$/g, "").toUpperCase();
|
|
4136
|
+
var keyOf = (sheet, ref) => `${normSheet(sheet)}!${normRef(ref)}`;
|
|
4137
|
+
function parseKey(key) {
|
|
4138
|
+
const bang = key.indexOf("!");
|
|
4139
|
+
const sheet = key.slice(0, bang);
|
|
4140
|
+
const ref = key.slice(bang + 1);
|
|
4141
|
+
const { row: row2, column: column2 } = parseCellRef(ref);
|
|
4142
|
+
return { sheet, ref, row: row2, col: column2 };
|
|
4143
|
+
}
|
|
4144
|
+
var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _RecalcEngine_instances, rememberSheet_fn, recalcFrom_fn, dependentClosure_fn, directDependents_fn, evaluatePass_fn, writeResult_fn, isOccupied_fn, clearSpill_fn, evaluateFormulaValue_fn, sheetLike_fn, buildWorkbookLike_fn;
|
|
4145
|
+
var RecalcEngine = class {
|
|
4146
|
+
constructor(model, registry = FunctionRegistry.default) {
|
|
4147
|
+
__privateAdd(this, _RecalcEngine_instances);
|
|
4148
|
+
__privateAdd(this, _model);
|
|
4149
|
+
__privateAdd(this, _registry);
|
|
4150
|
+
__privateAdd(this, _formulas, /* @__PURE__ */ new Map());
|
|
4151
|
+
/** upper-cased sheet key → original casing, for case-correct model I/O. */
|
|
4152
|
+
__privateAdd(this, _sheetNames, /* @__PURE__ */ new Map());
|
|
4153
|
+
/** anchor cellKey → set of spilled (non-anchor) cellKeys it currently owns. */
|
|
4154
|
+
__privateAdd(this, _spills, /* @__PURE__ */ new Map());
|
|
4155
|
+
/** spilled cellKey → the anchor cellKey that owns it. */
|
|
4156
|
+
__privateAdd(this, _spillOwner, /* @__PURE__ */ new Map());
|
|
4157
|
+
__privateSet(this, _model, model);
|
|
4158
|
+
__privateSet(this, _registry, registry);
|
|
4159
|
+
}
|
|
4160
|
+
// ── Registration ─────────────────────────────────────────────────────────────
|
|
4161
|
+
/**
|
|
4162
|
+
* Registers (or replaces) a formula for a cell and recalculates that cell and
|
|
4163
|
+
* everything downstream of it. Returns the recomputed cells in the order they
|
|
4164
|
+
* were evaluated. The leading '=' of the formula is optional.
|
|
4165
|
+
*/
|
|
4166
|
+
setCellFormula(sheet, ref, formula) {
|
|
4167
|
+
if (!formula) throw new Error("Formula cannot be empty.");
|
|
4168
|
+
const text = formula.startsWith("=") ? formula.slice(1) : formula;
|
|
4169
|
+
const s = normSheet(sheet);
|
|
4170
|
+
const r = normRef(ref);
|
|
4171
|
+
const { row: row2, column: column2 } = parseCellRef(r);
|
|
4172
|
+
const ast = parseFormula(text);
|
|
4173
|
+
const { refs, volatile } = extractReferences(
|
|
4174
|
+
ast,
|
|
4175
|
+
s,
|
|
4176
|
+
__privateGet(this, _registry),
|
|
4177
|
+
__privateGet(this, _model).getDefinedName ? (n, sh) => __privateGet(this, _model).getDefinedName(n, sh) : void 0
|
|
4178
|
+
);
|
|
4179
|
+
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
4180
|
+
__privateGet(this, _formulas).set(keyOf(s, r), {
|
|
4181
|
+
sheet: s,
|
|
4182
|
+
sheetName: sheet,
|
|
4183
|
+
ref: r,
|
|
4184
|
+
row: row2,
|
|
4185
|
+
col: column2,
|
|
4186
|
+
formula: text,
|
|
4187
|
+
refs,
|
|
4188
|
+
volatile
|
|
4189
|
+
});
|
|
4190
|
+
__privateGet(this, _model).setCellFormula?.(sheet, r, text);
|
|
4191
|
+
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }], keyOf(s, r));
|
|
4192
|
+
}
|
|
4193
|
+
/**
|
|
4194
|
+
* Removes a formula from a cell and recalculates its dependents (which may now
|
|
4195
|
+
* read a plain input value instead). Returns the recomputed dependent cells.
|
|
4196
|
+
*/
|
|
4197
|
+
clearCellFormula(sheet, ref) {
|
|
4198
|
+
const s = normSheet(sheet);
|
|
4199
|
+
const r = normRef(ref);
|
|
4200
|
+
const existed = __privateGet(this, _formulas).delete(keyOf(s, r));
|
|
4201
|
+
if (!existed) return [];
|
|
4202
|
+
__privateGet(this, _model).clearCell?.(sheet, r);
|
|
4203
|
+
const { row: row2, column: column2 } = parseCellRef(r);
|
|
4204
|
+
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4205
|
+
}
|
|
4206
|
+
/**
|
|
4207
|
+
* Writes a raw input value to a cell and recalculates every formula that
|
|
4208
|
+
* depends on it. Use this for non-formula edits so dependents stay current.
|
|
4209
|
+
*/
|
|
4210
|
+
setCellValue(sheet, ref, value2) {
|
|
4211
|
+
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
4212
|
+
const s = normSheet(sheet);
|
|
4213
|
+
const r = normRef(ref);
|
|
4214
|
+
__privateGet(this, _model).setCellValue(sheet, r, value2);
|
|
4215
|
+
const { row: row2, column: column2 } = parseCellRef(r);
|
|
4216
|
+
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4217
|
+
}
|
|
4218
|
+
/**
|
|
4219
|
+
* Signals that a cell changed by some external means (the model was already
|
|
4220
|
+
* updated) and recalculates its dependents.
|
|
4221
|
+
*/
|
|
4222
|
+
onCellChanged(sheet, ref) {
|
|
4223
|
+
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
4224
|
+
const s = normSheet(sheet);
|
|
4225
|
+
const r = normRef(ref);
|
|
4226
|
+
const { row: row2, column: column2 } = parseCellRef(r);
|
|
4227
|
+
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4228
|
+
}
|
|
4229
|
+
/** Recalculates every registered formula in dependency order. */
|
|
4230
|
+
recalcAll() {
|
|
4231
|
+
return __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...__privateGet(this, _formulas).values()]).results;
|
|
4232
|
+
}
|
|
4233
|
+
/** The precedents recorded for a formula cell (mainly for tests/inspection). */
|
|
4234
|
+
getPrecedents(sheet, ref) {
|
|
4235
|
+
return __privateGet(this, _formulas).get(keyOf(sheet, ref))?.refs ?? [];
|
|
4236
|
+
}
|
|
4237
|
+
};
|
|
4238
|
+
_model = new WeakMap();
|
|
4239
|
+
_registry = new WeakMap();
|
|
4240
|
+
_formulas = new WeakMap();
|
|
4241
|
+
_sheetNames = new WeakMap();
|
|
4242
|
+
_spills = new WeakMap();
|
|
4243
|
+
_spillOwner = new WeakMap();
|
|
4244
|
+
_RecalcEngine_instances = new WeakSet();
|
|
4245
|
+
rememberSheet_fn = function(name) {
|
|
4246
|
+
__privateGet(this, _sheetNames).set(normSheet(name), name);
|
|
4247
|
+
};
|
|
4248
|
+
// ── Internals ────────────────────────────────────────────────────────────────
|
|
4249
|
+
/**
|
|
4250
|
+
* Recomputes formulas affected by the given changed cells, to a fixpoint.
|
|
4251
|
+
*
|
|
4252
|
+
* A single topological pass is not enough once dynamic arrays exist: a
|
|
4253
|
+
* formula that spills changes cells the graph could not know about until the
|
|
4254
|
+
* array was computed, and those cells may have their own dependents. So we
|
|
4255
|
+
* evaluate a pass, collect every cell whose value actually changed (formula
|
|
4256
|
+
* outputs plus spilled/cleared cells), then recompute the dependents of those
|
|
4257
|
+
* cells, repeating until nothing new is affected (bounded for safety).
|
|
4258
|
+
*/
|
|
4259
|
+
recalcFrom_fn = function(seeds, includeKey) {
|
|
4260
|
+
const results = [];
|
|
4261
|
+
const evaluated = /* @__PURE__ */ new Set();
|
|
4262
|
+
let pending = __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, seeds, evaluated);
|
|
4263
|
+
for (const e of __privateGet(this, _formulas).values()) {
|
|
4264
|
+
if (e.volatile) pending.set(keyOf(e.sheet, e.ref), e);
|
|
4265
|
+
}
|
|
4266
|
+
if (includeKey) {
|
|
4267
|
+
const self = __privateGet(this, _formulas).get(includeKey);
|
|
4268
|
+
if (self) pending.set(includeKey, self);
|
|
4269
|
+
}
|
|
4270
|
+
let guard = 0;
|
|
4271
|
+
const maxIterations = __privateGet(this, _formulas).size + 8;
|
|
4272
|
+
while (pending.size > 0 && guard++ <= maxIterations) {
|
|
4273
|
+
const { results: passResults, changed } = __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...pending.values()]);
|
|
4274
|
+
results.push(...passResults);
|
|
4275
|
+
for (const k of pending.keys()) evaluated.add(k);
|
|
4276
|
+
pending = __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, changed, evaluated);
|
|
4277
|
+
}
|
|
4278
|
+
return results;
|
|
4279
|
+
};
|
|
4280
|
+
/** Formula entries (not yet evaluated) whose precedents include one of the cells. */
|
|
4281
|
+
dependentClosure_fn = function(cells, evaluated) {
|
|
4282
|
+
const out = /* @__PURE__ */ new Map();
|
|
4283
|
+
const queue = [...cells];
|
|
4284
|
+
while (queue.length > 0) {
|
|
4285
|
+
const cell = queue.shift();
|
|
4286
|
+
for (const dep of __privateMethod(this, _RecalcEngine_instances, directDependents_fn).call(this, cell)) {
|
|
4287
|
+
const k = keyOf(dep.sheet, dep.ref);
|
|
4288
|
+
if (out.has(k) || evaluated.has(k)) continue;
|
|
4289
|
+
out.set(k, dep);
|
|
4290
|
+
queue.push({ sheet: dep.sheet, row: dep.row, col: dep.col });
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
return out;
|
|
4294
|
+
};
|
|
4295
|
+
/** Formula entries whose precedents include the given cell. */
|
|
4296
|
+
directDependents_fn = function(cell) {
|
|
4297
|
+
const out = [];
|
|
4298
|
+
for (const e of __privateGet(this, _formulas).values()) {
|
|
4299
|
+
if (e.refs.some((ref) => refContains(ref, cell.sheet, cell.row, cell.col))) out.push(e);
|
|
4300
|
+
}
|
|
4301
|
+
return out;
|
|
4302
|
+
};
|
|
4303
|
+
/**
|
|
4304
|
+
* Topologically sorts the given entries and evaluates them, writing results
|
|
4305
|
+
* (scalars or spilled arrays) back to the model. Returns the results and the
|
|
4306
|
+
* flat list of cells whose value changed. Entries left in a cycle are #REF!.
|
|
4307
|
+
*/
|
|
4308
|
+
evaluatePass_fn = function(entries) {
|
|
4309
|
+
const inSet = /* @__PURE__ */ new Map();
|
|
4310
|
+
for (const e of entries) inSet.set(keyOf(e.sheet, e.ref), e);
|
|
4311
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
4312
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
4313
|
+
for (const k of inSet.keys()) {
|
|
4314
|
+
indegree.set(k, 0);
|
|
4315
|
+
dependents.set(k, []);
|
|
4316
|
+
}
|
|
4317
|
+
for (const e of entries) {
|
|
4318
|
+
const depKey = keyOf(e.sheet, e.ref);
|
|
4319
|
+
for (const [pk, pe] of inSet) {
|
|
4320
|
+
if (pk === depKey) continue;
|
|
4321
|
+
if (e.refs.some((ref) => refContains(ref, pe.sheet, pe.row, pe.col))) {
|
|
4322
|
+
dependents.get(pk).push(depKey);
|
|
4323
|
+
indegree.set(depKey, indegree.get(depKey) + 1);
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
const ready = [];
|
|
4328
|
+
for (const [k, d] of indegree) if (d === 0) ready.push(k);
|
|
4329
|
+
const order = [];
|
|
4330
|
+
while (ready.length > 0) {
|
|
4331
|
+
const k = ready.shift();
|
|
4332
|
+
order.push(k);
|
|
4333
|
+
for (const d of dependents.get(k)) {
|
|
4334
|
+
indegree.set(d, indegree.get(d) - 1);
|
|
4335
|
+
if (indegree.get(d) === 0) ready.push(d);
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
const results = [];
|
|
4339
|
+
const changed = [];
|
|
4340
|
+
const emitted = new Set(order);
|
|
4341
|
+
for (const k of order) {
|
|
4342
|
+
const e = inSet.get(k);
|
|
4343
|
+
const { result, changed: c } = __privateMethod(this, _RecalcEngine_instances, writeResult_fn).call(this, e);
|
|
4344
|
+
results.push(result);
|
|
4345
|
+
changed.push(...c);
|
|
4346
|
+
}
|
|
4347
|
+
for (const [k, e] of inSet) {
|
|
4348
|
+
if (emitted.has(k)) continue;
|
|
4349
|
+
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, keyOf(e.sheet, e.ref), changed);
|
|
4350
|
+
const value2 = FormulaValue.errorRef.toObject();
|
|
4351
|
+
__privateGet(this, _model).setCellValue(e.sheetName, e.ref, value2);
|
|
4352
|
+
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
4353
|
+
results.push({ sheet: e.sheetName, ref: e.ref, value: value2, circular: true });
|
|
4354
|
+
}
|
|
4355
|
+
return { results, changed };
|
|
4356
|
+
};
|
|
4357
|
+
/**
|
|
4358
|
+
* Evaluates one formula and writes its result. Scalars go to the anchor cell.
|
|
4359
|
+
* Arrays spill across a block anchored at the formula cell: on a collision
|
|
4360
|
+
* with existing content the anchor becomes #SPILL! and nothing spills; when
|
|
4361
|
+
* an array shrinks, previously-spilled cells it no longer covers are cleared.
|
|
4362
|
+
*/
|
|
4363
|
+
writeResult_fn = function(e) {
|
|
4364
|
+
const anchorKey = keyOf(e.sheet, e.ref);
|
|
4365
|
+
const fv2 = __privateMethod(this, _RecalcEngine_instances, evaluateFormulaValue_fn).call(this, e);
|
|
4366
|
+
const changed = [];
|
|
4367
|
+
const prevSpill = __privateGet(this, _spills).get(anchorKey) ?? /* @__PURE__ */ new Set();
|
|
4368
|
+
if (!fv2.isArray) {
|
|
4369
|
+
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
|
|
4370
|
+
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
4371
|
+
const value2 = fv2.toObject();
|
|
4372
|
+
__privateGet(this, _model).setCellValue(e.sheetName, e.ref, value2);
|
|
4373
|
+
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
4374
|
+
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
4375
|
+
}
|
|
4376
|
+
const arr = fv2.arrayVal;
|
|
4377
|
+
const rows2 = arr.rows, cols = arr.columns;
|
|
4378
|
+
let collides = false;
|
|
4379
|
+
for (let dr = 0; dr < rows2 && !collides; dr++) {
|
|
4380
|
+
for (let dc = 0; dc < cols && !collides; dc++) {
|
|
4381
|
+
if (dr === 0 && dc === 0) continue;
|
|
4382
|
+
const r = e.row + dr, c = e.col + dc;
|
|
4383
|
+
const cellKey = keyOf(e.sheet, cellRefFromRowCol(r, c));
|
|
4384
|
+
if (__privateMethod(this, _RecalcEngine_instances, isOccupied_fn).call(this, cellKey, e.sheetName, cellRefFromRowCol(r, c), anchorKey, prevSpill)) {
|
|
4385
|
+
collides = true;
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
if (collides) {
|
|
4390
|
+
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
|
|
4391
|
+
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
4392
|
+
const value2 = FormulaValue.error(9 /* Spill */).toObject();
|
|
4393
|
+
__privateGet(this, _model).setCellValue(e.sheetName, e.ref, value2);
|
|
4394
|
+
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
4395
|
+
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
4396
|
+
}
|
|
4397
|
+
const newSpill = /* @__PURE__ */ new Set();
|
|
4398
|
+
for (let dr = 0; dr < rows2; dr++) {
|
|
4399
|
+
for (let dc = 0; dc < cols; dc++) {
|
|
4400
|
+
const r = e.row + dr, c = e.col + dc;
|
|
4401
|
+
const ref = cellRefFromRowCol(r, c);
|
|
4402
|
+
__privateGet(this, _model).setCellValue(e.sheetName, ref, arr.get(dr, dc).toObject());
|
|
4403
|
+
changed.push({ sheet: e.sheet, row: r, col: c });
|
|
4404
|
+
if (dr !== 0 || dc !== 0) {
|
|
4405
|
+
const cellKey = keyOf(e.sheet, ref);
|
|
4406
|
+
newSpill.add(cellKey);
|
|
4407
|
+
__privateGet(this, _spillOwner).set(cellKey, anchorKey);
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
for (const oldKey of prevSpill) {
|
|
4412
|
+
if (newSpill.has(oldKey)) continue;
|
|
4413
|
+
const cell = parseKey(oldKey);
|
|
4414
|
+
__privateGet(this, _model).setCellValue(__privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null);
|
|
4415
|
+
__privateGet(this, _spillOwner).delete(oldKey);
|
|
4416
|
+
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
4417
|
+
}
|
|
4418
|
+
__privateGet(this, _spills).set(anchorKey, newSpill);
|
|
4419
|
+
const spillRef = `${e.ref}:${cellRefFromRowCol(e.row + rows2 - 1, e.col + cols - 1)}`;
|
|
4420
|
+
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, spillRef);
|
|
4421
|
+
return {
|
|
4422
|
+
result: { sheet: e.sheetName, ref: e.ref, value: arr.get(0, 0).toObject(), spill: { rows: rows2, cols } },
|
|
4423
|
+
changed
|
|
4424
|
+
};
|
|
4425
|
+
};
|
|
4426
|
+
/** Whether a target cell already holds content that blocks a spill. */
|
|
4427
|
+
isOccupied_fn = function(cellKey, sheetName, ref, anchorKey, prevSpill) {
|
|
4428
|
+
if (__privateGet(this, _formulas).has(cellKey) && cellKey !== anchorKey) return true;
|
|
4429
|
+
const owner = __privateGet(this, _spillOwner).get(cellKey);
|
|
4430
|
+
if (owner && owner !== anchorKey) return true;
|
|
4431
|
+
if (prevSpill.has(cellKey)) return false;
|
|
4432
|
+
const v = __privateGet(this, _model).getCellValue(sheetName, ref);
|
|
4433
|
+
return v !== null && v !== void 0 && v !== "";
|
|
4434
|
+
};
|
|
4435
|
+
/** Clears an anchor's spilled cells (not the anchor itself) and forgets them. */
|
|
4436
|
+
clearSpill_fn = function(anchorKey, changed) {
|
|
4437
|
+
const spill = __privateGet(this, _spills).get(anchorKey);
|
|
4438
|
+
if (!spill) return;
|
|
4439
|
+
for (const cellKey of spill) {
|
|
4440
|
+
const cell = parseKey(cellKey);
|
|
4441
|
+
__privateGet(this, _model).setCellValue(__privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null);
|
|
4442
|
+
__privateGet(this, _spillOwner).delete(cellKey);
|
|
4443
|
+
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
4444
|
+
}
|
|
4445
|
+
__privateGet(this, _spills).delete(anchorKey);
|
|
4446
|
+
};
|
|
4447
|
+
evaluateFormulaValue_fn = function(e) {
|
|
4448
|
+
const wb = __privateMethod(this, _RecalcEngine_instances, buildWorkbookLike_fn).call(this);
|
|
4449
|
+
const ws = __privateMethod(this, _RecalcEngine_instances, sheetLike_fn).call(this, e.sheetName);
|
|
4450
|
+
return evaluateFormula(e.formula, ws, {
|
|
4451
|
+
workbook: wb,
|
|
4452
|
+
formulaRow: e.row,
|
|
4453
|
+
formulaCol: e.col
|
|
4454
|
+
});
|
|
4455
|
+
};
|
|
4456
|
+
sheetLike_fn = function(sheetName) {
|
|
4457
|
+
return {
|
|
4458
|
+
name: sheetName,
|
|
4459
|
+
getCellValue: (ref) => __privateGet(this, _model).getCellValue(sheetName, ref),
|
|
4460
|
+
isRowHidden: __privateGet(this, _model).isRowHidden ? (row2) => __privateGet(this, _model).isRowHidden(sheetName, row2) : void 0,
|
|
4461
|
+
getCellFormula: __privateGet(this, _model).getCellFormula ? (ref) => __privateGet(this, _model).getCellFormula(sheetName, ref) : void 0,
|
|
4462
|
+
getSpillRange: __privateGet(this, _model).getSpillRange ? (ref) => __privateGet(this, _model).getSpillRange(sheetName, ref) : void 0
|
|
4463
|
+
};
|
|
4464
|
+
};
|
|
4465
|
+
buildWorkbookLike_fn = function() {
|
|
4466
|
+
const self = this;
|
|
4467
|
+
return {
|
|
4468
|
+
get worksheets() {
|
|
4469
|
+
return [...__privateGet(self, _sheetNames).values()].map((n) => {
|
|
4470
|
+
var _a;
|
|
4471
|
+
return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, n);
|
|
4472
|
+
});
|
|
4473
|
+
},
|
|
4474
|
+
getWorksheet: (name) => {
|
|
4475
|
+
var _a;
|
|
4476
|
+
return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, name);
|
|
4477
|
+
},
|
|
4478
|
+
getDefinedName: __privateGet(this, _model).getDefinedName ? (name, sheet) => __privateGet(this, _model).getDefinedName(name, sheet) : void 0
|
|
4479
|
+
};
|
|
4480
|
+
};
|
|
2902
4481
|
|
|
2903
|
-
export { ArrayValue, BinaryOpNode, BinaryOperator, BlankNode, BooleanNode, CellReferenceNode, ErrorNode, FormulaContext, FormulaError, FormulaException, FormulaHelper, FormulaLexer, FormulaNode, FormulaParser, FormulaValue, FunctionCallNode, FunctionRegistry, ImplicitIntersectionNode, LruCache, NamedRangeNode, NumberNode, RangeReferenceNode, SheetCellReferenceNode, SheetRangeReferenceNode, StringNode, TokenType, UnaryOpNode, UnaryOperator, clearAstCache, evaluateFormula, getAstCacheStats, parseFormula, registerDate, registerInfo, registerLogical, registerLookup, registerMath, registerStatistical, registerText, setAstCacheCapacity };
|
|
4482
|
+
export { ArrayValue, BinaryOpNode, BinaryOperator, BlankNode, BooleanNode, CallNode, CellReferenceNode, ErrorNode, FormulaContext, FormulaError, FormulaException, FormulaHelper, FormulaLexer, FormulaNode, FormulaParser, FormulaValue, FunctionCallNode, FunctionRegistry, ImplicitIntersectionNode, LruCache, NamedRangeNode, NumberNode, RangeReferenceNode, RecalcEngine, SheetCellReferenceNode, SheetRangeReferenceNode, SpillReferenceNode, StringNode, TokenType, UnaryOpNode, UnaryOperator, clearAstCache, createWorkbookRecalcModel, evaluateFormula, extractReferences, getAstCacheStats, parseFormula, refContains, registerAggregate, registerArray, registerDate, registerFinance, registerInfo, registerLogical, registerLookup, registerMath, registerMeta, registerStatistical, registerText, setAstCacheCapacity };
|
|
2904
4483
|
//# sourceMappingURL=index.js.map
|
|
2905
4484
|
//# sourceMappingURL=index.js.map
|