mulmoclaude 2.7.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/client/assets/Graph-ChAxkn9o.js +1 -0
  2. package/client/assets/dist-Bij44LdC.js +2 -0
  3. package/client/assets/echarts-GV3U9sVf.js +1 -0
  4. package/client/assets/echarts-gl-BXx4On0Y.js +4282 -0
  5. package/client/assets/echarts-wordcloud-_9uQUAfp.js +1 -0
  6. package/client/assets/emscripten-module-Q67P5WYC-BU3Hlttz.js +2160 -0
  7. package/client/assets/ffi-4V2b7xu3.js +1 -0
  8. package/client/assets/{index-lkpgbKtk.js → index-D6xzCZhP.js} +600 -589
  9. package/client/assets/index-DzMsXDnj.css +2 -0
  10. package/client/assets/{install-wxcddUsO.js → install-C1moNcrn.js} +1 -1
  11. package/client/assets/{marp-DcTywifN.js → marp-Du5Sym5b.js} +1 -1
  12. package/client/assets/module-ES6BEMUI-CDTIpMiA.js +6 -0
  13. package/client/index.html +4 -3
  14. package/package.json +5 -4
  15. package/server/api/routes/plugins.ts +8 -59
  16. package/server/build/mcp-server.mjs +609 -608
  17. package/server/index.ts +1 -0
  18. package/server/plugins/confined-file-ops.ts +136 -0
  19. package/server/plugins/runtime.ts +8 -115
  20. package/server/plugins/spreadsheet-builtin.ts +26 -0
  21. package/server/plugins/spreadsheet-save-outcome.ts +7 -0
  22. package/server/utils/files/store-resolvers.ts +1 -1
  23. package/src/lang/de.ts +0 -20
  24. package/src/lang/en.ts +0 -20
  25. package/src/lang/es.ts +0 -20
  26. package/src/lang/fr.ts +0 -20
  27. package/src/lang/ja.ts +0 -20
  28. package/src/lang/ko.ts +0 -20
  29. package/src/lang/pt-BR.ts +0 -20
  30. package/src/lang/zh.ts +0 -20
  31. package/src/plugins/spreadsheet/definition.ts +9 -127
  32. package/src/plugins/spreadsheet/index.ts +7 -4
  33. package/src/plugins/spreadsheet/meta.ts +2 -4
  34. package/client/assets/echarts-gl-Cq76Z6DA.js +0 -4282
  35. package/client/assets/index-Cz7JLE0k.css +0 -2
  36. package/server/utils/files/spreadsheet-store.ts +0 -26
  37. package/src/plugins/spreadsheet/Preview.vue +0 -31
  38. package/src/plugins/spreadsheet/View.vue +0 -1007
  39. package/src/plugins/spreadsheet/cellHighlights.ts +0 -78
  40. package/src/plugins/spreadsheet/engine/calculator.ts +0 -427
  41. package/src/plugins/spreadsheet/engine/cellBuilder.ts +0 -89
  42. package/src/plugins/spreadsheet/engine/cellEmpty.ts +0 -26
  43. package/src/plugins/spreadsheet/engine/cellFormatting.ts +0 -59
  44. package/src/plugins/spreadsheet/engine/coerce-boolean.ts +0 -26
  45. package/src/plugins/spreadsheet/engine/condition.ts +0 -182
  46. package/src/plugins/spreadsheet/engine/date-locale.ts +0 -47
  47. package/src/plugins/spreadsheet/engine/date-parser.ts +0 -234
  48. package/src/plugins/spreadsheet/engine/date-utils.ts +0 -73
  49. package/src/plugins/spreadsheet/engine/datedif.ts +0 -93
  50. package/src/plugins/spreadsheet/engine/engine.ts +0 -177
  51. package/src/plugins/spreadsheet/engine/evaluator.ts +0 -494
  52. package/src/plugins/spreadsheet/engine/financial-math.ts +0 -114
  53. package/src/plugins/spreadsheet/engine/formatter.ts +0 -158
  54. package/src/plugins/spreadsheet/engine/formulaError.ts +0 -67
  55. package/src/plugins/spreadsheet/engine/formulaRefs.ts +0 -202
  56. package/src/plugins/spreadsheet/engine/functions/date.ts +0 -212
  57. package/src/plugins/spreadsheet/engine/functions/financial.ts +0 -244
  58. package/src/plugins/spreadsheet/engine/functions/index.ts +0 -16
  59. package/src/plugins/spreadsheet/engine/functions/logical.ts +0 -221
  60. package/src/plugins/spreadsheet/engine/functions/lookup-math.ts +0 -28
  61. package/src/plugins/spreadsheet/engine/functions/lookup.ts +0 -257
  62. package/src/plugins/spreadsheet/engine/functions/mathematical.ts +0 -283
  63. package/src/plugins/spreadsheet/engine/functions/statistical-math.ts +0 -91
  64. package/src/plugins/spreadsheet/engine/functions/statistical.ts +0 -290
  65. package/src/plugins/spreadsheet/engine/functions/text.ts +0 -398
  66. package/src/plugins/spreadsheet/engine/index.ts +0 -34
  67. package/src/plugins/spreadsheet/engine/jsonCellLocator.ts +0 -111
  68. package/src/plugins/spreadsheet/engine/math-ops.ts +0 -98
  69. package/src/plugins/spreadsheet/engine/numericCoercion.ts +0 -61
  70. package/src/plugins/spreadsheet/engine/parser.ts +0 -40
  71. package/src/plugins/spreadsheet/engine/registry.ts +0 -201
  72. package/src/plugins/spreadsheet/engine/responseDecoder.ts +0 -68
  73. package/src/plugins/spreadsheet/engine/spreadsheet-errors.ts +0 -82
  74. package/src/plugins/spreadsheet/engine/textFormat.ts +0 -108
  75. package/src/plugins/spreadsheet/engine/translateFormula.ts +0 -69
  76. package/src/plugins/spreadsheet/engine/types.ts +0 -81
  77. package/src/plugins/spreadsheet/keyboardNav.ts +0 -38
@@ -1,212 +0,0 @@
1
- /**
2
- * Date and Time Functions
3
- * Excel stores dates as sequential serial numbers so they can be used in calculations.
4
- * January 1, 1900 is serial number 1.
5
- */
6
-
7
- import { functionRegistry, requiredArg, toNumber, toString, type FunctionHandler } from "../registry";
8
- import { computeDatedif } from "../datedif";
9
- import { dateToSerial, serialToDate } from "../date-utils";
10
-
11
- const nowHandler: FunctionHandler = () => {
12
- // Return current date and time as serial number
13
- // We need to adjust for timezone offset because Excel dates are "local" usually
14
- // But for simplicity we'll use local time converted to serial
15
- const now = new Date();
16
- // Create a UTC date that matches the local time components
17
- const localAsUtc = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds()));
18
- return dateToSerial(localAsUtc);
19
- };
20
-
21
- const todayHandler: FunctionHandler = () => {
22
- const now = new Date();
23
- const today = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
24
- return dateToSerial(today);
25
- };
26
-
27
- const dateHandler: FunctionHandler = (args, context) => {
28
- const year = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
29
- const month = toNumber(context.evaluateFormula(requiredArg(context, args, 1)));
30
- const day = toNumber(context.evaluateFormula(requiredArg(context, args, 2)));
31
-
32
- // JS Date constructor handles overflow (e.g. month 13 becomes Jan of next year)
33
- // Month is 0-indexed in JS, 1-indexed in Excel
34
- const date = new Date(Date.UTC(year, month - 1, day));
35
- return dateToSerial(date);
36
- };
37
-
38
- const timeHandler: FunctionHandler = (args, context) => {
39
- const hour = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
40
- const minute = toNumber(context.evaluateFormula(requiredArg(context, args, 1)));
41
- const second = toNumber(context.evaluateFormula(requiredArg(context, args, 2)));
42
-
43
- // Time is a fraction of a day
44
- // 1 hour = 1/24
45
- // 1 minute = 1/(24*60)
46
- // 1 second = 1/(24*60*60)
47
-
48
- // Normalize inputs (e.g. 25 hours)
49
- const totalSeconds = hour * 3600 + minute * 60 + second;
50
- const secondsPerDay = 86400;
51
-
52
- return (totalSeconds % secondsPerDay) / secondsPerDay;
53
- };
54
-
55
- const yearHandler: FunctionHandler = (args, context) => {
56
- const serial = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
57
- const date = serialToDate(serial);
58
- return date.getUTCFullYear();
59
- };
60
-
61
- const monthHandler: FunctionHandler = (args, context) => {
62
- const serial = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
63
- const date = serialToDate(serial);
64
- return date.getUTCMonth() + 1; // 1-indexed
65
- };
66
-
67
- const dayHandler: FunctionHandler = (args, context) => {
68
- const serial = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
69
- const date = serialToDate(serial);
70
- return date.getUTCDate();
71
- };
72
-
73
- const hourHandler: FunctionHandler = (args, context) => {
74
- const serial = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
75
- // Get fractional part
76
- const timePart = serial - Math.floor(serial);
77
- const totalSeconds = Math.round(timePart * 86400);
78
- return Math.floor(totalSeconds / 3600);
79
- };
80
-
81
- const minuteHandler: FunctionHandler = (args, context) => {
82
- const serial = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
83
- const timePart = serial - Math.floor(serial);
84
- const totalSeconds = Math.round(timePart * 86400);
85
- return Math.floor((totalSeconds % 3600) / 60);
86
- };
87
-
88
- const secondHandler: FunctionHandler = (args, context) => {
89
- const serial = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
90
- const timePart = serial - Math.floor(serial);
91
- const totalSeconds = Math.round(timePart * 86400);
92
- return totalSeconds % 60;
93
- };
94
-
95
- const datedifHandler: FunctionHandler = (args, context) => {
96
- const startSerial = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
97
- const endSerial = toNumber(context.evaluateFormula(requiredArg(context, args, 1)));
98
- const unit = toString(context.evaluateFormula(requiredArg(context, args, 2)));
99
-
100
- return computeDatedif(startSerial, endSerial, unit);
101
- };
102
-
103
- // Register functions
104
- functionRegistry.register({
105
- name: "NOW",
106
- handler: nowHandler,
107
- minArgs: 0,
108
- maxArgs: 0,
109
- description: "Returns the serial number of the current date and time",
110
- examples: ["NOW()"],
111
- category: "Date & Time",
112
- });
113
-
114
- functionRegistry.register({
115
- name: "TODAY",
116
- handler: todayHandler,
117
- minArgs: 0,
118
- maxArgs: 0,
119
- description: "Returns the serial number of today's date",
120
- examples: ["TODAY()"],
121
- category: "Date & Time",
122
- });
123
-
124
- functionRegistry.register({
125
- name: "DATE",
126
- handler: dateHandler,
127
- minArgs: 3,
128
- maxArgs: 3,
129
- description: "Returns the serial number that represents a particular date",
130
- examples: ["DATE(2023, 11, 25)"],
131
- category: "Date & Time",
132
- });
133
-
134
- functionRegistry.register({
135
- name: "TIME",
136
- handler: timeHandler,
137
- minArgs: 3,
138
- maxArgs: 3,
139
- description: "Returns the serial number of a particular time",
140
- examples: ["TIME(14, 30, 0)"],
141
- category: "Date & Time",
142
- });
143
-
144
- functionRegistry.register({
145
- name: "YEAR",
146
- handler: yearHandler,
147
- minArgs: 1,
148
- maxArgs: 1,
149
- description: "Converts a serial number to a year",
150
- examples: ["YEAR(TODAY())", "YEAR(A1)"],
151
- category: "Date & Time",
152
- });
153
-
154
- functionRegistry.register({
155
- name: "MONTH",
156
- handler: monthHandler,
157
- minArgs: 1,
158
- maxArgs: 1,
159
- description: "Converts a serial number to a month",
160
- examples: ["MONTH(TODAY())", "MONTH(A1)"],
161
- category: "Date & Time",
162
- });
163
-
164
- functionRegistry.register({
165
- name: "DAY",
166
- handler: dayHandler,
167
- minArgs: 1,
168
- maxArgs: 1,
169
- description: "Converts a serial number to a day of the month",
170
- examples: ["DAY(TODAY())", "DAY(A1)"],
171
- category: "Date & Time",
172
- });
173
-
174
- functionRegistry.register({
175
- name: "HOUR",
176
- handler: hourHandler,
177
- minArgs: 1,
178
- maxArgs: 1,
179
- description: "Converts a serial number to an hour",
180
- examples: ["HOUR(NOW())", "HOUR(A1)"],
181
- category: "Date & Time",
182
- });
183
-
184
- functionRegistry.register({
185
- name: "MINUTE",
186
- handler: minuteHandler,
187
- minArgs: 1,
188
- maxArgs: 1,
189
- description: "Converts a serial number to a minute",
190
- examples: ["MINUTE(NOW())", "MINUTE(A1)"],
191
- category: "Date & Time",
192
- });
193
-
194
- functionRegistry.register({
195
- name: "SECOND",
196
- handler: secondHandler,
197
- minArgs: 1,
198
- maxArgs: 1,
199
- description: "Converts a serial number to a second",
200
- examples: ["SECOND(NOW())", "SECOND(A1)"],
201
- category: "Date & Time",
202
- });
203
-
204
- functionRegistry.register({
205
- name: "DATEDIF",
206
- handler: datedifHandler,
207
- minArgs: 3,
208
- maxArgs: 3,
209
- description: "Calculates the number of days, months, or years between two dates",
210
- examples: ['DATEDIF(A1, B1, "Y")', 'DATEDIF(DATE(2020,1,1), TODAY(), "D")'],
211
- category: "Date & Time",
212
- });
@@ -1,244 +0,0 @@
1
- /**
2
- * Financial Functions
3
- */
4
-
5
- import { functionRegistry, requiredArg, toNumber, type FunctionContext, type FunctionHandler } from "../registry";
6
- import { computeFv, computePmt, computeIpmt, computePpmt, computePv, computeNper, computeRate, computeNpv, computeIrr } from "../financial-math";
7
-
8
- /**
9
- * FV - Future Value
10
- * Calculates the future value of an investment based on periodic, constant payments and a constant interest rate.
11
- * FV(rate, nper, pmt, [pv], [type])
12
- * - rate: Interest rate per period
13
- * - nper: Total number of payment periods
14
- * - pmt: Payment made each period (negative for outflows)
15
- * - pv: Present value (optional, default 0)
16
- * - type: 0 = end of period, 1 = beginning of period (optional, default 0)
17
- */
18
- const fvHandler: FunctionHandler = (args, context) => {
19
- const rate = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
20
- const nper = toNumber(context.evaluateFormula(requiredArg(context, args, 1)));
21
- const pmt = toNumber(context.evaluateFormula(requiredArg(context, args, 2)));
22
- const pv = args.length >= 4 ? toNumber(context.evaluateFormula(requiredArg(context, args, 3))) : 0;
23
- const type = args.length >= 5 ? toNumber(context.evaluateFormula(requiredArg(context, args, 4))) : 0;
24
-
25
- return computeFv(rate, nper, pmt, pv, type);
26
- };
27
-
28
- /**
29
- * PV - Present Value
30
- * Calculates the present value of an investment based on periodic, constant payments and a constant interest rate.
31
- * PV(rate, nper, pmt, [fv], [type])
32
- */
33
- const pvHandler: FunctionHandler = (args, context) => {
34
- const rate = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
35
- const nper = toNumber(context.evaluateFormula(requiredArg(context, args, 1)));
36
- const pmt = toNumber(context.evaluateFormula(requiredArg(context, args, 2)));
37
- const fv = args.length >= 4 ? toNumber(context.evaluateFormula(requiredArg(context, args, 3))) : 0;
38
- const type = args.length >= 5 ? toNumber(context.evaluateFormula(requiredArg(context, args, 4))) : 0;
39
-
40
- return computePv(rate, nper, pmt, fv, type);
41
- };
42
-
43
- /**
44
- * PMT - Payment
45
- * Calculates the payment for a loan based on constant payments and a constant interest rate.
46
- * PMT(rate, nper, pv, [fv], [type])
47
- */
48
- const pmtHandler: FunctionHandler = (args, context) => {
49
- const rate = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
50
- const nper = toNumber(context.evaluateFormula(requiredArg(context, args, 1)));
51
- const pv = toNumber(context.evaluateFormula(requiredArg(context, args, 2)));
52
- const fv = args.length >= 4 ? toNumber(context.evaluateFormula(requiredArg(context, args, 3))) : 0;
53
- const type = args.length >= 5 ? toNumber(context.evaluateFormula(requiredArg(context, args, 4))) : 0;
54
-
55
- return computePmt(rate, nper, pv, fv, type);
56
- };
57
-
58
- /**
59
- * NPER - Number of Periods
60
- * Calculates the number of periods for an investment based on periodic, constant payments and a constant interest rate.
61
- * NPER(rate, pmt, pv, [fv], [type])
62
- */
63
- const nperHandler: FunctionHandler = (args, context) => {
64
- const rate = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
65
- const pmt = toNumber(context.evaluateFormula(requiredArg(context, args, 1)));
66
- const pv = toNumber(context.evaluateFormula(requiredArg(context, args, 2)));
67
- const fv = args.length >= 4 ? toNumber(context.evaluateFormula(requiredArg(context, args, 3))) : 0;
68
- const type = args.length >= 5 ? toNumber(context.evaluateFormula(requiredArg(context, args, 4))) : 0;
69
-
70
- return computeNper(rate, pmt, pv, fv, type);
71
- };
72
-
73
- /**
74
- * RATE - Interest Rate
75
- * Calculates the interest rate per period of an annuity.
76
- * RATE(nper, pmt, pv, [fv], [type], [guess])
77
- * Uses Newton-Raphson method for iteration
78
- */
79
- const rateHandler: FunctionHandler = (args, context) => {
80
- const nper = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
81
- const pmt = toNumber(context.evaluateFormula(requiredArg(context, args, 1)));
82
- const pv = toNumber(context.evaluateFormula(requiredArg(context, args, 2)));
83
- const fv = args.length >= 4 ? toNumber(context.evaluateFormula(requiredArg(context, args, 3))) : 0;
84
- const type = args.length >= 5 ? toNumber(context.evaluateFormula(requiredArg(context, args, 4))) : 0;
85
- const guess = args.length >= 6 ? toNumber(context.evaluateFormula(requiredArg(context, args, 5))) : 0.1;
86
-
87
- return computeRate(nper, pmt, pv, fv, type, guess);
88
- };
89
-
90
- // IPMT and PPMT take the SAME six operands — (rate, per, nper, pv, [fv], [type])
91
- // — and differ only in which annuity component they compute. One factory parses
92
- // the shared shape so the two handlers can't drift in their optional-arg defaults.
93
- type PeriodicComponent = (rate: number, per: number, nper: number, pv: number, fv: number, type: number) => number;
94
-
95
- const makePeriodicComponentHandler =
96
- (compute: PeriodicComponent): FunctionHandler =>
97
- (args, context) => {
98
- const rate = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
99
- const per = toNumber(context.evaluateFormula(requiredArg(context, args, 1)));
100
- const nper = toNumber(context.evaluateFormula(requiredArg(context, args, 2)));
101
- const pv = toNumber(context.evaluateFormula(requiredArg(context, args, 3)));
102
- const fv = args.length >= 5 ? toNumber(context.evaluateFormula(requiredArg(context, args, 4))) : 0;
103
- const type = args.length >= 6 ? toNumber(context.evaluateFormula(requiredArg(context, args, 5))) : 0;
104
-
105
- return compute(rate, per, nper, pv, fv, type);
106
- };
107
-
108
- /**
109
- * IPMT - Interest Payment
110
- * Calculates the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
111
- * IPMT(rate, per, nper, pv, [fv], [type])
112
- */
113
- const ipmtHandler: FunctionHandler = makePeriodicComponentHandler(computeIpmt);
114
-
115
- /**
116
- * PPMT - Principal Payment
117
- * Calculates the payment on the principal for a given period for an investment based on periodic, constant payments and a constant interest rate.
118
- * PPMT(rate, per, nper, pv, [fv], [type])
119
- */
120
- const ppmtHandler: FunctionHandler = makePeriodicComponentHandler(computePpmt);
121
-
122
- /**
123
- * NPV - Net Present Value
124
- * Calculates the net present value of an investment based on a discount rate and a series of future cash flows.
125
- * NPV(rate, value1, [value2], ...)
126
- */
127
- // Flatten NPV's value arguments into one ordered list of numeric cash flows:
128
- // ranges expand in place (numeric cells only), scalars contribute one value.
129
- // The order defines each flow's discount period, so ranges and the scalars
130
- // after them must stay in argument order.
131
- const collectCashFlows = (valueArgs: string[], context: FunctionContext): number[] =>
132
- valueArgs.flatMap((arg) => (arg.includes(":") ? context.getRangeValues(arg) : [context.evaluateFormula(arg)]).map(toNumber));
133
-
134
- const npvHandler: FunctionHandler = (args, context) => {
135
- const rate = toNumber(context.evaluateFormula(requiredArg(context, args, 0)));
136
- return computeNpv(rate, collectCashFlows(args.slice(1), context));
137
- };
138
-
139
- /**
140
- * IRR - Internal Rate of Return
141
- * Calculates the internal rate of return for a series of cash flows.
142
- * IRR(values, [guess])
143
- * Uses Newton-Raphson method for iteration
144
- */
145
- const irrHandler: FunctionHandler = (args, context) => {
146
- const values = context.getRangeValues(requiredArg(context, args, 0)).map(toNumber);
147
- const guess = args.length === 2 ? toNumber(context.evaluateFormula(requiredArg(context, args, 1))) : 0.1;
148
-
149
- if (values.length === 0) {
150
- throw new Error("IRR requires at least one value");
151
- }
152
-
153
- return computeIrr(values, guess);
154
- };
155
-
156
- // Register all financial functions
157
- functionRegistry.register({
158
- name: "FV",
159
- handler: fvHandler,
160
- minArgs: 3,
161
- maxArgs: 5,
162
- description: "Calculates the future value of an investment based on periodic, constant payments and a constant interest rate",
163
- examples: ["FV(0.06/12, 12, -100, -1000, 1)", "FV(A1, A2, A3)"],
164
- category: "Financial",
165
- });
166
-
167
- functionRegistry.register({
168
- name: "PV",
169
- handler: pvHandler,
170
- minArgs: 3,
171
- maxArgs: 5,
172
- description: "Calculates the present value of an investment based on periodic, constant payments and a constant interest rate",
173
- examples: ["PV(0.08/12, 12*20, 500, 0, 0)", "PV(A1, A2, A3)"],
174
- category: "Financial",
175
- });
176
-
177
- functionRegistry.register({
178
- name: "PMT",
179
- handler: pmtHandler,
180
- minArgs: 3,
181
- maxArgs: 5,
182
- description: "Calculates the payment for a loan based on constant payments and a constant interest rate",
183
- examples: ["PMT(0.06/12, 30*12, 250000)", "PMT(A1, A2, A3)"],
184
- category: "Financial",
185
- });
186
-
187
- functionRegistry.register({
188
- name: "NPER",
189
- handler: nperHandler,
190
- minArgs: 3,
191
- maxArgs: 5,
192
- description: "Calculates the number of periods for an investment based on periodic, constant payments and a constant interest rate",
193
- examples: ["NPER(0.06/12, -1000, 50000, 0, 0)", "NPER(A1, A2, A3)"],
194
- category: "Financial",
195
- });
196
-
197
- functionRegistry.register({
198
- name: "RATE",
199
- handler: rateHandler,
200
- minArgs: 3,
201
- maxArgs: 6,
202
- description: "Calculates the interest rate per period of an annuity using iteration",
203
- examples: ["RATE(12, -100, 1000, 0, 0, 0.1)", "RATE(A1, A2, A3)"],
204
- category: "Financial",
205
- });
206
-
207
- functionRegistry.register({
208
- name: "IPMT",
209
- handler: ipmtHandler,
210
- minArgs: 4,
211
- maxArgs: 6,
212
- description: "Calculates the interest payment for a given period for an investment",
213
- examples: ["IPMT(0.06/12, 1, 30*12, 250000)", "IPMT(A1, A2, A3, A4)"],
214
- category: "Financial",
215
- });
216
-
217
- functionRegistry.register({
218
- name: "PPMT",
219
- handler: ppmtHandler,
220
- minArgs: 4,
221
- maxArgs: 6,
222
- description: "Calculates the payment on the principal for a given period for an investment",
223
- examples: ["PPMT(0.06/12, 1, 30*12, 250000)", "PPMT(A1, A2, A3, A4)"],
224
- category: "Financial",
225
- });
226
-
227
- functionRegistry.register({
228
- name: "NPV",
229
- handler: npvHandler,
230
- minArgs: 2,
231
- description: "Calculates the net present value of an investment based on a discount rate and a series of future cash flows",
232
- examples: ["NPV(0.1, -10000, 3000, 4200, 6800)", "NPV(A1, B1:B10)"],
233
- category: "Financial",
234
- });
235
-
236
- functionRegistry.register({
237
- name: "IRR",
238
- handler: irrHandler,
239
- minArgs: 1,
240
- maxArgs: 2,
241
- description: "Calculates the internal rate of return for a series of cash flows",
242
- examples: ["IRR(A1:A5, 0.1)", "IRR(B1:B10)"],
243
- category: "Financial",
244
- });
@@ -1,16 +0,0 @@
1
- /**
2
- * Spreadsheet Functions Index
3
- *
4
- * Imports all function categories to register them with the function registry.
5
- * Import this file to load all available spreadsheet functions.
6
- */
7
-
8
- import "./statistical";
9
- import "./mathematical";
10
- import "./logical";
11
- import "./text";
12
- import "./financial";
13
- import "./lookup";
14
- import "./date";
15
-
16
- export { functionRegistry } from "../registry";
@@ -1,221 +0,0 @@
1
- /**
2
- * Logical Functions
3
- */
4
-
5
- import { evaluateConditionValues, readOperand, renderConditionOperand } from "../condition";
6
- import { findCellRefs, substituteCellRefs } from "../evaluator";
7
- import { functionRegistry, requiredArg, type FunctionHandler } from "../registry";
8
- import { isErrorResult, isSpreadsheetErrorValue, NA_ERROR } from "../spreadsheet-errors";
9
- import { coerceToBoolean } from "../coerce-boolean";
10
- import type { CellValue } from "../types";
11
-
12
- const ifHandler: FunctionHandler = (args, context) => {
13
- const condition = requiredArg(context, args, 0);
14
- const trueValue = requiredArg(context, args, 1);
15
- const falseValue = requiredArg(context, args, 2);
16
-
17
- // Evaluate condition - use evaluateFormula to handle nested functions like MONTH()
18
- const conditionValue = context.evaluateFormula(condition);
19
- const conditionResult = coerceToBoolean(conditionValue);
20
-
21
- // Return the appropriate value based on condition
22
- const resultValue = conditionResult ? trueValue : falseValue;
23
-
24
- // If result is a quoted string, return the string without quotes
25
- if (/^["'](.*)["']$/.test(resultValue)) {
26
- return resultValue.slice(1, -1);
27
- }
28
-
29
- // Everything else — a nested call, an arithmetic expression, a reference — is
30
- // evaluated by the engine. Hand-rolling it here silently returned a plausible
31
- // wrong value twice over: a hard-coded list of nine function names sent
32
- // `ROUND(A1,1)` back as its own text, and the fallback read `A1+1` through
33
- // `parseFloat("3+1")`, yielding 3.
34
- return context.evaluateFormula(resultValue);
35
- };
36
-
37
- const andHandler: FunctionHandler = (args, context) => {
38
- for (const arg of args) {
39
- if (!coerceToBoolean(context.evaluateFormula(arg.trim()))) {
40
- return false;
41
- }
42
- }
43
- return true;
44
- };
45
-
46
- const orHandler: FunctionHandler = (args, context) => {
47
- for (const arg of args) {
48
- if (coerceToBoolean(context.evaluateFormula(arg.trim()))) {
49
- return true;
50
- }
51
- }
52
- return false;
53
- };
54
-
55
- const notHandler: FunctionHandler = (args, context) => {
56
- return !coerceToBoolean(context.evaluateFormula(requiredArg(context, args, 0)));
57
- };
58
-
59
- const iferrorHandler: FunctionHandler = (args, context) => {
60
- try {
61
- const result = context.evaluateFormula(requiredArg(context, args, 0));
62
- // Catches NaN/∞ and the formula error VALUES functions return (a math domain
63
- // miss like SQRT(-1) → #NUM!), so IFERROR(SQRT(-1), 0) is 0. Text that
64
- // merely spells an error is not an error value, so it passes through
65
- // whether it was written as a literal (IFERROR("#NUM!", 42)) or computed
66
- // (IFERROR(CONCAT("#N","UM!"), 42)) — the computed case is why errors carry
67
- // provenance at all (#2451).
68
- if (isErrorResult(result)) {
69
- return context.evaluateFormula(requiredArg(context, args, 1));
70
- }
71
- return result;
72
- } catch {
73
- // If evaluation throws an error, return the fallback value
74
- return context.evaluateFormula(requiredArg(context, args, 1));
75
- }
76
- };
77
-
78
- const ifnaHandler: FunctionHandler = (args, context) => {
79
- const result = context.evaluateFormula(requiredArg(context, args, 0));
80
- const isNotAvailable = isSpreadsheetErrorValue(result) && result.code === NA_ERROR.code;
81
- if (result === null || result === undefined || isNotAvailable) {
82
- return context.evaluateFormula(requiredArg(context, args, 1));
83
- }
84
- return result;
85
- };
86
-
87
- const ifsHandler: FunctionHandler = (args, context) => {
88
- if (args.length < 2 || args.length % 2 !== 0) {
89
- throw new Error("IFS requires an even number of arguments (condition-value pairs)");
90
- }
91
-
92
- // Iterate through condition-value pairs
93
- for (let i = 0; i < args.length; i += 2) {
94
- const condition = requiredArg(context, args, i);
95
- const value = requiredArg(context, args, i + 1);
96
-
97
- // Substitute references by POSITION (back to front), skipping any that sit
98
- // inside a quoted string literal: `IFS(A1="B2", …)` must compare A1 to the
99
- // TEXT "B2", not to cell B2's value (Codex review). findCellRefs already
100
- // skips literals and matches absolute / sheet-qualified refs, so this also
101
- // avoids the earlier regex double-escaping. renderConditionOperand quotes a
102
- // text cell so its own operators are not re-parsed as comparisons.
103
- const condExpr = substituteCellRefs(condition, findCellRefs(condition), (ref) => renderConditionOperand(context.getCellValue(ref)));
104
-
105
- // Parsed, not executed. This used to call `eval` on `condExpr`, which is
106
- // the substituted text — so a cell containing `globalThis.x = 1` ran as
107
- // code whenever an IFS referenced it, and so did anything written into the
108
- // formula itself. `readOperand` resolves the simple operands (TRUE/FALSE ->
109
- // boolean, quoted text, numbers); an arithmetic expression it leaves as raw
110
- // text is handed to the engine's safe evaluator so `A1+1>10` is computed.
111
- // Only the top-level comparison is applied — the condition is never run.
112
- const evaluateOperand = (operand: string): CellValue => {
113
- const parsed = readOperand(operand);
114
- return typeof parsed === "string" && parsed === operand.trim() ? context.evaluateFormula(operand) : parsed;
115
- };
116
- if (evaluateConditionValues(condExpr, evaluateOperand)) {
117
- // If result is a quoted string, return without quotes
118
-
119
- if (/^["'](.*)["']$/.test(value)) {
120
- return value.slice(1, -1);
121
- }
122
- // Otherwise evaluate as formula or expression
123
- return context.evaluateFormula(value);
124
- }
125
- }
126
-
127
- // If no conditions match, return error
128
- return NA_ERROR;
129
- };
130
-
131
- const trueHandler: FunctionHandler = () => true;
132
-
133
- const falseHandler: FunctionHandler = () => false;
134
-
135
- // Register all logical functions
136
- functionRegistry.register({
137
- name: "IF",
138
- handler: ifHandler,
139
- minArgs: 3,
140
- maxArgs: 3,
141
- description: "Returns one value if a condition is true and another if false",
142
- examples: ['IF(A1>10, "High", "Low")', "IF(B2>=5, SUM(C1:C10), 0)"],
143
- category: "Logical",
144
- });
145
-
146
- functionRegistry.register({
147
- name: "AND",
148
- handler: andHandler,
149
- minArgs: 1,
150
- description: "Returns TRUE if all arguments are true",
151
- examples: ["AND(A1>5, B1<10)", "AND(A1>0, B1>0, C1>0)"],
152
- category: "Logical",
153
- });
154
-
155
- functionRegistry.register({
156
- name: "OR",
157
- handler: orHandler,
158
- minArgs: 1,
159
- description: "Returns TRUE if any argument is true",
160
- examples: ["OR(A1>5, B1<10)", "OR(A1>0, B1>0)"],
161
- category: "Logical",
162
- });
163
-
164
- functionRegistry.register({
165
- name: "NOT",
166
- handler: notHandler,
167
- minArgs: 1,
168
- maxArgs: 1,
169
- description: "Reverses the logical value of its argument",
170
- examples: ["NOT(A1>5)", "NOT(B1)"],
171
- category: "Logical",
172
- });
173
-
174
- functionRegistry.register({
175
- name: "IFERROR",
176
- handler: iferrorHandler,
177
- minArgs: 2,
178
- maxArgs: 2,
179
- description: "Returns a value if expression is an error, otherwise returns the expression",
180
- examples: ["IFERROR(A1/B1, 0)", 'IFERROR(VLOOKUP(A1, B1:C10, 2), "Not found")'],
181
- category: "Logical",
182
- });
183
-
184
- functionRegistry.register({
185
- name: "IFNA",
186
- handler: ifnaHandler,
187
- minArgs: 2,
188
- maxArgs: 2,
189
- description: "Returns a value if expression is #N/A, otherwise returns the expression",
190
- examples: ['IFNA(A1, "N/A")', "IFNA(MATCH(A1, B1:B10), 0)"],
191
- category: "Logical",
192
- });
193
-
194
- functionRegistry.register({
195
- name: "IFS",
196
- handler: ifsHandler,
197
- minArgs: 2,
198
- description: "Checks multiple conditions and returns the first true result",
199
- examples: ['IFS(A1>90, "A", A1>80, "B", A1>70, "C")', 'IFS(B1="Yes", 1, B1="No", 0)'],
200
- category: "Logical",
201
- });
202
-
203
- functionRegistry.register({
204
- name: "TRUE",
205
- handler: trueHandler,
206
- minArgs: 0,
207
- maxArgs: 0,
208
- description: "Returns the logical value TRUE",
209
- examples: ["TRUE()", "IF(A1>0, TRUE(), FALSE())"],
210
- category: "Logical",
211
- });
212
-
213
- functionRegistry.register({
214
- name: "FALSE",
215
- handler: falseHandler,
216
- minArgs: 0,
217
- maxArgs: 0,
218
- description: "Returns the logical value FALSE",
219
- examples: ["FALSE()", "IF(A1>0, TRUE(), FALSE())"],
220
- category: "Logical",
221
- });