@cj-tech-master/excelts 9.3.1 → 9.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/modules/excel/cell.d.ts +18 -0
- package/dist/browser/modules/excel/cell.js +21 -0
- package/dist/browser/modules/excel/utils/cell-format.js +85 -13
- package/dist/browser/modules/excel/workbook.browser.d.ts +57 -0
- package/dist/browser/modules/excel/workbook.browser.js +49 -0
- package/dist/browser/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/browser/modules/formula/compile/binder.js +48 -6
- package/dist/browser/modules/formula/compile/bound-ast.d.ts +16 -2
- package/dist/browser/modules/formula/compile/bound-ast.js +1 -0
- package/dist/browser/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/browser/modules/formula/functions/_shared.d.ts +19 -0
- package/dist/browser/modules/formula/functions/_shared.js +47 -0
- package/dist/browser/modules/formula/functions/conditional.js +103 -22
- package/dist/browser/modules/formula/functions/date.js +105 -23
- package/dist/browser/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/browser/modules/formula/functions/engineering.d.ts +2 -2
- package/dist/browser/modules/formula/functions/engineering.js +103 -151
- package/dist/browser/modules/formula/functions/financial.js +210 -184
- package/dist/browser/modules/formula/functions/lookup.js +224 -157
- package/dist/browser/modules/formula/functions/math.d.ts +26 -0
- package/dist/browser/modules/formula/functions/math.js +249 -69
- package/dist/browser/modules/formula/functions/statistical.js +221 -171
- package/dist/browser/modules/formula/functions/text.js +112 -52
- package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/browser/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/browser/modules/formula/materialize/types.d.ts +15 -0
- package/dist/browser/modules/formula/runtime/evaluator.d.ts +8 -0
- package/dist/browser/modules/formula/runtime/evaluator.js +582 -162
- package/dist/browser/modules/formula/runtime/function-registry.d.ts +5 -0
- package/dist/browser/modules/formula/runtime/function-registry.js +59 -13
- package/dist/browser/modules/formula/runtime/values.d.ts +13 -0
- package/dist/browser/modules/formula/runtime/values.js +20 -2
- package/dist/browser/modules/formula/syntax/ast.d.ts +14 -2
- package/dist/browser/modules/formula/syntax/ast.js +1 -0
- package/dist/browser/modules/formula/syntax/parser.js +29 -7
- package/dist/browser/modules/formula/syntax/token-types.d.ts +4 -0
- package/dist/browser/modules/formula/syntax/token-types.js +9 -0
- package/dist/browser/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/modules/excel/cell.js +21 -0
- package/dist/cjs/modules/excel/utils/cell-format.js +85 -13
- package/dist/cjs/modules/excel/workbook.browser.js +49 -0
- package/dist/cjs/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/cjs/modules/formula/compile/binder.js +48 -6
- package/dist/cjs/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/cjs/modules/formula/functions/_shared.js +48 -0
- package/dist/cjs/modules/formula/functions/conditional.js +103 -22
- package/dist/cjs/modules/formula/functions/date.js +104 -22
- package/dist/cjs/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/cjs/modules/formula/functions/engineering.js +109 -157
- package/dist/cjs/modules/formula/functions/financial.js +209 -183
- package/dist/cjs/modules/formula/functions/lookup.js +224 -157
- package/dist/cjs/modules/formula/functions/math.js +254 -70
- package/dist/cjs/modules/formula/functions/statistical.js +222 -172
- package/dist/cjs/modules/formula/functions/text.js +112 -52
- package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/cjs/modules/formula/runtime/evaluator.js +581 -161
- package/dist/cjs/modules/formula/runtime/function-registry.js +57 -11
- package/dist/cjs/modules/formula/runtime/values.js +21 -2
- package/dist/cjs/modules/formula/syntax/parser.js +29 -7
- package/dist/cjs/modules/formula/syntax/token-types.js +9 -0
- package/dist/cjs/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/esm/index.js +2 -0
- package/dist/esm/modules/excel/cell.js +21 -0
- package/dist/esm/modules/excel/utils/cell-format.js +85 -13
- package/dist/esm/modules/excel/workbook.browser.js +49 -0
- package/dist/esm/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/esm/modules/formula/compile/binder.js +48 -6
- package/dist/esm/modules/formula/compile/bound-ast.js +1 -0
- package/dist/esm/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/esm/modules/formula/functions/_shared.js +47 -0
- package/dist/esm/modules/formula/functions/conditional.js +103 -22
- package/dist/esm/modules/formula/functions/date.js +105 -23
- package/dist/esm/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/esm/modules/formula/functions/engineering.js +103 -151
- package/dist/esm/modules/formula/functions/financial.js +210 -184
- package/dist/esm/modules/formula/functions/lookup.js +224 -157
- package/dist/esm/modules/formula/functions/math.js +249 -69
- package/dist/esm/modules/formula/functions/statistical.js +221 -171
- package/dist/esm/modules/formula/functions/text.js +112 -52
- package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/esm/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/esm/modules/formula/runtime/evaluator.js +582 -162
- package/dist/esm/modules/formula/runtime/function-registry.js +59 -13
- package/dist/esm/modules/formula/runtime/values.js +20 -2
- package/dist/esm/modules/formula/syntax/ast.js +1 -0
- package/dist/esm/modules/formula/syntax/parser.js +29 -7
- package/dist/esm/modules/formula/syntax/token-types.js +9 -0
- package/dist/esm/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/iife/excelts.iife.js +1502 -1379
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +26 -26
- package/dist/types/index.d.ts +1 -0
- package/dist/types/modules/excel/cell.d.ts +18 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +57 -0
- package/dist/types/modules/formula/compile/bound-ast.d.ts +16 -2
- package/dist/types/modules/formula/functions/_shared.d.ts +19 -0
- package/dist/types/modules/formula/functions/engineering.d.ts +2 -2
- package/dist/types/modules/formula/functions/math.d.ts +26 -0
- package/dist/types/modules/formula/materialize/types.d.ts +15 -0
- package/dist/types/modules/formula/runtime/evaluator.d.ts +8 -0
- package/dist/types/modules/formula/runtime/function-registry.d.ts +5 -0
- package/dist/types/modules/formula/runtime/values.d.ts +13 -0
- package/dist/types/modules/formula/syntax/ast.d.ts +14 -2
- package/dist/types/modules/formula/syntax/token-types.d.ts +4 -0
- package/package.json +1 -1
|
@@ -57,32 +57,73 @@ export function fnFILTER(args) {
|
|
|
57
57
|
if (!dataArr || !includeArr) {
|
|
58
58
|
return ERRORS.VALUE;
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
|
|
60
|
+
const ifEmpty = args.length > 2 && args[2].kind !== RVKind.Blank ? topLeft(args[2]) : null;
|
|
61
|
+
// Decide filter orientation from the include vector's shape:
|
|
62
|
+
// - N×1 vector matching data.height → ROW filter (classic).
|
|
63
|
+
// - 1×N vector matching data.width → COL filter (Excel supports
|
|
64
|
+
// both; previously we hard-required the row shape and silently
|
|
65
|
+
// rejected column filters with #VALUE!).
|
|
66
|
+
// A 1×1 include vector is treated as a row filter over 1 row.
|
|
67
|
+
const isRowFilter = includeArr.width === 1 && includeArr.height === dataArr.height;
|
|
68
|
+
const isColFilter = !isRowFilter && includeArr.height === 1 && includeArr.width === dataArr.width;
|
|
69
|
+
if (!isRowFilter && !isColFilter) {
|
|
65
70
|
return ERRORS.VALUE;
|
|
66
71
|
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
const cellIsTruthy = (cell) => {
|
|
73
|
+
if (cell.kind === RVKind.Error) {
|
|
74
|
+
return cell;
|
|
75
|
+
}
|
|
76
|
+
if (cell.kind === RVKind.Boolean) {
|
|
77
|
+
return cell.value;
|
|
78
|
+
}
|
|
79
|
+
if (cell.kind === RVKind.Number) {
|
|
80
|
+
return cell.value !== 0;
|
|
81
|
+
}
|
|
82
|
+
// Blank / String / anything else → treated as FALSE.
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
if (isRowFilter) {
|
|
86
|
+
const resultRows = [];
|
|
87
|
+
for (let r = 0; r < dataArr.height; r++) {
|
|
88
|
+
const truthy = cellIsTruthy(getCell(includeArr, r, 0));
|
|
89
|
+
if (typeof truthy !== "boolean") {
|
|
90
|
+
return truthy;
|
|
79
91
|
}
|
|
80
|
-
|
|
92
|
+
if (truthy) {
|
|
93
|
+
const row = [];
|
|
94
|
+
for (let c = 0; c < dataArr.width; c++) {
|
|
95
|
+
row.push(getCell(dataArr, r, c));
|
|
96
|
+
}
|
|
97
|
+
resultRows.push(row);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (resultRows.length === 0) {
|
|
101
|
+
return ifEmpty !== null ? rvArray([[ifEmpty]]) : ERRORS.CALC;
|
|
102
|
+
}
|
|
103
|
+
return rvArray(resultRows);
|
|
104
|
+
}
|
|
105
|
+
// Column filter — pick a subset of columns across every row.
|
|
106
|
+
const keepCols = [];
|
|
107
|
+
for (let c = 0; c < dataArr.width; c++) {
|
|
108
|
+
const truthy = cellIsTruthy(getCell(includeArr, 0, c));
|
|
109
|
+
if (typeof truthy !== "boolean") {
|
|
110
|
+
return truthy;
|
|
111
|
+
}
|
|
112
|
+
if (truthy) {
|
|
113
|
+
keepCols.push(c);
|
|
81
114
|
}
|
|
82
115
|
}
|
|
83
|
-
if (
|
|
116
|
+
if (keepCols.length === 0) {
|
|
84
117
|
return ifEmpty !== null ? rvArray([[ifEmpty]]) : ERRORS.CALC;
|
|
85
118
|
}
|
|
119
|
+
const resultRows = [];
|
|
120
|
+
for (let r = 0; r < dataArr.height; r++) {
|
|
121
|
+
const row = new Array(keepCols.length);
|
|
122
|
+
for (let i = 0; i < keepCols.length; i++) {
|
|
123
|
+
row[i] = getCell(dataArr, r, keepCols[i]);
|
|
124
|
+
}
|
|
125
|
+
resultRows.push(row);
|
|
126
|
+
}
|
|
86
127
|
return rvArray(resultRows);
|
|
87
128
|
}
|
|
88
129
|
export function fnSORT(args) {
|
|
@@ -98,15 +139,26 @@ export function fnSORT(args) {
|
|
|
98
139
|
}
|
|
99
140
|
rows.push(row);
|
|
100
141
|
}
|
|
101
|
-
const sortIndexV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(1);
|
|
142
|
+
const sortIndexV = args.length > 1 && args[1].kind !== RVKind.Blank ? toNumberRV(topLeft(args[1])) : rvNumber(1);
|
|
102
143
|
if (isError(sortIndexV)) {
|
|
103
144
|
return sortIndexV;
|
|
104
145
|
}
|
|
105
|
-
|
|
146
|
+
// Blank `sort_order` → Excel's default 1 (ascending). Without this
|
|
147
|
+
// guard the blank coerces through `toNumberRV` to 0, which multiplies
|
|
148
|
+
// the comparator output and produces a no-op sort — every pair is
|
|
149
|
+
// treated as equal and the output order is engine-defined noise.
|
|
150
|
+
const sortOrderV = args.length > 2 && args[2].kind !== RVKind.Blank ? toNumberRV(topLeft(args[2])) : rvNumber(1);
|
|
106
151
|
if (isError(sortOrderV)) {
|
|
107
152
|
return sortOrderV;
|
|
108
153
|
}
|
|
109
|
-
|
|
154
|
+
// Excel restricts `sort_order` to exactly 1 or -1; any other integer
|
|
155
|
+
// (0, 2, 3, …) is #VALUE!. Previously zero silently disabled the sort.
|
|
156
|
+
if (sortOrderV.value !== 1 && sortOrderV.value !== -1) {
|
|
157
|
+
return ERRORS.VALUE;
|
|
158
|
+
}
|
|
159
|
+
const byColV = args.length > 3
|
|
160
|
+
? toBooleanRV(topLeft(args[3]))
|
|
161
|
+
: { kind: RVKind.Boolean, value: false };
|
|
110
162
|
if (isError(byColV)) {
|
|
111
163
|
return byColV;
|
|
112
164
|
}
|
|
@@ -140,11 +192,15 @@ export function fnUNIQUE(args) {
|
|
|
140
192
|
if (!dataArr) {
|
|
141
193
|
return ERRORS.VALUE;
|
|
142
194
|
}
|
|
143
|
-
const byColV = args.length > 1
|
|
195
|
+
const byColV = args.length > 1
|
|
196
|
+
? toBooleanRV(topLeft(args[1]))
|
|
197
|
+
: { kind: RVKind.Boolean, value: false };
|
|
144
198
|
if (isError(byColV)) {
|
|
145
199
|
return byColV;
|
|
146
200
|
}
|
|
147
|
-
const exactlyOnceV = args.length > 2
|
|
201
|
+
const exactlyOnceV = args.length > 2
|
|
202
|
+
? toBooleanRV(topLeft(args[2]))
|
|
203
|
+
: { kind: RVKind.Boolean, value: false };
|
|
148
204
|
if (isError(exactlyOnceV)) {
|
|
149
205
|
return exactlyOnceV;
|
|
150
206
|
}
|
|
@@ -245,10 +301,18 @@ export function fnSORTBY(args) {
|
|
|
245
301
|
if (!keyArr) {
|
|
246
302
|
return ERRORS.VALUE;
|
|
247
303
|
}
|
|
248
|
-
|
|
304
|
+
// Blank order → default 1 (ascending); anything other than ±1 is
|
|
305
|
+
// #VALUE!. Mirrors the SORT validation — a blank used to produce
|
|
306
|
+
// 0 via `toNumberRV`, silently disabling the sort key.
|
|
307
|
+
const orderV = i + 1 < args.length && args[i + 1].kind !== RVKind.Blank
|
|
308
|
+
? toNumberRV(topLeft(args[i + 1]))
|
|
309
|
+
: rvNumber(1);
|
|
249
310
|
if (isError(orderV)) {
|
|
250
311
|
return orderV;
|
|
251
312
|
}
|
|
313
|
+
if (orderV.value !== 1 && orderV.value !== -1) {
|
|
314
|
+
return ERRORS.VALUE;
|
|
315
|
+
}
|
|
252
316
|
sortKeys.push({ arr: keyArr, order: orderV.value });
|
|
253
317
|
}
|
|
254
318
|
data.sort((a, b) => {
|
|
@@ -265,7 +329,7 @@ export function fnSORTBY(args) {
|
|
|
265
329
|
return rvArray(data.map(d => d.row));
|
|
266
330
|
}
|
|
267
331
|
export function fnSUBTOTAL(args) {
|
|
268
|
-
const funcNumV = toNumberRV(args[0]);
|
|
332
|
+
const funcNumV = toNumberRV(topLeft(args[0]));
|
|
269
333
|
if (isError(funcNumV)) {
|
|
270
334
|
return funcNumV;
|
|
271
335
|
}
|
|
@@ -317,7 +381,7 @@ export function fnSUBTOTAL(args) {
|
|
|
317
381
|
}
|
|
318
382
|
}
|
|
319
383
|
export function fnAGGREGATE(args) {
|
|
320
|
-
const funcNumV = toNumberRV(args[0]);
|
|
384
|
+
const funcNumV = toNumberRV(topLeft(args[0]));
|
|
321
385
|
if (isError(funcNumV)) {
|
|
322
386
|
return funcNumV;
|
|
323
387
|
}
|
|
@@ -331,7 +395,7 @@ export function fnAGGREGATE(args) {
|
|
|
331
395
|
// 5 → ignore hidden rows (keep nested)
|
|
332
396
|
// 6 → ignore errors (keep nested)
|
|
333
397
|
// 7 → ignore hidden rows + errors (keep nested)
|
|
334
|
-
const optV = args[1] !== undefined ? toNumberRV(args[1]) : undefined;
|
|
398
|
+
const optV = args[1] !== undefined ? toNumberRV(topLeft(args[1])) : undefined;
|
|
335
399
|
if (optV && isError(optV)) {
|
|
336
400
|
return optV;
|
|
337
401
|
}
|
|
@@ -396,19 +460,22 @@ export function fnAGGREGATE(args) {
|
|
|
396
460
|
}
|
|
397
461
|
}
|
|
398
462
|
export function fnSEQUENCE(args) {
|
|
399
|
-
const rowsV = toNumberRV(args[0]);
|
|
463
|
+
const rowsV = toNumberRV(topLeft(args[0]));
|
|
400
464
|
if (isError(rowsV)) {
|
|
401
465
|
return rowsV;
|
|
402
466
|
}
|
|
403
|
-
|
|
467
|
+
// Blank `cols` / `start` / `step` → Excel defaults 1 / 1 / 1. Without
|
|
468
|
+
// the blank guards, `SEQUENCE(5, )` coerces blank → 0 → `colCount < 1`
|
|
469
|
+
// → #NUM! instead of Excel's single-column sequence.
|
|
470
|
+
const colsV = args.length > 1 && args[1].kind !== RVKind.Blank ? toNumberRV(topLeft(args[1])) : rvNumber(1);
|
|
404
471
|
if (isError(colsV)) {
|
|
405
472
|
return colsV;
|
|
406
473
|
}
|
|
407
|
-
const startV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(1);
|
|
474
|
+
const startV = args.length > 2 && args[2].kind !== RVKind.Blank ? toNumberRV(topLeft(args[2])) : rvNumber(1);
|
|
408
475
|
if (isError(startV)) {
|
|
409
476
|
return startV;
|
|
410
477
|
}
|
|
411
|
-
const stepV = args.length > 3 ? toNumberRV(args[3]) : rvNumber(1);
|
|
478
|
+
const stepV = args.length > 3 && args[3].kind !== RVKind.Blank ? toNumberRV(topLeft(args[3])) : rvNumber(1);
|
|
412
479
|
if (isError(stepV)) {
|
|
413
480
|
return stepV;
|
|
414
481
|
}
|
|
@@ -440,23 +507,29 @@ export function fnSEQUENCE(args) {
|
|
|
440
507
|
return rvArray(result);
|
|
441
508
|
}
|
|
442
509
|
export function fnRANDARRAY(args) {
|
|
443
|
-
|
|
510
|
+
// Each arg has a distinct Excel default; blank must be distinguished
|
|
511
|
+
// from an explicit 0, which would cause rows/cols validation to
|
|
512
|
+
// reject the call. Blanks → engine defaults (rows=1, cols=1, min=0,
|
|
513
|
+
// max=1, whole=FALSE).
|
|
514
|
+
const rowsV = args.length > 0 && args[0].kind !== RVKind.Blank ? toNumberRV(topLeft(args[0])) : rvNumber(1);
|
|
444
515
|
if (isError(rowsV)) {
|
|
445
516
|
return rowsV;
|
|
446
517
|
}
|
|
447
|
-
const colsV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(1);
|
|
518
|
+
const colsV = args.length > 1 && args[1].kind !== RVKind.Blank ? toNumberRV(topLeft(args[1])) : rvNumber(1);
|
|
448
519
|
if (isError(colsV)) {
|
|
449
520
|
return colsV;
|
|
450
521
|
}
|
|
451
|
-
const minV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(0);
|
|
522
|
+
const minV = args.length > 2 && args[2].kind !== RVKind.Blank ? toNumberRV(topLeft(args[2])) : rvNumber(0);
|
|
452
523
|
if (isError(minV)) {
|
|
453
524
|
return minV;
|
|
454
525
|
}
|
|
455
|
-
const maxV = args.length > 3 ? toNumberRV(args[3]) : rvNumber(1);
|
|
526
|
+
const maxV = args.length > 3 && args[3].kind !== RVKind.Blank ? toNumberRV(topLeft(args[3])) : rvNumber(1);
|
|
456
527
|
if (isError(maxV)) {
|
|
457
528
|
return maxV;
|
|
458
529
|
}
|
|
459
|
-
const wholeV = args.length > 4
|
|
530
|
+
const wholeV = args.length > 4 && args[4].kind !== RVKind.Blank
|
|
531
|
+
? toBooleanRV(topLeft(args[4]))
|
|
532
|
+
: { kind: RVKind.Boolean, value: false };
|
|
460
533
|
if (isError(wholeV)) {
|
|
461
534
|
return wholeV;
|
|
462
535
|
}
|
|
@@ -503,11 +576,13 @@ export function fnTOCOL(args) {
|
|
|
503
576
|
return rvArray([[topLeft(args[0])]]);
|
|
504
577
|
}
|
|
505
578
|
const arr = args[0];
|
|
506
|
-
const ignoreV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(0);
|
|
579
|
+
const ignoreV = args.length > 1 ? toNumberRV(topLeft(args[1])) : rvNumber(0);
|
|
507
580
|
if (isError(ignoreV)) {
|
|
508
581
|
return ignoreV;
|
|
509
582
|
}
|
|
510
|
-
const scanV = args.length > 2
|
|
583
|
+
const scanV = args.length > 2
|
|
584
|
+
? toBooleanRV(topLeft(args[2]))
|
|
585
|
+
: { kind: RVKind.Boolean, value: false };
|
|
511
586
|
if (isError(scanV)) {
|
|
512
587
|
return scanV;
|
|
513
588
|
}
|
|
@@ -545,11 +620,13 @@ export function fnTOROW(args) {
|
|
|
545
620
|
return rvArray([[topLeft(args[0])]]);
|
|
546
621
|
}
|
|
547
622
|
const arr = args[0];
|
|
548
|
-
const ignoreV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(0);
|
|
623
|
+
const ignoreV = args.length > 1 ? toNumberRV(topLeft(args[1])) : rvNumber(0);
|
|
549
624
|
if (isError(ignoreV)) {
|
|
550
625
|
return ignoreV;
|
|
551
626
|
}
|
|
552
|
-
const scanV = args.length > 2
|
|
627
|
+
const scanV = args.length > 2
|
|
628
|
+
? toBooleanRV(topLeft(args[2]))
|
|
629
|
+
: { kind: RVKind.Boolean, value: false };
|
|
553
630
|
if (isError(scanV)) {
|
|
554
631
|
return scanV;
|
|
555
632
|
}
|
|
@@ -589,11 +666,18 @@ export function fnCHOOSEROWS(args) {
|
|
|
589
666
|
}
|
|
590
667
|
const result = [];
|
|
591
668
|
for (let i = 1; i < args.length; i++) {
|
|
592
|
-
const nV = toNumberRV(args[i]);
|
|
669
|
+
const nV = toNumberRV(topLeft(args[i]));
|
|
593
670
|
if (isError(nV)) {
|
|
594
671
|
return nV;
|
|
595
672
|
}
|
|
596
|
-
|
|
673
|
+
// Excel truncates toward zero before the bounds check; without this,
|
|
674
|
+
// `CHOOSEROWS(A1:A5, 2.5)` would compute `idx = 1.5` and end up
|
|
675
|
+
// reading `d.rows[1.5]` (= undefined), throwing on the next index.
|
|
676
|
+
const raw = Math.trunc(nV.value);
|
|
677
|
+
if (raw === 0) {
|
|
678
|
+
return ERRORS.VALUE;
|
|
679
|
+
}
|
|
680
|
+
const idx = raw > 0 ? raw - 1 : d.height + raw;
|
|
597
681
|
if (idx < 0 || idx >= d.height) {
|
|
598
682
|
return ERRORS.VALUE;
|
|
599
683
|
}
|
|
@@ -612,11 +696,17 @@ export function fnCHOOSECOLS(args) {
|
|
|
612
696
|
}
|
|
613
697
|
const ci = [];
|
|
614
698
|
for (let i = 1; i < args.length; i++) {
|
|
615
|
-
const nV = toNumberRV(args[i]);
|
|
699
|
+
const nV = toNumberRV(topLeft(args[i]));
|
|
616
700
|
if (isError(nV)) {
|
|
617
701
|
return nV;
|
|
618
702
|
}
|
|
619
|
-
|
|
703
|
+
// Truncate fractional indices before the bounds check — see CHOOSEROWS
|
|
704
|
+
// for the rationale (fractional indices silently crash getCell).
|
|
705
|
+
const raw = Math.trunc(nV.value);
|
|
706
|
+
if (raw === 0) {
|
|
707
|
+
return ERRORS.VALUE;
|
|
708
|
+
}
|
|
709
|
+
const idx = raw > 0 ? raw - 1 : d.width + raw;
|
|
620
710
|
if (idx < 0 || idx >= d.width) {
|
|
621
711
|
return ERRORS.VALUE;
|
|
622
712
|
}
|
|
@@ -706,18 +796,24 @@ export function fnTAKE(args) {
|
|
|
706
796
|
if (!d) {
|
|
707
797
|
return ERRORS.VALUE;
|
|
708
798
|
}
|
|
709
|
-
const rowsV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(d.height);
|
|
799
|
+
const rowsV = args.length > 1 ? toNumberRV(topLeft(args[1])) : rvNumber(d.height);
|
|
710
800
|
if (isError(rowsV)) {
|
|
711
801
|
return rowsV;
|
|
712
802
|
}
|
|
713
|
-
const colsV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(d.width);
|
|
803
|
+
const colsV = args.length > 2 ? toNumberRV(topLeft(args[2])) : rvNumber(d.width);
|
|
714
804
|
if (isError(colsV)) {
|
|
715
805
|
return colsV;
|
|
716
806
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
807
|
+
// Truncate toward zero before using these as array indices. Without
|
|
808
|
+
// it, `TAKE(A1:A5, 2.7)` lands `rE = 2.7` in the slice bounds; V8
|
|
809
|
+
// silently walks the integer range `0..2` but a misuse of the value
|
|
810
|
+
// elsewhere (`d.rows[r]` with `r` fractional) would return undefined.
|
|
811
|
+
const rows = Math.trunc(rowsV.value);
|
|
812
|
+
const cols = Math.trunc(colsV.value);
|
|
813
|
+
const rS = rows >= 0 ? 0 : Math.max(0, d.height + rows);
|
|
814
|
+
const rE = rows >= 0 ? Math.min(rows, d.height) : d.height;
|
|
815
|
+
const cS = cols >= 0 ? 0 : Math.max(0, d.width + cols);
|
|
816
|
+
const cE = cols >= 0 ? Math.min(cols, d.width) : d.width;
|
|
721
817
|
const result = [];
|
|
722
818
|
for (let r = rS; r < rE; r++) {
|
|
723
819
|
const row = [];
|
|
@@ -733,18 +829,21 @@ export function fnDROP(args) {
|
|
|
733
829
|
if (!d) {
|
|
734
830
|
return ERRORS.VALUE;
|
|
735
831
|
}
|
|
736
|
-
const rowsV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(0);
|
|
832
|
+
const rowsV = args.length > 1 ? toNumberRV(topLeft(args[1])) : rvNumber(0);
|
|
737
833
|
if (isError(rowsV)) {
|
|
738
834
|
return rowsV;
|
|
739
835
|
}
|
|
740
|
-
const colsV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(0);
|
|
836
|
+
const colsV = args.length > 2 ? toNumberRV(topLeft(args[2])) : rvNumber(0);
|
|
741
837
|
if (isError(colsV)) {
|
|
742
838
|
return colsV;
|
|
743
839
|
}
|
|
744
|
-
|
|
745
|
-
const
|
|
746
|
-
const
|
|
747
|
-
const
|
|
840
|
+
// Truncate — see TAKE for rationale.
|
|
841
|
+
const rows = Math.trunc(rowsV.value);
|
|
842
|
+
const cols = Math.trunc(colsV.value);
|
|
843
|
+
const rS = rows >= 0 ? rows : 0;
|
|
844
|
+
const rE = rows >= 0 ? d.height : d.height + rows;
|
|
845
|
+
const cS = cols >= 0 ? cols : 0;
|
|
846
|
+
const cE = cols >= 0 ? d.width : d.width + cols;
|
|
748
847
|
const result = [];
|
|
749
848
|
for (let r = rS; r < rE; r++) {
|
|
750
849
|
const row = [];
|
|
@@ -768,18 +867,22 @@ export function fnWRAPROWS(args) {
|
|
|
768
867
|
flat.push(getCell(arr, r, c));
|
|
769
868
|
}
|
|
770
869
|
}
|
|
771
|
-
const wcV = toNumberRV(args[1]);
|
|
870
|
+
const wcV = toNumberRV(topLeft(args[1]));
|
|
772
871
|
if (isError(wcV)) {
|
|
773
872
|
return wcV;
|
|
774
873
|
}
|
|
775
|
-
|
|
874
|
+
// Truncate toward zero before using as a stride — `i += 2.5` and
|
|
875
|
+
// `row.length < 2.5` work by accidental float coercion in V8, but the
|
|
876
|
+
// intent is an integer wrap width; encode it explicitly.
|
|
877
|
+
const wc = Math.trunc(wcV.value);
|
|
878
|
+
if (wc < 1) {
|
|
776
879
|
return ERRORS.VALUE;
|
|
777
880
|
}
|
|
778
881
|
const pad = args.length > 2 ? topLeft(args[2]) : ERRORS.NA;
|
|
779
882
|
const result = [];
|
|
780
|
-
for (let i = 0; i < flat.length; i +=
|
|
781
|
-
const row = flat.slice(i, i +
|
|
782
|
-
while (row.length <
|
|
883
|
+
for (let i = 0; i < flat.length; i += wc) {
|
|
884
|
+
const row = flat.slice(i, i + wc);
|
|
885
|
+
while (row.length < wc) {
|
|
783
886
|
row.push(pad);
|
|
784
887
|
}
|
|
785
888
|
result.push(row);
|
|
@@ -797,20 +900,21 @@ export function fnWRAPCOLS(args) {
|
|
|
797
900
|
flat.push(getCell(arr, r, c));
|
|
798
901
|
}
|
|
799
902
|
}
|
|
800
|
-
const wcV = toNumberRV(args[1]);
|
|
903
|
+
const wcV = toNumberRV(topLeft(args[1]));
|
|
801
904
|
if (isError(wcV)) {
|
|
802
905
|
return wcV;
|
|
803
906
|
}
|
|
804
|
-
|
|
907
|
+
const wc = Math.trunc(wcV.value);
|
|
908
|
+
if (wc < 1) {
|
|
805
909
|
return ERRORS.VALUE;
|
|
806
910
|
}
|
|
807
911
|
const pad = args.length > 2 ? topLeft(args[2]) : ERRORS.NA;
|
|
808
|
-
const numCols = Math.ceil(flat.length /
|
|
912
|
+
const numCols = Math.ceil(flat.length / wc);
|
|
809
913
|
const result = [];
|
|
810
|
-
for (let r = 0; r <
|
|
914
|
+
for (let r = 0; r < wc; r++) {
|
|
811
915
|
const row = [];
|
|
812
916
|
for (let c = 0; c < numCols; c++) {
|
|
813
|
-
const idx = c *
|
|
917
|
+
const idx = c * wc + r;
|
|
814
918
|
row.push(idx < flat.length ? flat[idx] : pad);
|
|
815
919
|
}
|
|
816
920
|
result.push(row);
|
|
@@ -822,11 +926,11 @@ export function fnEXPAND(args) {
|
|
|
822
926
|
if (!d) {
|
|
823
927
|
return ERRORS.VALUE;
|
|
824
928
|
}
|
|
825
|
-
const rowsV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(d.height);
|
|
929
|
+
const rowsV = args.length > 1 ? toNumberRV(topLeft(args[1])) : rvNumber(d.height);
|
|
826
930
|
if (isError(rowsV)) {
|
|
827
931
|
return rowsV;
|
|
828
932
|
}
|
|
829
|
-
const colsV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(d.width);
|
|
933
|
+
const colsV = args.length > 2 ? toNumberRV(topLeft(args[2])) : rvNumber(d.width);
|
|
830
934
|
if (isError(colsV)) {
|
|
831
935
|
return colsV;
|
|
832
936
|
}
|
|
@@ -5,10 +5,10 @@ import type { RuntimeValue } from "../runtime/values.js";
|
|
|
5
5
|
type NativeFn = (args: RuntimeValue[]) => RuntimeValue;
|
|
6
6
|
export declare const fnBIN2DEC: NativeFn;
|
|
7
7
|
export declare const fnDEC2BIN: NativeFn;
|
|
8
|
-
export declare const fnHEX2DEC: NativeFn;
|
|
9
8
|
export declare const fnDEC2HEX: NativeFn;
|
|
10
|
-
export declare const fnOCT2DEC: NativeFn;
|
|
11
9
|
export declare const fnDEC2OCT: NativeFn;
|
|
10
|
+
export declare const fnHEX2DEC: NativeFn;
|
|
11
|
+
export declare const fnOCT2DEC: NativeFn;
|
|
12
12
|
export declare const fnBIN2HEX: NativeFn;
|
|
13
13
|
export declare const fnBIN2OCT: NativeFn;
|
|
14
14
|
export declare const fnHEX2BIN: NativeFn;
|