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.
- package/client/assets/Graph-ChAxkn9o.js +1 -0
- package/client/assets/dist-Bij44LdC.js +2 -0
- package/client/assets/echarts-GV3U9sVf.js +1 -0
- package/client/assets/echarts-gl-BXx4On0Y.js +4282 -0
- package/client/assets/echarts-wordcloud-_9uQUAfp.js +1 -0
- package/client/assets/emscripten-module-Q67P5WYC-BU3Hlttz.js +2160 -0
- package/client/assets/ffi-4V2b7xu3.js +1 -0
- package/client/assets/{index-lkpgbKtk.js → index-D6xzCZhP.js} +600 -589
- package/client/assets/index-DzMsXDnj.css +2 -0
- package/client/assets/{install-wxcddUsO.js → install-C1moNcrn.js} +1 -1
- package/client/assets/{marp-DcTywifN.js → marp-Du5Sym5b.js} +1 -1
- package/client/assets/module-ES6BEMUI-CDTIpMiA.js +6 -0
- package/client/index.html +4 -3
- package/package.json +5 -4
- package/server/api/routes/plugins.ts +8 -59
- package/server/build/mcp-server.mjs +609 -608
- package/server/index.ts +1 -0
- package/server/plugins/confined-file-ops.ts +136 -0
- package/server/plugins/runtime.ts +8 -115
- package/server/plugins/spreadsheet-builtin.ts +26 -0
- package/server/plugins/spreadsheet-save-outcome.ts +7 -0
- package/server/utils/files/store-resolvers.ts +1 -1
- package/src/lang/de.ts +0 -20
- package/src/lang/en.ts +0 -20
- package/src/lang/es.ts +0 -20
- package/src/lang/fr.ts +0 -20
- package/src/lang/ja.ts +0 -20
- package/src/lang/ko.ts +0 -20
- package/src/lang/pt-BR.ts +0 -20
- package/src/lang/zh.ts +0 -20
- package/src/plugins/spreadsheet/definition.ts +9 -127
- package/src/plugins/spreadsheet/index.ts +7 -4
- package/src/plugins/spreadsheet/meta.ts +2 -4
- package/client/assets/echarts-gl-Cq76Z6DA.js +0 -4282
- package/client/assets/index-Cz7JLE0k.css +0 -2
- package/server/utils/files/spreadsheet-store.ts +0 -26
- package/src/plugins/spreadsheet/Preview.vue +0 -31
- package/src/plugins/spreadsheet/View.vue +0 -1007
- package/src/plugins/spreadsheet/cellHighlights.ts +0 -78
- package/src/plugins/spreadsheet/engine/calculator.ts +0 -427
- package/src/plugins/spreadsheet/engine/cellBuilder.ts +0 -89
- package/src/plugins/spreadsheet/engine/cellEmpty.ts +0 -26
- package/src/plugins/spreadsheet/engine/cellFormatting.ts +0 -59
- package/src/plugins/spreadsheet/engine/coerce-boolean.ts +0 -26
- package/src/plugins/spreadsheet/engine/condition.ts +0 -182
- package/src/plugins/spreadsheet/engine/date-locale.ts +0 -47
- package/src/plugins/spreadsheet/engine/date-parser.ts +0 -234
- package/src/plugins/spreadsheet/engine/date-utils.ts +0 -73
- package/src/plugins/spreadsheet/engine/datedif.ts +0 -93
- package/src/plugins/spreadsheet/engine/engine.ts +0 -177
- package/src/plugins/spreadsheet/engine/evaluator.ts +0 -494
- package/src/plugins/spreadsheet/engine/financial-math.ts +0 -114
- package/src/plugins/spreadsheet/engine/formatter.ts +0 -158
- package/src/plugins/spreadsheet/engine/formulaError.ts +0 -67
- package/src/plugins/spreadsheet/engine/formulaRefs.ts +0 -202
- package/src/plugins/spreadsheet/engine/functions/date.ts +0 -212
- package/src/plugins/spreadsheet/engine/functions/financial.ts +0 -244
- package/src/plugins/spreadsheet/engine/functions/index.ts +0 -16
- package/src/plugins/spreadsheet/engine/functions/logical.ts +0 -221
- package/src/plugins/spreadsheet/engine/functions/lookup-math.ts +0 -28
- package/src/plugins/spreadsheet/engine/functions/lookup.ts +0 -257
- package/src/plugins/spreadsheet/engine/functions/mathematical.ts +0 -283
- package/src/plugins/spreadsheet/engine/functions/statistical-math.ts +0 -91
- package/src/plugins/spreadsheet/engine/functions/statistical.ts +0 -290
- package/src/plugins/spreadsheet/engine/functions/text.ts +0 -398
- package/src/plugins/spreadsheet/engine/index.ts +0 -34
- package/src/plugins/spreadsheet/engine/jsonCellLocator.ts +0 -111
- package/src/plugins/spreadsheet/engine/math-ops.ts +0 -98
- package/src/plugins/spreadsheet/engine/numericCoercion.ts +0 -61
- package/src/plugins/spreadsheet/engine/parser.ts +0 -40
- package/src/plugins/spreadsheet/engine/registry.ts +0 -201
- package/src/plugins/spreadsheet/engine/responseDecoder.ts +0 -68
- package/src/plugins/spreadsheet/engine/spreadsheet-errors.ts +0 -82
- package/src/plugins/spreadsheet/engine/textFormat.ts +0 -108
- package/src/plugins/spreadsheet/engine/translateFormula.ts +0 -69
- package/src/plugins/spreadsheet/engine/types.ts +0 -81
- 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
|
-
});
|