@devmm/puredocs-excel-formula 1.0.0 → 1.0.2
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 +1941 -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 +1933 -48
- package/dist/index.js.map +1 -1
- package/package.json +45 -45
- package/LICENSE +0 -21
package/dist/index.cjs
CHANGED
|
@@ -31,15 +31,21 @@ var FormulaError = /* @__PURE__ */ ((FormulaError2) => {
|
|
|
31
31
|
FormulaError2[FormulaError2["Spill"] = 9] = "Spill";
|
|
32
32
|
return FormulaError2;
|
|
33
33
|
})(FormulaError || {});
|
|
34
|
+
var TailCall = class {
|
|
35
|
+
constructor(lambda, args) {
|
|
36
|
+
this.lambda = lambda;
|
|
37
|
+
this.args = args;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
34
40
|
var _num, _obj;
|
|
35
41
|
var _FormulaValue = class _FormulaValue {
|
|
36
|
-
constructor(kind,
|
|
42
|
+
constructor(kind, num5 = 0, obj, errorCode = 0 /* None */) {
|
|
37
43
|
/** Numeric payload (also used for boolean: 1=true, 0=false). */
|
|
38
44
|
__privateAdd(this, _num);
|
|
39
|
-
/** String or
|
|
45
|
+
/** String, ArrayValue, or LambdaLike payload. */
|
|
40
46
|
__privateAdd(this, _obj);
|
|
41
47
|
this.kind = kind;
|
|
42
|
-
__privateSet(this, _num,
|
|
48
|
+
__privateSet(this, _num, num5);
|
|
43
49
|
__privateSet(this, _obj, obj);
|
|
44
50
|
this.errorCode = errorCode;
|
|
45
51
|
}
|
|
@@ -79,6 +85,9 @@ var _FormulaValue = class _FormulaValue {
|
|
|
79
85
|
static array(a) {
|
|
80
86
|
return new _FormulaValue("array", 0, a);
|
|
81
87
|
}
|
|
88
|
+
static lambda(fn) {
|
|
89
|
+
return new _FormulaValue("lambda", 0, fn);
|
|
90
|
+
}
|
|
82
91
|
// ── Type checks ──────────────────────────────────────────────────────────
|
|
83
92
|
get isBlank() {
|
|
84
93
|
return this.kind === "blank";
|
|
@@ -98,6 +107,9 @@ var _FormulaValue = class _FormulaValue {
|
|
|
98
107
|
get isArray() {
|
|
99
108
|
return this.kind === "array";
|
|
100
109
|
}
|
|
110
|
+
get isLambda() {
|
|
111
|
+
return this.kind === "lambda";
|
|
112
|
+
}
|
|
101
113
|
get isNumeric() {
|
|
102
114
|
return this.kind === "number" || this.kind === "boolean" || this.kind === "blank";
|
|
103
115
|
}
|
|
@@ -114,6 +126,9 @@ var _FormulaValue = class _FormulaValue {
|
|
|
114
126
|
get arrayVal() {
|
|
115
127
|
return __privateGet(this, _obj) ?? ArrayValue.empty;
|
|
116
128
|
}
|
|
129
|
+
get lambdaVal() {
|
|
130
|
+
return __privateGet(this, _obj);
|
|
131
|
+
}
|
|
117
132
|
// ── Coercion (Excel semantics, no exceptions) ─────────────────────────────
|
|
118
133
|
coerceToNumber() {
|
|
119
134
|
switch (this.kind) {
|
|
@@ -181,6 +196,8 @@ var _FormulaValue = class _FormulaValue {
|
|
|
181
196
|
return "";
|
|
182
197
|
case "error":
|
|
183
198
|
return _FormulaValue.errorToString(this.errorCode);
|
|
199
|
+
case "lambda":
|
|
200
|
+
return _FormulaValue.errorToString(8 /* Calc */);
|
|
184
201
|
default:
|
|
185
202
|
return "";
|
|
186
203
|
}
|
|
@@ -278,6 +295,8 @@ var _FormulaValue = class _FormulaValue {
|
|
|
278
295
|
return _FormulaValue.errorToString(this.errorCode);
|
|
279
296
|
case "array":
|
|
280
297
|
return this.arrayVal.toObjectArray();
|
|
298
|
+
case "lambda":
|
|
299
|
+
return _FormulaValue.errorToString(8 /* Calc */);
|
|
281
300
|
default:
|
|
282
301
|
return null;
|
|
283
302
|
}
|
|
@@ -426,10 +445,11 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
|
|
|
426
445
|
TokenType2[TokenType2["GreaterThanOrEqual"] = 21] = "GreaterThanOrEqual";
|
|
427
446
|
TokenType2[TokenType2["SheetReference"] = 22] = "SheetReference";
|
|
428
447
|
TokenType2[TokenType2["NamedRange"] = 23] = "NamedRange";
|
|
429
|
-
TokenType2[TokenType2["
|
|
430
|
-
TokenType2[TokenType2["
|
|
431
|
-
TokenType2[TokenType2["
|
|
432
|
-
TokenType2[TokenType2["
|
|
448
|
+
TokenType2[TokenType2["SpillReference"] = 24] = "SpillReference";
|
|
449
|
+
TokenType2[TokenType2["Exclamation"] = 25] = "Exclamation";
|
|
450
|
+
TokenType2[TokenType2["AtSign"] = 26] = "AtSign";
|
|
451
|
+
TokenType2[TokenType2["ErrorLiteral"] = 27] = "ErrorLiteral";
|
|
452
|
+
TokenType2[TokenType2["EOF"] = 28] = "EOF";
|
|
433
453
|
return TokenType2;
|
|
434
454
|
})(TokenType || {});
|
|
435
455
|
function makeToken(type, value2, position) {
|
|
@@ -472,7 +492,7 @@ var FormulaLexer = class {
|
|
|
472
492
|
const token = __privateMethod(this, _FormulaLexer_instances, readNextToken_fn).call(this);
|
|
473
493
|
if (token) tokens.push(token);
|
|
474
494
|
}
|
|
475
|
-
tokens.push(makeToken(
|
|
495
|
+
tokens.push(makeToken(28 /* EOF */, "", __privateGet(this, _pos)));
|
|
476
496
|
return tokens;
|
|
477
497
|
}
|
|
478
498
|
};
|
|
@@ -526,7 +546,7 @@ readErrorLiteral_fn = function() {
|
|
|
526
546
|
if (!KNOWN_ERRORS.has(upper2)) {
|
|
527
547
|
throw new FormulaException(`Unknown error literal "${raw}" at position ${start}.`);
|
|
528
548
|
}
|
|
529
|
-
return makeToken(
|
|
549
|
+
return makeToken(27 /* ErrorLiteral */, upper2, start);
|
|
530
550
|
};
|
|
531
551
|
readQuotedSheetRef_fn = function() {
|
|
532
552
|
const start = __privateWrapper(this, _pos)._++;
|
|
@@ -605,7 +625,13 @@ readIdentifier_fn = function() {
|
|
|
605
625
|
return makeToken(5 /* Function */, upper2, start);
|
|
606
626
|
}
|
|
607
627
|
const stripped = raw.replace(/\$/g, "");
|
|
608
|
-
if (isCellRef(stripped))
|
|
628
|
+
if (isCellRef(stripped)) {
|
|
629
|
+
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "#") {
|
|
630
|
+
__privateWrapper(this, _pos)._++;
|
|
631
|
+
return makeToken(24 /* SpillReference */, stripped, start);
|
|
632
|
+
}
|
|
633
|
+
return makeToken(3 /* CellReference */, stripped, start);
|
|
634
|
+
}
|
|
609
635
|
return makeToken(23 /* NamedRange */, raw, start);
|
|
610
636
|
};
|
|
611
637
|
readCellRefChars_fn = function() {
|
|
@@ -648,9 +674,9 @@ readOperator_fn = function() {
|
|
|
648
674
|
case "=":
|
|
649
675
|
return makeToken(16 /* Equal */, "=", start);
|
|
650
676
|
case "@":
|
|
651
|
-
return makeToken(
|
|
677
|
+
return makeToken(26 /* AtSign */, "@", start);
|
|
652
678
|
case "!":
|
|
653
|
-
return makeToken(
|
|
679
|
+
return makeToken(25 /* Exclamation */, "!", start);
|
|
654
680
|
case "<":
|
|
655
681
|
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === ">") {
|
|
656
682
|
__privateWrapper(this, _pos)._++;
|
|
@@ -687,6 +713,15 @@ function isCellRef(s) {
|
|
|
687
713
|
|
|
688
714
|
// src/formula-node.ts
|
|
689
715
|
var FormulaNode = class {
|
|
716
|
+
/**
|
|
717
|
+
* Evaluates this node in tail position. Returns a TailCall when the node's
|
|
718
|
+
* value is a call to a lambda in tail position (so the caller's trampoline can
|
|
719
|
+
* iterate without growing the stack); otherwise the final value. The default
|
|
720
|
+
* is a plain evaluate — only control-flow and call nodes override it.
|
|
721
|
+
*/
|
|
722
|
+
evaluateTail(ctx) {
|
|
723
|
+
return this.evaluate(ctx);
|
|
724
|
+
}
|
|
690
725
|
};
|
|
691
726
|
var NumberNode = class extends FormulaNode {
|
|
692
727
|
constructor(value2) {
|
|
@@ -778,6 +813,15 @@ var NamedRangeNode = class extends FormulaNode {
|
|
|
778
813
|
return ctx.resolveNamedRange(this.name);
|
|
779
814
|
}
|
|
780
815
|
};
|
|
816
|
+
var SpillReferenceNode = class extends FormulaNode {
|
|
817
|
+
constructor(anchorRef) {
|
|
818
|
+
super();
|
|
819
|
+
this.anchorRef = anchorRef;
|
|
820
|
+
}
|
|
821
|
+
evaluate(ctx) {
|
|
822
|
+
return ctx.resolveSpillRange(this.anchorRef);
|
|
823
|
+
}
|
|
824
|
+
};
|
|
781
825
|
var BinaryOperator = /* @__PURE__ */ ((BinaryOperator2) => {
|
|
782
826
|
BinaryOperator2[BinaryOperator2["Add"] = 0] = "Add";
|
|
783
827
|
BinaryOperator2[BinaryOperator2["Subtract"] = 1] = "Subtract";
|
|
@@ -901,10 +945,85 @@ var FunctionCallNode = class extends FormulaNode {
|
|
|
901
945
|
evaluate(ctx) {
|
|
902
946
|
return ctx.evaluateFunction(this.functionName, this.args);
|
|
903
947
|
}
|
|
948
|
+
evaluateTail(ctx) {
|
|
949
|
+
const name = this.functionName;
|
|
950
|
+
const a = this.args;
|
|
951
|
+
switch (name) {
|
|
952
|
+
case "IF": {
|
|
953
|
+
const cond = a[0].evaluate(ctx);
|
|
954
|
+
if (cond.isError) return cond;
|
|
955
|
+
const b = cond.coerceToBool();
|
|
956
|
+
if (b.isError) return b;
|
|
957
|
+
if (b.booleanValue) return a[1].evaluateTail(ctx);
|
|
958
|
+
return a.length > 2 ? a[2].evaluateTail(ctx) : FormulaValue.false_;
|
|
959
|
+
}
|
|
960
|
+
case "IFS": {
|
|
961
|
+
for (let i = 0; i + 1 < a.length; i += 2) {
|
|
962
|
+
const cond = a[i].evaluate(ctx);
|
|
963
|
+
if (cond.isError) return cond;
|
|
964
|
+
const b = cond.coerceToBool();
|
|
965
|
+
if (b.isError) return b;
|
|
966
|
+
if (b.booleanValue) return a[i + 1].evaluateTail(ctx);
|
|
967
|
+
}
|
|
968
|
+
return FormulaValue.errorNA;
|
|
969
|
+
}
|
|
970
|
+
case "IFERROR": {
|
|
971
|
+
const v = a[0].evaluate(ctx);
|
|
972
|
+
return v.isError ? a[1].evaluateTail(ctx) : v;
|
|
973
|
+
}
|
|
974
|
+
case "IFNA": {
|
|
975
|
+
const v = a[0].evaluate(ctx);
|
|
976
|
+
return v.isError && v.errorCode === 7 /* NA */ ? a[1].evaluateTail(ctx) : v;
|
|
977
|
+
}
|
|
978
|
+
case "LET": {
|
|
979
|
+
if (a.length < 3 || a.length % 2 === 0) break;
|
|
980
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
981
|
+
ctx.pushScope(bindings);
|
|
982
|
+
try {
|
|
983
|
+
for (let i = 0; i < a.length - 1; i += 2) {
|
|
984
|
+
const nn = a[i];
|
|
985
|
+
if (!(nn instanceof NamedRangeNode)) return FormulaValue.errorValue;
|
|
986
|
+
const val = a[i + 1].evaluate(ctx);
|
|
987
|
+
if (val.isError) return val;
|
|
988
|
+
bindings.set(nn.name.toUpperCase(), val);
|
|
989
|
+
}
|
|
990
|
+
return a[a.length - 1].evaluateTail(ctx);
|
|
991
|
+
} finally {
|
|
992
|
+
ctx.popScope();
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (!ctx.hasFunction(name)) {
|
|
997
|
+
const named = ctx.resolveNamedRange(name);
|
|
998
|
+
if (named.isLambda) {
|
|
999
|
+
return new TailCall(named.lambdaVal, a.map((arg2) => arg2.evaluate(ctx)));
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return this.evaluate(ctx);
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
var CallNode = class extends FormulaNode {
|
|
1006
|
+
constructor(callee, args) {
|
|
1007
|
+
super();
|
|
1008
|
+
this.callee = callee;
|
|
1009
|
+
this.args = args;
|
|
1010
|
+
}
|
|
1011
|
+
evaluate(ctx) {
|
|
1012
|
+
const fn = this.callee.evaluate(ctx);
|
|
1013
|
+
if (fn.isError) return fn;
|
|
1014
|
+
if (!fn.isLambda) return FormulaValue.errorValue;
|
|
1015
|
+
return fn.lambdaVal.call(this.args.map((a) => a.evaluate(ctx)));
|
|
1016
|
+
}
|
|
1017
|
+
evaluateTail(ctx) {
|
|
1018
|
+
const fn = this.callee.evaluate(ctx);
|
|
1019
|
+
if (fn.isError) return fn;
|
|
1020
|
+
if (!fn.isLambda) return FormulaValue.errorValue;
|
|
1021
|
+
return new TailCall(fn.lambdaVal, this.args.map((a) => a.evaluate(ctx)));
|
|
1022
|
+
}
|
|
904
1023
|
};
|
|
905
1024
|
|
|
906
1025
|
// src/formula-parser.ts
|
|
907
|
-
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;
|
|
1026
|
+
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;
|
|
908
1027
|
var FormulaParser = class {
|
|
909
1028
|
constructor(tokens) {
|
|
910
1029
|
__privateAdd(this, _FormulaParser_instances);
|
|
@@ -915,7 +1034,7 @@ var FormulaParser = class {
|
|
|
915
1034
|
parse() {
|
|
916
1035
|
__privateSet(this, _pos2, 0);
|
|
917
1036
|
const node = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
|
|
918
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type !==
|
|
1037
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type !== 28 /* EOF */) {
|
|
919
1038
|
throw new FormulaException(
|
|
920
1039
|
`Unexpected token '${__privateGet(this, _FormulaParser_instances, current_get).value}' at position ${__privateGet(this, _FormulaParser_instances, current_get).position}.`
|
|
921
1040
|
);
|
|
@@ -927,7 +1046,7 @@ _tokens = new WeakMap();
|
|
|
927
1046
|
_pos2 = new WeakMap();
|
|
928
1047
|
_FormulaParser_instances = new WeakSet();
|
|
929
1048
|
current_get = function() {
|
|
930
|
-
return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type:
|
|
1049
|
+
return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 28 /* EOF */, value: "", position: -1 };
|
|
931
1050
|
};
|
|
932
1051
|
advance_fn = function() {
|
|
933
1052
|
const t = __privateGet(this, _FormulaParser_instances, current_get);
|
|
@@ -992,7 +1111,7 @@ parseUnary_fn = function() {
|
|
|
992
1111
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
993
1112
|
return new UnaryOpNode(1 /* Plus */, __privateMethod(this, _FormulaParser_instances, parsePower_fn).call(this));
|
|
994
1113
|
}
|
|
995
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type ===
|
|
1114
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 26 /* AtSign */) {
|
|
996
1115
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
997
1116
|
return new ImplicitIntersectionNode(__privateMethod(this, _FormulaParser_instances, parsePower_fn).call(this));
|
|
998
1117
|
}
|
|
@@ -1007,13 +1126,22 @@ parsePower_fn = function() {
|
|
|
1007
1126
|
return left2;
|
|
1008
1127
|
};
|
|
1009
1128
|
parsePercent_fn = function() {
|
|
1010
|
-
let node = __privateMethod(this, _FormulaParser_instances,
|
|
1129
|
+
let node = __privateMethod(this, _FormulaParser_instances, parsePostfix_fn).call(this);
|
|
1011
1130
|
while (__privateGet(this, _FormulaParser_instances, current_get).type === 14 /* Percent */) {
|
|
1012
1131
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1013
1132
|
node = new UnaryOpNode(2 /* Percent */, node);
|
|
1014
1133
|
}
|
|
1015
1134
|
return node;
|
|
1016
1135
|
};
|
|
1136
|
+
/** Handles postfix call syntax on an expression, e.g. LAMBDA(x,x+1)(5). */
|
|
1137
|
+
parsePostfix_fn = function() {
|
|
1138
|
+
let node = __privateMethod(this, _FormulaParser_instances, parsePrimary_fn).call(this);
|
|
1139
|
+
while (__privateGet(this, _FormulaParser_instances, current_get).type === 6 /* LeftParen */) {
|
|
1140
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1141
|
+
node = new CallNode(node, __privateMethod(this, _FormulaParser_instances, parseArgList_fn).call(this));
|
|
1142
|
+
}
|
|
1143
|
+
return node;
|
|
1144
|
+
};
|
|
1017
1145
|
parsePrimary_fn = function() {
|
|
1018
1146
|
const token = __privateGet(this, _FormulaParser_instances, current_get);
|
|
1019
1147
|
switch (token.type) {
|
|
@@ -1029,7 +1157,7 @@ parsePrimary_fn = function() {
|
|
|
1029
1157
|
case 2 /* Boolean */:
|
|
1030
1158
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1031
1159
|
return new BooleanNode(token.value === "TRUE");
|
|
1032
|
-
case
|
|
1160
|
+
case 27 /* ErrorLiteral */:
|
|
1033
1161
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1034
1162
|
return new ErrorNode(FormulaValue.error(FormulaValue.errorFromString(token.value)));
|
|
1035
1163
|
case 3 /* CellReference */: {
|
|
@@ -1056,6 +1184,9 @@ parsePrimary_fn = function() {
|
|
|
1056
1184
|
case 23 /* NamedRange */:
|
|
1057
1185
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1058
1186
|
return new NamedRangeNode(token.value);
|
|
1187
|
+
case 24 /* SpillReference */:
|
|
1188
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1189
|
+
return new SpillReferenceNode(token.value);
|
|
1059
1190
|
case 5 /* Function */:
|
|
1060
1191
|
return __privateMethod(this, _FormulaParser_instances, parseFunction_fn).call(this);
|
|
1061
1192
|
case 6 /* LeftParen */: {
|
|
@@ -1073,6 +1204,10 @@ parsePrimary_fn = function() {
|
|
|
1073
1204
|
parseFunction_fn = function() {
|
|
1074
1205
|
const nameToken = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1075
1206
|
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 6 /* LeftParen */);
|
|
1207
|
+
return new FunctionCallNode(nameToken.value.toUpperCase(), __privateMethod(this, _FormulaParser_instances, parseArgList_fn).call(this));
|
|
1208
|
+
};
|
|
1209
|
+
/** Parses a comma-separated argument list up to and including the ')'. */
|
|
1210
|
+
parseArgList_fn = function() {
|
|
1076
1211
|
const args = [];
|
|
1077
1212
|
if (__privateGet(this, _FormulaParser_instances, current_get).type !== 7 /* RightParen */) {
|
|
1078
1213
|
if (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */) {
|
|
@@ -1090,7 +1225,7 @@ parseFunction_fn = function() {
|
|
|
1090
1225
|
}
|
|
1091
1226
|
}
|
|
1092
1227
|
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 7 /* RightParen */);
|
|
1093
|
-
return
|
|
1228
|
+
return args;
|
|
1094
1229
|
};
|
|
1095
1230
|
function comparisonOp(type) {
|
|
1096
1231
|
switch (type) {
|
|
@@ -1121,10 +1256,14 @@ function registerMath(r) {
|
|
|
1121
1256
|
r.register("COUNTA", countA, 1);
|
|
1122
1257
|
r.register("COUNTBLANK", countBlank, 1, 1);
|
|
1123
1258
|
r.register("COUNTIF", countIf, 2, 2);
|
|
1259
|
+
r.register("COUNTIFS", countIfs, 2);
|
|
1124
1260
|
r.register("SUMIF", sumIf, 2, 3);
|
|
1261
|
+
r.register("SUMIFS", sumIfs, 3);
|
|
1125
1262
|
r.register("AVERAGEIF", averageIf, 2, 3);
|
|
1263
|
+
r.register("AVERAGEIFS", averageIfs, 3);
|
|
1126
1264
|
r.register("PRODUCT", product, 1);
|
|
1127
1265
|
r.register("SUMPRODUCT", sumProduct, 1);
|
|
1266
|
+
r.register("SUMSQ", sumSq, 1);
|
|
1128
1267
|
r.register("ABS", abs, 1, 1);
|
|
1129
1268
|
r.register("ROUND", round, 2, 2);
|
|
1130
1269
|
r.register("ROUNDUP", roundUp, 2, 2);
|
|
@@ -1149,6 +1288,114 @@ function registerMath(r) {
|
|
|
1149
1288
|
r.register("EXP", exp, 1, 1);
|
|
1150
1289
|
r.register("FACT", fact, 1, 1);
|
|
1151
1290
|
r.register("COMBIN", combin, 2, 2);
|
|
1291
|
+
r.register("SIN", unary(Math.sin), 1, 1);
|
|
1292
|
+
r.register("COS", unary(Math.cos), 1, 1);
|
|
1293
|
+
r.register("TAN", unary(Math.tan), 1, 1);
|
|
1294
|
+
r.register("ASIN", asin, 1, 1);
|
|
1295
|
+
r.register("ACOS", acos, 1, 1);
|
|
1296
|
+
r.register("ATAN", unary(Math.atan), 1, 1);
|
|
1297
|
+
r.register("ATAN2", atan2, 2, 2);
|
|
1298
|
+
r.register("SINH", unary(Math.sinh), 1, 1);
|
|
1299
|
+
r.register("COSH", unary(Math.cosh), 1, 1);
|
|
1300
|
+
r.register("TANH", unary(Math.tanh), 1, 1);
|
|
1301
|
+
r.register("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
|
|
1302
|
+
r.register("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
|
|
1303
|
+
}
|
|
1304
|
+
function valueAt(v, i) {
|
|
1305
|
+
return v.isArray ? v.arrayVal.getFlat(i) : v;
|
|
1306
|
+
}
|
|
1307
|
+
function lengthOf(v) {
|
|
1308
|
+
return v.isArray ? v.arrayVal.length : 1;
|
|
1309
|
+
}
|
|
1310
|
+
function computeIfsMask(a, c, startIdx, len2) {
|
|
1311
|
+
const mask = new Uint8Array(len2).fill(1);
|
|
1312
|
+
for (let p = startIdx; p + 1 < a.length; p += 2) {
|
|
1313
|
+
const cr = a[p].evaluate(c);
|
|
1314
|
+
if (cr.isError) return { ok: false, error: cr };
|
|
1315
|
+
const crit = a[p + 1].evaluate(c);
|
|
1316
|
+
if (crit.isError) return { ok: false, error: crit };
|
|
1317
|
+
const critText = crit.asText();
|
|
1318
|
+
for (let i = 0; i < len2; i++) {
|
|
1319
|
+
if (!mask[i]) continue;
|
|
1320
|
+
if (!exports.FormulaHelper.matchesCriteria(valueAt(cr, i), critText)) mask[i] = 0;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
return { ok: true, mask };
|
|
1324
|
+
}
|
|
1325
|
+
function sumIfs(a, c) {
|
|
1326
|
+
if (a.length < 3 || a.length % 2 === 0) return FormulaValue.errorValue;
|
|
1327
|
+
const sumRange = a[0].evaluate(c);
|
|
1328
|
+
if (sumRange.isError) return sumRange;
|
|
1329
|
+
const len2 = lengthOf(sumRange);
|
|
1330
|
+
const m = computeIfsMask(a, c, 1, len2);
|
|
1331
|
+
if (!m.ok) return m.error;
|
|
1332
|
+
let total = 0;
|
|
1333
|
+
for (let i = 0; i < len2; i++) {
|
|
1334
|
+
if (!m.mask[i]) continue;
|
|
1335
|
+
const r = valueAt(sumRange, i).tryAsDouble();
|
|
1336
|
+
if (r.ok) total += r.value;
|
|
1337
|
+
}
|
|
1338
|
+
return FormulaValue.number(total);
|
|
1339
|
+
}
|
|
1340
|
+
function countIfs(a, c) {
|
|
1341
|
+
if (a.length < 2 || a.length % 2 !== 0) return FormulaValue.errorValue;
|
|
1342
|
+
const first = a[0].evaluate(c);
|
|
1343
|
+
if (first.isError) return first;
|
|
1344
|
+
const len2 = lengthOf(first);
|
|
1345
|
+
const m = computeIfsMask(a, c, 0, len2);
|
|
1346
|
+
if (!m.ok) return m.error;
|
|
1347
|
+
let n = 0;
|
|
1348
|
+
for (let i = 0; i < len2; i++) if (m.mask[i]) n++;
|
|
1349
|
+
return FormulaValue.number(n);
|
|
1350
|
+
}
|
|
1351
|
+
function averageIfs(a, c) {
|
|
1352
|
+
if (a.length < 3 || a.length % 2 === 0) return FormulaValue.errorValue;
|
|
1353
|
+
const avgRange = a[0].evaluate(c);
|
|
1354
|
+
if (avgRange.isError) return avgRange;
|
|
1355
|
+
const len2 = lengthOf(avgRange);
|
|
1356
|
+
const m = computeIfsMask(a, c, 1, len2);
|
|
1357
|
+
if (!m.ok) return m.error;
|
|
1358
|
+
let total = 0, cnt = 0;
|
|
1359
|
+
for (let i = 0; i < len2; i++) {
|
|
1360
|
+
if (!m.mask[i]) continue;
|
|
1361
|
+
const r = valueAt(avgRange, i).tryAsDouble();
|
|
1362
|
+
if (r.ok) {
|
|
1363
|
+
total += r.value;
|
|
1364
|
+
cnt++;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return cnt === 0 ? FormulaValue.errorDiv0 : FormulaValue.number(total / cnt);
|
|
1368
|
+
}
|
|
1369
|
+
function sumSq(a, c) {
|
|
1370
|
+
const r = exports.FormulaHelper.collectNumbers(a, c);
|
|
1371
|
+
if (!r.ok) return r.error;
|
|
1372
|
+
return FormulaValue.number(r.values.reduce((s, n) => s + n * n, 0));
|
|
1373
|
+
}
|
|
1374
|
+
function unary(fn) {
|
|
1375
|
+
return (a, c) => {
|
|
1376
|
+
const r = exports.FormulaHelper.evalDouble(a[0], c);
|
|
1377
|
+
return r.ok ? FormulaValue.number(fn(r.value)) : r.error;
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
function asin(a, c) {
|
|
1381
|
+
const r = exports.FormulaHelper.evalDouble(a[0], c);
|
|
1382
|
+
if (!r.ok) return r.error;
|
|
1383
|
+
if (r.value < -1 || r.value > 1) return FormulaValue.errorNum;
|
|
1384
|
+
return FormulaValue.number(Math.asin(r.value));
|
|
1385
|
+
}
|
|
1386
|
+
function acos(a, c) {
|
|
1387
|
+
const r = exports.FormulaHelper.evalDouble(a[0], c);
|
|
1388
|
+
if (!r.ok) return r.error;
|
|
1389
|
+
if (r.value < -1 || r.value > 1) return FormulaValue.errorNum;
|
|
1390
|
+
return FormulaValue.number(Math.acos(r.value));
|
|
1391
|
+
}
|
|
1392
|
+
function atan2(a, c) {
|
|
1393
|
+
const x = exports.FormulaHelper.evalDouble(a[0], c);
|
|
1394
|
+
if (!x.ok) return x.error;
|
|
1395
|
+
const y = exports.FormulaHelper.evalDouble(a[1], c);
|
|
1396
|
+
if (!y.ok) return y.error;
|
|
1397
|
+
if (x.value === 0 && y.value === 0) return FormulaValue.errorDiv0;
|
|
1398
|
+
return FormulaValue.number(Math.atan2(y.value, x.value));
|
|
1152
1399
|
}
|
|
1153
1400
|
function sum(a, c) {
|
|
1154
1401
|
const r = exports.FormulaHelper.collectNumbers(a, c);
|
|
@@ -1267,7 +1514,7 @@ function product(a, c) {
|
|
|
1267
1514
|
return FormulaValue.number(r.values.reduce((p, n) => p * n, 1));
|
|
1268
1515
|
}
|
|
1269
1516
|
function sumProduct(a, c) {
|
|
1270
|
-
const arrays = a.map((
|
|
1517
|
+
const arrays = a.map((arg2) => arg2.evaluate(c));
|
|
1271
1518
|
for (const v of arrays) if (v.isError) return v;
|
|
1272
1519
|
const first = arrays[0];
|
|
1273
1520
|
const len2 = first.isArray ? first.arrayVal.length : 1;
|
|
@@ -1468,6 +1715,8 @@ function registerText(r) {
|
|
|
1468
1715
|
r.register("CHAR", char_, 1, 1);
|
|
1469
1716
|
r.register("CODE", code, 1, 1);
|
|
1470
1717
|
r.register("T", t_, 1, 1);
|
|
1718
|
+
r.register("TEXTBEFORE", textBefore, 2, 3);
|
|
1719
|
+
r.register("TEXTAFTER", textAfter, 2, 3);
|
|
1471
1720
|
}
|
|
1472
1721
|
function str(a, c, idx) {
|
|
1473
1722
|
const r = exports.FormulaHelper.evalString(a[idx], c);
|
|
@@ -1596,8 +1845,8 @@ function replace(a, c) {
|
|
|
1596
1845
|
}
|
|
1597
1846
|
function concatenate(a, c) {
|
|
1598
1847
|
let result = "";
|
|
1599
|
-
for (const
|
|
1600
|
-
const v =
|
|
1848
|
+
for (const arg2 of a) {
|
|
1849
|
+
const v = arg2.evaluate(c);
|
|
1601
1850
|
if (v.isError) return v;
|
|
1602
1851
|
result += v.asText();
|
|
1603
1852
|
}
|
|
@@ -1724,6 +1973,45 @@ function t_(a, c) {
|
|
|
1724
1973
|
const v = a[0].evaluate(c);
|
|
1725
1974
|
return v.isText ? v : FormulaValue.emptyString;
|
|
1726
1975
|
}
|
|
1976
|
+
function textBefore(a, c) {
|
|
1977
|
+
return textSplitAt(a, c, "before");
|
|
1978
|
+
}
|
|
1979
|
+
function textAfter(a, c) {
|
|
1980
|
+
return textSplitAt(a, c, "after");
|
|
1981
|
+
}
|
|
1982
|
+
function textSplitAt(a, c, mode2) {
|
|
1983
|
+
const s = str(a, c, 0);
|
|
1984
|
+
if (!s.ok) return s.e;
|
|
1985
|
+
const d = str(a, c, 1);
|
|
1986
|
+
if (!d.ok) return d.e;
|
|
1987
|
+
let instance = 1;
|
|
1988
|
+
if (a.length > 2) {
|
|
1989
|
+
const r = num(a, c, 2);
|
|
1990
|
+
if (!r.ok) return r.e;
|
|
1991
|
+
instance = Math.trunc(r.n);
|
|
1992
|
+
}
|
|
1993
|
+
if (instance === 0) return FormulaValue.errorValue;
|
|
1994
|
+
const text = s.s;
|
|
1995
|
+
const delim = d.s;
|
|
1996
|
+
if (delim === "") {
|
|
1997
|
+
return FormulaValue.text(mode2 === "before" ? "" : text);
|
|
1998
|
+
}
|
|
1999
|
+
const positions = [];
|
|
2000
|
+
let from = 0;
|
|
2001
|
+
for (; ; ) {
|
|
2002
|
+
const idx = text.indexOf(delim, from);
|
|
2003
|
+
if (idx === -1) break;
|
|
2004
|
+
positions.push(idx);
|
|
2005
|
+
from = idx + delim.length;
|
|
2006
|
+
}
|
|
2007
|
+
if (positions.length === 0) return FormulaValue.errorNA;
|
|
2008
|
+
const nth = instance > 0 ? instance - 1 : positions.length + instance;
|
|
2009
|
+
if (nth < 0 || nth >= positions.length) return FormulaValue.errorNA;
|
|
2010
|
+
const pos = positions[nth];
|
|
2011
|
+
return FormulaValue.text(
|
|
2012
|
+
mode2 === "before" ? text.slice(0, pos) : text.slice(pos + delim.length)
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
1727
2015
|
|
|
1728
2016
|
// src/functions/logical-functions.ts
|
|
1729
2017
|
function registerLogical(r) {
|
|
@@ -1748,8 +2036,8 @@ function if_(a, c) {
|
|
|
1748
2036
|
}
|
|
1749
2037
|
function and(a, c) {
|
|
1750
2038
|
let hasValue = false;
|
|
1751
|
-
for (const
|
|
1752
|
-
const v =
|
|
2039
|
+
for (const arg2 of a) {
|
|
2040
|
+
const v = arg2.evaluate(c);
|
|
1753
2041
|
if (v.isError) return v;
|
|
1754
2042
|
if (v.isArray) {
|
|
1755
2043
|
for (const item of v.arrayVal.values()) {
|
|
@@ -1775,8 +2063,8 @@ function and(a, c) {
|
|
|
1775
2063
|
}
|
|
1776
2064
|
function or(a, c) {
|
|
1777
2065
|
let hasValue = false;
|
|
1778
|
-
for (const
|
|
1779
|
-
const v =
|
|
2066
|
+
for (const arg2 of a) {
|
|
2067
|
+
const v = arg2.evaluate(c);
|
|
1780
2068
|
if (v.isError) return v;
|
|
1781
2069
|
if (v.isArray) {
|
|
1782
2070
|
for (const item of v.arrayVal.values()) {
|
|
@@ -1809,8 +2097,8 @@ function not(a, c) {
|
|
|
1809
2097
|
}
|
|
1810
2098
|
function xor(a, c) {
|
|
1811
2099
|
let trueCount = 0;
|
|
1812
|
-
for (const
|
|
1813
|
-
const v =
|
|
2100
|
+
for (const arg2 of a) {
|
|
2101
|
+
const v = arg2.evaluate(c);
|
|
1814
2102
|
if (v.isError) return v;
|
|
1815
2103
|
const bv = v.coerceToBool();
|
|
1816
2104
|
if (bv.isError) return bv;
|
|
@@ -2070,14 +2358,18 @@ function datedif(a, c) {
|
|
|
2070
2358
|
function registerLookup(r) {
|
|
2071
2359
|
r.register("VLOOKUP", vlookup, 3, 4);
|
|
2072
2360
|
r.register("HLOOKUP", hlookup, 3, 4);
|
|
2361
|
+
r.register("XLOOKUP", xlookup, 3, 6);
|
|
2362
|
+
r.register("LOOKUP", lookup, 2, 3);
|
|
2073
2363
|
r.register("INDEX", index_, 2, 3);
|
|
2074
2364
|
r.register("MATCH", match, 2, 3);
|
|
2075
2365
|
r.register("CHOOSE", choose, 2);
|
|
2366
|
+
r.register("ADDRESS", address, 2, 5);
|
|
2076
2367
|
r.register("ROW", row, 0, 1);
|
|
2077
2368
|
r.register("COLUMN", column, 0, 1);
|
|
2078
2369
|
r.register("ROWS", rows, 1, 1);
|
|
2079
2370
|
r.register("COLUMNS", columns, 1, 1);
|
|
2080
2371
|
r.register("OFFSET", offset, 3, 5, true);
|
|
2372
|
+
r.register("INDIRECT", indirect, 1, 2, true);
|
|
2081
2373
|
}
|
|
2082
2374
|
function num2(a, c, i) {
|
|
2083
2375
|
const r = exports.FormulaHelper.evalDouble(a[i], c);
|
|
@@ -2143,6 +2435,104 @@ function hlookup(a, c) {
|
|
|
2143
2435
|
}
|
|
2144
2436
|
return best >= 0 ? tbl.get(rowIdx, best) : FormulaValue.errorNA;
|
|
2145
2437
|
}
|
|
2438
|
+
function wildcardRegex(pattern) {
|
|
2439
|
+
let out = "";
|
|
2440
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
2441
|
+
const ch = pattern[i];
|
|
2442
|
+
if (ch === "~" && i + 1 < pattern.length) {
|
|
2443
|
+
const next = pattern[++i];
|
|
2444
|
+
out += next.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2445
|
+
} else if (ch === "*") out += ".*";
|
|
2446
|
+
else if (ch === "?") out += ".";
|
|
2447
|
+
else out += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2448
|
+
}
|
|
2449
|
+
return new RegExp(`^${out}$`, "i");
|
|
2450
|
+
}
|
|
2451
|
+
function findMatch(n, get, lookupVal, matchMode, searchMode) {
|
|
2452
|
+
if ((searchMode === 2 || searchMode === -2) && matchMode !== 2) {
|
|
2453
|
+
const at = searchMode === 2 ? get : (j) => get(n - 1 - j);
|
|
2454
|
+
let lo = 0, hi = n - 1, lastLE = -1, firstGE = -1;
|
|
2455
|
+
while (lo <= hi) {
|
|
2456
|
+
const mid2 = lo + hi >>> 1;
|
|
2457
|
+
const cmp = FormulaValue.compare(at(mid2), lookupVal);
|
|
2458
|
+
if (cmp === 0) return searchMode === 2 ? mid2 : n - 1 - mid2;
|
|
2459
|
+
if (cmp < 0) {
|
|
2460
|
+
lastLE = mid2;
|
|
2461
|
+
lo = mid2 + 1;
|
|
2462
|
+
} else {
|
|
2463
|
+
firstGE = mid2;
|
|
2464
|
+
hi = mid2 - 1;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
const idx = matchMode === -1 ? lastLE : matchMode === 1 ? firstGE : -1;
|
|
2468
|
+
return idx < 0 ? -1 : searchMode === 2 ? idx : n - 1 - idx;
|
|
2469
|
+
}
|
|
2470
|
+
const reverse = searchMode === -1;
|
|
2471
|
+
const order = reverse ? Array.from({ length: n }, (_, k) => n - 1 - k) : Array.from({ length: n }, (_, k) => k);
|
|
2472
|
+
if (matchMode === 2) {
|
|
2473
|
+
const re = wildcardRegex(lookupVal.asText());
|
|
2474
|
+
for (const i of order) if (re.test(get(i).asText())) return i;
|
|
2475
|
+
return -1;
|
|
2476
|
+
}
|
|
2477
|
+
if (matchMode === 0) {
|
|
2478
|
+
for (const i of order) if (FormulaValue.areEqual(get(i), lookupVal)) return i;
|
|
2479
|
+
return -1;
|
|
2480
|
+
}
|
|
2481
|
+
let best = -1;
|
|
2482
|
+
let bestVal = null;
|
|
2483
|
+
for (const i of order) {
|
|
2484
|
+
const v = get(i);
|
|
2485
|
+
const cmp = FormulaValue.compare(v, lookupVal);
|
|
2486
|
+
if (cmp === 0) return i;
|
|
2487
|
+
if (matchMode === -1 && cmp < 0) {
|
|
2488
|
+
if (bestVal === null || FormulaValue.compare(v, bestVal) > 0) {
|
|
2489
|
+
best = i;
|
|
2490
|
+
bestVal = v;
|
|
2491
|
+
}
|
|
2492
|
+
} else if (matchMode === 1 && cmp > 0) {
|
|
2493
|
+
if (bestVal === null || FormulaValue.compare(v, bestVal) < 0) {
|
|
2494
|
+
best = i;
|
|
2495
|
+
bestVal = v;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
return best;
|
|
2500
|
+
}
|
|
2501
|
+
function xlookup(a, c) {
|
|
2502
|
+
const lookupVal = a[0].evaluate(c);
|
|
2503
|
+
if (lookupVal.isError) return lookupVal;
|
|
2504
|
+
const lookupRaw = a[1].evaluate(c);
|
|
2505
|
+
if (lookupRaw.isError) return lookupRaw;
|
|
2506
|
+
const returnRaw = a[2].evaluate(c);
|
|
2507
|
+
if (returnRaw.isError) return returnRaw;
|
|
2508
|
+
const matchMode = a.length > 4 ? num2(a, c, 4) : { ok: true, n: 0 };
|
|
2509
|
+
if (!matchMode.ok) return matchMode.e;
|
|
2510
|
+
const searchMode = a.length > 5 ? num2(a, c, 5) : { ok: true, n: 1 };
|
|
2511
|
+
if (!searchMode.ok) return searchMode.e;
|
|
2512
|
+
const mm = Math.trunc(matchMode.n), sm = Math.trunc(searchMode.n);
|
|
2513
|
+
const la = lookupRaw.isArray ? lookupRaw.arrayVal : ArrayValue.fromScalar(lookupRaw);
|
|
2514
|
+
if (la.rows > 1 && la.columns > 1) return FormulaValue.errorValue;
|
|
2515
|
+
const vertical = !(la.rows === 1 && la.columns > 1);
|
|
2516
|
+
const n = vertical ? la.rows : la.columns;
|
|
2517
|
+
const get = vertical ? (i) => la.get(i, 0) : (i) => la.get(0, i);
|
|
2518
|
+
const idx = findMatch(n, get, lookupVal, mm, sm);
|
|
2519
|
+
if (idx < 0) {
|
|
2520
|
+
return a.length > 3 ? a[3].evaluate(c) : FormulaValue.errorNA;
|
|
2521
|
+
}
|
|
2522
|
+
const ret = returnRaw.isArray ? returnRaw.arrayVal : ArrayValue.fromScalar(returnRaw);
|
|
2523
|
+
if (vertical) {
|
|
2524
|
+
if (ret.rows !== n) return FormulaValue.errorValue;
|
|
2525
|
+
if (ret.columns === 1) return ret.get(idx, 0);
|
|
2526
|
+
const out2 = new ArrayValue(1, ret.columns);
|
|
2527
|
+
for (let col = 0; col < ret.columns; col++) out2.set(0, col, ret.get(idx, col));
|
|
2528
|
+
return FormulaValue.array(out2);
|
|
2529
|
+
}
|
|
2530
|
+
if (ret.columns !== n) return FormulaValue.errorValue;
|
|
2531
|
+
if (ret.rows === 1) return ret.get(0, idx);
|
|
2532
|
+
const out = new ArrayValue(ret.rows, 1);
|
|
2533
|
+
for (let r = 0; r < ret.rows; r++) out.set(r, 0, ret.get(r, idx));
|
|
2534
|
+
return FormulaValue.array(out);
|
|
2535
|
+
}
|
|
2146
2536
|
function index_(a, c) {
|
|
2147
2537
|
const arrVal = a[0].evaluate(c);
|
|
2148
2538
|
if (arrVal.isError) return arrVal;
|
|
@@ -2206,6 +2596,86 @@ function choose(a, c) {
|
|
|
2206
2596
|
if (idx < 1 || idx >= a.length) return FormulaValue.errorValue;
|
|
2207
2597
|
return a[idx].evaluate(c);
|
|
2208
2598
|
}
|
|
2599
|
+
function approxMatchFlat(arr, lookupVal) {
|
|
2600
|
+
let best = -1;
|
|
2601
|
+
for (let i = 0; i < arr.length; i++) {
|
|
2602
|
+
if (FormulaValue.compare(arr.getFlat(i), lookupVal) <= 0) best = i;
|
|
2603
|
+
else break;
|
|
2604
|
+
}
|
|
2605
|
+
return best;
|
|
2606
|
+
}
|
|
2607
|
+
function lookup(a, c) {
|
|
2608
|
+
const lookupVal = a[0].evaluate(c);
|
|
2609
|
+
if (lookupVal.isError) return lookupVal;
|
|
2610
|
+
const vecVal = a[1].evaluate(c);
|
|
2611
|
+
if (vecVal.isError) return vecVal;
|
|
2612
|
+
if (a.length > 2) {
|
|
2613
|
+
const resVal = a[2].evaluate(c);
|
|
2614
|
+
if (resVal.isError) return resVal;
|
|
2615
|
+
const vecArr = vecVal.isArray ? vecVal.arrayVal : ArrayValue.fromScalar(vecVal);
|
|
2616
|
+
const idx = approxMatchFlat(vecArr, lookupVal);
|
|
2617
|
+
if (idx < 0) return FormulaValue.errorNA;
|
|
2618
|
+
if (resVal.isArray) return resVal.arrayVal.getFlat(idx);
|
|
2619
|
+
return idx === 0 ? resVal : FormulaValue.errorNA;
|
|
2620
|
+
}
|
|
2621
|
+
if (!vecVal.isArray) {
|
|
2622
|
+
return FormulaValue.areEqual(vecVal, lookupVal) ? vecVal : FormulaValue.errorNA;
|
|
2623
|
+
}
|
|
2624
|
+
const arr = vecVal.arrayVal;
|
|
2625
|
+
if (arr.columns >= arr.rows) {
|
|
2626
|
+
let best2 = -1;
|
|
2627
|
+
for (let col = 0; col < arr.columns; col++) {
|
|
2628
|
+
if (FormulaValue.compare(arr.get(0, col), lookupVal) <= 0) best2 = col;
|
|
2629
|
+
else break;
|
|
2630
|
+
}
|
|
2631
|
+
return best2 >= 0 ? arr.get(arr.rows - 1, best2) : FormulaValue.errorNA;
|
|
2632
|
+
}
|
|
2633
|
+
let best = -1;
|
|
2634
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
2635
|
+
if (FormulaValue.compare(arr.get(r, 0), lookupVal) <= 0) best = r;
|
|
2636
|
+
else break;
|
|
2637
|
+
}
|
|
2638
|
+
return best >= 0 ? arr.get(best, arr.columns - 1) : FormulaValue.errorNA;
|
|
2639
|
+
}
|
|
2640
|
+
function address(a, c) {
|
|
2641
|
+
const rn = num2(a, c, 0);
|
|
2642
|
+
if (!rn.ok) return rn.e;
|
|
2643
|
+
const cn = num2(a, c, 1);
|
|
2644
|
+
if (!cn.ok) return cn.e;
|
|
2645
|
+
const row2 = Math.round(rn.n);
|
|
2646
|
+
const col = Math.round(cn.n);
|
|
2647
|
+
if (row2 < 1 || col < 1) return FormulaValue.errorValue;
|
|
2648
|
+
let absNum = 1;
|
|
2649
|
+
if (a.length > 2) {
|
|
2650
|
+
const r = num2(a, c, 2);
|
|
2651
|
+
if (!r.ok) return r.e;
|
|
2652
|
+
absNum = Math.round(r.n);
|
|
2653
|
+
}
|
|
2654
|
+
if (absNum < 1 || absNum > 4) return FormulaValue.errorValue;
|
|
2655
|
+
let a1 = true;
|
|
2656
|
+
if (a.length > 3) {
|
|
2657
|
+
const v = a[3].evaluate(c);
|
|
2658
|
+
if (v.isError) return v;
|
|
2659
|
+
a1 = v.isBlank ? true : v.coerceToBool().booleanValue;
|
|
2660
|
+
}
|
|
2661
|
+
let sheetPrefix = "";
|
|
2662
|
+
if (a.length > 4) {
|
|
2663
|
+
const s = exports.FormulaHelper.evalString(a[4], c);
|
|
2664
|
+
if (!s.ok) return s.error;
|
|
2665
|
+
if (s.value !== "") sheetPrefix = `${s.value}!`;
|
|
2666
|
+
}
|
|
2667
|
+
const rowAbs = absNum === 1 || absNum === 2;
|
|
2668
|
+
const colAbs = absNum === 1 || absNum === 3;
|
|
2669
|
+
let ref;
|
|
2670
|
+
if (a1) {
|
|
2671
|
+
ref = `${colAbs ? "$" : ""}${puredocsExcel.columnLetter(col)}${rowAbs ? "$" : ""}${row2}`;
|
|
2672
|
+
} else {
|
|
2673
|
+
const rPart = rowAbs ? `R${row2}` : `R[${row2}]`;
|
|
2674
|
+
const cPart = colAbs ? `C${col}` : `C[${col}]`;
|
|
2675
|
+
ref = `${rPart}${cPart}`;
|
|
2676
|
+
}
|
|
2677
|
+
return FormulaValue.text(`${sheetPrefix}${ref}`);
|
|
2678
|
+
}
|
|
2209
2679
|
function row(a, c) {
|
|
2210
2680
|
if (a.length === 0) return FormulaValue.number(c.formulaRow || 1);
|
|
2211
2681
|
const node = a[0];
|
|
@@ -2244,6 +2714,61 @@ function columns(a, c) {
|
|
|
2244
2714
|
const v = a[0].evaluate(c);
|
|
2245
2715
|
return v.isArray ? FormulaValue.number(v.arrayVal.columns) : FormulaValue.one;
|
|
2246
2716
|
}
|
|
2717
|
+
function indirect(a, c) {
|
|
2718
|
+
const s = exports.FormulaHelper.evalString(a[0], c);
|
|
2719
|
+
if (!s.ok) return s.error;
|
|
2720
|
+
let a1 = true;
|
|
2721
|
+
if (a.length > 1) {
|
|
2722
|
+
const v = a[1].evaluate(c);
|
|
2723
|
+
if (v.isError) return v;
|
|
2724
|
+
a1 = v.isBlank ? true : v.coerceToBool().booleanValue;
|
|
2725
|
+
}
|
|
2726
|
+
const text = s.value.trim();
|
|
2727
|
+
if (text === "") return FormulaValue.errorRef;
|
|
2728
|
+
let sheet;
|
|
2729
|
+
let body = text;
|
|
2730
|
+
const bang = text.lastIndexOf("!");
|
|
2731
|
+
if (bang >= 0) {
|
|
2732
|
+
sheet = text.slice(0, bang).replace(/^'|'$/g, "");
|
|
2733
|
+
body = text.slice(bang + 1);
|
|
2734
|
+
}
|
|
2735
|
+
const colon = body.indexOf(":");
|
|
2736
|
+
try {
|
|
2737
|
+
if (colon >= 0) {
|
|
2738
|
+
const start = toA1(body.slice(0, colon), a1, c);
|
|
2739
|
+
const end = toA1(body.slice(colon + 1), a1, c);
|
|
2740
|
+
if (start === null || end === null) return FormulaValue.errorRef;
|
|
2741
|
+
return sheet !== void 0 ? c.getSheetRangeValues(sheet, start, end) : c.getRangeValues(start, end);
|
|
2742
|
+
}
|
|
2743
|
+
const cell = toA1(body, a1, c);
|
|
2744
|
+
if (cell === null) return FormulaValue.errorRef;
|
|
2745
|
+
return sheet !== void 0 ? c.getSheetCellValue(sheet, cell) : c.getCellValue(cell);
|
|
2746
|
+
} catch {
|
|
2747
|
+
return FormulaValue.errorRef;
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
function toA1(ref, a1, c) {
|
|
2751
|
+
const r = ref.trim();
|
|
2752
|
+
if (a1) {
|
|
2753
|
+
try {
|
|
2754
|
+
const { row: row3, column: column2 } = puredocsExcel.parseCellRef(r);
|
|
2755
|
+
return puredocsExcel.cellRefFromRowCol(row3, column2);
|
|
2756
|
+
} catch {
|
|
2757
|
+
return null;
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
const m = /^R(\[-?\d+\]|\d+)?C(\[-?\d+\]|\d+)?$/i.exec(r);
|
|
2761
|
+
if (!m) return null;
|
|
2762
|
+
const axis = (part, base) => {
|
|
2763
|
+
if (part === void 0) return base;
|
|
2764
|
+
if (part.startsWith("[")) return base + parseInt(part.slice(1, -1), 10);
|
|
2765
|
+
return parseInt(part, 10);
|
|
2766
|
+
};
|
|
2767
|
+
const row2 = axis(m[1], c.formulaRow || 1);
|
|
2768
|
+
const col = axis(m[2], c.formulaCol || 1);
|
|
2769
|
+
if (row2 === null || col === null || row2 < 1 || col < 1) return null;
|
|
2770
|
+
return puredocsExcel.cellRefFromRowCol(row2, col);
|
|
2771
|
+
}
|
|
2247
2772
|
function offset(a, c) {
|
|
2248
2773
|
const baseNode = a[0];
|
|
2249
2774
|
let baseRef;
|
|
@@ -2300,8 +2825,8 @@ function registerStatistical(r) {
|
|
|
2300
2825
|
}
|
|
2301
2826
|
function variance(nums, population) {
|
|
2302
2827
|
const mean = nums.reduce((s, n) => s + n, 0) / nums.length;
|
|
2303
|
-
const
|
|
2304
|
-
return
|
|
2828
|
+
const sumSq2 = nums.reduce((s, n) => s + (n - mean) ** 2, 0);
|
|
2829
|
+
return sumSq2 / (population ? nums.length : nums.length - 1);
|
|
2305
2830
|
}
|
|
2306
2831
|
function numsFromArray(v) {
|
|
2307
2832
|
if (v.isArray) {
|
|
@@ -2534,6 +3059,834 @@ function errorType(a, c) {
|
|
|
2534
3059
|
return FormulaValue.number(map[v.errorCode] ?? 7);
|
|
2535
3060
|
}
|
|
2536
3061
|
|
|
3062
|
+
// src/functions/finance-functions.ts
|
|
3063
|
+
function registerFinance(r) {
|
|
3064
|
+
r.register("PMT", pmt, 3, 5);
|
|
3065
|
+
r.register("FV", fv, 3, 5);
|
|
3066
|
+
r.register("PV", pv, 3, 5);
|
|
3067
|
+
r.register("NPV", npv, 2);
|
|
3068
|
+
r.register("IRR", irr, 1, 2);
|
|
3069
|
+
r.register("RATE", rate, 3, 6);
|
|
3070
|
+
r.register("NPER", nper, 3, 5);
|
|
3071
|
+
r.register("XNPV", xnpv, 3, 3);
|
|
3072
|
+
r.register("XIRR", xirr, 2, 3);
|
|
3073
|
+
}
|
|
3074
|
+
function arg(a, c, idx, dflt) {
|
|
3075
|
+
if (idx >= a.length) return { ok: true, n: dflt };
|
|
3076
|
+
const r = exports.FormulaHelper.evalDouble(a[idx], c);
|
|
3077
|
+
return r.ok ? { ok: true, n: r.value } : { ok: false, e: r.error };
|
|
3078
|
+
}
|
|
3079
|
+
function pmt(a, c) {
|
|
3080
|
+
const rate2 = arg(a, c, 0, 0);
|
|
3081
|
+
if (!rate2.ok) return rate2.e;
|
|
3082
|
+
const nper2 = arg(a, c, 1, 0);
|
|
3083
|
+
if (!nper2.ok) return nper2.e;
|
|
3084
|
+
const pvv = arg(a, c, 2, 0);
|
|
3085
|
+
if (!pvv.ok) return pvv.e;
|
|
3086
|
+
const fvv = arg(a, c, 3, 0);
|
|
3087
|
+
if (!fvv.ok) return fvv.e;
|
|
3088
|
+
const type = arg(a, c, 4, 0);
|
|
3089
|
+
if (!type.ok) return type.e;
|
|
3090
|
+
const r = rate2.n, n = nper2.n, present = pvv.n, future = fvv.n, t = type.n;
|
|
3091
|
+
if (n === 0) return FormulaValue.errorNum;
|
|
3092
|
+
if (r === 0) return FormulaValue.number(-(present + future) / n);
|
|
3093
|
+
const pow = Math.pow(1 + r, n);
|
|
3094
|
+
let payment = (-future - present * pow) * r / (pow - 1);
|
|
3095
|
+
if (t === 1) payment /= 1 + r;
|
|
3096
|
+
return FormulaValue.number(payment);
|
|
3097
|
+
}
|
|
3098
|
+
function fv(a, c) {
|
|
3099
|
+
const rate2 = arg(a, c, 0, 0);
|
|
3100
|
+
if (!rate2.ok) return rate2.e;
|
|
3101
|
+
const nper2 = arg(a, c, 1, 0);
|
|
3102
|
+
if (!nper2.ok) return nper2.e;
|
|
3103
|
+
const pmtv = arg(a, c, 2, 0);
|
|
3104
|
+
if (!pmtv.ok) return pmtv.e;
|
|
3105
|
+
const pvv = arg(a, c, 3, 0);
|
|
3106
|
+
if (!pvv.ok) return pvv.e;
|
|
3107
|
+
const type = arg(a, c, 4, 0);
|
|
3108
|
+
if (!type.ok) return type.e;
|
|
3109
|
+
const r = rate2.n, n = nper2.n, payment = pmtv.n, present = pvv.n, t = type.n;
|
|
3110
|
+
if (r === 0) return FormulaValue.number(-(present + payment * n));
|
|
3111
|
+
const pow = Math.pow(1 + r, n);
|
|
3112
|
+
return FormulaValue.number(-(present * pow + payment * (1 + r * t) * (pow - 1) / r));
|
|
3113
|
+
}
|
|
3114
|
+
function pv(a, c) {
|
|
3115
|
+
const rate2 = arg(a, c, 0, 0);
|
|
3116
|
+
if (!rate2.ok) return rate2.e;
|
|
3117
|
+
const nper2 = arg(a, c, 1, 0);
|
|
3118
|
+
if (!nper2.ok) return nper2.e;
|
|
3119
|
+
const pmtv = arg(a, c, 2, 0);
|
|
3120
|
+
if (!pmtv.ok) return pmtv.e;
|
|
3121
|
+
const fvv = arg(a, c, 3, 0);
|
|
3122
|
+
if (!fvv.ok) return fvv.e;
|
|
3123
|
+
const type = arg(a, c, 4, 0);
|
|
3124
|
+
if (!type.ok) return type.e;
|
|
3125
|
+
const r = rate2.n, n = nper2.n, payment = pmtv.n, future = fvv.n, t = type.n;
|
|
3126
|
+
if (r === 0) return FormulaValue.number(-(future + payment * n));
|
|
3127
|
+
const pow = Math.pow(1 + r, n);
|
|
3128
|
+
return FormulaValue.number(-(future + payment * (1 + r * t) * (pow - 1) / r) / pow);
|
|
3129
|
+
}
|
|
3130
|
+
function npv(a, c) {
|
|
3131
|
+
const rate2 = exports.FormulaHelper.evalDouble(a[0], c);
|
|
3132
|
+
if (!rate2.ok) return rate2.error;
|
|
3133
|
+
if (rate2.value === -1) return FormulaValue.errorDiv0;
|
|
3134
|
+
const flows = exports.FormulaHelper.collectNumbers(a.slice(1), c);
|
|
3135
|
+
if (!flows.ok) return flows.error;
|
|
3136
|
+
let total = 0;
|
|
3137
|
+
for (let i = 0; i < flows.values.length; i++) {
|
|
3138
|
+
total += flows.values[i] / Math.pow(1 + rate2.value, i + 1);
|
|
3139
|
+
}
|
|
3140
|
+
return FormulaValue.number(total);
|
|
3141
|
+
}
|
|
3142
|
+
function irr(a, c) {
|
|
3143
|
+
const flowsRes = exports.FormulaHelper.collectNumbers([a[0]], c);
|
|
3144
|
+
if (!flowsRes.ok) return flowsRes.error;
|
|
3145
|
+
const flows = flowsRes.values;
|
|
3146
|
+
if (flows.length < 2) return FormulaValue.errorNum;
|
|
3147
|
+
let guess = 0.1;
|
|
3148
|
+
if (a.length > 1) {
|
|
3149
|
+
const g = exports.FormulaHelper.evalDouble(a[1], c);
|
|
3150
|
+
if (!g.ok) return g.error;
|
|
3151
|
+
guess = g.value;
|
|
3152
|
+
}
|
|
3153
|
+
const npvAt = (rate3) => {
|
|
3154
|
+
let sum2 = 0;
|
|
3155
|
+
for (let i = 0; i < flows.length; i++) sum2 += flows[i] / Math.pow(1 + rate3, i);
|
|
3156
|
+
return sum2;
|
|
3157
|
+
};
|
|
3158
|
+
let rate2 = guess;
|
|
3159
|
+
for (let iter = 0; iter < 100; iter++) {
|
|
3160
|
+
const f = npvAt(rate2);
|
|
3161
|
+
if (Math.abs(f) < 1e-7) return FormulaValue.number(rate2);
|
|
3162
|
+
const h = 1e-6;
|
|
3163
|
+
const deriv = (npvAt(rate2 + h) - f) / h;
|
|
3164
|
+
if (deriv === 0) break;
|
|
3165
|
+
const next = rate2 - f / deriv;
|
|
3166
|
+
if (!isFinite(next) || next <= -1) break;
|
|
3167
|
+
if (Math.abs(next - rate2) < 1e-9) {
|
|
3168
|
+
rate2 = next;
|
|
3169
|
+
break;
|
|
3170
|
+
}
|
|
3171
|
+
rate2 = next;
|
|
3172
|
+
}
|
|
3173
|
+
if (Math.abs(npvAt(rate2)) < 1e-6) return FormulaValue.number(rate2);
|
|
3174
|
+
return FormulaValue.errorNum;
|
|
3175
|
+
}
|
|
3176
|
+
function tvm(r, n, pmt2, pv2, fv2, type) {
|
|
3177
|
+
if (r === 0) return pv2 + pmt2 * n + fv2;
|
|
3178
|
+
const pow = Math.pow(1 + r, n);
|
|
3179
|
+
return pv2 * pow + pmt2 * (1 + r * type) * (pow - 1) / r + fv2;
|
|
3180
|
+
}
|
|
3181
|
+
function rate(a, c) {
|
|
3182
|
+
const nper2 = arg(a, c, 0, 0);
|
|
3183
|
+
if (!nper2.ok) return nper2.e;
|
|
3184
|
+
const pmtv = arg(a, c, 1, 0);
|
|
3185
|
+
if (!pmtv.ok) return pmtv.e;
|
|
3186
|
+
const pvv = arg(a, c, 2, 0);
|
|
3187
|
+
if (!pvv.ok) return pvv.e;
|
|
3188
|
+
const fvv = arg(a, c, 3, 0);
|
|
3189
|
+
if (!fvv.ok) return fvv.e;
|
|
3190
|
+
const type = arg(a, c, 4, 0);
|
|
3191
|
+
if (!type.ok) return type.e;
|
|
3192
|
+
const gs = arg(a, c, 5, 0.1);
|
|
3193
|
+
if (!gs.ok) return gs.e;
|
|
3194
|
+
const n = nper2.n, pmt2 = pmtv.n, pv2 = pvv.n, fv2 = fvv.n, t = type.n;
|
|
3195
|
+
if (n === 0) return FormulaValue.errorNum;
|
|
3196
|
+
let r = gs.n;
|
|
3197
|
+
for (let iter = 0; iter < 100; iter++) {
|
|
3198
|
+
const f = tvm(r, n, pmt2, pv2, fv2, t);
|
|
3199
|
+
if (Math.abs(f) < 1e-8) return FormulaValue.number(r);
|
|
3200
|
+
const h = 1e-6;
|
|
3201
|
+
const deriv = (tvm(r + h, n, pmt2, pv2, fv2, t) - f) / h;
|
|
3202
|
+
if (deriv === 0) break;
|
|
3203
|
+
const next = r - f / deriv;
|
|
3204
|
+
if (!isFinite(next) || next <= -1) break;
|
|
3205
|
+
if (Math.abs(next - r) < 1e-10) {
|
|
3206
|
+
r = next;
|
|
3207
|
+
break;
|
|
3208
|
+
}
|
|
3209
|
+
r = next;
|
|
3210
|
+
}
|
|
3211
|
+
if (Math.abs(tvm(r, n, pmt2, pv2, fv2, t)) < 1e-6) return FormulaValue.number(r);
|
|
3212
|
+
return FormulaValue.errorNum;
|
|
3213
|
+
}
|
|
3214
|
+
function nper(a, c) {
|
|
3215
|
+
const rate2 = arg(a, c, 0, 0);
|
|
3216
|
+
if (!rate2.ok) return rate2.e;
|
|
3217
|
+
const pmtv = arg(a, c, 1, 0);
|
|
3218
|
+
if (!pmtv.ok) return pmtv.e;
|
|
3219
|
+
const pvv = arg(a, c, 2, 0);
|
|
3220
|
+
if (!pvv.ok) return pvv.e;
|
|
3221
|
+
const fvv = arg(a, c, 3, 0);
|
|
3222
|
+
if (!fvv.ok) return fvv.e;
|
|
3223
|
+
const type = arg(a, c, 4, 0);
|
|
3224
|
+
if (!type.ok) return type.e;
|
|
3225
|
+
const r = rate2.n, pmt2 = pmtv.n, pv2 = pvv.n, fv2 = fvv.n, t = type.n;
|
|
3226
|
+
if (r === 0) {
|
|
3227
|
+
if (pmt2 === 0) return FormulaValue.errorNum;
|
|
3228
|
+
return FormulaValue.number(-(pv2 + fv2) / pmt2);
|
|
3229
|
+
}
|
|
3230
|
+
const adj = pmt2 * (1 + r * t);
|
|
3231
|
+
const num5 = adj - fv2 * r;
|
|
3232
|
+
const den = adj + pv2 * r;
|
|
3233
|
+
if (den === 0 || num5 / den <= 0) return FormulaValue.errorNum;
|
|
3234
|
+
const result = Math.log(num5 / den) / Math.log(1 + r);
|
|
3235
|
+
if (!isFinite(result)) return FormulaValue.errorNum;
|
|
3236
|
+
return FormulaValue.number(result);
|
|
3237
|
+
}
|
|
3238
|
+
function flatNumbers(node, c) {
|
|
3239
|
+
const v = node.evaluate(c);
|
|
3240
|
+
if (v.isError) return { ok: false, e: v };
|
|
3241
|
+
const out = [];
|
|
3242
|
+
const push = (item) => {
|
|
3243
|
+
if (item.isError) return item;
|
|
3244
|
+
const d = item.tryAsDouble();
|
|
3245
|
+
if (!d.ok) return FormulaValue.errorValue;
|
|
3246
|
+
out.push(d.value);
|
|
3247
|
+
return null;
|
|
3248
|
+
};
|
|
3249
|
+
if (v.isArray) {
|
|
3250
|
+
for (const item of v.arrayVal.values()) {
|
|
3251
|
+
const err = push(item);
|
|
3252
|
+
if (err) return { ok: false, e: err };
|
|
3253
|
+
}
|
|
3254
|
+
} else {
|
|
3255
|
+
const err = push(v);
|
|
3256
|
+
if (err) return { ok: false, e: err };
|
|
3257
|
+
}
|
|
3258
|
+
return { ok: true, values: out };
|
|
3259
|
+
}
|
|
3260
|
+
function xnpvAt(rate2, values, days2) {
|
|
3261
|
+
let sum2 = 0;
|
|
3262
|
+
for (let i = 0; i < values.length; i++) {
|
|
3263
|
+
sum2 += values[i] / Math.pow(1 + rate2, days2[i] / 365);
|
|
3264
|
+
}
|
|
3265
|
+
return sum2;
|
|
3266
|
+
}
|
|
3267
|
+
function xnpv(a, c) {
|
|
3268
|
+
const rateRes = exports.FormulaHelper.evalDouble(a[0], c);
|
|
3269
|
+
if (!rateRes.ok) return rateRes.error;
|
|
3270
|
+
const rate2 = rateRes.value;
|
|
3271
|
+
if (rate2 <= -1) return FormulaValue.errorNum;
|
|
3272
|
+
const valsRes = flatNumbers(a[1], c);
|
|
3273
|
+
if (!valsRes.ok) return valsRes.e;
|
|
3274
|
+
const datesRes = flatNumbers(a[2], c);
|
|
3275
|
+
if (!datesRes.ok) return datesRes.e;
|
|
3276
|
+
const values = valsRes.values;
|
|
3277
|
+
const dates = datesRes.values;
|
|
3278
|
+
if (values.length === 0 || values.length !== dates.length) return FormulaValue.errorNum;
|
|
3279
|
+
const base = Math.trunc(dates[0]);
|
|
3280
|
+
const days2 = dates.map((d) => Math.trunc(d) - base);
|
|
3281
|
+
if (days2.some((d) => d < 0)) return FormulaValue.errorNum;
|
|
3282
|
+
return FormulaValue.number(xnpvAt(rate2, values, days2));
|
|
3283
|
+
}
|
|
3284
|
+
function xirr(a, c) {
|
|
3285
|
+
const valsRes = flatNumbers(a[0], c);
|
|
3286
|
+
if (!valsRes.ok) return valsRes.e;
|
|
3287
|
+
const datesRes = flatNumbers(a[1], c);
|
|
3288
|
+
if (!datesRes.ok) return datesRes.e;
|
|
3289
|
+
const values = valsRes.values;
|
|
3290
|
+
const dates = datesRes.values;
|
|
3291
|
+
if (values.length < 2 || values.length !== dates.length) return FormulaValue.errorNum;
|
|
3292
|
+
let guess = 0.1;
|
|
3293
|
+
if (a.length > 2) {
|
|
3294
|
+
const g = exports.FormulaHelper.evalDouble(a[2], c);
|
|
3295
|
+
if (!g.ok) return g.error;
|
|
3296
|
+
guess = g.value;
|
|
3297
|
+
}
|
|
3298
|
+
const base = Math.trunc(dates[0]);
|
|
3299
|
+
const days2 = dates.map((d) => Math.trunc(d) - base);
|
|
3300
|
+
if (days2.some((d) => d < 0)) return FormulaValue.errorNum;
|
|
3301
|
+
const derivAt = (rate3) => {
|
|
3302
|
+
let sum2 = 0;
|
|
3303
|
+
for (let i = 0; i < values.length; i++) {
|
|
3304
|
+
const e = days2[i] / 365;
|
|
3305
|
+
sum2 += -e * values[i] / Math.pow(1 + rate3, e + 1);
|
|
3306
|
+
}
|
|
3307
|
+
return sum2;
|
|
3308
|
+
};
|
|
3309
|
+
let rate2 = guess;
|
|
3310
|
+
for (let iter = 0; iter < 100; iter++) {
|
|
3311
|
+
const f = xnpvAt(rate2, values, days2);
|
|
3312
|
+
if (Math.abs(f) < 1e-7) return FormulaValue.number(rate2);
|
|
3313
|
+
const deriv = derivAt(rate2);
|
|
3314
|
+
if (deriv === 0) break;
|
|
3315
|
+
const next = rate2 - f / deriv;
|
|
3316
|
+
if (!isFinite(next) || next <= -1) break;
|
|
3317
|
+
if (Math.abs(next - rate2) < 1e-9) {
|
|
3318
|
+
rate2 = next;
|
|
3319
|
+
break;
|
|
3320
|
+
}
|
|
3321
|
+
rate2 = next;
|
|
3322
|
+
}
|
|
3323
|
+
if (Math.abs(xnpvAt(rate2, values, days2)) < 1e-6) return FormulaValue.number(rate2);
|
|
3324
|
+
return FormulaValue.errorNum;
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
// src/functions/meta-functions.ts
|
|
3328
|
+
var MAX_TAIL_ITERATIONS = 1e7;
|
|
3329
|
+
function registerMeta(r) {
|
|
3330
|
+
r.register("LET", let_, 3);
|
|
3331
|
+
r.register("LAMBDA", lambda_, 1);
|
|
3332
|
+
r.register("MAP", map_, 2);
|
|
3333
|
+
r.register("REDUCE", reduce_, 3, 3);
|
|
3334
|
+
r.register("SCAN", scan_, 3, 3);
|
|
3335
|
+
r.register("BYROW", byRow, 2, 2);
|
|
3336
|
+
r.register("BYCOL", byCol, 2, 2);
|
|
3337
|
+
}
|
|
3338
|
+
var asArray = (v) => v.isArray ? v.arrayVal : ArrayValue.fromScalar(v);
|
|
3339
|
+
function let_(a, c) {
|
|
3340
|
+
if (a.length < 3 || a.length % 2 === 0) return FormulaValue.errorValue;
|
|
3341
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3342
|
+
c.pushScope(bindings);
|
|
3343
|
+
try {
|
|
3344
|
+
for (let i = 0; i < a.length - 1; i += 2) {
|
|
3345
|
+
const nameNode = a[i];
|
|
3346
|
+
if (!(nameNode instanceof NamedRangeNode)) return FormulaValue.errorValue;
|
|
3347
|
+
const value2 = a[i + 1].evaluate(c);
|
|
3348
|
+
if (value2.isError) return value2;
|
|
3349
|
+
bindings.set(nameNode.name.toUpperCase(), value2);
|
|
3350
|
+
}
|
|
3351
|
+
return a[a.length - 1].evaluate(c);
|
|
3352
|
+
} finally {
|
|
3353
|
+
c.popScope();
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
function lambda_(a, c) {
|
|
3357
|
+
const paramNodes = a.slice(0, -1);
|
|
3358
|
+
const body = a[a.length - 1];
|
|
3359
|
+
const params = [];
|
|
3360
|
+
for (const p of paramNodes) {
|
|
3361
|
+
if (!(p instanceof NamedRangeNode)) return FormulaValue.errorValue;
|
|
3362
|
+
params.push(p.name.toUpperCase());
|
|
3363
|
+
}
|
|
3364
|
+
const ctx = c;
|
|
3365
|
+
const lambda = {
|
|
3366
|
+
arity: params.length,
|
|
3367
|
+
// One tail-aware evaluation of the body: binds params, then evaluates in
|
|
3368
|
+
// tail mode so a self/other tail call surfaces as a TailCall.
|
|
3369
|
+
step(args) {
|
|
3370
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
3371
|
+
params.forEach((p, i) => bindings.set(p, args[i] ?? FormulaValue.blank));
|
|
3372
|
+
ctx.pushScope(bindings);
|
|
3373
|
+
try {
|
|
3374
|
+
return body.evaluateTail(ctx);
|
|
3375
|
+
} finally {
|
|
3376
|
+
ctx.popScope();
|
|
3377
|
+
}
|
|
3378
|
+
},
|
|
3379
|
+
// Run to completion. Tail calls iterate here (O(1) stack); only non-tail
|
|
3380
|
+
// recursion re-enters call() and is bounded by the recursion guard.
|
|
3381
|
+
call(args) {
|
|
3382
|
+
const guard = ctx.recursionGuard;
|
|
3383
|
+
if (guard) {
|
|
3384
|
+
if (guard.depth >= guard.max) return FormulaValue.errorNum;
|
|
3385
|
+
guard.depth++;
|
|
3386
|
+
}
|
|
3387
|
+
try {
|
|
3388
|
+
let current = lambda;
|
|
3389
|
+
let currentArgs = args;
|
|
3390
|
+
for (let iter = 0; ; iter++) {
|
|
3391
|
+
if (iter > MAX_TAIL_ITERATIONS) return FormulaValue.errorNum;
|
|
3392
|
+
const r = current.step ? current.step(currentArgs) : current.call(currentArgs);
|
|
3393
|
+
if (r instanceof TailCall) {
|
|
3394
|
+
current = r.lambda;
|
|
3395
|
+
currentArgs = r.args;
|
|
3396
|
+
continue;
|
|
3397
|
+
}
|
|
3398
|
+
return r;
|
|
3399
|
+
}
|
|
3400
|
+
} finally {
|
|
3401
|
+
if (guard) guard.depth--;
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
};
|
|
3405
|
+
return FormulaValue.lambda(lambda);
|
|
3406
|
+
}
|
|
3407
|
+
function lambdaArg(node, c) {
|
|
3408
|
+
const v = node.evaluate(c);
|
|
3409
|
+
if (v.isError) return { ok: false, e: v };
|
|
3410
|
+
if (!v.isLambda) return { ok: false, e: FormulaValue.errorValue };
|
|
3411
|
+
return { ok: true, fn: v.lambdaVal };
|
|
3412
|
+
}
|
|
3413
|
+
function map_(a, c) {
|
|
3414
|
+
const fn = lambdaArg(a[a.length - 1], c);
|
|
3415
|
+
if (!fn.ok) return fn.e;
|
|
3416
|
+
const arrays = a.slice(0, -1).map((n) => asArray(n.evaluate(c)));
|
|
3417
|
+
if (arrays.length === 0) return FormulaValue.errorValue;
|
|
3418
|
+
const rows2 = arrays[0].rows, cols = arrays[0].columns;
|
|
3419
|
+
const out = new ArrayValue(rows2, cols);
|
|
3420
|
+
for (let r = 0; r < rows2; r++) {
|
|
3421
|
+
for (let col = 0; col < cols; col++) {
|
|
3422
|
+
out.set(r, col, fn.fn.call(arrays.map((ar) => ar.get(r, col))));
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
return FormulaValue.array(out);
|
|
3426
|
+
}
|
|
3427
|
+
function reduce_(a, c) {
|
|
3428
|
+
let acc = a[0].evaluate(c);
|
|
3429
|
+
if (acc.isError) return acc;
|
|
3430
|
+
const arr = asArray(a[1].evaluate(c));
|
|
3431
|
+
const fn = lambdaArg(a[2], c);
|
|
3432
|
+
if (!fn.ok) return fn.e;
|
|
3433
|
+
for (const v of arr.values()) {
|
|
3434
|
+
acc = fn.fn.call([acc, v]);
|
|
3435
|
+
if (acc.isError) return acc;
|
|
3436
|
+
}
|
|
3437
|
+
return acc;
|
|
3438
|
+
}
|
|
3439
|
+
function scan_(a, c) {
|
|
3440
|
+
let acc = a[0].evaluate(c);
|
|
3441
|
+
if (acc.isError) return acc;
|
|
3442
|
+
const arr = asArray(a[1].evaluate(c));
|
|
3443
|
+
const fn = lambdaArg(a[2], c);
|
|
3444
|
+
if (!fn.ok) return fn.e;
|
|
3445
|
+
const out = new ArrayValue(arr.rows, arr.columns);
|
|
3446
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
3447
|
+
for (let col = 0; col < arr.columns; col++) {
|
|
3448
|
+
acc = fn.fn.call([acc, arr.get(r, col)]);
|
|
3449
|
+
out.set(r, col, acc);
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
return FormulaValue.array(out);
|
|
3453
|
+
}
|
|
3454
|
+
function byRow(a, c) {
|
|
3455
|
+
const arr = asArray(a[0].evaluate(c));
|
|
3456
|
+
const fn = lambdaArg(a[1], c);
|
|
3457
|
+
if (!fn.ok) return fn.e;
|
|
3458
|
+
const out = new ArrayValue(arr.rows, 1);
|
|
3459
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
3460
|
+
const row2 = new ArrayValue(1, arr.columns);
|
|
3461
|
+
for (let col = 0; col < arr.columns; col++) row2.set(0, col, arr.get(r, col));
|
|
3462
|
+
out.set(r, 0, fn.fn.call([FormulaValue.array(row2)]));
|
|
3463
|
+
}
|
|
3464
|
+
return FormulaValue.array(out);
|
|
3465
|
+
}
|
|
3466
|
+
function byCol(a, c) {
|
|
3467
|
+
const arr = asArray(a[0].evaluate(c));
|
|
3468
|
+
const fn = lambdaArg(a[1], c);
|
|
3469
|
+
if (!fn.ok) return fn.e;
|
|
3470
|
+
const out = new ArrayValue(1, arr.columns);
|
|
3471
|
+
for (let col = 0; col < arr.columns; col++) {
|
|
3472
|
+
const column2 = new ArrayValue(arr.rows, 1);
|
|
3473
|
+
for (let r = 0; r < arr.rows; r++) column2.set(r, 0, arr.get(r, col));
|
|
3474
|
+
out.set(0, col, fn.fn.call([FormulaValue.array(column2)]));
|
|
3475
|
+
}
|
|
3476
|
+
return FormulaValue.array(out);
|
|
3477
|
+
}
|
|
3478
|
+
function registerAggregate(r) {
|
|
3479
|
+
r.register("SUBTOTAL", subtotal, 2);
|
|
3480
|
+
r.register("AGGREGATE", aggregate, 3);
|
|
3481
|
+
}
|
|
3482
|
+
function collectValues(a, c, startIdx, opts) {
|
|
3483
|
+
const out = [];
|
|
3484
|
+
const ws = c.worksheet;
|
|
3485
|
+
for (let i = startIdx; i < a.length; i++) {
|
|
3486
|
+
const node = a[i];
|
|
3487
|
+
if (node instanceof RangeReferenceNode) {
|
|
3488
|
+
const b = c.getRangeBounds(node.startRef, node.endRef);
|
|
3489
|
+
for (let row2 = b.startRow; row2 <= b.endRow; row2++) {
|
|
3490
|
+
if (opts.excludeHidden && ws.isRowHidden?.(row2)) continue;
|
|
3491
|
+
for (let col = b.startCol; col <= b.endCol; col++) {
|
|
3492
|
+
const ref = puredocsExcel.cellRefFromRowCol(row2, col);
|
|
3493
|
+
if (opts.nestedPattern) {
|
|
3494
|
+
const f = ws.getCellFormula?.(ref);
|
|
3495
|
+
if (f && opts.nestedPattern.test(f)) continue;
|
|
3496
|
+
}
|
|
3497
|
+
const v = c.getCellValue(ref);
|
|
3498
|
+
if (v.isError) {
|
|
3499
|
+
if (opts.ignoreErrors) continue;
|
|
3500
|
+
return { ok: false, error: v };
|
|
3501
|
+
}
|
|
3502
|
+
out.push(v);
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
} else {
|
|
3506
|
+
const v = node.evaluate(c);
|
|
3507
|
+
if (v.isError) {
|
|
3508
|
+
if (opts.ignoreErrors) continue;
|
|
3509
|
+
return { ok: false, error: v };
|
|
3510
|
+
}
|
|
3511
|
+
if (v.isArray) {
|
|
3512
|
+
for (const item of v.arrayVal.values()) {
|
|
3513
|
+
if (item.isError) {
|
|
3514
|
+
if (opts.ignoreErrors) continue;
|
|
3515
|
+
return { ok: false, error: item };
|
|
3516
|
+
}
|
|
3517
|
+
out.push(item);
|
|
3518
|
+
}
|
|
3519
|
+
} else {
|
|
3520
|
+
out.push(v);
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
return { ok: true, values: out };
|
|
3525
|
+
}
|
|
3526
|
+
function applyAggregate(fn, values, extra) {
|
|
3527
|
+
const nums = [];
|
|
3528
|
+
let countA2 = 0;
|
|
3529
|
+
for (const v of values) {
|
|
3530
|
+
if (!v.isBlank) countA2++;
|
|
3531
|
+
const r = v.tryAsDouble();
|
|
3532
|
+
if (r.ok && !v.isText && !v.isBlank) nums.push(r.value);
|
|
3533
|
+
}
|
|
3534
|
+
const sum2 = () => nums.reduce((s, n) => s + n, 0);
|
|
3535
|
+
const avg = () => nums.length ? sum2() / nums.length : NaN;
|
|
3536
|
+
const variance2 = (sample) => {
|
|
3537
|
+
const n = nums.length;
|
|
3538
|
+
if (sample ? n < 2 : n < 1) return NaN;
|
|
3539
|
+
const m = avg();
|
|
3540
|
+
const ss = nums.reduce((s, x) => s + (x - m) ** 2, 0);
|
|
3541
|
+
return ss / (sample ? n - 1 : n);
|
|
3542
|
+
};
|
|
3543
|
+
const sorted = () => [...nums].sort((x, y) => x - y);
|
|
3544
|
+
switch (fn) {
|
|
3545
|
+
case 1:
|
|
3546
|
+
return isNaN(avg()) ? FormulaValue.errorDiv0 : FormulaValue.number(avg());
|
|
3547
|
+
case 2:
|
|
3548
|
+
return FormulaValue.number(nums.length);
|
|
3549
|
+
// COUNT
|
|
3550
|
+
case 3:
|
|
3551
|
+
return FormulaValue.number(countA2);
|
|
3552
|
+
// COUNTA
|
|
3553
|
+
case 4:
|
|
3554
|
+
return nums.length ? FormulaValue.number(Math.max(...nums)) : FormulaValue.zero;
|
|
3555
|
+
case 5:
|
|
3556
|
+
return nums.length ? FormulaValue.number(Math.min(...nums)) : FormulaValue.zero;
|
|
3557
|
+
case 6:
|
|
3558
|
+
return FormulaValue.number(nums.reduce((p, n) => p * n, 1));
|
|
3559
|
+
// PRODUCT
|
|
3560
|
+
case 7: {
|
|
3561
|
+
const v = variance2(true);
|
|
3562
|
+
return isNaN(v) ? FormulaValue.errorDiv0 : FormulaValue.number(Math.sqrt(v));
|
|
3563
|
+
}
|
|
3564
|
+
// STDEV.S
|
|
3565
|
+
case 8: {
|
|
3566
|
+
const v = variance2(false);
|
|
3567
|
+
return isNaN(v) ? FormulaValue.errorDiv0 : FormulaValue.number(Math.sqrt(v));
|
|
3568
|
+
}
|
|
3569
|
+
// STDEV.P
|
|
3570
|
+
case 9:
|
|
3571
|
+
return FormulaValue.number(sum2());
|
|
3572
|
+
// SUM
|
|
3573
|
+
case 10: {
|
|
3574
|
+
const v = variance2(true);
|
|
3575
|
+
return isNaN(v) ? FormulaValue.errorDiv0 : FormulaValue.number(v);
|
|
3576
|
+
}
|
|
3577
|
+
// VAR.S
|
|
3578
|
+
case 11: {
|
|
3579
|
+
const v = variance2(false);
|
|
3580
|
+
return isNaN(v) ? FormulaValue.errorDiv0 : FormulaValue.number(v);
|
|
3581
|
+
}
|
|
3582
|
+
// VAR.P
|
|
3583
|
+
// AGGREGATE-only selectors:
|
|
3584
|
+
case 12: {
|
|
3585
|
+
const s = sorted();
|
|
3586
|
+
const m = s.length;
|
|
3587
|
+
if (!m) return FormulaValue.errorNum;
|
|
3588
|
+
const mid2 = Math.floor(m / 2);
|
|
3589
|
+
return FormulaValue.number(m % 2 ? s[mid2] : (s[mid2 - 1] + s[mid2]) / 2);
|
|
3590
|
+
}
|
|
3591
|
+
// MEDIAN
|
|
3592
|
+
case 14: {
|
|
3593
|
+
const s = sorted();
|
|
3594
|
+
const k = Math.round(extra ?? 0);
|
|
3595
|
+
return k >= 1 && k <= s.length ? FormulaValue.number(s[s.length - k]) : FormulaValue.errorNum;
|
|
3596
|
+
}
|
|
3597
|
+
case 15: {
|
|
3598
|
+
const s = sorted();
|
|
3599
|
+
const k = Math.round(extra ?? 0);
|
|
3600
|
+
return k >= 1 && k <= s.length ? FormulaValue.number(s[k - 1]) : FormulaValue.errorNum;
|
|
3601
|
+
}
|
|
3602
|
+
default:
|
|
3603
|
+
return FormulaValue.errorValue;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
function subtotal(a, c) {
|
|
3607
|
+
const fnVal = a[0].evaluate(c);
|
|
3608
|
+
if (fnVal.isError) return fnVal;
|
|
3609
|
+
const raw = fnVal.tryAsDouble();
|
|
3610
|
+
if (!raw.ok) return FormulaValue.errorValue;
|
|
3611
|
+
const code2 = Math.round(raw.value);
|
|
3612
|
+
const excludeHidden = code2 >= 101 && code2 <= 111;
|
|
3613
|
+
const fn = excludeHidden ? code2 - 100 : code2;
|
|
3614
|
+
if (fn < 1 || fn > 11) return FormulaValue.errorValue;
|
|
3615
|
+
const collected = collectValues(a, c, 1, {
|
|
3616
|
+
excludeHidden,
|
|
3617
|
+
nestedPattern: /^\s*SUBTOTAL\s*\(/i,
|
|
3618
|
+
ignoreErrors: false
|
|
3619
|
+
});
|
|
3620
|
+
if (!collected.ok) return collected.error;
|
|
3621
|
+
return applyAggregate(fn, collected.values);
|
|
3622
|
+
}
|
|
3623
|
+
function aggregate(a, c) {
|
|
3624
|
+
const fnVal = a[0].evaluate(c);
|
|
3625
|
+
if (fnVal.isError) return fnVal;
|
|
3626
|
+
const optVal = a[1].evaluate(c);
|
|
3627
|
+
if (optVal.isError) return optVal;
|
|
3628
|
+
const fnR = fnVal.tryAsDouble();
|
|
3629
|
+
const optR = optVal.tryAsDouble();
|
|
3630
|
+
if (!fnR.ok || !optR.ok) return FormulaValue.errorValue;
|
|
3631
|
+
const fn = Math.round(fnR.value);
|
|
3632
|
+
const option = Math.round(optR.value);
|
|
3633
|
+
if (fn < 1 || fn > 19) return FormulaValue.errorValue;
|
|
3634
|
+
const excludeHidden = option === 1 || option === 3 || option === 5 || option === 7;
|
|
3635
|
+
const ignoreErrors = option === 2 || option === 3 || option === 6 || option === 7;
|
|
3636
|
+
const ignoreNested = option <= 3 || option === 5 || option === 7;
|
|
3637
|
+
let extra;
|
|
3638
|
+
let endIdx = a.length;
|
|
3639
|
+
if (fn === 14 || fn === 15) {
|
|
3640
|
+
const kVal = a[a.length - 1].evaluate(c);
|
|
3641
|
+
const kR = kVal.tryAsDouble();
|
|
3642
|
+
if (!kR.ok) return FormulaValue.errorValue;
|
|
3643
|
+
extra = kR.value;
|
|
3644
|
+
endIdx = a.length - 1;
|
|
3645
|
+
}
|
|
3646
|
+
const collected = collectValues(a.slice(0, endIdx), c, 2, {
|
|
3647
|
+
excludeHidden,
|
|
3648
|
+
nestedPattern: ignoreNested ? /^\s*(SUBTOTAL|AGGREGATE)\s*\(/i : null,
|
|
3649
|
+
ignoreErrors
|
|
3650
|
+
});
|
|
3651
|
+
if (!collected.ok) return collected.error;
|
|
3652
|
+
return applyAggregate(fn, collected.values, extra);
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
// src/functions/array-functions.ts
|
|
3656
|
+
function registerArray(r) {
|
|
3657
|
+
r.register("TRANSPOSE", transpose, 1, 1);
|
|
3658
|
+
r.register("SEQUENCE", sequence, 1, 4);
|
|
3659
|
+
r.register("UNIQUE", unique, 1, 3);
|
|
3660
|
+
r.register("SORT", sort, 1, 4);
|
|
3661
|
+
r.register("SORTBY", sortBy, 2);
|
|
3662
|
+
r.register("FILTER", filter, 2, 3);
|
|
3663
|
+
r.register("MMULT", mmult, 2, 2);
|
|
3664
|
+
r.register("TEXTSPLIT", textSplit, 2, 6);
|
|
3665
|
+
}
|
|
3666
|
+
var asArray2 = (v) => v.isArray ? v.arrayVal : ArrayValue.fromScalar(v);
|
|
3667
|
+
function fromRows(rows2) {
|
|
3668
|
+
const nr = rows2.length;
|
|
3669
|
+
const nc = nr > 0 ? rows2[0].length : 0;
|
|
3670
|
+
const arr = new ArrayValue(nr, nc);
|
|
3671
|
+
for (let r = 0; r < nr; r++) for (let c = 0; c < nc; c++) arr.set(r, c, rows2[r][c]);
|
|
3672
|
+
return FormulaValue.array(arr);
|
|
3673
|
+
}
|
|
3674
|
+
function toRows(arr) {
|
|
3675
|
+
const rows2 = [];
|
|
3676
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
3677
|
+
const row2 = [];
|
|
3678
|
+
for (let c = 0; c < arr.columns; c++) row2.push(arr.get(r, c));
|
|
3679
|
+
rows2.push(row2);
|
|
3680
|
+
}
|
|
3681
|
+
return rows2;
|
|
3682
|
+
}
|
|
3683
|
+
var num4 = (a, c, i, dflt) => {
|
|
3684
|
+
if (i >= a.length) return { ok: true, n: dflt };
|
|
3685
|
+
const r = exports.FormulaHelper.evalDouble(a[i], c);
|
|
3686
|
+
return r.ok ? { ok: true, n: r.value } : { ok: false, e: r.error };
|
|
3687
|
+
};
|
|
3688
|
+
function transpose(a, c) {
|
|
3689
|
+
const v = a[0].evaluate(c);
|
|
3690
|
+
if (v.isError) return v;
|
|
3691
|
+
return FormulaValue.array(asArray2(v).transpose());
|
|
3692
|
+
}
|
|
3693
|
+
function sequence(a, c) {
|
|
3694
|
+
const rows2 = num4(a, c, 0, 1);
|
|
3695
|
+
if (!rows2.ok) return rows2.e;
|
|
3696
|
+
const cols = num4(a, c, 1, 1);
|
|
3697
|
+
if (!cols.ok) return cols.e;
|
|
3698
|
+
const start = num4(a, c, 2, 1);
|
|
3699
|
+
if (!start.ok) return start.e;
|
|
3700
|
+
const step = num4(a, c, 3, 1);
|
|
3701
|
+
if (!step.ok) return step.e;
|
|
3702
|
+
const R = Math.trunc(rows2.n), C = Math.trunc(cols.n);
|
|
3703
|
+
if (R < 1 || C < 1) return FormulaValue.errorValue;
|
|
3704
|
+
const arr = new ArrayValue(R, C);
|
|
3705
|
+
for (let r = 0; r < R; r++) {
|
|
3706
|
+
for (let col = 0; col < C; col++) {
|
|
3707
|
+
arr.set(r, col, FormulaValue.number(start.n + (r * C + col) * step.n));
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
return FormulaValue.array(arr);
|
|
3711
|
+
}
|
|
3712
|
+
function rowsEqual(x, y) {
|
|
3713
|
+
if (x.length !== y.length) return false;
|
|
3714
|
+
for (let i = 0; i < x.length; i++) if (!FormulaValue.areEqual(x[i], y[i])) return false;
|
|
3715
|
+
return true;
|
|
3716
|
+
}
|
|
3717
|
+
function unique(a, c) {
|
|
3718
|
+
const v = a[0].evaluate(c);
|
|
3719
|
+
if (v.isError) return v;
|
|
3720
|
+
const byCol2 = a.length > 1 ? a[1].evaluate(c).coerceToBool().booleanValue : false;
|
|
3721
|
+
const exactlyOnce = a.length > 2 ? a[2].evaluate(c).coerceToBool().booleanValue : false;
|
|
3722
|
+
let rows2 = toRows(byCol2 ? asArray2(v).transpose() : asArray2(v));
|
|
3723
|
+
const counts = rows2.map((r) => rows2.filter((o) => rowsEqual(o, r)).length);
|
|
3724
|
+
const seen = [];
|
|
3725
|
+
const result = [];
|
|
3726
|
+
rows2.forEach((r, i) => {
|
|
3727
|
+
if (seen.some((o) => rowsEqual(o, r))) return;
|
|
3728
|
+
seen.push(r);
|
|
3729
|
+
if (exactlyOnce && counts[i] > 1) return;
|
|
3730
|
+
result.push(r);
|
|
3731
|
+
});
|
|
3732
|
+
if (result.length === 0) return FormulaValue.error(8 /* Calc */);
|
|
3733
|
+
const out = fromRows(result);
|
|
3734
|
+
return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
|
|
3735
|
+
}
|
|
3736
|
+
function sort(a, c) {
|
|
3737
|
+
const v = a[0].evaluate(c);
|
|
3738
|
+
if (v.isError) return v;
|
|
3739
|
+
const sortIndex = num4(a, c, 1, 1);
|
|
3740
|
+
if (!sortIndex.ok) return sortIndex.e;
|
|
3741
|
+
const sortOrder = num4(a, c, 2, 1);
|
|
3742
|
+
if (!sortOrder.ok) return sortOrder.e;
|
|
3743
|
+
const byCol2 = a.length > 3 ? a[3].evaluate(c).coerceToBool().booleanValue : false;
|
|
3744
|
+
const base = byCol2 ? asArray2(v).transpose() : asArray2(v);
|
|
3745
|
+
const rows2 = toRows(base);
|
|
3746
|
+
const idx = Math.trunc(sortIndex.n) - 1;
|
|
3747
|
+
const dir = sortOrder.n < 0 ? -1 : 1;
|
|
3748
|
+
if (rows2.length > 0 && (idx < 0 || idx >= rows2[0].length)) return FormulaValue.errorValue;
|
|
3749
|
+
const sorted = rows2.map((r, i) => ({ r, i })).sort((x, y) => {
|
|
3750
|
+
const cmp = FormulaValue.compare(x.r[idx], y.r[idx]) * dir;
|
|
3751
|
+
return cmp !== 0 ? cmp : x.i - y.i;
|
|
3752
|
+
}).map((o) => o.r);
|
|
3753
|
+
const out = fromRows(sorted);
|
|
3754
|
+
return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
|
|
3755
|
+
}
|
|
3756
|
+
function sortBy(a, c) {
|
|
3757
|
+
const v = a[0].evaluate(c);
|
|
3758
|
+
if (v.isError) return v;
|
|
3759
|
+
const data = asArray2(v);
|
|
3760
|
+
const keys = [];
|
|
3761
|
+
let i = 1;
|
|
3762
|
+
while (i < a.length) {
|
|
3763
|
+
const byVal = a[i].evaluate(c);
|
|
3764
|
+
if (byVal.isError) return byVal;
|
|
3765
|
+
i++;
|
|
3766
|
+
let dir = 1;
|
|
3767
|
+
if (i < a.length) {
|
|
3768
|
+
const maybeOrder = a[i].evaluate(c);
|
|
3769
|
+
if (maybeOrder.isError) return maybeOrder;
|
|
3770
|
+
if (!maybeOrder.isArray && maybeOrder.isNumeric) {
|
|
3771
|
+
const d = maybeOrder.tryAsDouble();
|
|
3772
|
+
if (d.ok) {
|
|
3773
|
+
dir = d.value < 0 ? -1 : 1;
|
|
3774
|
+
i++;
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
keys.push({ vals: [...asArray2(byVal).values()], dir });
|
|
3779
|
+
}
|
|
3780
|
+
if (keys.length === 0) return FormulaValue.errorValue;
|
|
3781
|
+
const n = keys[0].vals.length;
|
|
3782
|
+
if (keys.some((k) => k.vals.length !== n)) return FormulaValue.errorValue;
|
|
3783
|
+
const byRow2 = n === data.rows;
|
|
3784
|
+
const byColumn = !byRow2 && n === data.columns;
|
|
3785
|
+
if (!byRow2 && !byColumn) return FormulaValue.errorValue;
|
|
3786
|
+
const perm = Array.from({ length: n }, (_, k) => k);
|
|
3787
|
+
perm.sort((x, y) => {
|
|
3788
|
+
for (const key of keys) {
|
|
3789
|
+
const cmp = FormulaValue.compare(key.vals[x], key.vals[y]) * key.dir;
|
|
3790
|
+
if (cmp !== 0) return cmp;
|
|
3791
|
+
}
|
|
3792
|
+
return x - y;
|
|
3793
|
+
});
|
|
3794
|
+
const out = new ArrayValue(data.rows, data.columns);
|
|
3795
|
+
if (byRow2) {
|
|
3796
|
+
for (let r = 0; r < data.rows; r++) {
|
|
3797
|
+
const src = perm[r];
|
|
3798
|
+
for (let col = 0; col < data.columns; col++) out.set(r, col, data.get(src, col));
|
|
3799
|
+
}
|
|
3800
|
+
} else {
|
|
3801
|
+
for (let col = 0; col < data.columns; col++) {
|
|
3802
|
+
const src = perm[col];
|
|
3803
|
+
for (let r = 0; r < data.rows; r++) out.set(r, col, data.get(r, src));
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
return FormulaValue.array(out);
|
|
3807
|
+
}
|
|
3808
|
+
function filter(a, c) {
|
|
3809
|
+
const v = a[0].evaluate(c);
|
|
3810
|
+
if (v.isError) return v;
|
|
3811
|
+
const inc = a[1].evaluate(c);
|
|
3812
|
+
if (inc.isError) return inc;
|
|
3813
|
+
const arr = asArray2(v);
|
|
3814
|
+
const incArr = asArray2(inc);
|
|
3815
|
+
const perRow = incArr.rows === arr.rows && incArr.columns === 1;
|
|
3816
|
+
const perCol = incArr.columns === arr.columns && incArr.rows === 1;
|
|
3817
|
+
if (!perRow && !perCol) return FormulaValue.errorValue;
|
|
3818
|
+
const keptRows = [];
|
|
3819
|
+
if (perRow) {
|
|
3820
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
3821
|
+
if (!incArr.get(r, 0).coerceToBool().booleanValue) continue;
|
|
3822
|
+
const row2 = [];
|
|
3823
|
+
for (let col = 0; col < arr.columns; col++) row2.push(arr.get(r, col));
|
|
3824
|
+
keptRows.push(row2);
|
|
3825
|
+
}
|
|
3826
|
+
} else {
|
|
3827
|
+
const keepCols = [];
|
|
3828
|
+
for (let col = 0; col < arr.columns; col++) {
|
|
3829
|
+
if (incArr.get(0, col).coerceToBool().booleanValue) keepCols.push(col);
|
|
3830
|
+
}
|
|
3831
|
+
for (let r = 0; r < arr.rows; r++) {
|
|
3832
|
+
keptRows.push(keepCols.map((col) => arr.get(r, col)));
|
|
3833
|
+
}
|
|
3834
|
+
if (keepCols.length === 0) keptRows.length = 0;
|
|
3835
|
+
}
|
|
3836
|
+
if (keptRows.length === 0 || keptRows[0] && keptRows[0].length === 0) {
|
|
3837
|
+
return a.length > 2 ? a[2].evaluate(c) : FormulaValue.error(8 /* Calc */);
|
|
3838
|
+
}
|
|
3839
|
+
return fromRows(keptRows);
|
|
3840
|
+
}
|
|
3841
|
+
function mmult(a, c) {
|
|
3842
|
+
const av = a[0].evaluate(c);
|
|
3843
|
+
if (av.isError) return av;
|
|
3844
|
+
const bv = a[1].evaluate(c);
|
|
3845
|
+
if (bv.isError) return bv;
|
|
3846
|
+
const A = asArray2(av), B = asArray2(bv);
|
|
3847
|
+
if (A.columns !== B.rows) return FormulaValue.errorValue;
|
|
3848
|
+
const out = new ArrayValue(A.rows, B.columns);
|
|
3849
|
+
for (let i = 0; i < A.rows; i++) {
|
|
3850
|
+
for (let j = 0; j < B.columns; j++) {
|
|
3851
|
+
let sum2 = 0;
|
|
3852
|
+
for (let k = 0; k < A.columns; k++) {
|
|
3853
|
+
const x = A.get(i, k).tryAsDouble();
|
|
3854
|
+
const y = B.get(k, j).tryAsDouble();
|
|
3855
|
+
if (!x.ok || !y.ok) return FormulaValue.errorValue;
|
|
3856
|
+
sum2 += x.value * y.value;
|
|
3857
|
+
}
|
|
3858
|
+
out.set(i, j, FormulaValue.number(sum2));
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
return FormulaValue.array(out);
|
|
3862
|
+
}
|
|
3863
|
+
function textSplit(a, c) {
|
|
3864
|
+
const t = exports.FormulaHelper.evalString(a[0], c);
|
|
3865
|
+
if (!t.ok) return t.error;
|
|
3866
|
+
const colD = exports.FormulaHelper.evalString(a[1], c);
|
|
3867
|
+
if (!colD.ok) return colD.error;
|
|
3868
|
+
let rowD = "";
|
|
3869
|
+
if (a.length > 2) {
|
|
3870
|
+
const rd = a[2].evaluate(c);
|
|
3871
|
+
if (rd.isError) return rd;
|
|
3872
|
+
if (!rd.isBlank) {
|
|
3873
|
+
const r = exports.FormulaHelper.evalString(a[2], c);
|
|
3874
|
+
if (!r.ok) return r.error;
|
|
3875
|
+
rowD = r.value;
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
const split = (s, d) => d === "" ? [s] : s.split(d);
|
|
3879
|
+
const lines = rowD ? split(t.value, rowD) : [t.value];
|
|
3880
|
+
const grid = lines.map((line) => split(line, colD.value));
|
|
3881
|
+
const width = grid.reduce((m, r) => Math.max(m, r.length), 0);
|
|
3882
|
+
const rows2 = grid.map((r) => {
|
|
3883
|
+
const row2 = [];
|
|
3884
|
+
for (let i = 0; i < width; i++) row2.push(FormulaValue.text(r[i] ?? ""));
|
|
3885
|
+
return row2;
|
|
3886
|
+
});
|
|
3887
|
+
return fromRows(rows2);
|
|
3888
|
+
}
|
|
3889
|
+
|
|
2537
3890
|
// src/function-registry.ts
|
|
2538
3891
|
var _default, _functions, _FunctionRegistry_instances, registerBuiltIns_fn;
|
|
2539
3892
|
var _FunctionRegistry = class _FunctionRegistry {
|
|
@@ -2592,6 +3945,10 @@ registerBuiltIns_fn = function() {
|
|
|
2592
3945
|
registerLookup(this);
|
|
2593
3946
|
registerStatistical(this);
|
|
2594
3947
|
registerInfo(this);
|
|
3948
|
+
registerFinance(this);
|
|
3949
|
+
registerMeta(this);
|
|
3950
|
+
registerAggregate(this);
|
|
3951
|
+
registerArray(this);
|
|
2595
3952
|
};
|
|
2596
3953
|
__privateAdd(_FunctionRegistry, _default);
|
|
2597
3954
|
var FunctionRegistry = _FunctionRegistry;
|
|
@@ -2613,8 +3970,8 @@ exports.FormulaHelper = void 0;
|
|
|
2613
3970
|
FormulaHelper2.evalString = evalString;
|
|
2614
3971
|
function collectNumbers(args, ctx) {
|
|
2615
3972
|
const nums = [];
|
|
2616
|
-
for (const
|
|
2617
|
-
const v =
|
|
3973
|
+
for (const arg2 of args) {
|
|
3974
|
+
const v = arg2.evaluate(ctx);
|
|
2618
3975
|
if (v.isError) return { ok: false, error: v };
|
|
2619
3976
|
if (v.isArray) {
|
|
2620
3977
|
for (const item of v.arrayVal.values()) {
|
|
@@ -2634,8 +3991,8 @@ exports.FormulaHelper = void 0;
|
|
|
2634
3991
|
FormulaHelper2.collectNumbers = collectNumbers;
|
|
2635
3992
|
function flattenArgs(args, ctx) {
|
|
2636
3993
|
const result = [];
|
|
2637
|
-
for (const
|
|
2638
|
-
const v =
|
|
3994
|
+
for (const arg2 of args) {
|
|
3995
|
+
const v = arg2.evaluate(ctx);
|
|
2639
3996
|
if (v.isArray) {
|
|
2640
3997
|
for (const item of v.arrayVal.values()) result.push(item);
|
|
2641
3998
|
} else {
|
|
@@ -2673,9 +4030,9 @@ exports.FormulaHelper = void 0;
|
|
|
2673
4030
|
const pattern = wildcardToRegex(criteria);
|
|
2674
4031
|
return pattern.test(value2.asText());
|
|
2675
4032
|
}
|
|
2676
|
-
const
|
|
2677
|
-
if (!isNaN(
|
|
2678
|
-
return FormulaValue.areEqual(value2, FormulaValue.number(
|
|
4033
|
+
const num5 = parseFloat(criteria);
|
|
4034
|
+
if (!isNaN(num5)) {
|
|
4035
|
+
return FormulaValue.areEqual(value2, FormulaValue.number(num5));
|
|
2679
4036
|
}
|
|
2680
4037
|
return value2.asText().toUpperCase() === criteria.toUpperCase();
|
|
2681
4038
|
}
|
|
@@ -2687,18 +4044,33 @@ exports.FormulaHelper = void 0;
|
|
|
2687
4044
|
})(exports.FormulaHelper || (exports.FormulaHelper = {}));
|
|
2688
4045
|
|
|
2689
4046
|
// src/formula-context.ts
|
|
2690
|
-
var _sheet, _functions2, _evaluating;
|
|
4047
|
+
var _sheet, _functions2, _evaluating, _scopes, _FormulaContext_instances, lookupScope_fn;
|
|
2691
4048
|
var _FormulaContext = class _FormulaContext {
|
|
2692
4049
|
constructor(sheet, formulaRow = 0, formulaCol = 0, evaluating, registry) {
|
|
4050
|
+
__privateAdd(this, _FormulaContext_instances);
|
|
2693
4051
|
__privateAdd(this, _sheet);
|
|
2694
4052
|
__privateAdd(this, _functions2);
|
|
2695
4053
|
__privateAdd(this, _evaluating);
|
|
4054
|
+
/**
|
|
4055
|
+
* Lexical scope stack for LET/LAMBDA local names. Each frame maps an
|
|
4056
|
+
* upper-cased local name to its value. Names resolve innermost-first and take
|
|
4057
|
+
* precedence over workbook defined names.
|
|
4058
|
+
*/
|
|
4059
|
+
__privateAdd(this, _scopes, []);
|
|
2696
4060
|
__privateSet(this, _sheet, sheet);
|
|
2697
4061
|
this.formulaRow = formulaRow;
|
|
2698
4062
|
this.formulaCol = formulaCol;
|
|
2699
4063
|
__privateSet(this, _evaluating, evaluating ?? /* @__PURE__ */ new Set());
|
|
2700
4064
|
__privateSet(this, _functions2, registry ?? FunctionRegistry.default);
|
|
2701
4065
|
}
|
|
4066
|
+
/** Pushes a new binding frame (used by LET/LAMBDA). */
|
|
4067
|
+
pushScope(bindings) {
|
|
4068
|
+
__privateGet(this, _scopes).push(bindings);
|
|
4069
|
+
}
|
|
4070
|
+
/** Pops the innermost binding frame. */
|
|
4071
|
+
popScope() {
|
|
4072
|
+
__privateGet(this, _scopes).pop();
|
|
4073
|
+
}
|
|
2702
4074
|
get worksheet() {
|
|
2703
4075
|
return __privateGet(this, _sheet);
|
|
2704
4076
|
}
|
|
@@ -2750,8 +4122,21 @@ var _FormulaContext = class _FormulaContext {
|
|
|
2750
4122
|
return FormulaValue.errorRef;
|
|
2751
4123
|
}
|
|
2752
4124
|
}
|
|
2753
|
-
resolveNamedRange(
|
|
2754
|
-
|
|
4125
|
+
resolveNamedRange(name) {
|
|
4126
|
+
const scoped = __privateMethod(this, _FormulaContext_instances, lookupScope_fn).call(this, name);
|
|
4127
|
+
if (scoped !== void 0) return scoped;
|
|
4128
|
+
return this.nameResolver ? this.nameResolver(name) : FormulaValue.errorName;
|
|
4129
|
+
}
|
|
4130
|
+
/**
|
|
4131
|
+
* Resolves a spill-range reference (A1#) to the array spilling from the anchor
|
|
4132
|
+
* cell. Requires the worksheet to expose getSpillRange; otherwise #REF!.
|
|
4133
|
+
*/
|
|
4134
|
+
resolveSpillRange(anchorRef) {
|
|
4135
|
+
const spillRef = __privateGet(this, _sheet).getSpillRange?.(anchorRef);
|
|
4136
|
+
if (!spillRef) return FormulaValue.errorRef;
|
|
4137
|
+
const colon = spillRef.indexOf(":");
|
|
4138
|
+
if (colon < 0) return this.getCellValue(spillRef);
|
|
4139
|
+
return this.getRangeValues(spillRef.slice(0, colon), spillRef.slice(colon + 1));
|
|
2755
4140
|
}
|
|
2756
4141
|
getRangeBounds(startRef, endRef) {
|
|
2757
4142
|
const s = puredocsExcel.parseCellRef(startRef);
|
|
@@ -2759,13 +4144,34 @@ var _FormulaContext = class _FormulaContext {
|
|
|
2759
4144
|
return { startRow: s.row, startCol: s.column, endRow: e.row, endCol: e.column };
|
|
2760
4145
|
}
|
|
2761
4146
|
// ── Function dispatch ─────────────────────────────────────────────────────
|
|
4147
|
+
/** True if a built-in function with this name is registered. */
|
|
4148
|
+
hasFunction(name) {
|
|
4149
|
+
return __privateGet(this, _functions2).has(name);
|
|
4150
|
+
}
|
|
2762
4151
|
evaluateFunction(name, args) {
|
|
4152
|
+
if (!__privateGet(this, _functions2).has(name)) {
|
|
4153
|
+
const named = this.resolveNamedRange(name);
|
|
4154
|
+
if (named.isLambda) {
|
|
4155
|
+
return named.lambdaVal.call(args.map((a) => a.evaluate(this)));
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
2763
4158
|
return __privateGet(this, _functions2).execute(name, args, this);
|
|
2764
4159
|
}
|
|
2765
4160
|
};
|
|
2766
4161
|
_sheet = new WeakMap();
|
|
2767
4162
|
_functions2 = new WeakMap();
|
|
2768
4163
|
_evaluating = new WeakMap();
|
|
4164
|
+
_scopes = new WeakMap();
|
|
4165
|
+
_FormulaContext_instances = new WeakSet();
|
|
4166
|
+
lookupScope_fn = function(name) {
|
|
4167
|
+
if (__privateGet(this, _scopes).length === 0) return void 0;
|
|
4168
|
+
const key = name.toUpperCase();
|
|
4169
|
+
for (let i = __privateGet(this, _scopes).length - 1; i >= 0; i--) {
|
|
4170
|
+
const v = __privateGet(this, _scopes)[i].get(key);
|
|
4171
|
+
if (v !== void 0) return v;
|
|
4172
|
+
}
|
|
4173
|
+
return void 0;
|
|
4174
|
+
};
|
|
2769
4175
|
var FormulaContext = _FormulaContext;
|
|
2770
4176
|
|
|
2771
4177
|
// src/lru-cache.ts
|
|
@@ -2872,16 +4278,63 @@ function parseFormula(formula) {
|
|
|
2872
4278
|
astCache.set(stripped, ast);
|
|
2873
4279
|
return ast;
|
|
2874
4280
|
}
|
|
4281
|
+
var DEFAULT_LAMBDA_DEPTH = 1024;
|
|
4282
|
+
function buildContext(sheet, opts) {
|
|
4283
|
+
const evaluating = opts.evaluating ?? /* @__PURE__ */ new Set();
|
|
4284
|
+
const ctx = new FormulaContext(
|
|
4285
|
+
sheet,
|
|
4286
|
+
opts.formulaRow ?? 0,
|
|
4287
|
+
opts.formulaCol ?? 0,
|
|
4288
|
+
evaluating
|
|
4289
|
+
);
|
|
4290
|
+
ctx.recursionGuard = opts.recursionGuard ?? { depth: 0, max: DEFAULT_LAMBDA_DEPTH };
|
|
4291
|
+
const wb = opts.workbook;
|
|
4292
|
+
if (wb) {
|
|
4293
|
+
ctx.workbook = wb;
|
|
4294
|
+
if (typeof wb.getDefinedName === "function") {
|
|
4295
|
+
const resolving = opts.resolvingNames ?? /* @__PURE__ */ new Set();
|
|
4296
|
+
const resolved = opts.resolvedNames ?? /* @__PURE__ */ new Map();
|
|
4297
|
+
const guard = ctx.recursionGuard;
|
|
4298
|
+
ctx.nameResolver = (name) => resolveName(name, sheet, wb, evaluating, resolving, resolved, guard);
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
return ctx;
|
|
4302
|
+
}
|
|
4303
|
+
function resolveName(name, sheet, workbook, evaluating, resolving, resolved, recursionGuard) {
|
|
4304
|
+
const key = `${sheet.name}\0${name.toUpperCase()}`;
|
|
4305
|
+
const cached = resolved.get(key);
|
|
4306
|
+
if (cached !== void 0) return cached;
|
|
4307
|
+
if (resolving.has(key)) return FormulaValue.errorRef;
|
|
4308
|
+
const refersTo = workbook.getDefinedName(name, sheet.name);
|
|
4309
|
+
if (refersTo == null || refersTo === "") return FormulaValue.errorName;
|
|
4310
|
+
resolving.add(key);
|
|
4311
|
+
try {
|
|
4312
|
+
const ast = parseFormula(refersTo);
|
|
4313
|
+
const ctx = buildContext(sheet, {
|
|
4314
|
+
workbook,
|
|
4315
|
+
evaluating,
|
|
4316
|
+
resolvingNames: resolving,
|
|
4317
|
+
resolvedNames: resolved,
|
|
4318
|
+
recursionGuard
|
|
4319
|
+
});
|
|
4320
|
+
const value2 = ast.evaluate(ctx);
|
|
4321
|
+
if (value2.isLambda) resolved.set(key, value2);
|
|
4322
|
+
return value2;
|
|
4323
|
+
} catch {
|
|
4324
|
+
return FormulaValue.errorName;
|
|
4325
|
+
} finally {
|
|
4326
|
+
resolving.delete(key);
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
2875
4329
|
function evaluateFormula(formula, sheet, options) {
|
|
2876
4330
|
if (!formula) return FormulaValue.blank;
|
|
2877
4331
|
try {
|
|
2878
4332
|
const ast = parseFormula(formula);
|
|
2879
|
-
const ctx =
|
|
2880
|
-
|
|
2881
|
-
options?.formulaRow
|
|
2882
|
-
options?.formulaCol
|
|
2883
|
-
);
|
|
2884
|
-
if (options?.workbook) ctx.workbook = options.workbook;
|
|
4333
|
+
const ctx = buildContext(sheet, {
|
|
4334
|
+
workbook: options?.workbook,
|
|
4335
|
+
formulaRow: options?.formulaRow,
|
|
4336
|
+
formulaCol: options?.formulaCol
|
|
4337
|
+
});
|
|
2885
4338
|
return ast.evaluate(ctx);
|
|
2886
4339
|
} catch {
|
|
2887
4340
|
return FormulaValue.errorValue;
|
|
@@ -2901,12 +4354,445 @@ function getAstCacheStats() {
|
|
|
2901
4354
|
function setAstCacheCapacity(capacity) {
|
|
2902
4355
|
astCache.resize(capacity);
|
|
2903
4356
|
}
|
|
4357
|
+
function extractReferences(ast, formulaSheet, registry = FunctionRegistry.default, resolveName2) {
|
|
4358
|
+
const refs = [];
|
|
4359
|
+
let volatile = false;
|
|
4360
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
4361
|
+
const cell = (sheet, ref) => {
|
|
4362
|
+
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(ref);
|
|
4363
|
+
return { sheet: sheet.toUpperCase(), startRow: row2, startCol: column2, endRow: row2, endCol: column2 };
|
|
4364
|
+
};
|
|
4365
|
+
const range = (sheet, startRef, endRef) => {
|
|
4366
|
+
const s = puredocsExcel.parseCellRef(startRef);
|
|
4367
|
+
const e = puredocsExcel.parseCellRef(endRef);
|
|
4368
|
+
return {
|
|
4369
|
+
sheet: sheet.toUpperCase(),
|
|
4370
|
+
startRow: Math.min(s.row, e.row),
|
|
4371
|
+
startCol: Math.min(s.column, e.column),
|
|
4372
|
+
endRow: Math.max(s.row, e.row),
|
|
4373
|
+
endCol: Math.max(s.column, e.column)
|
|
4374
|
+
};
|
|
4375
|
+
};
|
|
4376
|
+
const walk = (node) => {
|
|
4377
|
+
if (node instanceof CellReferenceNode) {
|
|
4378
|
+
refs.push(cell(formulaSheet, node.reference));
|
|
4379
|
+
} else if (node instanceof RangeReferenceNode) {
|
|
4380
|
+
refs.push(range(formulaSheet, node.startRef, node.endRef));
|
|
4381
|
+
} else if (node instanceof SheetCellReferenceNode) {
|
|
4382
|
+
refs.push(cell(node.sheetName, node.cellReference));
|
|
4383
|
+
} else if (node instanceof SheetRangeReferenceNode) {
|
|
4384
|
+
refs.push(range(node.sheetName, node.startRef, node.endRef));
|
|
4385
|
+
} else if (node instanceof NamedRangeNode) {
|
|
4386
|
+
expandName(node.name);
|
|
4387
|
+
} else if (node instanceof SpillReferenceNode) {
|
|
4388
|
+
refs.push(cell(formulaSheet, node.anchorRef));
|
|
4389
|
+
} else if (node instanceof FunctionCallNode) {
|
|
4390
|
+
if (registry.isVolatile(node.functionName)) volatile = true;
|
|
4391
|
+
for (const arg2 of node.args) walk(arg2);
|
|
4392
|
+
} else if (node instanceof BinaryOpNode) {
|
|
4393
|
+
walk(node.left);
|
|
4394
|
+
walk(node.right);
|
|
4395
|
+
} else if (node instanceof UnaryOpNode) {
|
|
4396
|
+
walk(node.operand);
|
|
4397
|
+
} else if (node instanceof ImplicitIntersectionNode) {
|
|
4398
|
+
walk(node.inner);
|
|
4399
|
+
}
|
|
4400
|
+
};
|
|
4401
|
+
const expandName = (name) => {
|
|
4402
|
+
if (!resolveName2) return;
|
|
4403
|
+
const key = name.toUpperCase();
|
|
4404
|
+
if (seenNames.has(key)) return;
|
|
4405
|
+
seenNames.add(key);
|
|
4406
|
+
const refersTo = resolveName2(name, formulaSheet);
|
|
4407
|
+
if (!refersTo) return;
|
|
4408
|
+
try {
|
|
4409
|
+
walk(parseFormula(refersTo));
|
|
4410
|
+
} catch {
|
|
4411
|
+
}
|
|
4412
|
+
};
|
|
4413
|
+
walk(ast);
|
|
4414
|
+
return { refs, volatile };
|
|
4415
|
+
}
|
|
4416
|
+
function refContains(ref, sheet, row2, col) {
|
|
4417
|
+
return ref.sheet === sheet && row2 >= ref.startRow && row2 <= ref.endRow && col >= ref.startCol && col <= ref.endCol;
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
// src/recalc-engine.ts
|
|
4421
|
+
function createWorkbookRecalcModel(workbook) {
|
|
4422
|
+
return {
|
|
4423
|
+
getCellValue: (sheet, ref) => workbook.getWorksheet(sheet).getCellValue(ref),
|
|
4424
|
+
setCellValue: (sheet, ref, value2) => {
|
|
4425
|
+
workbook.getWorksheet(sheet).getCell(ref).setComputedValue(value2);
|
|
4426
|
+
},
|
|
4427
|
+
setCellFormula: (sheet, ref, formula) => {
|
|
4428
|
+
workbook.getWorksheet(sheet).getCell(ref).setFormula(formula);
|
|
4429
|
+
},
|
|
4430
|
+
clearCell: (sheet, ref) => {
|
|
4431
|
+
workbook.getWorksheet(sheet).getCell(ref).clear();
|
|
4432
|
+
},
|
|
4433
|
+
setCellArrayRef: (sheet, ref, spillRef) => {
|
|
4434
|
+
workbook.getWorksheet(sheet).getCell(ref).setArrayRef(spillRef);
|
|
4435
|
+
},
|
|
4436
|
+
getCellFormula: (sheet, ref) => workbook.getWorksheet(sheet).getCellFormula?.(ref) ?? null,
|
|
4437
|
+
getSpillRange: (sheet, ref) => workbook.getWorksheet(sheet).getSpillRange?.(ref) ?? null,
|
|
4438
|
+
isRowHidden: (sheet, row2) => workbook.getWorksheet(sheet).isRowHidden?.(row2) ?? false,
|
|
4439
|
+
getDefinedName: (name, sheet) => workbook.getDefinedName(name, sheet)
|
|
4440
|
+
};
|
|
4441
|
+
}
|
|
4442
|
+
var normSheet = (s) => s.toUpperCase();
|
|
4443
|
+
var normRef = (r) => r.replace(/\$/g, "").toUpperCase();
|
|
4444
|
+
var keyOf = (sheet, ref) => `${normSheet(sheet)}!${normRef(ref)}`;
|
|
4445
|
+
function parseKey(key) {
|
|
4446
|
+
const bang = key.indexOf("!");
|
|
4447
|
+
const sheet = key.slice(0, bang);
|
|
4448
|
+
const ref = key.slice(bang + 1);
|
|
4449
|
+
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(ref);
|
|
4450
|
+
return { sheet, ref, row: row2, col: column2 };
|
|
4451
|
+
}
|
|
4452
|
+
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;
|
|
4453
|
+
var RecalcEngine = class {
|
|
4454
|
+
constructor(model, registry = FunctionRegistry.default) {
|
|
4455
|
+
__privateAdd(this, _RecalcEngine_instances);
|
|
4456
|
+
__privateAdd(this, _model);
|
|
4457
|
+
__privateAdd(this, _registry);
|
|
4458
|
+
__privateAdd(this, _formulas, /* @__PURE__ */ new Map());
|
|
4459
|
+
/** upper-cased sheet key → original casing, for case-correct model I/O. */
|
|
4460
|
+
__privateAdd(this, _sheetNames, /* @__PURE__ */ new Map());
|
|
4461
|
+
/** anchor cellKey → set of spilled (non-anchor) cellKeys it currently owns. */
|
|
4462
|
+
__privateAdd(this, _spills, /* @__PURE__ */ new Map());
|
|
4463
|
+
/** spilled cellKey → the anchor cellKey that owns it. */
|
|
4464
|
+
__privateAdd(this, _spillOwner, /* @__PURE__ */ new Map());
|
|
4465
|
+
__privateSet(this, _model, model);
|
|
4466
|
+
__privateSet(this, _registry, registry);
|
|
4467
|
+
}
|
|
4468
|
+
// ── Registration ─────────────────────────────────────────────────────────────
|
|
4469
|
+
/**
|
|
4470
|
+
* Registers (or replaces) a formula for a cell and recalculates that cell and
|
|
4471
|
+
* everything downstream of it. Returns the recomputed cells in the order they
|
|
4472
|
+
* were evaluated. The leading '=' of the formula is optional.
|
|
4473
|
+
*/
|
|
4474
|
+
setCellFormula(sheet, ref, formula) {
|
|
4475
|
+
if (!formula) throw new Error("Formula cannot be empty.");
|
|
4476
|
+
const text = formula.startsWith("=") ? formula.slice(1) : formula;
|
|
4477
|
+
const s = normSheet(sheet);
|
|
4478
|
+
const r = normRef(ref);
|
|
4479
|
+
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(r);
|
|
4480
|
+
const ast = parseFormula(text);
|
|
4481
|
+
const { refs, volatile } = extractReferences(
|
|
4482
|
+
ast,
|
|
4483
|
+
s,
|
|
4484
|
+
__privateGet(this, _registry),
|
|
4485
|
+
__privateGet(this, _model).getDefinedName ? (n, sh) => __privateGet(this, _model).getDefinedName(n, sh) : void 0
|
|
4486
|
+
);
|
|
4487
|
+
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
4488
|
+
__privateGet(this, _formulas).set(keyOf(s, r), {
|
|
4489
|
+
sheet: s,
|
|
4490
|
+
sheetName: sheet,
|
|
4491
|
+
ref: r,
|
|
4492
|
+
row: row2,
|
|
4493
|
+
col: column2,
|
|
4494
|
+
formula: text,
|
|
4495
|
+
refs,
|
|
4496
|
+
volatile
|
|
4497
|
+
});
|
|
4498
|
+
__privateGet(this, _model).setCellFormula?.(sheet, r, text);
|
|
4499
|
+
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }], keyOf(s, r));
|
|
4500
|
+
}
|
|
4501
|
+
/**
|
|
4502
|
+
* Removes a formula from a cell and recalculates its dependents (which may now
|
|
4503
|
+
* read a plain input value instead). Returns the recomputed dependent cells.
|
|
4504
|
+
*/
|
|
4505
|
+
clearCellFormula(sheet, ref) {
|
|
4506
|
+
const s = normSheet(sheet);
|
|
4507
|
+
const r = normRef(ref);
|
|
4508
|
+
const existed = __privateGet(this, _formulas).delete(keyOf(s, r));
|
|
4509
|
+
if (!existed) return [];
|
|
4510
|
+
__privateGet(this, _model).clearCell?.(sheet, r);
|
|
4511
|
+
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(r);
|
|
4512
|
+
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4513
|
+
}
|
|
4514
|
+
/**
|
|
4515
|
+
* Writes a raw input value to a cell and recalculates every formula that
|
|
4516
|
+
* depends on it. Use this for non-formula edits so dependents stay current.
|
|
4517
|
+
*/
|
|
4518
|
+
setCellValue(sheet, ref, value2) {
|
|
4519
|
+
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
4520
|
+
const s = normSheet(sheet);
|
|
4521
|
+
const r = normRef(ref);
|
|
4522
|
+
__privateGet(this, _model).setCellValue(sheet, r, value2);
|
|
4523
|
+
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(r);
|
|
4524
|
+
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4525
|
+
}
|
|
4526
|
+
/**
|
|
4527
|
+
* Signals that a cell changed by some external means (the model was already
|
|
4528
|
+
* updated) and recalculates its dependents.
|
|
4529
|
+
*/
|
|
4530
|
+
onCellChanged(sheet, ref) {
|
|
4531
|
+
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
4532
|
+
const s = normSheet(sheet);
|
|
4533
|
+
const r = normRef(ref);
|
|
4534
|
+
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(r);
|
|
4535
|
+
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4536
|
+
}
|
|
4537
|
+
/** Recalculates every registered formula in dependency order. */
|
|
4538
|
+
recalcAll() {
|
|
4539
|
+
return __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...__privateGet(this, _formulas).values()]).results;
|
|
4540
|
+
}
|
|
4541
|
+
/** The precedents recorded for a formula cell (mainly for tests/inspection). */
|
|
4542
|
+
getPrecedents(sheet, ref) {
|
|
4543
|
+
return __privateGet(this, _formulas).get(keyOf(sheet, ref))?.refs ?? [];
|
|
4544
|
+
}
|
|
4545
|
+
};
|
|
4546
|
+
_model = new WeakMap();
|
|
4547
|
+
_registry = new WeakMap();
|
|
4548
|
+
_formulas = new WeakMap();
|
|
4549
|
+
_sheetNames = new WeakMap();
|
|
4550
|
+
_spills = new WeakMap();
|
|
4551
|
+
_spillOwner = new WeakMap();
|
|
4552
|
+
_RecalcEngine_instances = new WeakSet();
|
|
4553
|
+
rememberSheet_fn = function(name) {
|
|
4554
|
+
__privateGet(this, _sheetNames).set(normSheet(name), name);
|
|
4555
|
+
};
|
|
4556
|
+
// ── Internals ────────────────────────────────────────────────────────────────
|
|
4557
|
+
/**
|
|
4558
|
+
* Recomputes formulas affected by the given changed cells, to a fixpoint.
|
|
4559
|
+
*
|
|
4560
|
+
* A single topological pass is not enough once dynamic arrays exist: a
|
|
4561
|
+
* formula that spills changes cells the graph could not know about until the
|
|
4562
|
+
* array was computed, and those cells may have their own dependents. So we
|
|
4563
|
+
* evaluate a pass, collect every cell whose value actually changed (formula
|
|
4564
|
+
* outputs plus spilled/cleared cells), then recompute the dependents of those
|
|
4565
|
+
* cells, repeating until nothing new is affected (bounded for safety).
|
|
4566
|
+
*/
|
|
4567
|
+
recalcFrom_fn = function(seeds, includeKey) {
|
|
4568
|
+
const results = [];
|
|
4569
|
+
const evaluated = /* @__PURE__ */ new Set();
|
|
4570
|
+
let pending = __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, seeds, evaluated);
|
|
4571
|
+
for (const e of __privateGet(this, _formulas).values()) {
|
|
4572
|
+
if (e.volatile) pending.set(keyOf(e.sheet, e.ref), e);
|
|
4573
|
+
}
|
|
4574
|
+
if (includeKey) {
|
|
4575
|
+
const self = __privateGet(this, _formulas).get(includeKey);
|
|
4576
|
+
if (self) pending.set(includeKey, self);
|
|
4577
|
+
}
|
|
4578
|
+
let guard = 0;
|
|
4579
|
+
const maxIterations = __privateGet(this, _formulas).size + 8;
|
|
4580
|
+
while (pending.size > 0 && guard++ <= maxIterations) {
|
|
4581
|
+
const { results: passResults, changed } = __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...pending.values()]);
|
|
4582
|
+
results.push(...passResults);
|
|
4583
|
+
for (const k of pending.keys()) evaluated.add(k);
|
|
4584
|
+
pending = __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, changed, evaluated);
|
|
4585
|
+
}
|
|
4586
|
+
return results;
|
|
4587
|
+
};
|
|
4588
|
+
/** Formula entries (not yet evaluated) whose precedents include one of the cells. */
|
|
4589
|
+
dependentClosure_fn = function(cells, evaluated) {
|
|
4590
|
+
const out = /* @__PURE__ */ new Map();
|
|
4591
|
+
const queue = [...cells];
|
|
4592
|
+
while (queue.length > 0) {
|
|
4593
|
+
const cell = queue.shift();
|
|
4594
|
+
for (const dep of __privateMethod(this, _RecalcEngine_instances, directDependents_fn).call(this, cell)) {
|
|
4595
|
+
const k = keyOf(dep.sheet, dep.ref);
|
|
4596
|
+
if (out.has(k) || evaluated.has(k)) continue;
|
|
4597
|
+
out.set(k, dep);
|
|
4598
|
+
queue.push({ sheet: dep.sheet, row: dep.row, col: dep.col });
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
return out;
|
|
4602
|
+
};
|
|
4603
|
+
/** Formula entries whose precedents include the given cell. */
|
|
4604
|
+
directDependents_fn = function(cell) {
|
|
4605
|
+
const out = [];
|
|
4606
|
+
for (const e of __privateGet(this, _formulas).values()) {
|
|
4607
|
+
if (e.refs.some((ref) => refContains(ref, cell.sheet, cell.row, cell.col))) out.push(e);
|
|
4608
|
+
}
|
|
4609
|
+
return out;
|
|
4610
|
+
};
|
|
4611
|
+
/**
|
|
4612
|
+
* Topologically sorts the given entries and evaluates them, writing results
|
|
4613
|
+
* (scalars or spilled arrays) back to the model. Returns the results and the
|
|
4614
|
+
* flat list of cells whose value changed. Entries left in a cycle are #REF!.
|
|
4615
|
+
*/
|
|
4616
|
+
evaluatePass_fn = function(entries) {
|
|
4617
|
+
const inSet = /* @__PURE__ */ new Map();
|
|
4618
|
+
for (const e of entries) inSet.set(keyOf(e.sheet, e.ref), e);
|
|
4619
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
4620
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
4621
|
+
for (const k of inSet.keys()) {
|
|
4622
|
+
indegree.set(k, 0);
|
|
4623
|
+
dependents.set(k, []);
|
|
4624
|
+
}
|
|
4625
|
+
for (const e of entries) {
|
|
4626
|
+
const depKey = keyOf(e.sheet, e.ref);
|
|
4627
|
+
for (const [pk, pe] of inSet) {
|
|
4628
|
+
if (pk === depKey) continue;
|
|
4629
|
+
if (e.refs.some((ref) => refContains(ref, pe.sheet, pe.row, pe.col))) {
|
|
4630
|
+
dependents.get(pk).push(depKey);
|
|
4631
|
+
indegree.set(depKey, indegree.get(depKey) + 1);
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
4635
|
+
const ready = [];
|
|
4636
|
+
for (const [k, d] of indegree) if (d === 0) ready.push(k);
|
|
4637
|
+
const order = [];
|
|
4638
|
+
while (ready.length > 0) {
|
|
4639
|
+
const k = ready.shift();
|
|
4640
|
+
order.push(k);
|
|
4641
|
+
for (const d of dependents.get(k)) {
|
|
4642
|
+
indegree.set(d, indegree.get(d) - 1);
|
|
4643
|
+
if (indegree.get(d) === 0) ready.push(d);
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
const results = [];
|
|
4647
|
+
const changed = [];
|
|
4648
|
+
const emitted = new Set(order);
|
|
4649
|
+
for (const k of order) {
|
|
4650
|
+
const e = inSet.get(k);
|
|
4651
|
+
const { result, changed: c } = __privateMethod(this, _RecalcEngine_instances, writeResult_fn).call(this, e);
|
|
4652
|
+
results.push(result);
|
|
4653
|
+
changed.push(...c);
|
|
4654
|
+
}
|
|
4655
|
+
for (const [k, e] of inSet) {
|
|
4656
|
+
if (emitted.has(k)) continue;
|
|
4657
|
+
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, keyOf(e.sheet, e.ref), changed);
|
|
4658
|
+
const value2 = FormulaValue.errorRef.toObject();
|
|
4659
|
+
__privateGet(this, _model).setCellValue(e.sheetName, e.ref, value2);
|
|
4660
|
+
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
4661
|
+
results.push({ sheet: e.sheetName, ref: e.ref, value: value2, circular: true });
|
|
4662
|
+
}
|
|
4663
|
+
return { results, changed };
|
|
4664
|
+
};
|
|
4665
|
+
/**
|
|
4666
|
+
* Evaluates one formula and writes its result. Scalars go to the anchor cell.
|
|
4667
|
+
* Arrays spill across a block anchored at the formula cell: on a collision
|
|
4668
|
+
* with existing content the anchor becomes #SPILL! and nothing spills; when
|
|
4669
|
+
* an array shrinks, previously-spilled cells it no longer covers are cleared.
|
|
4670
|
+
*/
|
|
4671
|
+
writeResult_fn = function(e) {
|
|
4672
|
+
const anchorKey = keyOf(e.sheet, e.ref);
|
|
4673
|
+
const fv2 = __privateMethod(this, _RecalcEngine_instances, evaluateFormulaValue_fn).call(this, e);
|
|
4674
|
+
const changed = [];
|
|
4675
|
+
const prevSpill = __privateGet(this, _spills).get(anchorKey) ?? /* @__PURE__ */ new Set();
|
|
4676
|
+
if (!fv2.isArray) {
|
|
4677
|
+
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
|
|
4678
|
+
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
4679
|
+
const value2 = fv2.toObject();
|
|
4680
|
+
__privateGet(this, _model).setCellValue(e.sheetName, e.ref, value2);
|
|
4681
|
+
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
4682
|
+
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
4683
|
+
}
|
|
4684
|
+
const arr = fv2.arrayVal;
|
|
4685
|
+
const rows2 = arr.rows, cols = arr.columns;
|
|
4686
|
+
let collides = false;
|
|
4687
|
+
for (let dr = 0; dr < rows2 && !collides; dr++) {
|
|
4688
|
+
for (let dc = 0; dc < cols && !collides; dc++) {
|
|
4689
|
+
if (dr === 0 && dc === 0) continue;
|
|
4690
|
+
const r = e.row + dr, c = e.col + dc;
|
|
4691
|
+
const cellKey = keyOf(e.sheet, puredocsExcel.cellRefFromRowCol(r, c));
|
|
4692
|
+
if (__privateMethod(this, _RecalcEngine_instances, isOccupied_fn).call(this, cellKey, e.sheetName, puredocsExcel.cellRefFromRowCol(r, c), anchorKey, prevSpill)) {
|
|
4693
|
+
collides = true;
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
if (collides) {
|
|
4698
|
+
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
|
|
4699
|
+
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
4700
|
+
const value2 = FormulaValue.error(9 /* Spill */).toObject();
|
|
4701
|
+
__privateGet(this, _model).setCellValue(e.sheetName, e.ref, value2);
|
|
4702
|
+
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
4703
|
+
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
4704
|
+
}
|
|
4705
|
+
const newSpill = /* @__PURE__ */ new Set();
|
|
4706
|
+
for (let dr = 0; dr < rows2; dr++) {
|
|
4707
|
+
for (let dc = 0; dc < cols; dc++) {
|
|
4708
|
+
const r = e.row + dr, c = e.col + dc;
|
|
4709
|
+
const ref = puredocsExcel.cellRefFromRowCol(r, c);
|
|
4710
|
+
__privateGet(this, _model).setCellValue(e.sheetName, ref, arr.get(dr, dc).toObject());
|
|
4711
|
+
changed.push({ sheet: e.sheet, row: r, col: c });
|
|
4712
|
+
if (dr !== 0 || dc !== 0) {
|
|
4713
|
+
const cellKey = keyOf(e.sheet, ref);
|
|
4714
|
+
newSpill.add(cellKey);
|
|
4715
|
+
__privateGet(this, _spillOwner).set(cellKey, anchorKey);
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
for (const oldKey of prevSpill) {
|
|
4720
|
+
if (newSpill.has(oldKey)) continue;
|
|
4721
|
+
const cell = parseKey(oldKey);
|
|
4722
|
+
__privateGet(this, _model).setCellValue(__privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null);
|
|
4723
|
+
__privateGet(this, _spillOwner).delete(oldKey);
|
|
4724
|
+
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
4725
|
+
}
|
|
4726
|
+
__privateGet(this, _spills).set(anchorKey, newSpill);
|
|
4727
|
+
const spillRef = `${e.ref}:${puredocsExcel.cellRefFromRowCol(e.row + rows2 - 1, e.col + cols - 1)}`;
|
|
4728
|
+
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, spillRef);
|
|
4729
|
+
return {
|
|
4730
|
+
result: { sheet: e.sheetName, ref: e.ref, value: arr.get(0, 0).toObject(), spill: { rows: rows2, cols } },
|
|
4731
|
+
changed
|
|
4732
|
+
};
|
|
4733
|
+
};
|
|
4734
|
+
/** Whether a target cell already holds content that blocks a spill. */
|
|
4735
|
+
isOccupied_fn = function(cellKey, sheetName, ref, anchorKey, prevSpill) {
|
|
4736
|
+
if (__privateGet(this, _formulas).has(cellKey) && cellKey !== anchorKey) return true;
|
|
4737
|
+
const owner = __privateGet(this, _spillOwner).get(cellKey);
|
|
4738
|
+
if (owner && owner !== anchorKey) return true;
|
|
4739
|
+
if (prevSpill.has(cellKey)) return false;
|
|
4740
|
+
const v = __privateGet(this, _model).getCellValue(sheetName, ref);
|
|
4741
|
+
return v !== null && v !== void 0 && v !== "";
|
|
4742
|
+
};
|
|
4743
|
+
/** Clears an anchor's spilled cells (not the anchor itself) and forgets them. */
|
|
4744
|
+
clearSpill_fn = function(anchorKey, changed) {
|
|
4745
|
+
const spill = __privateGet(this, _spills).get(anchorKey);
|
|
4746
|
+
if (!spill) return;
|
|
4747
|
+
for (const cellKey of spill) {
|
|
4748
|
+
const cell = parseKey(cellKey);
|
|
4749
|
+
__privateGet(this, _model).setCellValue(__privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null);
|
|
4750
|
+
__privateGet(this, _spillOwner).delete(cellKey);
|
|
4751
|
+
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
4752
|
+
}
|
|
4753
|
+
__privateGet(this, _spills).delete(anchorKey);
|
|
4754
|
+
};
|
|
4755
|
+
evaluateFormulaValue_fn = function(e) {
|
|
4756
|
+
const wb = __privateMethod(this, _RecalcEngine_instances, buildWorkbookLike_fn).call(this);
|
|
4757
|
+
const ws = __privateMethod(this, _RecalcEngine_instances, sheetLike_fn).call(this, e.sheetName);
|
|
4758
|
+
return evaluateFormula(e.formula, ws, {
|
|
4759
|
+
workbook: wb,
|
|
4760
|
+
formulaRow: e.row,
|
|
4761
|
+
formulaCol: e.col
|
|
4762
|
+
});
|
|
4763
|
+
};
|
|
4764
|
+
sheetLike_fn = function(sheetName) {
|
|
4765
|
+
return {
|
|
4766
|
+
name: sheetName,
|
|
4767
|
+
getCellValue: (ref) => __privateGet(this, _model).getCellValue(sheetName, ref),
|
|
4768
|
+
isRowHidden: __privateGet(this, _model).isRowHidden ? (row2) => __privateGet(this, _model).isRowHidden(sheetName, row2) : void 0,
|
|
4769
|
+
getCellFormula: __privateGet(this, _model).getCellFormula ? (ref) => __privateGet(this, _model).getCellFormula(sheetName, ref) : void 0,
|
|
4770
|
+
getSpillRange: __privateGet(this, _model).getSpillRange ? (ref) => __privateGet(this, _model).getSpillRange(sheetName, ref) : void 0
|
|
4771
|
+
};
|
|
4772
|
+
};
|
|
4773
|
+
buildWorkbookLike_fn = function() {
|
|
4774
|
+
const self = this;
|
|
4775
|
+
return {
|
|
4776
|
+
get worksheets() {
|
|
4777
|
+
return [...__privateGet(self, _sheetNames).values()].map((n) => {
|
|
4778
|
+
var _a;
|
|
4779
|
+
return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, n);
|
|
4780
|
+
});
|
|
4781
|
+
},
|
|
4782
|
+
getWorksheet: (name) => {
|
|
4783
|
+
var _a;
|
|
4784
|
+
return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, name);
|
|
4785
|
+
},
|
|
4786
|
+
getDefinedName: __privateGet(this, _model).getDefinedName ? (name, sheet) => __privateGet(this, _model).getDefinedName(name, sheet) : void 0
|
|
4787
|
+
};
|
|
4788
|
+
};
|
|
2904
4789
|
|
|
2905
4790
|
exports.ArrayValue = ArrayValue;
|
|
2906
4791
|
exports.BinaryOpNode = BinaryOpNode;
|
|
2907
4792
|
exports.BinaryOperator = BinaryOperator;
|
|
2908
4793
|
exports.BlankNode = BlankNode;
|
|
2909
4794
|
exports.BooleanNode = BooleanNode;
|
|
4795
|
+
exports.CallNode = CallNode;
|
|
2910
4796
|
exports.CellReferenceNode = CellReferenceNode;
|
|
2911
4797
|
exports.ErrorNode = ErrorNode;
|
|
2912
4798
|
exports.FormulaContext = FormulaContext;
|
|
@@ -2923,21 +4809,30 @@ exports.LruCache = LruCache;
|
|
|
2923
4809
|
exports.NamedRangeNode = NamedRangeNode;
|
|
2924
4810
|
exports.NumberNode = NumberNode;
|
|
2925
4811
|
exports.RangeReferenceNode = RangeReferenceNode;
|
|
4812
|
+
exports.RecalcEngine = RecalcEngine;
|
|
2926
4813
|
exports.SheetCellReferenceNode = SheetCellReferenceNode;
|
|
2927
4814
|
exports.SheetRangeReferenceNode = SheetRangeReferenceNode;
|
|
4815
|
+
exports.SpillReferenceNode = SpillReferenceNode;
|
|
2928
4816
|
exports.StringNode = StringNode;
|
|
2929
4817
|
exports.TokenType = TokenType;
|
|
2930
4818
|
exports.UnaryOpNode = UnaryOpNode;
|
|
2931
4819
|
exports.UnaryOperator = UnaryOperator;
|
|
2932
4820
|
exports.clearAstCache = clearAstCache;
|
|
4821
|
+
exports.createWorkbookRecalcModel = createWorkbookRecalcModel;
|
|
2933
4822
|
exports.evaluateFormula = evaluateFormula;
|
|
4823
|
+
exports.extractReferences = extractReferences;
|
|
2934
4824
|
exports.getAstCacheStats = getAstCacheStats;
|
|
2935
4825
|
exports.parseFormula = parseFormula;
|
|
4826
|
+
exports.refContains = refContains;
|
|
4827
|
+
exports.registerAggregate = registerAggregate;
|
|
4828
|
+
exports.registerArray = registerArray;
|
|
2936
4829
|
exports.registerDate = registerDate;
|
|
4830
|
+
exports.registerFinance = registerFinance;
|
|
2937
4831
|
exports.registerInfo = registerInfo;
|
|
2938
4832
|
exports.registerLogical = registerLogical;
|
|
2939
4833
|
exports.registerLookup = registerLookup;
|
|
2940
4834
|
exports.registerMath = registerMath;
|
|
4835
|
+
exports.registerMeta = registerMeta;
|
|
2941
4836
|
exports.registerStatistical = registerStatistical;
|
|
2942
4837
|
exports.registerText = registerText;
|
|
2943
4838
|
exports.setAstCacheCapacity = setAstCacheCapacity;
|