@pixerate/editor 0.23.0 → 0.25.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/README.md +167 -2
- package/THIRD_PARTY_NOTICES.md +80 -0
- package/dist/agent/index.cjs +10 -8
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +4 -3
- package/dist/agent/index.d.ts +4 -3
- package/dist/agent/index.js +4 -2
- package/dist/agent/index.js.map +1 -1
- package/dist/canvas/index.cjs +4437 -411
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +934 -4
- package/dist/canvas/index.d.ts +934 -4
- package/dist/canvas/index.js +4309 -283
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-4BVP64JJ.cjs +1406 -0
- package/dist/chunk-4BVP64JJ.cjs.map +1 -0
- package/dist/chunk-664LQYFJ.js +115 -0
- package/dist/chunk-664LQYFJ.js.map +1 -0
- package/dist/chunk-7WISYZFO.cjs +740 -0
- package/dist/chunk-7WISYZFO.cjs.map +1 -0
- package/dist/chunk-AL6JMGWW.cjs +783 -0
- package/dist/chunk-AL6JMGWW.cjs.map +1 -0
- package/dist/chunk-BVP3TSLH.cjs +69 -0
- package/dist/chunk-BVP3TSLH.cjs.map +1 -0
- package/dist/{chunk-RIKBJLS2.cjs → chunk-C7P22VIP.cjs} +2001 -279
- package/dist/chunk-C7P22VIP.cjs.map +1 -0
- package/dist/chunk-D6FH2YAX.cjs +131 -0
- package/dist/chunk-D6FH2YAX.cjs.map +1 -0
- package/dist/chunk-ECCCYKGL.js +740 -0
- package/dist/chunk-ECCCYKGL.js.map +1 -0
- package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
- package/dist/chunk-FD4LPFAE.cjs.map +1 -0
- package/dist/{chunk-QBJ22YZR.js → chunk-FH6F67DN.js} +1999 -277
- package/dist/chunk-FH6F67DN.js.map +1 -0
- package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
- package/dist/chunk-FPENXAEJ.js.map +1 -0
- package/dist/chunk-HLIMTC6N.js +1 -0
- package/dist/chunk-HLIMTC6N.js.map +1 -0
- package/dist/chunk-HRMEDT2L.js +1406 -0
- package/dist/chunk-HRMEDT2L.js.map +1 -0
- package/dist/{chunk-QGHGJ56O.js → chunk-IXHSYOVY.js} +2518 -122
- package/dist/chunk-IXHSYOVY.js.map +1 -0
- package/dist/chunk-MKA3AHLV.cjs +256 -0
- package/dist/chunk-MKA3AHLV.cjs.map +1 -0
- package/dist/chunk-NAXVVS5I.js +783 -0
- package/dist/chunk-NAXVVS5I.js.map +1 -0
- package/dist/chunk-O5RYH2PG.js +69 -0
- package/dist/chunk-O5RYH2PG.js.map +1 -0
- package/dist/chunk-OXSV64JX.js +689 -0
- package/dist/chunk-OXSV64JX.js.map +1 -0
- package/dist/chunk-P3RQ5UJE.cjs +689 -0
- package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
- package/dist/chunk-P6ODPZ4W.js +256 -0
- package/dist/chunk-P6ODPZ4W.js.map +1 -0
- package/dist/chunk-PL4JB3BF.cjs +1 -0
- package/dist/chunk-PL4JB3BF.cjs.map +1 -0
- package/dist/{chunk-GVSMRPHB.cjs → chunk-PO6A6QQH.cjs} +2607 -211
- package/dist/chunk-PO6A6QQH.cjs.map +1 -0
- package/dist/{chunk-JJDLPEWL.js → chunk-QGYDWPRM.js} +2 -2
- package/dist/chunk-RR7F5GUM.js +5726 -0
- package/dist/chunk-RR7F5GUM.js.map +1 -0
- package/dist/chunk-TNNJXF46.cjs +115 -0
- package/dist/chunk-TNNJXF46.cjs.map +1 -0
- package/dist/chunk-U5HZSFCS.js +131 -0
- package/dist/chunk-U5HZSFCS.js.map +1 -0
- package/dist/{chunk-MJZBNWG7.js → chunk-UAKDG62Z.js} +309 -581
- package/dist/chunk-UAKDG62Z.js.map +1 -0
- package/dist/{chunk-CL37JGFW.cjs → chunk-V3CHHWOE.cjs} +4 -4
- package/dist/{chunk-CL37JGFW.cjs.map → chunk-V3CHHWOE.cjs.map} +1 -1
- package/dist/chunk-WSQKHXW7.cjs +5726 -0
- package/dist/chunk-WSQKHXW7.cjs.map +1 -0
- package/dist/{chunk-AA4ZEVCC.cjs → chunk-X4N4WUKB.cjs} +336 -608
- package/dist/chunk-X4N4WUKB.cjs.map +1 -0
- package/dist/commands/index.cjs +74 -1
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.d.cts +38 -353
- package/dist/commands/index.d.ts +38 -353
- package/dist/commands/index.js +73 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/comment-store-BEMgVlAA.d.cts +135 -0
- package/dist/comment-store-BEMgVlAA.d.ts +135 -0
- package/dist/compare/index.cjs +15 -0
- package/dist/compare/index.cjs.map +1 -0
- package/dist/compare/index.d.cts +58 -0
- package/dist/compare/index.d.ts +58 -0
- package/dist/compare/index.js +15 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/controller-BIJiNoVs.d.cts +1842 -0
- package/dist/controller-BIJiNoVs.d.ts +1842 -0
- package/dist/documents-CHT1U5Vs.d.ts +86 -0
- package/dist/documents-Gp7wb0Me.d.cts +86 -0
- package/dist/extensions/index.cjs +100 -5
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +16 -4
- package/dist/extensions/index.d.ts +16 -4
- package/dist/extensions/index.js +100 -5
- package/dist/generation/index.cjs +2317 -0
- package/dist/generation/index.cjs.map +1 -0
- package/dist/generation/index.d.cts +1015 -0
- package/dist/generation/index.d.ts +1015 -0
- package/dist/generation/index.js +2317 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/grammar/index.cjs +48 -3
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.d.cts +322 -100
- package/dist/grammar/index.d.ts +322 -100
- package/dist/grammar/index.js +54 -9
- package/dist/image-editor/index.cjs +4392 -481
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +1249 -97
- package/dist/image-editor/index.d.ts +1249 -97
- package/dist/image-editor/index.js +4281 -370
- package/dist/image-editor/index.js.map +1 -1
- package/dist/index-7a6bHTLy.d.ts +1754 -0
- package/dist/index-Csd5qI8u.d.cts +1754 -0
- package/dist/index.cjs +1682 -99
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -21
- package/dist/index.d.ts +118 -21
- package/dist/index.js +1630 -47
- package/dist/index.js.map +1 -1
- package/dist/mention-text-CDnCFd3c.d.cts +53 -0
- package/dist/mention-text-CDnCFd3c.d.ts +53 -0
- package/dist/migrate-iTlm3a8h.d.cts +112 -0
- package/dist/migrate-iTlm3a8h.d.ts +112 -0
- package/dist/model-C1zApQM8.d.cts +219 -0
- package/dist/model-C1zApQM8.d.ts +219 -0
- package/dist/persistence/index.cjs +676 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +230 -0
- package/dist/persistence/index.d.ts +230 -0
- package/dist/persistence/index.js +676 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/{registry-D5-R90p0.d.cts → registry-BDe-vlv3.d.ts} +2 -40
- package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.cts} +12 -1
- package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.ts} +12 -1
- package/dist/registry-D32LpIc4.d.cts +354 -0
- package/dist/{registry-D5-R90p0.d.ts → registry-DnzpgY27.d.cts} +2 -40
- package/dist/registry-evKTP4ls.d.ts +354 -0
- package/dist/schema-DYDGdYL9.d.cts +41 -0
- package/dist/schema-DYDGdYL9.d.ts +41 -0
- package/dist/serializers/index.cjs +12 -3
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +40 -12
- package/dist/serializers/index.d.ts +40 -12
- package/dist/serializers/index.js +12 -3
- package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
- package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
- package/dist/spreadsheet/index.cjs +161 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +611 -20
- package/dist/spreadsheet/index.d.ts +611 -20
- package/dist/spreadsheet/index.js +168 -10
- package/dist/storage-BpFfspHm.d.cts +120 -0
- package/dist/storage-BpFfspHm.d.ts +120 -0
- package/dist/tokens/index.cjs +115 -0
- package/dist/tokens/index.cjs.map +1 -0
- package/dist/tokens/index.d.cts +212 -0
- package/dist/tokens/index.d.ts +212 -0
- package/dist/tokens/index.js +115 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
- package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
- package/dist/{track-changes-BSF58RK0.d.cts → track-changes-D_VAPELi.d.cts} +110 -144
- package/dist/{track-changes-BSF58RK0.d.ts → track-changes-Db_fGEvx.d.ts} +110 -144
- package/package.json +21 -1
- package/dist/chunk-4SUNJZWB.cjs.map +0 -1
- package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
- package/dist/chunk-AXV2ZW34.cjs +0 -118
- package/dist/chunk-AXV2ZW34.cjs.map +0 -1
- package/dist/chunk-B2ZRLN4S.js +0 -118
- package/dist/chunk-B2ZRLN4S.js.map +0 -1
- package/dist/chunk-GVSMRPHB.cjs.map +0 -1
- package/dist/chunk-K3R5NG2D.js +0 -3100
- package/dist/chunk-K3R5NG2D.js.map +0 -1
- package/dist/chunk-MJZBNWG7.js.map +0 -1
- package/dist/chunk-OYXSKQFR.js.map +0 -1
- package/dist/chunk-QBJ22YZR.js.map +0 -1
- package/dist/chunk-QGHGJ56O.js.map +0 -1
- package/dist/chunk-RIKBJLS2.cjs.map +0 -1
- package/dist/chunk-XDMXXC4Z.cjs +0 -3100
- package/dist/chunk-XDMXXC4Z.cjs.map +0 -1
- package/dist/controller-pePCJLT1.d.cts +0 -959
- package/dist/controller-pePCJLT1.d.ts +0 -959
- package/dist/index-CCr_z9kF.d.cts +0 -899
- package/dist/index-Ck6ajwXh.d.ts +0 -899
- /package/dist/{chunk-JJDLPEWL.js.map → chunk-QGYDWPRM.js.map} +0 -0
|
@@ -1,4 +1,30 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;// src/spreadsheet/formula/lexer.ts
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;// src/spreadsheet/formula/lexer.ts
|
|
2
|
+
var MAX_QUOTED_SHEET_LENGTH = 2 * 31 + 2;
|
|
3
|
+
var isWordStart = (char) => /[A-Za-z_$\\]/.test(char) || char > "\x7F" && /\p{L}/u.test(char);
|
|
4
|
+
var isWordChar = (char) => /[A-Za-z0-9_$.\\?]/.test(char) || char > "\x7F" && /[\p{L}\p{N}]/u.test(char);
|
|
5
|
+
var CELL_WORD = /^\$?[A-Za-z]+\$?[0-9]+$/;
|
|
6
|
+
function readQuotedSheet(input, start) {
|
|
7
|
+
let name = "";
|
|
8
|
+
let j = start + 1;
|
|
9
|
+
while (j < input.length && j - start <= MAX_QUOTED_SHEET_LENGTH) {
|
|
10
|
+
if (input[j] === "'") {
|
|
11
|
+
if (input[j + 1] === "'") {
|
|
12
|
+
name += "'";
|
|
13
|
+
j += 2;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (input[j + 1] !== "!" || input[j + 2] === "=" || name === "") return null;
|
|
17
|
+
const colon = name.indexOf(":");
|
|
18
|
+
if (colon > 0 && colon < name.length - 1) {
|
|
19
|
+
return { sheet: name.slice(0, colon), sheetEnd: name.slice(colon + 1), next: j + 2 };
|
|
20
|
+
}
|
|
21
|
+
return { sheet: name, next: j + 2 };
|
|
22
|
+
}
|
|
23
|
+
name += input[j];
|
|
24
|
+
j++;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
2
28
|
function tokenizeFormula(formula) {
|
|
3
29
|
const tokens = [];
|
|
4
30
|
let input = formula.trim();
|
|
@@ -29,6 +55,13 @@ function tokenizeFormula(formula) {
|
|
|
29
55
|
});
|
|
30
56
|
continue;
|
|
31
57
|
}
|
|
58
|
+
if (char === "'") {
|
|
59
|
+
const prefix = readQuotedSheet(input, i);
|
|
60
|
+
if (prefix) {
|
|
61
|
+
i = readQualifiedReference(input, prefix.next, i, prefix.sheet, prefix.sheetEnd, offset, tokens);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
32
65
|
if (char === '"' || char === "'") {
|
|
33
66
|
const quote2 = char;
|
|
34
67
|
const start = i;
|
|
@@ -48,7 +81,8 @@ function tokenizeFormula(formula) {
|
|
|
48
81
|
tokens.push({
|
|
49
82
|
type: "STRING",
|
|
50
83
|
value: str,
|
|
51
|
-
position: start + offset
|
|
84
|
+
position: start + offset,
|
|
85
|
+
end: i + offset
|
|
52
86
|
});
|
|
53
87
|
continue;
|
|
54
88
|
}
|
|
@@ -75,7 +109,8 @@ function tokenizeFormula(formula) {
|
|
|
75
109
|
tokens.push({
|
|
76
110
|
type: "PROPERTY_REF",
|
|
77
111
|
value: prop.trim(),
|
|
78
|
-
position: start + offset
|
|
112
|
+
position: start + offset,
|
|
113
|
+
end: i + offset
|
|
79
114
|
});
|
|
80
115
|
continue;
|
|
81
116
|
}
|
|
@@ -109,65 +144,96 @@ function tokenizeFormula(formula) {
|
|
|
109
144
|
i++;
|
|
110
145
|
continue;
|
|
111
146
|
}
|
|
112
|
-
if (
|
|
147
|
+
if (isWordStart(char)) {
|
|
113
148
|
const start = i;
|
|
114
|
-
while (i < input.length &&
|
|
149
|
+
while (i < input.length && isWordChar(input[i])) {
|
|
115
150
|
i++;
|
|
116
151
|
}
|
|
117
152
|
const rawWord = input.slice(start, i);
|
|
153
|
+
if (input[i] === "!" && input[i + 1] !== "=") {
|
|
154
|
+
i = readQualifiedReference(input, i + 1, start, rawWord, void 0, offset, tokens);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
118
157
|
if (i < input.length && input[i] === ":") {
|
|
119
158
|
const colonPos = i;
|
|
120
159
|
i++;
|
|
121
160
|
const secondStart = i;
|
|
122
|
-
while (i < input.length &&
|
|
161
|
+
while (i < input.length && isWordChar(input[i])) {
|
|
123
162
|
i++;
|
|
124
163
|
}
|
|
125
164
|
const secondWord = input.slice(secondStart, i);
|
|
165
|
+
if (secondWord.length > 0 && input[i] === "!" && input[i + 1] !== "=") {
|
|
166
|
+
i = readQualifiedReference(input, i + 1, start, rawWord, secondWord, offset, tokens);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
126
169
|
if (secondWord.length > 0) {
|
|
127
170
|
tokens.push({
|
|
128
171
|
type: "RANGE",
|
|
129
172
|
value: `${rawWord}:${secondWord}`.toUpperCase(),
|
|
130
|
-
position: start + offset
|
|
173
|
+
position: start + offset,
|
|
174
|
+
end: i + offset
|
|
131
175
|
});
|
|
132
176
|
continue;
|
|
133
177
|
} else {
|
|
134
178
|
i = colonPos;
|
|
135
179
|
}
|
|
136
180
|
}
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
tokens.push({
|
|
140
|
-
type: "BOOLEAN",
|
|
141
|
-
value: upper,
|
|
142
|
-
position: start + offset
|
|
143
|
-
});
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (/^\$?[A-Za-z]+\$?[0-9]+$/.test(rawWord)) {
|
|
147
|
-
if (input[i] === "#") {
|
|
148
|
-
i++;
|
|
149
|
-
tokens.push({ type: "SPILL_REF", value: upper.replace(/\$/g, ""), position: start + offset });
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
tokens.push({
|
|
153
|
-
type: "CELL_REF",
|
|
154
|
-
value: upper.replace(/\$/g, ""),
|
|
155
|
-
position: start + offset
|
|
156
|
-
});
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
tokens.push({
|
|
160
|
-
type: "IDENTIFIER",
|
|
161
|
-
value: upper,
|
|
162
|
-
position: start + offset
|
|
163
|
-
});
|
|
181
|
+
pushWord(input, rawWord, start, i, offset, tokens);
|
|
182
|
+
if (tokens[tokens.length - 1].type === "SPILL_REF") i++;
|
|
164
183
|
continue;
|
|
165
184
|
}
|
|
166
185
|
throw new Error(`Unexpected character '${char}' at position ${i + offset}`);
|
|
167
186
|
}
|
|
168
|
-
tokens.push({ type: "EOF", value: "", position: input.length + offset });
|
|
187
|
+
tokens.push({ type: "EOF", value: "", position: input.length + offset, end: input.length + offset });
|
|
188
|
+
for (const token of tokens) {
|
|
189
|
+
if (token.end === void 0) token.end = token.position + Math.max(token.value.length, 1);
|
|
190
|
+
}
|
|
169
191
|
return tokens;
|
|
170
192
|
}
|
|
193
|
+
function pushWord(input, rawWord, start, end, offset, tokens, qualified) {
|
|
194
|
+
const upper = rawWord.toUpperCase();
|
|
195
|
+
const position = _nullishCoalesce(_optionalChain([qualified, 'optionalAccess', _2 => _2.position]), () => ( start + offset));
|
|
196
|
+
const extra = qualified ? { sheet: qualified.sheet, ...qualified.sheetEnd ? { sheetEnd: qualified.sheetEnd } : {}, refStart: qualified.refStart } : {};
|
|
197
|
+
if (!qualified && (upper === "TRUE" || upper === "FALSE")) {
|
|
198
|
+
tokens.push({ type: "BOOLEAN", value: upper, position, end: end + offset });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (CELL_WORD.test(rawWord)) {
|
|
202
|
+
if (input[end] === "#") {
|
|
203
|
+
tokens.push({ type: "SPILL_REF", value: upper.replace(/\$/g, ""), position, end: end + 1 + offset, ...extra });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
tokens.push({ type: "CELL_REF", value: upper.replace(/\$/g, ""), position, end: end + offset, ...extra });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
tokens.push({ type: "IDENTIFIER", value: upper, position, end: end + offset, ...extra });
|
|
210
|
+
}
|
|
211
|
+
function readQualifiedReference(input, from, prefixStart, sheet, sheetEnd, offset, tokens) {
|
|
212
|
+
let i = from;
|
|
213
|
+
while (i < input.length && isWordChar(input[i])) i++;
|
|
214
|
+
const word = input.slice(from, i);
|
|
215
|
+
if (!word) throw new Error(`Expected a reference after '!' at position ${from + offset}`);
|
|
216
|
+
const qualified = { sheet, sheetEnd, refStart: from + offset, position: prefixStart + offset };
|
|
217
|
+
if (input[i] === ":") {
|
|
218
|
+
let j = i + 1;
|
|
219
|
+
while (j < input.length && isWordChar(input[j])) j++;
|
|
220
|
+
const second = input.slice(i + 1, j);
|
|
221
|
+
if (second) {
|
|
222
|
+
tokens.push({
|
|
223
|
+
type: "RANGE",
|
|
224
|
+
value: `${word}:${second}`.toUpperCase(),
|
|
225
|
+
position: qualified.position,
|
|
226
|
+
end: j + offset,
|
|
227
|
+
sheet,
|
|
228
|
+
...sheetEnd ? { sheetEnd } : {},
|
|
229
|
+
refStart: qualified.refStart
|
|
230
|
+
});
|
|
231
|
+
return j;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
pushWord(input, word, from, i, offset, tokens, qualified);
|
|
235
|
+
return tokens[tokens.length - 1].type === "SPILL_REF" ? i + 1 : i;
|
|
236
|
+
}
|
|
171
237
|
|
|
172
238
|
// src/spreadsheet/formula/parser.ts
|
|
173
239
|
var WHOLE_COLUMN_END_ROW = 1048576;
|
|
@@ -205,6 +271,7 @@ function parseRange(rangeStr) {
|
|
|
205
271
|
endRow: endRef.row
|
|
206
272
|
};
|
|
207
273
|
}
|
|
274
|
+
var qualifier = (token) => token.sheet === void 0 ? {} : token.sheetEnd === void 0 ? { sheet: token.sheet } : { sheet: token.sheet, sheetEnd: token.sheetEnd };
|
|
208
275
|
var FormulaParser = (_class = class {
|
|
209
276
|
__init() {this.tokens = []}
|
|
210
277
|
__init2() {this.current = 0}
|
|
@@ -275,7 +342,22 @@ var FormulaParser = (_class = class {
|
|
|
275
342
|
const argument = this.unary();
|
|
276
343
|
return { type: "UnaryOp", op, argument };
|
|
277
344
|
}
|
|
278
|
-
|
|
345
|
+
let expr = this.primary();
|
|
346
|
+
while (this.check("LPAREN") && (expr.type === "FunctionCall" || expr.type === "Invoke" || this.previous().type === "RPAREN")) {
|
|
347
|
+
this.advance();
|
|
348
|
+
expr = { type: "Invoke", callee: expr, args: this.argumentList() };
|
|
349
|
+
}
|
|
350
|
+
return expr;
|
|
351
|
+
}
|
|
352
|
+
/** Arguments after `(` up to and including `)`. Empty positions are `Missing`. */
|
|
353
|
+
argumentList() {
|
|
354
|
+
const args = [];
|
|
355
|
+
if (this.match("RPAREN")) return args;
|
|
356
|
+
do {
|
|
357
|
+
args.push(this.check("COMMA") || this.check("RPAREN") ? { type: "Missing" } : this.expression());
|
|
358
|
+
} while (this.match("COMMA"));
|
|
359
|
+
this.consume("RPAREN", `Expected ')' after function arguments`);
|
|
360
|
+
return args;
|
|
279
361
|
}
|
|
280
362
|
primary() {
|
|
281
363
|
const token = this.peek();
|
|
@@ -297,22 +379,27 @@ var FormulaParser = (_class = class {
|
|
|
297
379
|
}
|
|
298
380
|
if (token.type === "CELL_REF") {
|
|
299
381
|
this.advance();
|
|
382
|
+
if (token.sheet === void 0 && this.check("LPAREN")) {
|
|
383
|
+
this.advance();
|
|
384
|
+
return { type: "FunctionCall", name: token.value, args: this.argumentList() };
|
|
385
|
+
}
|
|
300
386
|
const parsed = parseCellReference(token.value);
|
|
301
387
|
return {
|
|
302
388
|
type: "CellRef",
|
|
303
389
|
ref: token.value,
|
|
304
390
|
col: parsed.col,
|
|
305
|
-
row: parsed.row
|
|
391
|
+
row: parsed.row,
|
|
392
|
+
...qualifier(token)
|
|
306
393
|
};
|
|
307
394
|
}
|
|
308
395
|
if (token.type === "RANGE") {
|
|
309
396
|
this.advance();
|
|
310
|
-
return parseRange(token.value);
|
|
397
|
+
return { ...parseRange(token.value), ...qualifier(token) };
|
|
311
398
|
}
|
|
312
399
|
if (token.type === "SPILL_REF") {
|
|
313
400
|
this.advance();
|
|
314
401
|
const parsed = parseCellReference(token.value);
|
|
315
|
-
return { type: "SpillRef", ref: token.value, col: parsed.col, row: parsed.row };
|
|
402
|
+
return { type: "SpillRef", ref: token.value, col: parsed.col, row: parsed.row, ...qualifier(token) };
|
|
316
403
|
}
|
|
317
404
|
if (token.type === "PROPERTY_REF") {
|
|
318
405
|
this.advance();
|
|
@@ -325,21 +412,20 @@ var FormulaParser = (_class = class {
|
|
|
325
412
|
const name = token.value;
|
|
326
413
|
this.advance();
|
|
327
414
|
if (this.check("LPAREN")) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (!this.check("RPAREN")) {
|
|
331
|
-
do {
|
|
332
|
-
args.push(this.expression());
|
|
333
|
-
} while (this.match("COMMA"));
|
|
415
|
+
if (token.sheet !== void 0) {
|
|
416
|
+
throw new Error(`Unexpected '(' after ${token.sheet}!${name} at position ${this.peek().position}`);
|
|
334
417
|
}
|
|
335
|
-
this.consume("
|
|
418
|
+
this.consume("LPAREN", `Expected '(' after function ${name}`);
|
|
336
419
|
return {
|
|
337
420
|
type: "FunctionCall",
|
|
338
421
|
name,
|
|
339
|
-
args
|
|
422
|
+
args: this.argumentList()
|
|
340
423
|
};
|
|
341
424
|
}
|
|
342
|
-
|
|
425
|
+
if (token.sheetEnd !== void 0) {
|
|
426
|
+
throw new Error(`A 3-D reference can't name ${name} at position ${token.position}`);
|
|
427
|
+
}
|
|
428
|
+
return token.sheet !== void 0 ? { type: "Name", name, sheet: token.sheet } : { type: "Name", name };
|
|
343
429
|
}
|
|
344
430
|
if (this.match("LPAREN")) {
|
|
345
431
|
const expr = this.expression();
|
|
@@ -392,6 +478,35 @@ function parseFormula(formula) {
|
|
|
392
478
|
return parser.parse();
|
|
393
479
|
}
|
|
394
480
|
|
|
481
|
+
// src/spreadsheet/formula/lambda.ts
|
|
482
|
+
var FormulaLambda = class {
|
|
483
|
+
constructor(params, body, env, sheet, native, label = "LAMBDA") {
|
|
484
|
+
this.params = params;
|
|
485
|
+
this.body = body;
|
|
486
|
+
this.env = env;
|
|
487
|
+
this.sheet = sheet;
|
|
488
|
+
this.native = native;
|
|
489
|
+
this.label = label;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
toString() {
|
|
498
|
+
return `[${this.label}]`;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
var isFormulaLambda = (value) => value instanceof FormulaLambda;
|
|
502
|
+
var MAX_LAMBDA_DEPTH = 1e3;
|
|
503
|
+
function lookupEnvironment(env, name) {
|
|
504
|
+
for (let e = env; e; e = e.parent) {
|
|
505
|
+
if (e.vars.has(name)) return e;
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
|
|
395
510
|
// src/spreadsheet/formula/values.ts
|
|
396
511
|
var FORMULA_ERRORS = /* @__PURE__ */ new Set([
|
|
397
512
|
"#DIV/0!",
|
|
@@ -597,7 +712,7 @@ function parseIsoDate(value) {
|
|
|
597
712
|
var isoDateHasTime = (value) => /[ T]\d/.test(value.trim());
|
|
598
713
|
function isoDateTime(value) {
|
|
599
714
|
if (value.length < 8 || value.charCodeAt(4) !== 45) return null;
|
|
600
|
-
return _nullishCoalesce(_optionalChain([parseIsoDate, 'call',
|
|
715
|
+
return _nullishCoalesce(_optionalChain([parseIsoDate, 'call', _3 => _3(value), 'optionalAccess', _4 => _4.getTime, 'call', _5 => _5()]), () => ( null));
|
|
601
716
|
}
|
|
602
717
|
function dateToSerial(date) {
|
|
603
718
|
const utc = Date.UTC(
|
|
@@ -982,209 +1097,1343 @@ function parseCriteria(criterion) {
|
|
|
982
1097
|
};
|
|
983
1098
|
}
|
|
984
1099
|
|
|
985
|
-
// src/spreadsheet/
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
const
|
|
1003
|
-
let
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
if (
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1100
|
+
// src/spreadsheet/numfmt/decimal.ts
|
|
1101
|
+
var MAX_DECIMALS = 400;
|
|
1102
|
+
function significantDigits(a, n) {
|
|
1103
|
+
const s = a.toExponential(Math.max(0, Math.min(100, n - 1)));
|
|
1104
|
+
const e = s.indexOf("e");
|
|
1105
|
+
const digits = [];
|
|
1106
|
+
for (let i = 0; i < e; i++) {
|
|
1107
|
+
const c = s.charCodeAt(i);
|
|
1108
|
+
if (c >= 48 && c <= 57) digits.push(c - 48);
|
|
1109
|
+
}
|
|
1110
|
+
return { digits, exp: Number(s.slice(e + 1)) };
|
|
1111
|
+
}
|
|
1112
|
+
function exactDigits(a) {
|
|
1113
|
+
const view = new DataView(new ArrayBuffer(8));
|
|
1114
|
+
view.setFloat64(0, a);
|
|
1115
|
+
const hi = view.getUint32(0);
|
|
1116
|
+
const lo = view.getUint32(4);
|
|
1117
|
+
const biased = hi >>> 20 & 2047;
|
|
1118
|
+
let mantissa = BigInt(hi & 1048575) << 32n | BigInt(lo);
|
|
1119
|
+
let exp2;
|
|
1120
|
+
if (biased === 0) {
|
|
1121
|
+
exp2 = -1074;
|
|
1122
|
+
} else {
|
|
1123
|
+
mantissa |= 1n << 52n;
|
|
1124
|
+
exp2 = biased - 1075;
|
|
1125
|
+
}
|
|
1126
|
+
const digits = exp2 >= 0 ? (mantissa << BigInt(exp2)).toString() : (mantissa * 5n ** BigInt(-exp2)).toString();
|
|
1127
|
+
return digits.replace(/0+$/, "");
|
|
1128
|
+
}
|
|
1129
|
+
function isExactTie(a) {
|
|
1130
|
+
const isTie = (ds) => ds.length >= 16 && ds[15] === "5" && /^0*$/.test(ds.slice(16));
|
|
1131
|
+
return isTie(significantDigits(a, 30).digits.join("")) && isTie(exactDigits(a).padEnd(16, "0"));
|
|
1132
|
+
}
|
|
1133
|
+
function decimalDigits(a) {
|
|
1134
|
+
if (!Number.isFinite(a) || a <= 0) return { digits: new Array(15).fill(0), exp: 0 };
|
|
1135
|
+
const { digits: d16, exp } = significantDigits(a, 16);
|
|
1136
|
+
const digits = d16.slice(0, 15);
|
|
1137
|
+
while (digits.length < 15) digits.push(0);
|
|
1138
|
+
const sixteenth = _nullishCoalesce(d16[15], () => ( 0));
|
|
1139
|
+
if (sixteenth <= 4) return { digits, exp };
|
|
1140
|
+
if (sixteenth === 5) {
|
|
1141
|
+
if (isExactTie(a)) return { digits, exp };
|
|
1142
|
+
const r = significantDigits(a, 15);
|
|
1143
|
+
while (r.digits.length < 15) r.digits.push(0);
|
|
1144
|
+
return r;
|
|
1145
|
+
}
|
|
1146
|
+
let e = exp;
|
|
1147
|
+
let carry = true;
|
|
1148
|
+
for (let i = 14; i >= 0; i--) {
|
|
1149
|
+
if (digits[i] === 9) {
|
|
1150
|
+
digits[i] = 0;
|
|
1151
|
+
} else {
|
|
1152
|
+
digits[i] += 1;
|
|
1153
|
+
carry = false;
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (carry) {
|
|
1158
|
+
digits.fill(0);
|
|
1159
|
+
digits[0] = 1;
|
|
1160
|
+
e += 1;
|
|
1161
|
+
}
|
|
1162
|
+
return { digits, exp: e };
|
|
1163
|
+
}
|
|
1164
|
+
function fixedFromDigits(digits, exp, decimals) {
|
|
1165
|
+
const decimalsCalc = Math.min(decimals, MAX_DECIMALS);
|
|
1166
|
+
const keep = exp + decimalsCalc + 1;
|
|
1167
|
+
let m;
|
|
1168
|
+
if (keep <= 0) {
|
|
1169
|
+
m = keep === 0 && (_nullishCoalesce(digits[0], () => ( 0))) >= 5 ? [1] : [];
|
|
1170
|
+
} else if (keep >= digits.length) {
|
|
1171
|
+
m = digits.slice();
|
|
1172
|
+
while (m.length < keep) m.push(0);
|
|
1173
|
+
} else {
|
|
1174
|
+
m = digits.slice(0, keep);
|
|
1175
|
+
if ((_nullishCoalesce(digits[keep], () => ( 0))) >= 5) {
|
|
1176
|
+
let carry = true;
|
|
1177
|
+
for (let i = m.length - 1; i >= 0; i--) {
|
|
1178
|
+
if (m[i] === 9) {
|
|
1179
|
+
m[i] = 0;
|
|
1180
|
+
} else {
|
|
1181
|
+
m[i] += 1;
|
|
1182
|
+
carry = false;
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (carry) m.unshift(1);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
let lead = 0;
|
|
1190
|
+
while (lead < m.length && m[lead] === 0) lead++;
|
|
1191
|
+
m = m.slice(lead);
|
|
1192
|
+
const toS = (ds) => ds.join("");
|
|
1193
|
+
let int;
|
|
1194
|
+
let frac;
|
|
1195
|
+
if (m.length <= decimalsCalc) {
|
|
1196
|
+
int = "";
|
|
1197
|
+
frac = "0".repeat(decimalsCalc - m.length) + toS(m);
|
|
1198
|
+
} else {
|
|
1199
|
+
const split = m.length - decimalsCalc;
|
|
1200
|
+
int = toS(m.slice(0, split));
|
|
1201
|
+
frac = toS(m.slice(split));
|
|
1202
|
+
}
|
|
1203
|
+
if (decimals > decimalsCalc) frac += "0".repeat(decimals - decimalsCalc);
|
|
1204
|
+
return [int, frac];
|
|
1205
|
+
}
|
|
1206
|
+
function fixed(a, decimals) {
|
|
1207
|
+
if (!Number.isFinite(a) || a <= 0) return ["", "0".repeat(Math.min(decimals, MAX_DECIMALS * 4))];
|
|
1208
|
+
const { digits, exp } = decimalDigits(a);
|
|
1209
|
+
return fixedFromDigits(digits, exp, decimals);
|
|
1210
|
+
}
|
|
1211
|
+
var charCount = (s) => Array.from(s).length;
|
|
1212
|
+
function formatGeneralFit(n, maxChars) {
|
|
1213
|
+
if (!Number.isFinite(n)) return null;
|
|
1214
|
+
if (n === 0) return maxChars >= 1 ? "0" : null;
|
|
1215
|
+
const sign = n < 0 ? "-" : "";
|
|
1216
|
+
const { digits, exp: x } = decimalDigits(Math.abs(n));
|
|
1217
|
+
const intLen = sign.length + Math.max(x, 0) + 1;
|
|
1218
|
+
if (x >= -4 && intLen <= maxChars) {
|
|
1219
|
+
const avail = maxChars - intLen;
|
|
1220
|
+
let decimals = avail >= 2 ? avail - 1 : 0;
|
|
1221
|
+
for (; ; ) {
|
|
1222
|
+
const [i, f0] = fixedFromDigits(digits, x, decimals);
|
|
1223
|
+
const f = f0.replace(/0+$/, "");
|
|
1224
|
+
if (i === "" && f === "") break;
|
|
1225
|
+
let s = sign + (i === "" ? "0" : i);
|
|
1226
|
+
if (f !== "") s += `.${f}`;
|
|
1227
|
+
if (charCount(s) <= maxChars) return s;
|
|
1228
|
+
if (decimals === 0) break;
|
|
1229
|
+
decimals -= 1;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
for (let md = Math.min(maxChars, 14); md >= 0; md--) {
|
|
1233
|
+
let e = x;
|
|
1234
|
+
let [mi, mf] = fixedFromDigits(digits, 0, md);
|
|
1235
|
+
if (mi.length > 1) {
|
|
1236
|
+
e += 1;
|
|
1237
|
+
[mi, mf] = fixedFromDigits(digits, -1, md);
|
|
1238
|
+
}
|
|
1239
|
+
mf = mf.replace(/0+$/, "");
|
|
1240
|
+
let s = sign + (mi === "" ? "0" : mi);
|
|
1241
|
+
if (mf !== "") s += `.${mf}`;
|
|
1242
|
+
s += `E${e < 0 ? "-" : "+"}${String(Math.abs(e)).padStart(2, "0")}`;
|
|
1243
|
+
if (charCount(s) <= maxChars) return s;
|
|
1244
|
+
}
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// src/spreadsheet/numfmt/builtin.ts
|
|
1249
|
+
var BUILTINS = [
|
|
1250
|
+
[0, "General"],
|
|
1251
|
+
[1, "0"],
|
|
1252
|
+
[2, "0.00"],
|
|
1253
|
+
[3, "#,##0"],
|
|
1254
|
+
[4, "#,##0.00"],
|
|
1255
|
+
[5, '"$"#,##0_);\\("$"#,##0\\)'],
|
|
1256
|
+
[6, '"$"#,##0_);[Red]\\("$"#,##0\\)'],
|
|
1257
|
+
[7, '"$"#,##0.00_);\\("$"#,##0.00\\)'],
|
|
1258
|
+
[8, '"$"#,##0.00_);[Red]\\("$"#,##0.00\\)'],
|
|
1259
|
+
[9, "0%"],
|
|
1260
|
+
[10, "0.00%"],
|
|
1261
|
+
[11, "0.00E+00"],
|
|
1262
|
+
[12, "# ?/?"],
|
|
1263
|
+
[13, "# ??/??"],
|
|
1264
|
+
[14, "m/d/yyyy"],
|
|
1265
|
+
[15, "d-mmm-yy"],
|
|
1266
|
+
[16, "d-mmm"],
|
|
1267
|
+
[17, "mmm-yy"],
|
|
1268
|
+
[18, "h:mm AM/PM"],
|
|
1269
|
+
[19, "h:mm:ss AM/PM"],
|
|
1270
|
+
[20, "h:mm"],
|
|
1271
|
+
[21, "h:mm:ss"],
|
|
1272
|
+
[22, "m/d/yyyy h:mm"],
|
|
1273
|
+
[37, "#,##0 ;(#,##0)"],
|
|
1274
|
+
[38, "#,##0 ;[Red](#,##0)"],
|
|
1275
|
+
[39, "#,##0.00;(#,##0.00)"],
|
|
1276
|
+
[40, "#,##0.00;[Red](#,##0.00)"],
|
|
1277
|
+
[41, '_(* #,##0_);_(* \\(#,##0\\);_(* "-"_);_(@_)'],
|
|
1278
|
+
[42, '_("$"* #,##0_);_("$"* \\(#,##0\\);_("$"* "-"_);_(@_)'],
|
|
1279
|
+
[43, '_(* #,##0.00_);_(* \\(#,##0.00\\);_(* "-"??_);_(@_)'],
|
|
1280
|
+
[44, '_("$"* #,##0.00_);_("$"* \\(#,##0.00\\);_("$"* "-"??_);_(@_)'],
|
|
1281
|
+
[45, "mm:ss"],
|
|
1282
|
+
[46, "[h]:mm:ss"],
|
|
1283
|
+
[47, "mmss.0"],
|
|
1284
|
+
[48, "##0.0E+0"],
|
|
1285
|
+
[49, "@"]
|
|
1286
|
+
];
|
|
1287
|
+
function builtinNumberFormat(id) {
|
|
1288
|
+
return _optionalChain([BUILTINS, 'access', _6 => _6.find, 'call', _7 => _7(([i]) => i === id), 'optionalAccess', _8 => _8[1]]);
|
|
1289
|
+
}
|
|
1290
|
+
function builtinNumberFormatId(code) {
|
|
1291
|
+
const exact = BUILTINS.find(([, c]) => c === code);
|
|
1292
|
+
if (exact) return exact[0];
|
|
1293
|
+
const norm = (s) => s.replace(/\\/g, "").toLowerCase();
|
|
1294
|
+
const n = norm(code);
|
|
1295
|
+
return _optionalChain([BUILTINS, 'access', _9 => _9.find, 'call', _10 => _10(([, c]) => norm(c) === n), 'optionalAccess', _11 => _11[0]]);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// src/spreadsheet/numfmt/parse.ts
|
|
1299
|
+
var MAX_SECTIONS = 4;
|
|
1300
|
+
var isCalendarPart = (d) => d.k === "year" || d.k === "month" || d.k === "day";
|
|
1301
|
+
function literalText(tok) {
|
|
1302
|
+
switch (tok.t) {
|
|
1303
|
+
case "lit":
|
|
1304
|
+
case "denom":
|
|
1305
|
+
return tok.s;
|
|
1306
|
+
case "digit":
|
|
1307
|
+
return tok.c;
|
|
1308
|
+
case "point":
|
|
1309
|
+
return ".";
|
|
1310
|
+
case "comma":
|
|
1311
|
+
return ",";
|
|
1312
|
+
case "percent":
|
|
1313
|
+
return "%";
|
|
1314
|
+
case "exp":
|
|
1315
|
+
return `${tok.upper ? "E" : "e"}${tok.plus ? "+" : "-"}`;
|
|
1316
|
+
case "slash":
|
|
1317
|
+
return "/";
|
|
1318
|
+
default:
|
|
1319
|
+
return "";
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
function testCond(c, v) {
|
|
1323
|
+
switch (c.op) {
|
|
1324
|
+
case "=":
|
|
1325
|
+
return v === c.value;
|
|
1326
|
+
case "<>":
|
|
1327
|
+
return v !== c.value;
|
|
1328
|
+
case "<":
|
|
1329
|
+
return v < c.value;
|
|
1330
|
+
case "<=":
|
|
1331
|
+
return v <= c.value;
|
|
1332
|
+
case ">":
|
|
1333
|
+
return v > c.value;
|
|
1334
|
+
case ">=":
|
|
1335
|
+
return v >= c.value;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
var isDigitTok = (t, c) => _optionalChain([t, 'optionalAccess', _12 => _12.t]) === "digit" && (c === void 0 || t.c === c);
|
|
1339
|
+
function splitSections(code) {
|
|
1340
|
+
const cs = Array.from(code);
|
|
1341
|
+
const lastClose = cs.lastIndexOf("]");
|
|
1342
|
+
const out = [""];
|
|
1343
|
+
let quoted = false;
|
|
1344
|
+
let escape = false;
|
|
1345
|
+
let i = 0;
|
|
1346
|
+
while (i < cs.length) {
|
|
1347
|
+
const c = cs[i];
|
|
1348
|
+
i += 1;
|
|
1349
|
+
const cur = out.length - 1;
|
|
1350
|
+
if (escape) {
|
|
1351
|
+
out[cur] += c;
|
|
1352
|
+
escape = false;
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
if (c === '"') {
|
|
1356
|
+
quoted = !quoted;
|
|
1357
|
+
out[cur] += c;
|
|
1358
|
+
} else if ((c === "\\" || c === "_" || c === "*") && !quoted) {
|
|
1359
|
+
escape = true;
|
|
1360
|
+
out[cur] += c;
|
|
1361
|
+
} else if (c === "[" && !quoted && lastClose >= i) {
|
|
1362
|
+
let s = c;
|
|
1363
|
+
while (i < cs.length) {
|
|
1364
|
+
const d = cs[i];
|
|
1365
|
+
i += 1;
|
|
1366
|
+
s += d;
|
|
1367
|
+
if (d === "]") break;
|
|
1368
|
+
}
|
|
1369
|
+
out[cur] += s;
|
|
1370
|
+
} else if (c === ";" && !quoted) {
|
|
1371
|
+
if (out.length >= MAX_SECTIONS) break;
|
|
1372
|
+
out.push("");
|
|
1046
1373
|
} else {
|
|
1047
|
-
|
|
1374
|
+
out[cur] += c;
|
|
1048
1375
|
}
|
|
1049
1376
|
}
|
|
1050
1377
|
return out;
|
|
1051
1378
|
}
|
|
1052
|
-
var
|
|
1053
|
-
function
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if (
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1379
|
+
var lowerAscii = (c) => c >= "A" && c <= "Z" ? c.toLowerCase() : c;
|
|
1380
|
+
function startsWithCi(cs, i, pat) {
|
|
1381
|
+
for (let k = 0; k < pat.length; k++) {
|
|
1382
|
+
const c = cs[i + k];
|
|
1383
|
+
if (c === void 0 || lowerAscii(c) !== pat[k]) return false;
|
|
1384
|
+
}
|
|
1385
|
+
return true;
|
|
1386
|
+
}
|
|
1387
|
+
function pushLit(toks, s) {
|
|
1388
|
+
if (s === "") return;
|
|
1389
|
+
const last = toks[toks.length - 1];
|
|
1390
|
+
if (_optionalChain([last, 'optionalAccess', _13 => _13.t]) === "lit") last.s += s;
|
|
1391
|
+
else toks.push({ t: "lit", s });
|
|
1392
|
+
}
|
|
1393
|
+
var CURRENCY_CHARS = /* @__PURE__ */ new Set(["$", "\u20AC", "\xA3", "\xA5", "\u20B9", "\u20A9", "\u20BD", "\xA2", "\u20AA", "\u20AB", "\u0E3F"]);
|
|
1394
|
+
var isCurrencyChar = (c) => CURRENCY_CHARS.has(c);
|
|
1395
|
+
var NAMED_COLORS = /* @__PURE__ */ new Set(["black", "blue", "cyan", "green", "magenta", "red", "white", "yellow"]);
|
|
1396
|
+
function parseColor(lc) {
|
|
1397
|
+
if (NAMED_COLORS.has(lc)) return lc;
|
|
1398
|
+
if (!lc.startsWith("color")) return void 0;
|
|
1399
|
+
const rest = lc.slice(5).trim();
|
|
1400
|
+
if (!/^\+?\d{1,3}$/.test(rest)) return void 0;
|
|
1401
|
+
const n = Number(rest);
|
|
1402
|
+
return n >= 1 && n <= 56 ? `color${n}` : void 0;
|
|
1403
|
+
}
|
|
1404
|
+
function parseCond(s) {
|
|
1405
|
+
const t = s.trim();
|
|
1406
|
+
let op;
|
|
1407
|
+
let rest;
|
|
1408
|
+
if (t.startsWith("<=")) [op, rest] = ["<=", t.slice(2)];
|
|
1409
|
+
else if (t.startsWith(">=")) [op, rest] = [">=", t.slice(2)];
|
|
1410
|
+
else if (t.startsWith("<>")) [op, rest] = ["<>", t.slice(2)];
|
|
1411
|
+
else if (t.startsWith("<")) [op, rest] = ["<", t.slice(1)];
|
|
1412
|
+
else if (t.startsWith(">")) [op, rest] = [">", t.slice(1)];
|
|
1413
|
+
else if (t.startsWith("=")) [op, rest] = ["=", t.slice(1)];
|
|
1414
|
+
else return void 0;
|
|
1415
|
+
const r = rest.trim();
|
|
1416
|
+
if (!/^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i.test(r)) return void 0;
|
|
1417
|
+
const value = Number(r);
|
|
1418
|
+
return Number.isFinite(value) ? { op, value } : void 0;
|
|
1419
|
+
}
|
|
1420
|
+
function bracket(raw, content) {
|
|
1421
|
+
const lc = content.trim().toLowerCase();
|
|
1422
|
+
const color = parseColor(lc);
|
|
1423
|
+
if (color) {
|
|
1424
|
+
raw.color = color;
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
if (lc.startsWith("<") || lc.startsWith(">") || lc.startsWith("=")) {
|
|
1428
|
+
const c = parseCond(lc);
|
|
1429
|
+
if (c) raw.cond = c;
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
if (content.startsWith("$")) {
|
|
1433
|
+
const sym = content.slice(1).split("-")[0];
|
|
1434
|
+
if (sym !== "") {
|
|
1435
|
+
raw.currency = true;
|
|
1436
|
+
pushLit(raw.toks, sym);
|
|
1437
|
+
}
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
const first = lc[0];
|
|
1441
|
+
if ((first === "h" || first === "m" || first === "s") && Array.from(lc).every((c) => c === first)) {
|
|
1442
|
+
const n = Math.min(Array.from(lc).length, 255);
|
|
1443
|
+
raw.toks.push({ t: "date", d: { k: first === "h" ? "eh" : first === "m" ? "em" : "es", n } });
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
function tokenize(src) {
|
|
1447
|
+
const cs = Array.from(src);
|
|
1448
|
+
const raw = { toks: [], currency: false };
|
|
1449
|
+
const toks = raw.toks;
|
|
1450
|
+
const lastClose = cs.lastIndexOf("]");
|
|
1451
|
+
let i = 0;
|
|
1452
|
+
while (i < cs.length) {
|
|
1453
|
+
const c = cs[i];
|
|
1454
|
+
if (c === '"') {
|
|
1455
|
+
let s = "";
|
|
1456
|
+
i += 1;
|
|
1457
|
+
while (i < cs.length) {
|
|
1458
|
+
const d = cs[i];
|
|
1459
|
+
i += 1;
|
|
1460
|
+
if (d === '"') break;
|
|
1461
|
+
s += d;
|
|
1462
|
+
}
|
|
1463
|
+
if (Array.from(s).some(isCurrencyChar)) raw.currency = true;
|
|
1464
|
+
pushLit(toks, s);
|
|
1077
1465
|
continue;
|
|
1078
1466
|
}
|
|
1079
|
-
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1467
|
+
if (c === "\\") {
|
|
1468
|
+
const d = cs[i + 1];
|
|
1469
|
+
if (d !== void 0) {
|
|
1470
|
+
if (isCurrencyChar(d)) raw.currency = true;
|
|
1471
|
+
pushLit(toks, d);
|
|
1472
|
+
}
|
|
1473
|
+
i += 2;
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
if (c === "_") {
|
|
1477
|
+
if (cs[i + 1] !== void 0) pushLit(toks, " ");
|
|
1478
|
+
i += 2;
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
if (c === "*") {
|
|
1482
|
+
const d = cs[i + 1];
|
|
1483
|
+
if (d !== void 0) toks.push({ t: "fill", c: d });
|
|
1484
|
+
i += 2;
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
if (c === "[") {
|
|
1488
|
+
const close = lastClose > i ? cs.indexOf("]", i + 1) : -1;
|
|
1489
|
+
if (close !== -1) {
|
|
1490
|
+
bracket(raw, cs.slice(i + 1, close).join(""));
|
|
1491
|
+
i = close + 1;
|
|
1087
1492
|
} else {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
}
|
|
1092
|
-
const percents = (prefix + suffix).split("%").length - 1;
|
|
1093
|
-
value = Math.abs(value) * Math.pow(100, percents);
|
|
1094
|
-
if (!pattern) return `${sign}${prefix}${suffix}`;
|
|
1095
|
-
const expMatch = /^([^Ee]*)[Ee]([+-])(0+)$/.exec(pattern);
|
|
1096
|
-
if (expMatch) {
|
|
1097
|
-
const [, mantissa, expSign, expDigits] = expMatch;
|
|
1098
|
-
const decimals = (_nullishCoalesce(mantissa.split(".")[1], () => ( ""))).length;
|
|
1099
|
-
const [m, e] = value.toExponential(decimals).split("e");
|
|
1100
|
-
const exponent = Number(e);
|
|
1101
|
-
const expText = String(Math.abs(exponent)).padStart(expDigits.length, "0");
|
|
1102
|
-
const shownSign = exponent < 0 ? "-" : expSign === "+" ? "+" : "";
|
|
1103
|
-
return `${sign}${prefix}${m}E${shownSign}${expText}${suffix}`;
|
|
1104
|
-
}
|
|
1105
|
-
const [intPattern, decPattern = ""] = pattern.split(".");
|
|
1106
|
-
const maxDecimals = (_nullishCoalesce(decPattern.match(/[0#?]/g), () => ( []))).length;
|
|
1107
|
-
const minDecimals = (_nullishCoalesce(decPattern.match(/0/g), () => ( []))).length;
|
|
1108
|
-
const minInt = (_nullishCoalesce(intPattern.match(/0/g), () => ( []))).length;
|
|
1109
|
-
const grouping = intPattern.includes(",");
|
|
1110
|
-
const rounded = roundHalfAwayFromZero(value, maxDecimals);
|
|
1111
|
-
let [intPart, decPart = ""] = rounded.toFixed(maxDecimals).split(".");
|
|
1112
|
-
while (decPart.length > minDecimals && decPart.endsWith("0")) decPart = decPart.slice(0, -1);
|
|
1113
|
-
if (intPart === "0" && minInt === 0) intPart = "";
|
|
1114
|
-
intPart = intPart.padStart(minInt, "0");
|
|
1115
|
-
if (grouping) intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
1116
|
-
const showSign = sign && /[1-9]/.test(intPart + decPart) ? sign : "";
|
|
1117
|
-
return `${showSign}${prefix}${intPart}${decPart ? `.${decPart}` : ""}${suffix}`;
|
|
1118
|
-
}
|
|
1119
|
-
function splitSections(format) {
|
|
1120
|
-
const sections = [""];
|
|
1121
|
-
let quoted = false;
|
|
1122
|
-
for (let i = 0; i < format.length; i++) {
|
|
1123
|
-
const char = format[i];
|
|
1124
|
-
if (char === '"') quoted = !quoted;
|
|
1125
|
-
if (char === "\\" && i + 1 < format.length) {
|
|
1126
|
-
sections[sections.length - 1] += char + format[++i];
|
|
1493
|
+
pushLit(toks, "[");
|
|
1494
|
+
i += 1;
|
|
1495
|
+
}
|
|
1127
1496
|
continue;
|
|
1128
1497
|
}
|
|
1129
|
-
if (
|
|
1130
|
-
else
|
|
1498
|
+
if (c === "0" || c === "#" || c === "?") toks.push({ t: "digit", c });
|
|
1499
|
+
else if (c === ".") toks.push({ t: "point" });
|
|
1500
|
+
else if (c === ",") toks.push({ t: "comma" });
|
|
1501
|
+
else if (c === "%") toks.push({ t: "percent" });
|
|
1502
|
+
else if (c === "@") toks.push({ t: "at" });
|
|
1503
|
+
else if (c === "/") {
|
|
1504
|
+
toks.push({ t: "slash" });
|
|
1505
|
+
i += 1;
|
|
1506
|
+
if (cs[i] !== void 0 && cs[i] >= "1" && cs[i] <= "9") {
|
|
1507
|
+
let s = "";
|
|
1508
|
+
while (i < cs.length && cs[i] >= "0" && cs[i] <= "9") {
|
|
1509
|
+
s += cs[i];
|
|
1510
|
+
i += 1;
|
|
1511
|
+
}
|
|
1512
|
+
toks.push({ t: "denom", s });
|
|
1513
|
+
}
|
|
1514
|
+
continue;
|
|
1515
|
+
} else if ((c === "e" || c === "E") && (cs[i + 1] === "+" || cs[i + 1] === "-")) {
|
|
1516
|
+
toks.push({ t: "exp", upper: c === "E", plus: cs[i + 1] === "+" });
|
|
1517
|
+
i += 2;
|
|
1518
|
+
continue;
|
|
1519
|
+
} else if ((c === "G" || c === "g") && startsWithCi(cs, i, "general")) {
|
|
1520
|
+
toks.push({ t: "general" });
|
|
1521
|
+
i += 7;
|
|
1522
|
+
continue;
|
|
1523
|
+
} else if ((c === "A" || c === "a") && startsWithCi(cs, i, "am/pm")) {
|
|
1524
|
+
toks.push({ t: "date", d: { k: "ampm" } });
|
|
1525
|
+
i += 5;
|
|
1526
|
+
continue;
|
|
1527
|
+
} else if ((c === "A" || c === "a") && startsWithCi(cs, i, "a/p")) {
|
|
1528
|
+
toks.push({ t: "date", d: { k: "ap", a: c, p: _nullishCoalesce(cs[i + 2], () => ( "P")) } });
|
|
1529
|
+
i += 3;
|
|
1530
|
+
continue;
|
|
1531
|
+
} else if ("yYmMdDhHsSeE".includes(c)) {
|
|
1532
|
+
const lc = lowerAscii(c);
|
|
1533
|
+
let n = 0;
|
|
1534
|
+
while (cs[i + n] !== void 0 && lowerAscii(cs[i + n]) === lc) n += 1;
|
|
1535
|
+
const n8 = Math.min(n, 255);
|
|
1536
|
+
const d = lc === "y" ? { k: "year", n: n8 } : lc === "e" ? { k: "year", n: 4 } : lc === "m" ? { k: "month", n: n8 } : lc === "d" ? { k: "day", n: n8 } : lc === "h" ? { k: "hour", n: n8 } : { k: "second", n: n8 };
|
|
1537
|
+
toks.push({ t: "date", d });
|
|
1538
|
+
i += Math.max(n, 1);
|
|
1539
|
+
continue;
|
|
1540
|
+
} else {
|
|
1541
|
+
if (isCurrencyChar(c)) raw.currency = true;
|
|
1542
|
+
pushLit(toks, c);
|
|
1543
|
+
}
|
|
1544
|
+
i += 1;
|
|
1131
1545
|
}
|
|
1132
|
-
return
|
|
1546
|
+
return raw;
|
|
1133
1547
|
}
|
|
1548
|
+
var datePartOf = (t) => _optionalChain([t, 'optionalAccess', _14 => _14.t]) === "date" ? t.d : void 0;
|
|
1549
|
+
function analyze(raw) {
|
|
1550
|
+
const toks = raw.toks;
|
|
1551
|
+
const isDate = toks.some((t) => t.t === "date");
|
|
1552
|
+
const hasGeneral = toks.some((t) => t.t === "general");
|
|
1553
|
+
const sec = {
|
|
1554
|
+
toks: [],
|
|
1555
|
+
color: raw.color,
|
|
1556
|
+
cond: raw.cond,
|
|
1557
|
+
kind: "number",
|
|
1558
|
+
thousands: false,
|
|
1559
|
+
scale: 0,
|
|
1560
|
+
percent: 0,
|
|
1561
|
+
hasAt: toks.some((t) => t.t === "at"),
|
|
1562
|
+
hasDigits: false,
|
|
1563
|
+
exp: false,
|
|
1564
|
+
fraction: false,
|
|
1565
|
+
currency: raw.currency,
|
|
1566
|
+
fill: toks.some((t) => t.t === "fill")
|
|
1567
|
+
};
|
|
1568
|
+
if (isDate) {
|
|
1569
|
+
sec.kind = "date";
|
|
1570
|
+
const prevPart = [];
|
|
1571
|
+
let last;
|
|
1572
|
+
for (let i2 = 0; i2 < toks.length; i2++) {
|
|
1573
|
+
prevPart.push(last);
|
|
1574
|
+
const d = datePartOf(toks[i2]);
|
|
1575
|
+
if (d) last = d;
|
|
1576
|
+
}
|
|
1577
|
+
const nextPart = new Array(toks.length);
|
|
1578
|
+
let next;
|
|
1579
|
+
for (let i2 = toks.length - 1; i2 >= 0; i2--) {
|
|
1580
|
+
nextPart[i2] = next;
|
|
1581
|
+
const d = datePartOf(toks[i2]);
|
|
1582
|
+
if (d) next = d;
|
|
1583
|
+
}
|
|
1584
|
+
for (let i2 = 0; i2 < toks.length; i2++) {
|
|
1585
|
+
const t = toks[i2];
|
|
1586
|
+
if (t.t !== "date" || t.d.k !== "month" || t.d.n > 2) continue;
|
|
1587
|
+
const prev = prevPart[i2];
|
|
1588
|
+
const nxt = nextPart[i2];
|
|
1589
|
+
const afterH = _optionalChain([prev, 'optionalAccess', _15 => _15.k]) === "hour" || _optionalChain([prev, 'optionalAccess', _16 => _16.k]) === "eh";
|
|
1590
|
+
const beforeS = _optionalChain([nxt, 'optionalAccess', _17 => _17.k]) === "second" || _optionalChain([nxt, 'optionalAccess', _18 => _18.k]) === "es";
|
|
1591
|
+
if (afterH || beforeS) toks[i2] = { t: "date", d: { k: "minute", n: t.d.n } };
|
|
1592
|
+
}
|
|
1593
|
+
const out2 = [];
|
|
1594
|
+
let i = 0;
|
|
1595
|
+
while (i < toks.length) {
|
|
1596
|
+
const t = toks[i];
|
|
1597
|
+
if (t.t === "point" && isDigitTok(toks[i + 1], "0")) {
|
|
1598
|
+
let n = 0;
|
|
1599
|
+
while (isDigitTok(toks[i + 1 + n], "0")) n += 1;
|
|
1600
|
+
out2.push({ t: "date", d: { k: "subsec", n: Math.min(n, 255) } });
|
|
1601
|
+
i += 1 + n;
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
if (t.t === "date" || t.t === "fill") out2.push(t);
|
|
1605
|
+
else pushLit(out2, literalText(t));
|
|
1606
|
+
i += 1;
|
|
1607
|
+
}
|
|
1608
|
+
sec.toks = out2;
|
|
1609
|
+
return sec;
|
|
1610
|
+
}
|
|
1611
|
+
if (hasGeneral) {
|
|
1612
|
+
sec.kind = "general";
|
|
1613
|
+
const out2 = [];
|
|
1614
|
+
for (const t of toks) {
|
|
1615
|
+
if (t.t === "general" || t.t === "fill") out2.push(t);
|
|
1616
|
+
else pushLit(out2, literalText(t));
|
|
1617
|
+
}
|
|
1618
|
+
sec.toks = out2;
|
|
1619
|
+
return sec;
|
|
1620
|
+
}
|
|
1621
|
+
sec.hasDigits = toks.some((t) => t.t === "digit");
|
|
1622
|
+
if (sec.hasAt && !sec.hasDigits) {
|
|
1623
|
+
sec.kind = "text";
|
|
1624
|
+
const out2 = [];
|
|
1625
|
+
for (const t of toks) {
|
|
1626
|
+
if (t.t === "at" || t.t === "fill") out2.push(t);
|
|
1627
|
+
else pushLit(out2, literalText(t));
|
|
1628
|
+
}
|
|
1629
|
+
sec.toks = out2;
|
|
1630
|
+
return sec;
|
|
1631
|
+
}
|
|
1632
|
+
let seenPoint = false;
|
|
1633
|
+
let seenExp = false;
|
|
1634
|
+
let seenDigit = false;
|
|
1635
|
+
let fractionSlash = false;
|
|
1636
|
+
const digitAfterCommas = new Array(toks.length).fill(false);
|
|
1637
|
+
const firstNonLitAfter = new Array(toks.length);
|
|
1638
|
+
let after = false;
|
|
1639
|
+
let nonLit;
|
|
1640
|
+
for (let j = toks.length - 1; j >= 0; j--) {
|
|
1641
|
+
digitAfterCommas[j] = after;
|
|
1642
|
+
firstNonLitAfter[j] = nonLit;
|
|
1643
|
+
const t = toks[j];
|
|
1644
|
+
if (t.t !== "comma") after = t.t === "digit";
|
|
1645
|
+
if (t.t !== "lit") nonLit = t;
|
|
1646
|
+
}
|
|
1647
|
+
let lastExp = -1;
|
|
1648
|
+
for (let j = toks.length - 1; j >= 0; j--) {
|
|
1649
|
+
if (toks[j].t === "exp") {
|
|
1650
|
+
lastExp = j;
|
|
1651
|
+
break;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
const firstDigit = toks.findIndex((t) => t.t === "digit");
|
|
1655
|
+
const scientific = firstDigit !== -1 && toks.slice(firstDigit).some((t) => t.t === "exp");
|
|
1656
|
+
for (let i = 0; i < toks.length; i++) {
|
|
1657
|
+
const t = toks[i];
|
|
1658
|
+
let replace;
|
|
1659
|
+
switch (t.t) {
|
|
1660
|
+
case "digit":
|
|
1661
|
+
seenDigit = true;
|
|
1662
|
+
break;
|
|
1663
|
+
case "point":
|
|
1664
|
+
if (seenPoint || seenExp) replace = { t: "lit", s: "." };
|
|
1665
|
+
else seenPoint = true;
|
|
1666
|
+
break;
|
|
1667
|
+
case "percent":
|
|
1668
|
+
sec.percent += 1;
|
|
1669
|
+
break;
|
|
1670
|
+
case "exp":
|
|
1671
|
+
if (seenExp || !seenDigit) replace = { t: "lit", s: literalText(t) };
|
|
1672
|
+
else {
|
|
1673
|
+
seenExp = true;
|
|
1674
|
+
sec.exp = true;
|
|
1675
|
+
}
|
|
1676
|
+
break;
|
|
1677
|
+
case "comma": {
|
|
1678
|
+
const nextDigit = digitAfterCommas[i];
|
|
1679
|
+
if (seenDigit && nextDigit && !seenPoint && !seenExp) {
|
|
1680
|
+
sec.thousands = true;
|
|
1681
|
+
replace = { t: "lit", s: "" };
|
|
1682
|
+
} else if (seenDigit && scientific) {
|
|
1683
|
+
replace = { t: "lit", s: "" };
|
|
1684
|
+
} else if (seenDigit && !nextDigit) {
|
|
1685
|
+
sec.scale += 1;
|
|
1686
|
+
replace = { t: "lit", s: "" };
|
|
1687
|
+
} else {
|
|
1688
|
+
replace = { t: "lit", s: "," };
|
|
1689
|
+
}
|
|
1690
|
+
break;
|
|
1691
|
+
}
|
|
1692
|
+
case "slash": {
|
|
1693
|
+
const nextOk = _optionalChain([toks, 'access', _19 => _19[i + 1], 'optionalAccess', _20 => _20.t]) === "denom" || _optionalChain([firstNonLitAfter, 'access', _21 => _21[i], 'optionalAccess', _22 => _22.t]) === "digit";
|
|
1694
|
+
const hasExp = seenExp || lastExp > i;
|
|
1695
|
+
if (!fractionSlash && seenDigit && nextOk && !seenPoint && !hasExp) {
|
|
1696
|
+
fractionSlash = true;
|
|
1697
|
+
sec.fraction = true;
|
|
1698
|
+
} else {
|
|
1699
|
+
replace = { t: "lit", s: "/" };
|
|
1700
|
+
}
|
|
1701
|
+
break;
|
|
1702
|
+
}
|
|
1703
|
+
case "denom":
|
|
1704
|
+
if (!(_optionalChain([toks, 'access', _23 => _23[i - 1], 'optionalAccess', _24 => _24.t]) === "slash" && sec.fraction)) replace = { t: "lit", s: t.s };
|
|
1705
|
+
break;
|
|
1706
|
+
default:
|
|
1707
|
+
break;
|
|
1708
|
+
}
|
|
1709
|
+
if (replace) toks[i] = replace;
|
|
1710
|
+
}
|
|
1711
|
+
if (sec.fraction) {
|
|
1712
|
+
for (let i = 0; i < toks.length; i++) if (toks[i].t === "point") toks[i] = { t: "lit", s: "." };
|
|
1713
|
+
}
|
|
1714
|
+
const out = [];
|
|
1715
|
+
for (const t of toks) {
|
|
1716
|
+
if (t.t === "lit") pushLit(out, t.s);
|
|
1717
|
+
else out.push(t);
|
|
1718
|
+
}
|
|
1719
|
+
sec.toks = out;
|
|
1720
|
+
return sec;
|
|
1721
|
+
}
|
|
1722
|
+
function parseSections(code) {
|
|
1723
|
+
return splitSections(code).map((s) => analyze(tokenize(s)));
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/spreadsheet/numfmt/render.ts
|
|
1727
|
+
var MAX_DATE_SERIAL = 2958465;
|
|
1134
1728
|
var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
1135
|
-
var
|
|
1136
|
-
var
|
|
1137
|
-
function
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1729
|
+
var WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
1730
|
+
var UNIX_1899_12_302 = -25569;
|
|
1731
|
+
function civilFromDays(z0) {
|
|
1732
|
+
const z = z0 + 719468;
|
|
1733
|
+
const era = Math.floor(z / 146097);
|
|
1734
|
+
const doe = z - era * 146097;
|
|
1735
|
+
const yoe = Math.floor((doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365);
|
|
1736
|
+
const doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100));
|
|
1737
|
+
const mp = Math.floor((5 * doy + 2) / 153);
|
|
1738
|
+
const d = doy - Math.floor((153 * mp + 2) / 5) + 1;
|
|
1739
|
+
const m = mp < 10 ? mp + 3 : mp - 9;
|
|
1740
|
+
return { y: yoe + era * 400 + (m <= 2 ? 1 : 0), m, d };
|
|
1741
|
+
}
|
|
1742
|
+
function dateFromSerial(day) {
|
|
1743
|
+
const weekday = ((day + 6) % 7 + 7) % 7;
|
|
1744
|
+
if (day === 60) return { year: 1900, month: 2, day: 29, weekday };
|
|
1745
|
+
if (day === 0) return { year: 1900, month: 1, day: 0, weekday };
|
|
1746
|
+
const real = day < 60 ? day + 1 : day;
|
|
1747
|
+
const { y, m, d } = civilFromDays(real + UNIX_1899_12_302);
|
|
1748
|
+
return { year: y, month: m, day: d, weekday };
|
|
1749
|
+
}
|
|
1750
|
+
var Out = (_class2 = class {constructor() { _class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this); }
|
|
1751
|
+
__init3() {this.text = ""}
|
|
1752
|
+
|
|
1753
|
+
/** True when every digit shown is zero (a number section that rounded to zero). */
|
|
1754
|
+
__init4() {this.zero = false}
|
|
1755
|
+
push(s) {
|
|
1756
|
+
this.text += s;
|
|
1757
|
+
}
|
|
1758
|
+
setFill(c) {
|
|
1759
|
+
if (!this.fill) this.fill = { char: c, index: this.text.length };
|
|
1760
|
+
}
|
|
1761
|
+
prepend(s) {
|
|
1762
|
+
this.text = s + this.text;
|
|
1763
|
+
if (this.fill) this.fill.index += s.length;
|
|
1764
|
+
}
|
|
1765
|
+
}, _class2);
|
|
1766
|
+
function renderText(sec, text) {
|
|
1767
|
+
const out = new Out();
|
|
1768
|
+
for (const t of sec.toks) {
|
|
1769
|
+
if (t.t === "at") out.push(text);
|
|
1770
|
+
else if (t.t === "fill") out.setFill(t.c);
|
|
1771
|
+
else out.push(literalText(t));
|
|
1772
|
+
}
|
|
1773
|
+
return out;
|
|
1774
|
+
}
|
|
1775
|
+
function renderGeneral(sec, v, width) {
|
|
1776
|
+
const out = new Out();
|
|
1777
|
+
for (const t of sec.toks) {
|
|
1778
|
+
if (t.t === "general") out.push(_nullishCoalesce(formatGeneralFit(v, width), () => ( "#".repeat(Math.min(Math.max(width, 1), 11)))));
|
|
1779
|
+
else if (t.t === "fill") out.setFill(t.c);
|
|
1780
|
+
else out.push(literalText(t));
|
|
1781
|
+
}
|
|
1782
|
+
return out;
|
|
1783
|
+
}
|
|
1784
|
+
var padNum = (n, w) => w >= 2 ? String(n).padStart(Math.min(w, 20), "0") : String(n);
|
|
1785
|
+
function renderDate(sec, v) {
|
|
1786
|
+
if (!Number.isFinite(v) || v < 0 || v >= MAX_DATE_SERIAL + 1) return null;
|
|
1787
|
+
let subDigits = 0;
|
|
1788
|
+
for (const t of sec.toks) if (t.t === "date" && t.d.k === "subsec") subDigits = Math.max(subDigits, t.d.n);
|
|
1789
|
+
subDigits = Math.min(subDigits, 3);
|
|
1790
|
+
const unit = 10 ** (3 - subDigits);
|
|
1791
|
+
let day = Math.floor(v);
|
|
1792
|
+
let ms = Math.round((v - Math.floor(v)) * 864e5 / unit) * unit;
|
|
1793
|
+
if (ms >= 864e5) {
|
|
1794
|
+
ms -= 864e5;
|
|
1795
|
+
day += 1;
|
|
1796
|
+
}
|
|
1797
|
+
const totalMs = day * 864e5 + ms;
|
|
1798
|
+
const needsDate = sec.toks.some((t) => t.t === "date" && isCalendarPart(t.d));
|
|
1799
|
+
if (needsDate && day > MAX_DATE_SERIAL) return null;
|
|
1800
|
+
const dt = needsDate ? dateFromSerial(day) : dateFromSerial(0);
|
|
1801
|
+
const ampm = sec.toks.some((t) => t.t === "date" && (t.d.k === "ampm" || t.d.k === "ap"));
|
|
1802
|
+
const hour = Math.floor(ms / 36e5);
|
|
1803
|
+
const minute = Math.floor(ms / 6e4) % 60;
|
|
1804
|
+
const second = Math.floor(ms / 1e3) % 60;
|
|
1805
|
+
const milli = ms % 1e3;
|
|
1806
|
+
const monthName = _nullishCoalesce(MONTHS[dt.month - 1], () => ( ""));
|
|
1807
|
+
const dayName = _nullishCoalesce(WEEKDAYS[dt.weekday], () => ( ""));
|
|
1808
|
+
const out = new Out();
|
|
1809
|
+
for (const t of sec.toks) {
|
|
1810
|
+
if (t.t === "fill") {
|
|
1811
|
+
out.setFill(t.c);
|
|
1142
1812
|
continue;
|
|
1143
1813
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
parts.push({ literal: !isCode, text: match });
|
|
1814
|
+
if (t.t !== "date") {
|
|
1815
|
+
out.push(literalText(t));
|
|
1816
|
+
continue;
|
|
1148
1817
|
}
|
|
1818
|
+
const d = t.d;
|
|
1819
|
+
let s;
|
|
1820
|
+
switch (d.k) {
|
|
1821
|
+
case "year":
|
|
1822
|
+
s = d.n <= 2 ? String((dt.year % 100 + 100) % 100).padStart(2, "0") : String(dt.year).padStart(4, "0");
|
|
1823
|
+
break;
|
|
1824
|
+
case "month":
|
|
1825
|
+
s = d.n <= 2 ? padNum(dt.month, d.n) : d.n === 3 ? monthName.slice(0, 3) : d.n === 5 ? monthName.slice(0, 1) : monthName;
|
|
1826
|
+
break;
|
|
1827
|
+
case "day":
|
|
1828
|
+
s = d.n <= 2 ? padNum(dt.day, d.n) : d.n === 3 ? dayName.slice(0, 3) : dayName;
|
|
1829
|
+
break;
|
|
1830
|
+
case "hour":
|
|
1831
|
+
s = padNum(ampm ? hour % 12 || 12 : hour, d.n);
|
|
1832
|
+
break;
|
|
1833
|
+
case "minute":
|
|
1834
|
+
s = padNum(minute, d.n);
|
|
1835
|
+
break;
|
|
1836
|
+
case "second":
|
|
1837
|
+
s = padNum(second, d.n);
|
|
1838
|
+
break;
|
|
1839
|
+
case "subsec": {
|
|
1840
|
+
s = `.${String(milli).padStart(3, "0")}`.slice(0, 1 + Math.min(d.n, 3));
|
|
1841
|
+
if (d.n > 3) s += "0".repeat(d.n - 3);
|
|
1842
|
+
break;
|
|
1843
|
+
}
|
|
1844
|
+
case "eh":
|
|
1845
|
+
s = padNum(Math.floor(totalMs / 36e5), d.n);
|
|
1846
|
+
break;
|
|
1847
|
+
case "em":
|
|
1848
|
+
s = padNum(Math.floor(totalMs / 6e4), d.n);
|
|
1849
|
+
break;
|
|
1850
|
+
case "es":
|
|
1851
|
+
s = padNum(Math.floor(totalMs / 1e3), d.n);
|
|
1852
|
+
break;
|
|
1853
|
+
case "ampm":
|
|
1854
|
+
s = hour >= 12 ? "PM" : "AM";
|
|
1855
|
+
break;
|
|
1856
|
+
case "ap":
|
|
1857
|
+
s = hour >= 12 ? d.p : d.a;
|
|
1858
|
+
break;
|
|
1859
|
+
}
|
|
1860
|
+
out.push(s);
|
|
1149
1861
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1862
|
+
return out;
|
|
1863
|
+
}
|
|
1864
|
+
var digitChar = (toks, i) => {
|
|
1865
|
+
const t = toks[i];
|
|
1866
|
+
return _optionalChain([t, 'optionalAccess', _25 => _25.t]) === "digit" ? t.c : "0";
|
|
1867
|
+
};
|
|
1868
|
+
function fillInt(toks, idxs, digits, thousands, forceZero, pieces) {
|
|
1869
|
+
let rem = digits.length;
|
|
1870
|
+
let pos = 0;
|
|
1871
|
+
const count = idxs.length;
|
|
1872
|
+
for (let k = 0; k < count; k++) {
|
|
1873
|
+
const ti = idxs[count - 1 - k];
|
|
1874
|
+
const leftmost = k + 1 === count;
|
|
1875
|
+
const ph = digitChar(toks, ti);
|
|
1876
|
+
const rev = [];
|
|
1877
|
+
const pushDigit = (c) => {
|
|
1878
|
+
if (thousands && pos > 0 && pos % 3 === 0) rev.push(",");
|
|
1879
|
+
rev.push(c);
|
|
1880
|
+
pos += 1;
|
|
1881
|
+
};
|
|
1882
|
+
const take = leftmost ? rem : Math.min(rem, 1);
|
|
1883
|
+
if (take === 0) {
|
|
1884
|
+
if (ph === "0") pushDigit("0");
|
|
1885
|
+
else if (forceZero && k === 0) pushDigit("0");
|
|
1886
|
+
else if (ph === "?") {
|
|
1887
|
+
if (thousands && pos > 0 && pos % 3 === 0) rev.push(" ");
|
|
1888
|
+
rev.push(" ");
|
|
1889
|
+
pos += 1;
|
|
1890
|
+
}
|
|
1891
|
+
} else {
|
|
1892
|
+
for (let n = 0; n < take; n++) {
|
|
1893
|
+
rem -= 1;
|
|
1894
|
+
pushDigit(_nullishCoalesce(digits[rem], () => ( "0")));
|
|
1895
|
+
}
|
|
1164
1896
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1897
|
+
pieces[ti] = rev.reverse().join("");
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
function withCommas(digits, thousands) {
|
|
1901
|
+
if (!thousands) return digits;
|
|
1902
|
+
const n = digits.length;
|
|
1903
|
+
let s = "";
|
|
1904
|
+
for (let i = 0; i < n; i++) {
|
|
1905
|
+
if (i > 0 && (n - i) % 3 === 0) s += ",";
|
|
1906
|
+
s += digits[i];
|
|
1907
|
+
}
|
|
1908
|
+
return s;
|
|
1909
|
+
}
|
|
1910
|
+
function fillFrac(toks, idxs, frac, pieces) {
|
|
1911
|
+
let lastSig = -1;
|
|
1912
|
+
for (let i = frac.length - 1; i >= 0; i--) {
|
|
1913
|
+
if (frac[i] !== "0") {
|
|
1914
|
+
lastSig = i;
|
|
1915
|
+
break;
|
|
1169
1916
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1917
|
+
}
|
|
1918
|
+
idxs.forEach((ti, i) => {
|
|
1919
|
+
const ph = digitChar(toks, ti);
|
|
1920
|
+
pieces[ti] = i <= lastSig ? _nullishCoalesce(frac[i], () => ( "0")) : ph === "0" ? "0" : ph === "?" ? " " : "";
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
function digitIndices(toks, start, end) {
|
|
1924
|
+
const out = [];
|
|
1925
|
+
for (let i = start; i < end; i++) if (_optionalChain([toks, 'access', _26 => _26[i], 'optionalAccess', _27 => _27.t]) === "digit") out.push(i);
|
|
1926
|
+
return out;
|
|
1927
|
+
}
|
|
1928
|
+
function renderNumber(sec, v) {
|
|
1929
|
+
let x = Math.abs(v);
|
|
1930
|
+
if (sec.percent > 0 && !sec.exp) x *= 100 ** Math.min(sec.percent, 20);
|
|
1931
|
+
if (sec.scale > 0) x /= 1e3 ** Math.min(sec.scale, 110);
|
|
1932
|
+
if (!Number.isFinite(x)) x = Number.MAX_VALUE;
|
|
1933
|
+
const toks = sec.toks;
|
|
1934
|
+
const n = toks.length;
|
|
1935
|
+
const pieces = new Array(n).fill("");
|
|
1936
|
+
const before = new Array(n + 1).fill("");
|
|
1937
|
+
const lits = new Array(n);
|
|
1938
|
+
let zero = false;
|
|
1939
|
+
if (sec.fraction) {
|
|
1940
|
+
renderFraction(sec, x, pieces, lits);
|
|
1941
|
+
} else {
|
|
1942
|
+
const pointIdx = toks.findIndex((t) => t.t === "point");
|
|
1943
|
+
const expIdx = toks.findIndex((t) => t.t === "exp");
|
|
1944
|
+
const point = pointIdx === -1 ? void 0 : pointIdx;
|
|
1945
|
+
const exp = expIdx === -1 ? void 0 : expIdx;
|
|
1946
|
+
const intEnd = _nullishCoalesce(_nullishCoalesce(point, () => ( exp)), () => ( n));
|
|
1947
|
+
const fracEnd = _nullishCoalesce(exp, () => ( n));
|
|
1948
|
+
const intIdx = digitIndices(toks, 0, intEnd);
|
|
1949
|
+
const fracIdx = point === void 0 ? [] : digitIndices(toks, point + 1, fracEnd);
|
|
1950
|
+
let intS;
|
|
1951
|
+
let fracS;
|
|
1952
|
+
if (exp !== void 0) {
|
|
1953
|
+
const expDigitIdx = digitIndices(toks, exp + 1, n);
|
|
1954
|
+
const step = Math.max(intIdx.length, 1);
|
|
1955
|
+
let e;
|
|
1956
|
+
if (x === 0) {
|
|
1957
|
+
e = 0;
|
|
1958
|
+
intS = "0".repeat(intIdx.length);
|
|
1959
|
+
fracS = "0".repeat(fracIdx.length);
|
|
1960
|
+
} else {
|
|
1961
|
+
const { digits, exp: xe } = decimalDigits(x);
|
|
1962
|
+
e = xe - (xe % step + step) % step;
|
|
1963
|
+
[intS, fracS] = fixedFromDigits(digits, xe - e, fracIdx.length);
|
|
1964
|
+
if (intS.length > step) {
|
|
1965
|
+
e += step;
|
|
1966
|
+
[intS, fracS] = fixedFromDigits(digits, xe - e, fracIdx.length);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
const et = toks[exp];
|
|
1970
|
+
const upper = et.t === "exp" ? et.upper : true;
|
|
1971
|
+
const plus = et.t === "exp" ? et.plus : true;
|
|
1972
|
+
if (intS === "" && /^0*$/.test(fracS)) e = 0;
|
|
1973
|
+
const head = upper ? "E" : "e";
|
|
1974
|
+
const sign = e < 0 ? "-" : plus ? "+" : "";
|
|
1975
|
+
const expDigits = String(Math.abs(e));
|
|
1976
|
+
if (expDigitIdx.length > 0) {
|
|
1977
|
+
pieces[exp] = head;
|
|
1978
|
+
before[expDigitIdx[0]] = sign;
|
|
1979
|
+
fillInt(toks, expDigitIdx, expDigits, false, false, pieces);
|
|
1980
|
+
} else {
|
|
1981
|
+
pieces[exp] = `${head}${sign}${expDigits}`;
|
|
1982
|
+
}
|
|
1983
|
+
} else {
|
|
1984
|
+
[intS, fracS] = fixed(x, fracIdx.length);
|
|
1985
|
+
}
|
|
1986
|
+
zero = /^0*$/.test(intS + fracS);
|
|
1987
|
+
fillInt(toks, intIdx, intS, sec.thousands, false, pieces);
|
|
1988
|
+
const anchor = _nullishCoalesce(point, () => ( exp));
|
|
1989
|
+
if (intIdx.length === 0 && intS !== "" && anchor !== void 0) before[anchor] = withCommas(intS, sec.thousands);
|
|
1990
|
+
fillFrac(toks, fracIdx, fracS, pieces);
|
|
1991
|
+
if (point !== void 0) pieces[point] = ".";
|
|
1992
|
+
}
|
|
1993
|
+
const out = new Out();
|
|
1994
|
+
for (let i = 0; i < n; i++) {
|
|
1995
|
+
out.push(before[i]);
|
|
1996
|
+
const t = toks[i];
|
|
1997
|
+
if (t.t === "lit") out.push(_nullishCoalesce(lits[i], () => ( t.s)));
|
|
1998
|
+
else if (t.t === "percent") out.push("%");
|
|
1999
|
+
else if (t.t === "fill") out.setFill(t.c);
|
|
2000
|
+
else if (t.t === "at" || t.t === "general" || t.t === "date" || t.t === "comma") continue;
|
|
2001
|
+
else out.push(pieces[i]);
|
|
2002
|
+
}
|
|
2003
|
+
out.zero = zero;
|
|
2004
|
+
return out;
|
|
2005
|
+
}
|
|
2006
|
+
function approximate(x, maxDen) {
|
|
2007
|
+
if (Number.isNaN(x) || x <= 0 || !Number.isFinite(x)) return [0, 1];
|
|
2008
|
+
if (x >= 1e15) return [Math.round(x), 1];
|
|
2009
|
+
let [p0, q0, p1, q1] = [0, 1, 1, 0];
|
|
2010
|
+
let r = x;
|
|
2011
|
+
for (let iter = 0; iter < 64; iter++) {
|
|
2012
|
+
const aF = Math.floor(r);
|
|
2013
|
+
if (aF > 1e15) break;
|
|
2014
|
+
const q2 = aF * q1 + q0;
|
|
2015
|
+
if (q2 > maxDen) break;
|
|
2016
|
+
const p2 = aF * p1 + p0;
|
|
2017
|
+
[p0, q0, p1, q1] = [p1, q1, p2, q2];
|
|
2018
|
+
const frac = r - aF;
|
|
2019
|
+
if (frac < 1e-12) break;
|
|
2020
|
+
r = 1 / frac;
|
|
2021
|
+
}
|
|
2022
|
+
if (q1 === 0) return [Math.round(x), 1];
|
|
2023
|
+
const k = Math.floor((maxDen - q0) / q1);
|
|
2024
|
+
const pb = p0 + k * p1;
|
|
2025
|
+
const qb = q0 + k * q1;
|
|
2026
|
+
const errA = Math.abs(p1 / q1 - x);
|
|
2027
|
+
const errB = qb > 0 ? Math.abs(pb / qb - x) : Infinity;
|
|
2028
|
+
return errB < errA ? [pb, qb] : [p1, q1];
|
|
2029
|
+
}
|
|
2030
|
+
function shownWidth(t) {
|
|
2031
|
+
if (t.t === "lit" || t.t === "denom") return Array.from(t.s).length;
|
|
2032
|
+
return t.t === "digit" || t.t === "slash" ? 1 : 0;
|
|
2033
|
+
}
|
|
2034
|
+
function blankRange(toks, start, end, spaces, pieces, lits) {
|
|
2035
|
+
for (let i = start; i < end && i < toks.length; i++) {
|
|
2036
|
+
const t = toks[i];
|
|
2037
|
+
const s = spaces ? " ".repeat(shownWidth(t)) : "";
|
|
2038
|
+
if (t.t === "lit") lits[i] = s;
|
|
2039
|
+
else if (t.t === "digit" || t.t === "slash" || t.t === "denom") pieces[i] = s;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
function renderFraction(sec, x, pieces, lits) {
|
|
2043
|
+
const toks = sec.toks;
|
|
2044
|
+
const isDigit = (i) => _optionalChain([toks, 'access', _28 => _28[i], 'optionalAccess', _29 => _29.t]) === "digit";
|
|
2045
|
+
const slash = toks.findIndex((t) => t.t === "slash");
|
|
2046
|
+
if (slash === -1) return;
|
|
2047
|
+
let numLast = -1;
|
|
2048
|
+
for (let i = slash - 1; i >= 0; i--) {
|
|
2049
|
+
if (isDigit(i)) {
|
|
2050
|
+
numLast = i;
|
|
2051
|
+
break;
|
|
1174
2052
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
2053
|
+
}
|
|
2054
|
+
if (numLast === -1) return;
|
|
2055
|
+
let numStart = numLast;
|
|
2056
|
+
while (numStart > 0 && isDigit(numStart - 1)) numStart -= 1;
|
|
2057
|
+
const wholeIdx = digitIndices(toks, 0, numStart);
|
|
2058
|
+
const numIdx = digitIndices(toks, numStart, numLast + 1);
|
|
2059
|
+
const sepStart = wholeIdx.length > 0 ? wholeIdx[wholeIdx.length - 1] + 1 : numStart;
|
|
2060
|
+
let fixedDen;
|
|
2061
|
+
let denStart;
|
|
2062
|
+
const afterSlash = toks[slash + 1];
|
|
2063
|
+
if (_optionalChain([afterSlash, 'optionalAccess', _30 => _30.t]) === "denom") {
|
|
2064
|
+
const parsed = Number(afterSlash.s);
|
|
2065
|
+
fixedDen = Math.min(Math.max(Number.isFinite(parsed) ? parsed : 1e9, 1), 1e9);
|
|
2066
|
+
denStart = slash + 1;
|
|
2067
|
+
} else {
|
|
2068
|
+
denStart = slash + 1;
|
|
2069
|
+
while (_optionalChain([toks, 'access', _31 => _31[denStart], 'optionalAccess', _32 => _32.t]) === "lit") denStart += 1;
|
|
2070
|
+
}
|
|
2071
|
+
let denEnd = denStart;
|
|
2072
|
+
if (fixedDen !== void 0) denEnd += 1;
|
|
2073
|
+
else while (isDigit(denEnd)) denEnd += 1;
|
|
2074
|
+
const denIdx = digitIndices(toks, denStart, denEnd);
|
|
2075
|
+
const hasQ = (idx) => idx.some((i) => isDigitTok2(toks[i], "?"));
|
|
2076
|
+
const hasWhole = wholeIdx.length > 0;
|
|
2077
|
+
let whole = hasWhole ? Math.floor(x) : 0;
|
|
2078
|
+
const f = hasWhole ? x - Math.floor(x) : x;
|
|
2079
|
+
let num;
|
|
2080
|
+
let den;
|
|
2081
|
+
if (fixedDen !== void 0) {
|
|
2082
|
+
num = Math.round(f * fixedDen);
|
|
2083
|
+
den = fixedDen;
|
|
2084
|
+
} else {
|
|
2085
|
+
const digits = Math.min(Math.max(denIdx.length, 1), 7);
|
|
2086
|
+
[num, den] = approximate(f, 10 ** digits - 1);
|
|
2087
|
+
}
|
|
2088
|
+
if (hasWhole && num >= den && den > 0) {
|
|
2089
|
+
whole += Math.floor(num / den);
|
|
2090
|
+
num %= den;
|
|
2091
|
+
}
|
|
2092
|
+
const wholeS = whole >= 1 ? fixed(whole, 0)[0] : "";
|
|
2093
|
+
const numZeroPh = numIdx.some((i) => isDigitTok2(toks[i], "0"));
|
|
2094
|
+
if (hasWhole && num === 0 && !numZeroPh) {
|
|
2095
|
+
fillInt(toks, wholeIdx, wholeS, sec.thousands, true, pieces);
|
|
2096
|
+
const spaces = hasQ(wholeIdx) || hasQ(numIdx) || hasQ(denIdx);
|
|
2097
|
+
blankRange(toks, sepStart, denEnd, spaces, pieces, lits);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
if (hasWhole) {
|
|
2101
|
+
const unitsHash = isDigitTok2(toks[wholeIdx[wholeIdx.length - 1]], "#");
|
|
2102
|
+
const force = wholeS === "" && num === 0 && !unitsHash;
|
|
2103
|
+
fillInt(toks, wholeIdx, wholeS, sec.thousands, force, pieces);
|
|
2104
|
+
const wholeShown = wholeIdx.some((i) => /[0-9]/.test(_nullishCoalesce(pieces[i], () => ( ""))));
|
|
2105
|
+
if (!wholeShown) {
|
|
2106
|
+
const spaces = hasQ(wholeIdx) || hasQ(numIdx);
|
|
2107
|
+
blankRange(toks, sepStart, numStart, spaces, pieces, lits);
|
|
1177
2108
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
2109
|
+
}
|
|
2110
|
+
fillInt(toks, numIdx, fixed(num, 0)[0] || "0", false, true, pieces);
|
|
2111
|
+
pieces[slash] = "/";
|
|
2112
|
+
const denS = fixed(den, 0)[0] || "0";
|
|
2113
|
+
if (fixedDen !== void 0) {
|
|
2114
|
+
pieces[denStart] = denS;
|
|
2115
|
+
} else {
|
|
2116
|
+
const zeros = denIdx.filter((i) => isDigitTok2(toks[i], "0")).length;
|
|
2117
|
+
const padded = "0".repeat(Math.max(zeros - denS.length, 0)) + denS;
|
|
2118
|
+
const count = denIdx.length;
|
|
2119
|
+
denIdx.forEach((ti, k) => {
|
|
2120
|
+
let s = k + 1 === count ? padded.slice(k) : _nullishCoalesce(padded[k], () => ( ""));
|
|
2121
|
+
if (s === "") s = isDigitTok2(toks[ti], "?") ? " " : isDigitTok2(toks[ti], "0") ? "0" : "";
|
|
2122
|
+
pieces[ti] = s;
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
function isDigitTok2(t, c) {
|
|
2127
|
+
return _optionalChain([t, 'optionalAccess', _33 => _33.t]) === "digit" && t.c === c;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
// src/spreadsheet/numfmt/index.ts
|
|
2131
|
+
var GENERAL_WIDTH = 11;
|
|
2132
|
+
var OVERFLOW = "########";
|
|
2133
|
+
var MAX_NUMBER_FORMAT_CODE_CHARS = 255;
|
|
2134
|
+
function tooLong(code) {
|
|
2135
|
+
return code.length > MAX_NUMBER_FORMAT_CODE_CHARS && Array.from(code).length > MAX_NUMBER_FORMAT_CODE_CHARS;
|
|
2136
|
+
}
|
|
2137
|
+
var SPECIAL_CODES = ["00000", "00000-0000", "[<=9999999]###-####;(###) ###-####", "000-00-0000"];
|
|
2138
|
+
var NumberFormat = class _NumberFormat {
|
|
2139
|
+
|
|
2140
|
+
|
|
2141
|
+
|
|
2142
|
+
/** Sections that apply to numbers (a trailing text section is excluded). */
|
|
2143
|
+
|
|
2144
|
+
/** Index of the section that applies to text, if any. */
|
|
2145
|
+
|
|
2146
|
+
constructor(code, uncapped) {
|
|
2147
|
+
this.code = code;
|
|
2148
|
+
const trimmed = code.trim();
|
|
2149
|
+
if (trimmed === "" || trimmed.toLowerCase() === "general" || trimmed === "checkbox" || !uncapped && tooLong(code)) {
|
|
2150
|
+
this.general = true;
|
|
2151
|
+
this.sections = [];
|
|
2152
|
+
this.numSections = 0;
|
|
2153
|
+
this.textSection = void 0;
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
this.general = false;
|
|
2157
|
+
this.sections = parseSections(code);
|
|
2158
|
+
const len = this.sections.length;
|
|
2159
|
+
const lastIsText = _optionalChain([this, 'access', _34 => _34.sections, 'access', _35 => _35[len - 1], 'optionalAccess', _36 => _36.kind]) === "text";
|
|
2160
|
+
this.textSection = len >= 4 ? 3 : lastIsText ? len - 1 : void 0;
|
|
2161
|
+
this.numSections = len >= 4 ? 3 : lastIsText ? len - 1 : len;
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Parses a format code. Never throws: unknown or malformed parts degrade to
|
|
2165
|
+
* literal text, and a code longer than 255 characters degrades to General.
|
|
2166
|
+
*/
|
|
2167
|
+
static parse(code) {
|
|
2168
|
+
return new _NumberFormat(code, false);
|
|
2169
|
+
}
|
|
2170
|
+
/** `parse` without the 255-character cap (for tests that reach renderer edge cases). */
|
|
2171
|
+
static parseUncapped(code) {
|
|
2172
|
+
return new _NumberFormat(code, true);
|
|
2173
|
+
}
|
|
2174
|
+
/** Like `parse`, but remembers recently parsed codes (bounded), so formatting many values parses a code once. */
|
|
2175
|
+
static parseCached(code) {
|
|
2176
|
+
if (code.length > CACHE_MAX_CODE_LENGTH) return _NumberFormat.parse(code);
|
|
2177
|
+
let fmt = parseCache.get(code);
|
|
2178
|
+
if (!fmt) {
|
|
2179
|
+
fmt = _NumberFormat.parse(code);
|
|
2180
|
+
if (parseCache.size >= CACHE_CAPACITY) parseCache.clear();
|
|
2181
|
+
parseCache.set(code, fmt);
|
|
2182
|
+
}
|
|
2183
|
+
return fmt;
|
|
2184
|
+
}
|
|
2185
|
+
static general() {
|
|
2186
|
+
return _NumberFormat.parse("General");
|
|
2187
|
+
}
|
|
2188
|
+
isGeneral() {
|
|
2189
|
+
return this.general;
|
|
2190
|
+
}
|
|
2191
|
+
numberSections() {
|
|
2192
|
+
return this.sections.slice(0, this.numSections);
|
|
2193
|
+
}
|
|
2194
|
+
/** True when the code contains date or time tokens. */
|
|
2195
|
+
isDate() {
|
|
2196
|
+
return this.numberSections().some((s) => s.kind === "date");
|
|
2197
|
+
}
|
|
2198
|
+
isPercent() {
|
|
2199
|
+
return this.numberSections().some((s) => s.kind === "number" && s.percent > 0);
|
|
2200
|
+
}
|
|
2201
|
+
/** True for a pure text format such as `@`. */
|
|
2202
|
+
isText() {
|
|
2203
|
+
return this.numSections === 0 && this.textSection !== void 0;
|
|
2204
|
+
}
|
|
2205
|
+
/** Best guess at the format category. */
|
|
2206
|
+
kind() {
|
|
2207
|
+
if (this.general) return "general";
|
|
2208
|
+
if (this.isText()) return "text";
|
|
2209
|
+
if (SPECIAL_CODES.includes(this.code)) return "special";
|
|
2210
|
+
const s = this.sections[0];
|
|
2211
|
+
if (!s) return "custom";
|
|
2212
|
+
if (s.kind === "general") return "general";
|
|
2213
|
+
if (s.kind === "text") return "text";
|
|
2214
|
+
if (s.kind === "date") {
|
|
2215
|
+
const calendar = s.toks.some((t) => t.t === "date" && (t.d.k === "year" || t.d.k === "month" || t.d.k === "day"));
|
|
2216
|
+
return calendar ? "date" : "time";
|
|
2217
|
+
}
|
|
2218
|
+
if (s.fraction) return "fraction";
|
|
2219
|
+
if (s.exp) return "scientific";
|
|
2220
|
+
if (s.percent > 0) return "percentage";
|
|
2221
|
+
if (s.fill && s.hasDigits) return "accounting";
|
|
2222
|
+
if (s.currency && s.hasDigits) return "currency";
|
|
2223
|
+
if (s.hasDigits) return "number";
|
|
2224
|
+
return "custom";
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Picks the number section for `v`, and whether the minus sign is dropped
|
|
2228
|
+
* (a negative section shows the absolute value). Null means General.
|
|
2229
|
+
*/
|
|
2230
|
+
select(v) {
|
|
2231
|
+
const secs = this.numberSections();
|
|
2232
|
+
const k = secs.length;
|
|
2233
|
+
const s0 = secs[0];
|
|
2234
|
+
if (!s0) return null;
|
|
2235
|
+
const c0 = s0.cond;
|
|
2236
|
+
const c1 = _optionalChain([secs, 'access', _37 => _37[1], 'optionalAccess', _38 => _38.cond]);
|
|
2237
|
+
if (c0 || c1) {
|
|
2238
|
+
let idx;
|
|
2239
|
+
if (c0 && testCond(c0, v)) idx = 0;
|
|
2240
|
+
else if (c1 && testCond(c1, v)) idx = 1;
|
|
2241
|
+
else if (c0 && c1) idx = 2;
|
|
2242
|
+
else if (!c0) idx = 0;
|
|
2243
|
+
else idx = 1;
|
|
2244
|
+
const sec = secs[idx];
|
|
2245
|
+
return sec ? { sec, dropSign: k >= 2 } : null;
|
|
2246
|
+
}
|
|
2247
|
+
if (v < 0 && k >= 2) return { sec: secs[1], dropSign: true };
|
|
2248
|
+
if (v === 0 && k >= 3) return { sec: secs[2], dropSign: false };
|
|
2249
|
+
return { sec: s0, dropSign: false };
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Formats a number. `overflow` is true when the value can't be shown (a
|
|
2253
|
+
* date out of range, painted as `########`).
|
|
2254
|
+
*/
|
|
2255
|
+
formatNumber(n) {
|
|
2256
|
+
const general = () => ({
|
|
2257
|
+
text: _nullishCoalesce(formatGeneralFit(n, GENERAL_WIDTH), () => ( OVERFLOW)),
|
|
2258
|
+
numeric: true,
|
|
2259
|
+
overflow: false
|
|
2260
|
+
});
|
|
2261
|
+
if (!Number.isFinite(n)) return { text: "#NUM!", numeric: false, overflow: true };
|
|
2262
|
+
if (this.general) return general();
|
|
2263
|
+
const picked = this.select(n);
|
|
2264
|
+
if (!picked) return general();
|
|
2265
|
+
const { sec, dropSign } = picked;
|
|
2266
|
+
const minus = n < 0 && !dropSign;
|
|
2267
|
+
let out;
|
|
2268
|
+
switch (sec.kind) {
|
|
2269
|
+
case "text":
|
|
2270
|
+
out = renderText(sec, _nullishCoalesce(formatGeneralFit(n, GENERAL_WIDTH), () => ( "")));
|
|
2271
|
+
break;
|
|
2272
|
+
case "general":
|
|
2273
|
+
out = renderGeneral(sec, Math.abs(n), minus ? GENERAL_WIDTH - 1 : GENERAL_WIDTH);
|
|
2274
|
+
break;
|
|
2275
|
+
case "date":
|
|
2276
|
+
out = minus ? null : renderDate(sec, Math.abs(n));
|
|
2277
|
+
break;
|
|
2278
|
+
default:
|
|
2279
|
+
out = renderNumber(sec, Math.abs(n));
|
|
1183
2280
|
}
|
|
1184
|
-
if (
|
|
1185
|
-
if (
|
|
1186
|
-
return
|
|
1187
|
-
}
|
|
2281
|
+
if (!out) return withColor({ text: OVERFLOW, numeric: true, overflow: true }, sec.color);
|
|
2282
|
+
if (minus && sec.kind !== "text" && !out.zero) out.prepend("-");
|
|
2283
|
+
return withColor({ text: out.text, numeric: true, overflow: false, ...out.fill ? { fill: out.fill } : {} }, sec.color);
|
|
2284
|
+
}
|
|
2285
|
+
/** Formats text through the text section (`@`), or returns it unchanged. */
|
|
2286
|
+
formatText(text) {
|
|
2287
|
+
const sec = this.textSection === void 0 ? void 0 : this.sections[this.textSection];
|
|
2288
|
+
if (!sec) return { text, numeric: false };
|
|
2289
|
+
const out = renderText(sec, text);
|
|
2290
|
+
return withColor({ text: out.text, numeric: false, ...out.fill ? { fill: out.fill } : {} }, sec.color);
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Formats a cell value: numbers through the number sections, ISO date text
|
|
2294
|
+
* (`2026-03-04`, `2026-03-04 18:00:00`) and `Date`s as serial numbers, other
|
|
2295
|
+
* text through the text section; booleans show TRUE/FALSE and error values
|
|
2296
|
+
* themselves.
|
|
2297
|
+
*/
|
|
2298
|
+
format(value) {
|
|
2299
|
+
if (value === "" || value === null || value === void 0) return { text: "", numeric: false };
|
|
2300
|
+
if (typeof value === "boolean") return { text: value ? "TRUE" : "FALSE", numeric: false };
|
|
2301
|
+
if (typeof value === "number") {
|
|
2302
|
+
const { overflow: _overflow, ...rest } = this.formatNumber(value);
|
|
2303
|
+
return rest;
|
|
2304
|
+
}
|
|
2305
|
+
if (typeof value === "string" && FORMULA_ERRORS.has(value)) return { text: value, numeric: false };
|
|
2306
|
+
const serial = dateValueToSerial(value);
|
|
2307
|
+
if (serial !== null) {
|
|
2308
|
+
const { overflow: _overflow, ...rest } = this.formatNumber(serial);
|
|
2309
|
+
return rest;
|
|
2310
|
+
}
|
|
2311
|
+
return this.formatText(String(value));
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
function withColor(value, color) {
|
|
2315
|
+
if (color) value.color = color;
|
|
2316
|
+
return value;
|
|
2317
|
+
}
|
|
2318
|
+
var CACHE_CAPACITY = 64;
|
|
2319
|
+
var CACHE_MAX_CODE_LENGTH = 256;
|
|
2320
|
+
var parseCache = /* @__PURE__ */ new Map();
|
|
2321
|
+
function clearNumberFormatCache() {
|
|
2322
|
+
parseCache.clear();
|
|
2323
|
+
}
|
|
2324
|
+
var ISO_DATE_TIME = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?)?$/;
|
|
2325
|
+
function dateValueToSerial(value) {
|
|
2326
|
+
if (value instanceof Date) {
|
|
2327
|
+
if (isNaN(value.getTime())) return null;
|
|
2328
|
+
const serial2 = serialFromYmd(value.getFullYear(), value.getMonth() + 1, value.getDate());
|
|
2329
|
+
return serial2 === null ? null : serial2 + timeFraction(value.getHours(), value.getMinutes(), value.getSeconds() + value.getMilliseconds() / 1e3);
|
|
2330
|
+
}
|
|
2331
|
+
if (typeof value !== "string" || value.length < 8 || value.length > 30) return null;
|
|
2332
|
+
const m = ISO_DATE_TIME.exec(value.trim());
|
|
2333
|
+
if (!m) return null;
|
|
2334
|
+
const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
2335
|
+
if (mo < 1 || mo > 12 || d < 1 || d > daysInMonth(y, mo)) return null;
|
|
2336
|
+
const serial = serialFromYmd(y, mo, d);
|
|
2337
|
+
if (serial === null) return null;
|
|
2338
|
+
if (m[4] === void 0) return serial;
|
|
2339
|
+
const [hh, mm, ss] = [Number(m[4]), Number(m[5]), Number(_nullishCoalesce(m[6], () => ( 0)))];
|
|
2340
|
+
if (hh > 23 || mm > 59 || ss >= 60) return null;
|
|
2341
|
+
return serial + timeFraction(hh, mm, ss);
|
|
2342
|
+
}
|
|
2343
|
+
function formatNumberFormatValue(value, code) {
|
|
2344
|
+
return NumberFormat.parseCached(code).format(value);
|
|
2345
|
+
}
|
|
2346
|
+
function formatTextFunction(value, code) {
|
|
2347
|
+
if (typeof value === "string" && FORMULA_ERRORS.has(value)) return { error: value };
|
|
2348
|
+
if (code === "") return { text: "" };
|
|
2349
|
+
if (tooLong(code)) return { error: "#VALUE!" };
|
|
2350
|
+
const fmt = NumberFormat.parseCached(code);
|
|
2351
|
+
let n;
|
|
2352
|
+
if (value === "" || value === null || value === void 0) n = 0;
|
|
2353
|
+
else if (typeof value === "number") n = value;
|
|
2354
|
+
else if (typeof value === "boolean") return { text: fmt.formatText(value ? "TRUE" : "FALSE").text };
|
|
2355
|
+
else {
|
|
2356
|
+
const serial = dateValueToSerial(value);
|
|
2357
|
+
const parsed = _nullishCoalesce(serial, () => ( (typeof value === "string" ? parseNumberText(value) : null)));
|
|
2358
|
+
if (parsed === null) return { text: fmt.formatText(String(value)).text };
|
|
2359
|
+
n = parsed;
|
|
2360
|
+
}
|
|
2361
|
+
if (!Number.isFinite(n)) return { error: "#NUM!" };
|
|
2362
|
+
const f = fmt.formatNumber(n);
|
|
2363
|
+
return f.overflow ? { error: "#VALUE!" } : { text: f.text };
|
|
2364
|
+
}
|
|
2365
|
+
var INDEXED_COLORS = [
|
|
2366
|
+
"#000000",
|
|
2367
|
+
"#FFFFFF",
|
|
2368
|
+
"#FF0000",
|
|
2369
|
+
"#00FF00",
|
|
2370
|
+
"#0000FF",
|
|
2371
|
+
"#FFFF00",
|
|
2372
|
+
"#FF00FF",
|
|
2373
|
+
"#00FFFF",
|
|
2374
|
+
"#800000",
|
|
2375
|
+
"#008000",
|
|
2376
|
+
"#000080",
|
|
2377
|
+
"#808000",
|
|
2378
|
+
"#800080",
|
|
2379
|
+
"#008080",
|
|
2380
|
+
"#C0C0C0",
|
|
2381
|
+
"#808080",
|
|
2382
|
+
"#9999FF",
|
|
2383
|
+
"#993366",
|
|
2384
|
+
"#FFFFCC",
|
|
2385
|
+
"#CCFFFF",
|
|
2386
|
+
"#660066",
|
|
2387
|
+
"#FF8080",
|
|
2388
|
+
"#0066CC",
|
|
2389
|
+
"#CCCCFF",
|
|
2390
|
+
"#000080",
|
|
2391
|
+
"#FF00FF",
|
|
2392
|
+
"#FFFF00",
|
|
2393
|
+
"#00FFFF",
|
|
2394
|
+
"#800080",
|
|
2395
|
+
"#800000",
|
|
2396
|
+
"#008080",
|
|
2397
|
+
"#0000FF",
|
|
2398
|
+
"#00CCFF",
|
|
2399
|
+
"#CCFFFF",
|
|
2400
|
+
"#CCFFCC",
|
|
2401
|
+
"#FFFF99",
|
|
2402
|
+
"#99CCFF",
|
|
2403
|
+
"#FF99CC",
|
|
2404
|
+
"#CC99FF",
|
|
2405
|
+
"#FFCC99",
|
|
2406
|
+
"#3366FF",
|
|
2407
|
+
"#33CCCC",
|
|
2408
|
+
"#99CC00",
|
|
2409
|
+
"#FFCC00",
|
|
2410
|
+
"#FF9900",
|
|
2411
|
+
"#FF6600",
|
|
2412
|
+
"#666699",
|
|
2413
|
+
"#969696",
|
|
2414
|
+
"#003366",
|
|
2415
|
+
"#339966",
|
|
2416
|
+
"#003300",
|
|
2417
|
+
"#333300",
|
|
2418
|
+
"#993300",
|
|
2419
|
+
"#993366",
|
|
2420
|
+
"#333399",
|
|
2421
|
+
"#333333"
|
|
2422
|
+
];
|
|
2423
|
+
var NAMED_CSS = {
|
|
2424
|
+
black: "#000000",
|
|
2425
|
+
blue: "#0000FF",
|
|
2426
|
+
cyan: "#00FFFF",
|
|
2427
|
+
green: "#00FF00",
|
|
2428
|
+
magenta: "#FF00FF",
|
|
2429
|
+
red: "#FF0000",
|
|
2430
|
+
white: "#FFFFFF",
|
|
2431
|
+
yellow: "#FFFF00"
|
|
2432
|
+
};
|
|
2433
|
+
function numberFormatColorToCss(color) {
|
|
2434
|
+
const named = NAMED_CSS[color];
|
|
2435
|
+
if (named) return named;
|
|
2436
|
+
return _nullishCoalesce(INDEXED_COLORS[Number(color.slice(5)) - 1], () => ( "#000000"));
|
|
1188
2437
|
}
|
|
1189
2438
|
|
|
1190
2439
|
// src/spreadsheet/formula/functions/helpers.ts
|
|
@@ -2197,15 +3446,19 @@ var TEXT_FUNCTIONS = [
|
|
|
2197
3446
|
{
|
|
2198
3447
|
name: "TEXT",
|
|
2199
3448
|
category: "text",
|
|
2200
|
-
description: 'Formats a number or date as text using
|
|
3449
|
+
description: 'Formats a number or date as text using an Excel format code such as "0.00", "#,##0", "0%", "# ?/?" or "yyyy-mm-dd".',
|
|
2201
3450
|
args: [arg("value", "The number or date to format."), arg("format_text", "The format code.", "text")],
|
|
2202
3451
|
evaluate: (ctx) => {
|
|
2203
3452
|
const value = ctx.scalar(0);
|
|
2204
3453
|
if (isFormulaError(value)) return value;
|
|
2205
3454
|
const format = ctx.text(1);
|
|
2206
3455
|
if (failed(format)) return format;
|
|
2207
|
-
const result =
|
|
2208
|
-
|
|
3456
|
+
const result = formatTextFunction(value, format);
|
|
3457
|
+
if ("text" in result) return result.text;
|
|
3458
|
+
if (result.error === "#VALUE!") {
|
|
3459
|
+
return Array.from(format).length > 255 ? ctx.argError(1, "#VALUE!", "is longer than 255 characters.") : ctx.argError(0, "#VALUE!", `is outside the date range, so it can't use the format "${format}".`);
|
|
3460
|
+
}
|
|
3461
|
+
return result.error;
|
|
2209
3462
|
}
|
|
2210
3463
|
}
|
|
2211
3464
|
];
|
|
@@ -2270,15 +3523,15 @@ function tableLookup(ctx, vertical) {
|
|
|
2270
3523
|
if (failed(table)) return table;
|
|
2271
3524
|
const index = ctx.integer(2);
|
|
2272
3525
|
if (failed(index)) return index;
|
|
2273
|
-
const
|
|
2274
|
-
if (failed(
|
|
3526
|
+
const approximate2 = ctx.boolean(3, true);
|
|
3527
|
+
if (failed(approximate2)) return approximate2;
|
|
2275
3528
|
const span = vertical ? table.cols : table.rows;
|
|
2276
3529
|
if (index < 1) return ctx.argError(2, "#VALUE!", "must be 1 or more.");
|
|
2277
3530
|
if (index > span) {
|
|
2278
3531
|
return ctx.argError(2, "#REF!", `is larger than the number of ${vertical ? "columns" : "rows"} in the table (${span}).`);
|
|
2279
3532
|
}
|
|
2280
3533
|
const keys = vertical ? sliceRange(table, 0, table.rows, 0, 1) : sliceRange(table, 0, 1, 0, table.cols);
|
|
2281
|
-
const position = lookupIndex(keys, target,
|
|
3534
|
+
const position = lookupIndex(keys, target, approximate2 ? "sorted-asc" : "wildcard");
|
|
2282
3535
|
if (position === -1) return notFound(ctx, target);
|
|
2283
3536
|
return vertical ? rangeGet(table, position, index - 1) : rangeGet(table, index - 1, position);
|
|
2284
3537
|
}
|
|
@@ -2984,6 +4237,88 @@ var ELEMENTWISE_BUILTINS = {
|
|
|
2984
4237
|
IFNA: [0]
|
|
2985
4238
|
};
|
|
2986
4239
|
|
|
4240
|
+
// src/spreadsheet/formula/functions/lambda.ts
|
|
4241
|
+
var special = (name) => (ctx) => ctx.error("#ERROR!", `${name} must be evaluated by the formula evaluator.`);
|
|
4242
|
+
var LAMBDA_ARG = arg("lambda", "A LAMBDA function, e.g. LAMBDA(x, x*2), or a function name such as SUM.");
|
|
4243
|
+
var LAMBDA_FUNCTIONS = [
|
|
4244
|
+
{
|
|
4245
|
+
name: "LAMBDA",
|
|
4246
|
+
category: "logical",
|
|
4247
|
+
description: "Creates a reusable function: LAMBDA(x, y, x+y). Call it directly, LAMBDA(x, x+1)(2), store it in LET or a defined name, or pass it to MAP, REDUCE, SCAN, BYROW, BYCOL or MAKEARRAY.",
|
|
4248
|
+
args: [
|
|
4249
|
+
repeating(
|
|
4250
|
+
"parameter_or_calculation",
|
|
4251
|
+
"Parameter names, then the calculation that uses them (always the last argument)."
|
|
4252
|
+
)
|
|
4253
|
+
],
|
|
4254
|
+
evaluate: special("LAMBDA")
|
|
4255
|
+
},
|
|
4256
|
+
{
|
|
4257
|
+
name: "ISOMITTED",
|
|
4258
|
+
category: "logical",
|
|
4259
|
+
description: "Checks whether a LAMBDA parameter was left out of the call, e.g. LAMBDA(x, y, IF(ISOMITTED(y), x, x+y))(1,).",
|
|
4260
|
+
args: [arg("argument", "A LAMBDA parameter name.")],
|
|
4261
|
+
evaluate: special("ISOMITTED")
|
|
4262
|
+
},
|
|
4263
|
+
{
|
|
4264
|
+
name: "MAP",
|
|
4265
|
+
category: "logical",
|
|
4266
|
+
description: "Applies a LAMBDA to each value of one or more arrays: MAP(A1:A3, LAMBDA(v, v*2)).",
|
|
4267
|
+
args: [
|
|
4268
|
+
arg("array1", "An array to map."),
|
|
4269
|
+
repeating("array_or_lambda", "More arrays, then the LAMBDA taking one parameter per array.")
|
|
4270
|
+
],
|
|
4271
|
+
evaluate: special("MAP")
|
|
4272
|
+
},
|
|
4273
|
+
{
|
|
4274
|
+
name: "REDUCE",
|
|
4275
|
+
category: "logical",
|
|
4276
|
+
description: "Reduces an array to one value by applying LAMBDA(accumulator, value) to each value, row by row.",
|
|
4277
|
+
args: [
|
|
4278
|
+
arg("initial_value", "The starting accumulator (may be left empty)."),
|
|
4279
|
+
arg("array", "The array to reduce."),
|
|
4280
|
+
LAMBDA_ARG
|
|
4281
|
+
],
|
|
4282
|
+
evaluate: special("REDUCE")
|
|
4283
|
+
},
|
|
4284
|
+
{
|
|
4285
|
+
name: "SCAN",
|
|
4286
|
+
category: "logical",
|
|
4287
|
+
description: "Like REDUCE, but returns every intermediate accumulator value (running totals).",
|
|
4288
|
+
args: [
|
|
4289
|
+
arg("initial_value", "The starting accumulator (may be left empty)."),
|
|
4290
|
+
arg("array", "The array to scan."),
|
|
4291
|
+
LAMBDA_ARG
|
|
4292
|
+
],
|
|
4293
|
+
evaluate: special("SCAN")
|
|
4294
|
+
},
|
|
4295
|
+
{
|
|
4296
|
+
name: "BYROW",
|
|
4297
|
+
category: "logical",
|
|
4298
|
+
description: "Applies a LAMBDA to each row of an array and returns one value per row: BYROW(A1:C3, LAMBDA(r, SUM(r))).",
|
|
4299
|
+
args: [arg("array", "The array whose rows are passed to the LAMBDA."), LAMBDA_ARG],
|
|
4300
|
+
evaluate: special("BYROW")
|
|
4301
|
+
},
|
|
4302
|
+
{
|
|
4303
|
+
name: "BYCOL",
|
|
4304
|
+
category: "logical",
|
|
4305
|
+
description: "Applies a LAMBDA to each column of an array and returns one value per column.",
|
|
4306
|
+
args: [arg("array", "The array whose columns are passed to the LAMBDA."), LAMBDA_ARG],
|
|
4307
|
+
evaluate: special("BYCOL")
|
|
4308
|
+
},
|
|
4309
|
+
{
|
|
4310
|
+
name: "MAKEARRAY",
|
|
4311
|
+
category: "logical",
|
|
4312
|
+
description: "Builds an array of the given size from LAMBDA(row, column): MAKEARRAY(3, 3, LAMBDA(r, c, r*c)).",
|
|
4313
|
+
args: [
|
|
4314
|
+
arg("rows", "Number of rows.", "number"),
|
|
4315
|
+
arg("columns", "Number of columns.", "number"),
|
|
4316
|
+
LAMBDA_ARG
|
|
4317
|
+
],
|
|
4318
|
+
evaluate: special("MAKEARRAY")
|
|
4319
|
+
}
|
|
4320
|
+
];
|
|
4321
|
+
|
|
2987
4322
|
// src/spreadsheet/formula/functions/index.ts
|
|
2988
4323
|
var BUILTIN_FORMULA_FUNCTIONS = [
|
|
2989
4324
|
...MATH_FUNCTIONS,
|
|
@@ -2994,7 +4329,8 @@ var BUILTIN_FORMULA_FUNCTIONS = [
|
|
|
2994
4329
|
...LOOKUP_FUNCTIONS,
|
|
2995
4330
|
...DATE_FUNCTIONS,
|
|
2996
4331
|
...INFORMATION_FUNCTIONS,
|
|
2997
|
-
...ARRAY_FUNCTIONS
|
|
4332
|
+
...ARRAY_FUNCTIONS,
|
|
4333
|
+
...LAMBDA_FUNCTIONS
|
|
2998
4334
|
].map((def) => {
|
|
2999
4335
|
const elementwise = ELEMENTWISE_BUILTINS[def.name];
|
|
3000
4336
|
return elementwise && def.elementwise === void 0 ? { ...def, elementwise } : def;
|
|
@@ -3044,17 +4380,17 @@ function layoutOf(def) {
|
|
|
3044
4380
|
return { fixed: def.args.slice(0, groupStart), group: def.args.slice(groupStart), groupStart };
|
|
3045
4381
|
}
|
|
3046
4382
|
function getFormulaArgumentSpecIndex(def, argIndex) {
|
|
3047
|
-
const { fixed, group, groupStart } = layoutOf(def);
|
|
3048
|
-
if (argIndex <
|
|
4383
|
+
const { fixed: fixed2, group, groupStart } = layoutOf(def);
|
|
4384
|
+
if (argIndex < fixed2.length) return argIndex;
|
|
3049
4385
|
if (group.length === 0) return -1;
|
|
3050
4386
|
return groupStart + (argIndex - groupStart) % group.length;
|
|
3051
4387
|
}
|
|
3052
4388
|
function checkFormulaArgumentCount(def, count) {
|
|
3053
|
-
const { fixed, group } = layoutOf(def);
|
|
3054
|
-
const requiredFixed =
|
|
4389
|
+
const { fixed: fixed2, group } = layoutOf(def);
|
|
4390
|
+
const requiredFixed = fixed2.filter((arg2) => !arg2.optional).length;
|
|
3055
4391
|
const groupRequired = group.length > 0 && !group[0].optional;
|
|
3056
4392
|
const min2 = requiredFixed + (groupRequired ? group.length : 0);
|
|
3057
|
-
const max2 = group.length > 0 ? Infinity :
|
|
4393
|
+
const max2 = group.length > 0 ? Infinity : fixed2.length;
|
|
3058
4394
|
const given = `${count} ${count === 1 ? "was" : "were"} given`;
|
|
3059
4395
|
const plural = (n) => `${n} argument${n === 1 ? "" : "s"}`;
|
|
3060
4396
|
if (count < min2) {
|
|
@@ -3065,8 +4401,8 @@ function checkFormulaArgumentCount(def, count) {
|
|
|
3065
4401
|
const expected = min2 === max2 ? plural(max2) : `at most ${plural(max2)}`;
|
|
3066
4402
|
return max2 === 0 ? `${def.name} takes no arguments, but ${given}.` : `${def.name} takes ${expected}, but ${given}.`;
|
|
3067
4403
|
}
|
|
3068
|
-
if (group.length > 1 && count >
|
|
3069
|
-
const extra = (count -
|
|
4404
|
+
if (group.length > 1 && count > fixed2.length) {
|
|
4405
|
+
const extra = (count - fixed2.length) % group.length;
|
|
3070
4406
|
if (extra !== 0 && !(def.allowTrailingArgument && extra === 1)) {
|
|
3071
4407
|
const names = group.map((arg2) => arg2.name).join(", ");
|
|
3072
4408
|
return `${def.name} expects its repeating arguments (${names}) in complete groups.`;
|
|
@@ -3077,7 +4413,7 @@ function checkFormulaArgumentCount(def, count) {
|
|
|
3077
4413
|
function getFormulaSignatureParts(nameOrDef, activeArgIndex) {
|
|
3078
4414
|
const def = typeof nameOrDef === "string" ? getFormulaFunction(nameOrDef) : nameOrDef;
|
|
3079
4415
|
if (!def) return [];
|
|
3080
|
-
const { fixed, group, groupStart } = layoutOf(def);
|
|
4416
|
+
const { fixed: fixed2, group, groupStart } = layoutOf(def);
|
|
3081
4417
|
let activeSpec = -1;
|
|
3082
4418
|
let activeRepeat = 0;
|
|
3083
4419
|
if (activeArgIndex !== void 0 && activeArgIndex !== null && activeArgIndex >= 0) {
|
|
@@ -3091,7 +4427,7 @@ function getFormulaSignatureParts(nameOrDef, activeArgIndex) {
|
|
|
3091
4427
|
if (parts.length > 1) parts.push({ text: ", ", specIndex: null, active: false });
|
|
3092
4428
|
parts.push({ text, specIndex, active: specIndex === activeSpec && repeat === activeRepeat });
|
|
3093
4429
|
};
|
|
3094
|
-
|
|
4430
|
+
fixed2.forEach((arg2, i) => pushArg(arg2.optional ? `[${arg2.name}]` : arg2.name, i));
|
|
3095
4431
|
if (group.length > 0) {
|
|
3096
4432
|
const groupOptional = !!group[0].optional;
|
|
3097
4433
|
group.forEach((arg2, i) => {
|
|
@@ -3151,12 +4487,31 @@ function buildArray(rows, cols, fn) {
|
|
|
3151
4487
|
}
|
|
3152
4488
|
var isMultiArray = (value) => Array.isArray(value) && value.length > 1;
|
|
3153
4489
|
var firstValue = (value) => Array.isArray(value) ? value.length ? value[0] : "" : value;
|
|
3154
|
-
var
|
|
4490
|
+
var sheetLabel = (node) => node.sheet === void 0 ? "" : `${node.sheetEnd === void 0 ? node.sheet : `${node.sheet}:${node.sheetEnd}`}!`;
|
|
4491
|
+
var SPECIAL_FORMS = /* @__PURE__ */ new Set(["LET", "LAMBDA", "ISOMITTED", "MAP", "REDUCE", "SCAN", "BYROW", "BYCOL", "MAKEARRAY"]);
|
|
4492
|
+
function elementValue(value) {
|
|
4493
|
+
if (isFormulaLambda(value)) return "#CALC!";
|
|
4494
|
+
if (Array.isArray(value)) return value.length === 1 ? elementValue(value[0]) : "#CALC!";
|
|
4495
|
+
return value;
|
|
4496
|
+
}
|
|
4497
|
+
function asRange(value) {
|
|
4498
|
+
if (Array.isArray(value)) return toFormulaRange(value);
|
|
4499
|
+
return { rows: 1, cols: 1, values: [value] };
|
|
4500
|
+
}
|
|
4501
|
+
var LITERAL_BLANK = { type: "Literal", value: "" };
|
|
4502
|
+
var FormulaEvaluator = (_class3 = class {
|
|
3155
4503
|
|
|
3156
|
-
|
|
3157
|
-
// LET variables
|
|
3158
|
-
|
|
3159
|
-
|
|
4504
|
+
__init5() {this.diagnostics = []}
|
|
4505
|
+
// LET and LAMBDA variables (innermost first, chained to outer scopes).
|
|
4506
|
+
__init6() {this.env = null}
|
|
4507
|
+
// Sheet that unqualified references refer to while evaluating a
|
|
4508
|
+
// sheet-scoped defined name (undefined: the formula's own sheet).
|
|
4509
|
+
__init7() {this.defaultSheet = void 0}
|
|
4510
|
+
__init8() {this.lambdaDepth = 0}
|
|
4511
|
+
// Defined names being evaluated, to stop names that refer to themselves.
|
|
4512
|
+
__init9() {this.nameStack = []}
|
|
4513
|
+
__init10() {this.parsedNames = /* @__PURE__ */ new Map()}
|
|
4514
|
+
constructor(context = {}) {;_class3.prototype.__init5.call(this);_class3.prototype.__init6.call(this);_class3.prototype.__init7.call(this);_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);
|
|
3160
4515
|
this.context = context;
|
|
3161
4516
|
}
|
|
3162
4517
|
/**
|
|
@@ -3165,7 +4520,14 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3165
4520
|
*/
|
|
3166
4521
|
evaluate(astOrFormula) {
|
|
3167
4522
|
const ast = typeof astOrFormula === "string" ? parseFormula(astOrFormula) : astOrFormula;
|
|
3168
|
-
|
|
4523
|
+
const value = this.evaluateNode(ast);
|
|
4524
|
+
if (isFormulaLambda(value)) {
|
|
4525
|
+
return this.fail(
|
|
4526
|
+
"#CALC!",
|
|
4527
|
+
"The formula returns a LAMBDA function without calling it. Add its arguments in parentheses, e.g. LAMBDA(x, x+1)(2)."
|
|
4528
|
+
);
|
|
4529
|
+
}
|
|
4530
|
+
return value;
|
|
3169
4531
|
}
|
|
3170
4532
|
/**
|
|
3171
4533
|
* Evaluates a formula and explains errors. Never throws: malformed formulas
|
|
@@ -3175,9 +4537,15 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3175
4537
|
*/
|
|
3176
4538
|
evaluateWithDiagnostics(astOrFormula) {
|
|
3177
4539
|
const saved = this.diagnostics;
|
|
3178
|
-
const
|
|
4540
|
+
const savedEnv = this.env;
|
|
4541
|
+
const savedSheet = this.defaultSheet;
|
|
4542
|
+
const savedDepth = this.lambdaDepth;
|
|
4543
|
+
const savedNames = this.nameStack;
|
|
3179
4544
|
this.diagnostics = [];
|
|
3180
|
-
this.
|
|
4545
|
+
this.env = null;
|
|
4546
|
+
this.defaultSheet = void 0;
|
|
4547
|
+
this.lambdaDepth = 0;
|
|
4548
|
+
this.nameStack = [];
|
|
3181
4549
|
try {
|
|
3182
4550
|
let value = this.evaluate(astOrFormula);
|
|
3183
4551
|
let array;
|
|
@@ -3191,17 +4559,23 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3191
4559
|
const result = {
|
|
3192
4560
|
value,
|
|
3193
4561
|
error: code,
|
|
3194
|
-
message: _nullishCoalesce(_optionalChain([diagnostic, 'optionalAccess',
|
|
4562
|
+
message: _nullishCoalesce(_optionalChain([diagnostic, 'optionalAccess', _39 => _39.message]), () => ( describeFormulaError(code)))
|
|
3195
4563
|
};
|
|
3196
4564
|
if (array) result.array = array;
|
|
3197
4565
|
return result;
|
|
3198
4566
|
}
|
|
3199
4567
|
return array ? { value, array } : { value };
|
|
3200
4568
|
} catch (err) {
|
|
3201
|
-
|
|
4569
|
+
if (err instanceof RangeError) {
|
|
4570
|
+
return { value: "#NUM!", error: "#NUM!", message: "The calculation is nested too deeply (recursion limit)." };
|
|
4571
|
+
}
|
|
4572
|
+
return { value: "#ERROR!", error: "#ERROR!", message: _optionalChain([err, 'optionalAccess', _40 => _40.message]) || "Formula error" };
|
|
3202
4573
|
} finally {
|
|
3203
4574
|
this.diagnostics = saved;
|
|
3204
|
-
this.
|
|
4575
|
+
this.env = savedEnv;
|
|
4576
|
+
this.defaultSheet = savedSheet;
|
|
4577
|
+
this.lambdaDepth = savedDepth;
|
|
4578
|
+
this.nameStack = savedNames;
|
|
3205
4579
|
}
|
|
3206
4580
|
}
|
|
3207
4581
|
/** Records why an error happened and returns the error code. */
|
|
@@ -3219,11 +4593,16 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3219
4593
|
if (!this.context.resolveCell) {
|
|
3220
4594
|
throw new Error(`Cannot resolve cell reference ${node.ref}: no cell resolver provided`);
|
|
3221
4595
|
}
|
|
3222
|
-
|
|
4596
|
+
if (node.sheetEnd !== void 0) return this.evaluate3d(node);
|
|
4597
|
+
const value = this.context.resolveCell(node.col, node.row, _nullishCoalesce(node.sheet, () => ( this.defaultSheet)));
|
|
3223
4598
|
if (isFormulaError(value)) {
|
|
4599
|
+
const label = `${sheetLabel(node)}${node.ref}`;
|
|
4600
|
+
if (value === "#REF!" && node.sheet !== void 0) {
|
|
4601
|
+
return this.fail("#REF!", `${label} refers to a sheet that doesn't exist.`);
|
|
4602
|
+
}
|
|
3224
4603
|
this.fail(
|
|
3225
4604
|
value,
|
|
3226
|
-
value === "#CYCLE!" ? `Cell ${
|
|
4605
|
+
value === "#CYCLE!" ? `Cell ${label} is part of a circular reference.` : `Cell ${label} contains an error (${value}).`
|
|
3227
4606
|
);
|
|
3228
4607
|
}
|
|
3229
4608
|
return value;
|
|
@@ -3232,11 +4611,15 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3232
4611
|
return this.evaluateRange(node);
|
|
3233
4612
|
case "SpillRef":
|
|
3234
4613
|
return this.evaluateSpillRef(node);
|
|
3235
|
-
case "Name":
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
4614
|
+
case "Name":
|
|
4615
|
+
return this.evaluateName(node);
|
|
4616
|
+
case "Missing":
|
|
4617
|
+
return "";
|
|
4618
|
+
case "Invoke": {
|
|
4619
|
+
const callee = this.evaluateNode(node.callee);
|
|
4620
|
+
if (isFormulaLambda(callee)) return this.invokeLambda(callee, node.args);
|
|
4621
|
+
if (isFormulaError(callee)) return callee;
|
|
4622
|
+
return this.fail("#VALUE!", "Only a LAMBDA function can be called with arguments in parentheses.");
|
|
3240
4623
|
}
|
|
3241
4624
|
case "PropertyRef": {
|
|
3242
4625
|
if (!this.context.resolveProperty) {
|
|
@@ -3258,6 +4641,9 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3258
4641
|
if (isFormulaError(left)) return left;
|
|
3259
4642
|
const right = this.evaluateNode(node.right);
|
|
3260
4643
|
if (isFormulaError(right)) return right;
|
|
4644
|
+
if (isFormulaLambda(left) || isFormulaLambda(right)) {
|
|
4645
|
+
return this.fail("#VALUE!", `The ${node.op} operator can't use a LAMBDA function; call it with arguments first.`);
|
|
4646
|
+
}
|
|
3261
4647
|
if (isMultiArray(left) || isMultiArray(right)) {
|
|
3262
4648
|
const a = shapeOf(left);
|
|
3263
4649
|
const b = shapeOf(right);
|
|
@@ -3285,6 +4671,7 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3285
4671
|
}
|
|
3286
4672
|
}
|
|
3287
4673
|
unary(op, raw) {
|
|
4674
|
+
if (isFormulaLambda(raw)) return this.fail("#VALUE!", `The ${op} operator can't use a LAMBDA function.`);
|
|
3288
4675
|
const val = toNumber(raw);
|
|
3289
4676
|
if (isFormulaError(val)) {
|
|
3290
4677
|
return isFormulaError(raw) ? raw : this.fail("#VALUE!", `The ${op} operator can't use text (${quote(raw)}).`);
|
|
@@ -3379,7 +4766,7 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3379
4766
|
const value = this.evaluateNode(argument);
|
|
3380
4767
|
if (!Array.isArray(value)) return value;
|
|
3381
4768
|
if (value.length <= 1) return firstValue(value);
|
|
3382
|
-
const cell = _optionalChain([this, 'access',
|
|
4769
|
+
const cell = _optionalChain([this, 'access', _41 => _41.context, 'access', _42 => _42.getFormulaCell, 'optionalCall', _43 => _43()]);
|
|
3383
4770
|
if (argument.type === "Range" && cell) {
|
|
3384
4771
|
const range = toFormulaRange(value);
|
|
3385
4772
|
const top = Math.min(argument.startRow, argument.endRow);
|
|
@@ -3399,7 +4786,10 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3399
4786
|
if (!this.context.resolveSpill) {
|
|
3400
4787
|
throw new Error(`Cannot resolve spill range ${node.ref}#: no spill resolver provided`);
|
|
3401
4788
|
}
|
|
3402
|
-
|
|
4789
|
+
if (node.sheetEnd !== void 0) {
|
|
4790
|
+
return this.fail("#REF!", `A 3-D reference can't use the spill operator (${sheetLabel(node)}${node.ref}#).`);
|
|
4791
|
+
}
|
|
4792
|
+
const spill = this.context.resolveSpill(node.col, node.row, _nullishCoalesce(node.sheet, () => ( this.defaultSheet)));
|
|
3403
4793
|
if (typeof spill === "string" || !spill) {
|
|
3404
4794
|
const code = isFormulaError(spill) ? spill : "#REF!";
|
|
3405
4795
|
return this.fail(
|
|
@@ -3416,8 +4806,14 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3416
4806
|
if (!this.context.resolveRange) {
|
|
3417
4807
|
throw new Error(`Cannot resolve range ${node.raw}: no range resolver provided`);
|
|
3418
4808
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
4809
|
+
if (node.sheetEnd !== void 0) return this.evaluate3d(node);
|
|
4810
|
+
const values = this.context.resolveRange(node.startCol, node.startRow, node.endCol, node.endRow, _nullishCoalesce(node.sheet, () => ( this.defaultSheet)));
|
|
4811
|
+
if (!Array.isArray(values)) {
|
|
4812
|
+
if (values === "#REF!" && node.sheet !== void 0) {
|
|
4813
|
+
return this.fail("#REF!", `${sheetLabel(node)}${node.raw} refers to a sheet that doesn't exist.`);
|
|
4814
|
+
}
|
|
4815
|
+
return values;
|
|
4816
|
+
}
|
|
3421
4817
|
const cols = Math.abs(colNameToIndex(node.endCol) - colNameToIndex(node.startCol)) + 1;
|
|
3422
4818
|
if (values.length % cols === 0) withRangeShape(values, values.length / cols, cols);
|
|
3423
4819
|
const error = values.find(isFormulaError);
|
|
@@ -3425,11 +4821,28 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3425
4821
|
return values;
|
|
3426
4822
|
}
|
|
3427
4823
|
callFunction(node) {
|
|
4824
|
+
const variables = lookupEnvironment(this.env, node.name);
|
|
4825
|
+
if (variables) {
|
|
4826
|
+
const bound = variables.vars.get(node.name);
|
|
4827
|
+
if (isFormulaLambda(bound)) return this.invokeLambda(bound, node.args);
|
|
4828
|
+
}
|
|
3428
4829
|
const def = getFormulaFunction(node.name);
|
|
3429
|
-
if (!def)
|
|
4830
|
+
if (!def) {
|
|
4831
|
+
const named = this.lookupDefinedName(node.name);
|
|
4832
|
+
if (named) {
|
|
4833
|
+
const value = this.evaluateDefinedName(named, node.name);
|
|
4834
|
+
if (isFormulaLambda(value)) return this.invokeLambda(value, node.args);
|
|
4835
|
+
if (isFormulaError(value)) return value;
|
|
4836
|
+
return this.fail("#VALUE!", `The name ${node.name} doesn't refer to a LAMBDA function, so it can't be called.`);
|
|
4837
|
+
}
|
|
4838
|
+
if (variables) {
|
|
4839
|
+
return this.fail("#VALUE!", `The variable ${node.name} doesn't hold a LAMBDA function, so it can't be called.`);
|
|
4840
|
+
}
|
|
4841
|
+
return this.fail("#NAME?", `Unknown function "${node.name.toUpperCase()}".`);
|
|
4842
|
+
}
|
|
3430
4843
|
const countError = checkFormulaArgumentCount(def, node.args.length);
|
|
3431
4844
|
if (countError) return this.fail("#ERROR!", countError);
|
|
3432
|
-
if (def.name
|
|
4845
|
+
if (SPECIAL_FORMS.has(def.name)) return this.evaluateSpecialForm(def.name, node);
|
|
3433
4846
|
const args = this.lazyArguments(node.args);
|
|
3434
4847
|
if (def.elementwise) {
|
|
3435
4848
|
const lifted = this.liftOverArrays(def, args);
|
|
@@ -3461,7 +4874,7 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3461
4874
|
* multi-value array.
|
|
3462
4875
|
*/
|
|
3463
4876
|
liftOverArrays(def, args) {
|
|
3464
|
-
const liftable = (index) => _optionalChain([def, 'access',
|
|
4877
|
+
const liftable = (index) => _optionalChain([def, 'access', _44 => _44.args, 'access', _45 => _45[getFormulaArgumentSpecIndex(def, index)], 'optionalAccess', _46 => _46.type]) !== "range";
|
|
3465
4878
|
const triggers = def.elementwise === true ? args.map((a) => a.index) : def.elementwise;
|
|
3466
4879
|
if (!triggers.some((i) => i < args.length && liftable(i) && isMultiArray(args[i].value()))) return void 0;
|
|
3467
4880
|
let rows = 1;
|
|
@@ -3482,26 +4895,320 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3482
4895
|
return firstValue(this.finishCall(def, def.evaluate(this.createContext(def, elementArgs))));
|
|
3483
4896
|
});
|
|
3484
4897
|
}
|
|
4898
|
+
// --- Names, LET and LAMBDA ---
|
|
4899
|
+
/** A bare name: a variable, a defined name, or #NAME?. */
|
|
4900
|
+
evaluateName(node) {
|
|
4901
|
+
if (node.sheet === void 0) {
|
|
4902
|
+
const variables = lookupEnvironment(this.env, node.name);
|
|
4903
|
+
if (variables) return variables.vars.get(node.name);
|
|
4904
|
+
}
|
|
4905
|
+
const named = this.lookupDefinedName(node.name, node.sheet);
|
|
4906
|
+
if (named) return this.evaluateDefinedName(named, `${sheetLabel(node)}${node.name}`);
|
|
4907
|
+
if (node.sheet !== void 0) {
|
|
4908
|
+
return this.fail("#NAME?", `There is no name ${node.name} on sheet ${node.sheet}.`);
|
|
4909
|
+
}
|
|
4910
|
+
return this.fail("#NAME?", `This formula contains an unknown name.`);
|
|
4911
|
+
}
|
|
4912
|
+
lookupDefinedName(name, sheet) {
|
|
4913
|
+
return _nullishCoalesce(_optionalChain([this, 'access', _47 => _47.context, 'access', _48 => _48.resolveName, 'optionalCall', _49 => _49(name, _nullishCoalesce(sheet, () => ( this.defaultSheet)))]), () => ( null));
|
|
4914
|
+
}
|
|
4915
|
+
/**
|
|
4916
|
+
* Evaluates a defined name's formula, outside any LET/LAMBDA variables, with
|
|
4917
|
+
* unqualified references pointing at the name's sheet (for sheet-scoped
|
|
4918
|
+
* names).
|
|
4919
|
+
*/
|
|
4920
|
+
evaluateDefinedName(def, label) {
|
|
4921
|
+
const key = (_nullishCoalesce(def.key, () => ( label))).toUpperCase();
|
|
4922
|
+
if (this.nameStack.includes(key)) {
|
|
4923
|
+
return this.fail("#CYCLE!", `The name ${label} refers to itself.`);
|
|
4924
|
+
}
|
|
4925
|
+
let ast;
|
|
4926
|
+
if (typeof def.formula === "string") {
|
|
4927
|
+
const text = def.formula.startsWith("=") ? def.formula : `=${def.formula}`;
|
|
4928
|
+
let cached = this.parsedNames.get(text);
|
|
4929
|
+
if (!cached) {
|
|
4930
|
+
try {
|
|
4931
|
+
cached = parseFormula(text);
|
|
4932
|
+
} catch (e2) {
|
|
4933
|
+
return this.fail("#NAME?", `The name ${label} has a formula that can't be read.`);
|
|
4934
|
+
}
|
|
4935
|
+
if (this.parsedNames.size > 500) this.parsedNames.clear();
|
|
4936
|
+
this.parsedNames.set(text, cached);
|
|
4937
|
+
}
|
|
4938
|
+
ast = cached;
|
|
4939
|
+
} else {
|
|
4940
|
+
ast = def.formula;
|
|
4941
|
+
}
|
|
4942
|
+
const savedEnv = this.env;
|
|
4943
|
+
const savedSheet = this.defaultSheet;
|
|
4944
|
+
this.nameStack.push(key);
|
|
4945
|
+
this.env = null;
|
|
4946
|
+
if (def.sheet !== void 0) this.defaultSheet = def.sheet;
|
|
4947
|
+
try {
|
|
4948
|
+
return this.evaluateNode(ast);
|
|
4949
|
+
} finally {
|
|
4950
|
+
this.env = savedEnv;
|
|
4951
|
+
this.defaultSheet = savedSheet;
|
|
4952
|
+
this.nameStack.pop();
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
/** A 3-D reference (`Sheet1:Sheet3!A1`, `Sheet1:Sheet3!A1:B2`): every sheet's values, in tab order. */
|
|
4956
|
+
evaluate3d(node) {
|
|
4957
|
+
const label = node.type === "CellRef" ? `${sheetLabel(node)}${node.ref}` : `${sheetLabel(node)}${node.raw}`;
|
|
4958
|
+
const sheets = _optionalChain([this, 'access', _50 => _50.context, 'access', _51 => _51.resolveSheetSpan, 'optionalCall', _52 => _52(node.sheet, node.sheetEnd)]);
|
|
4959
|
+
if (!Array.isArray(sheets)) {
|
|
4960
|
+
return this.fail("#REF!", `${label} refers to a sheet that doesn't exist.`);
|
|
4961
|
+
}
|
|
4962
|
+
const values = [];
|
|
4963
|
+
for (const sheet of sheets) {
|
|
4964
|
+
const part = node.type === "CellRef" ? [this.context.resolveCell(node.col, node.row, sheet)] : this.context.resolveRange(node.startCol, node.startRow, node.endCol, node.endRow, sheet);
|
|
4965
|
+
if (!Array.isArray(part)) return this.fail("#REF!", `${label} refers to a sheet that doesn't exist.`);
|
|
4966
|
+
for (const value of part) values.push(_nullishCoalesce(value, () => ( "")));
|
|
4967
|
+
}
|
|
4968
|
+
const error = values.find(isFormulaError);
|
|
4969
|
+
if (error) this.fail(error, `The 3-D reference ${label} contains an error (${error}).`);
|
|
4970
|
+
return withRangeShape(values, values.length, 1);
|
|
4971
|
+
}
|
|
4972
|
+
/** Calls a lambda with argument expressions, evaluated in the caller's scope. */
|
|
4973
|
+
invokeLambda(lambda, argNodes) {
|
|
4974
|
+
const params = lambda.params;
|
|
4975
|
+
const positionsOk = argNodes.length === params.length || argNodes.length === 0 && params.length === 1;
|
|
4976
|
+
if (!positionsOk) {
|
|
4977
|
+
return this.fail(
|
|
4978
|
+
"#VALUE!",
|
|
4979
|
+
`${lambda.label} expects ${params.length} argument${params.length === 1 ? "" : "s"}, but ${argNodes.length} ${argNodes.length === 1 ? "was" : "were"} given.`
|
|
4980
|
+
);
|
|
4981
|
+
}
|
|
4982
|
+
const values = [];
|
|
4983
|
+
const omitted = /* @__PURE__ */ new Set();
|
|
4984
|
+
for (let i = 0; i < params.length; i++) {
|
|
4985
|
+
const node = argNodes[i];
|
|
4986
|
+
if (!node || node.type === "Missing") {
|
|
4987
|
+
omitted.add(i);
|
|
4988
|
+
values.push("");
|
|
4989
|
+
} else {
|
|
4990
|
+
values.push(this.evaluateNode(node));
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
return this.invokeWithValues(lambda, values, omitted);
|
|
4994
|
+
}
|
|
4995
|
+
/** Calls a lambda with argument values (`omitted`: positions left out, for ISOMITTED). */
|
|
4996
|
+
invokeWithValues(lambda, values, omitted) {
|
|
4997
|
+
if (this.lambdaDepth >= MAX_LAMBDA_DEPTH) {
|
|
4998
|
+
return this.fail("#NUM!", `LAMBDA calls are nested more than ${MAX_LAMBDA_DEPTH} deep (runaway recursion?).`);
|
|
4999
|
+
}
|
|
5000
|
+
if (lambda.native) return lambda.native(values);
|
|
5001
|
+
const vars = /* @__PURE__ */ new Map();
|
|
5002
|
+
let omittedNames;
|
|
5003
|
+
lambda.params.forEach((param, i) => {
|
|
5004
|
+
vars.set(param, _nullishCoalesce(values[i], () => ( "")));
|
|
5005
|
+
if (_optionalChain([omitted, 'optionalAccess', _53 => _53.has, 'call', _54 => _54(i)])) (omittedNames ??= /* @__PURE__ */ new Set()).add(param);
|
|
5006
|
+
});
|
|
5007
|
+
const savedEnv = this.env;
|
|
5008
|
+
const savedSheet = this.defaultSheet;
|
|
5009
|
+
this.env = { vars, omitted: omittedNames, parent: lambda.env };
|
|
5010
|
+
this.defaultSheet = lambda.sheet;
|
|
5011
|
+
this.lambdaDepth++;
|
|
5012
|
+
try {
|
|
5013
|
+
return this.evaluateNode(lambda.body);
|
|
5014
|
+
} finally {
|
|
5015
|
+
this.lambdaDepth--;
|
|
5016
|
+
this.env = savedEnv;
|
|
5017
|
+
this.defaultSheet = savedSheet;
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
evaluateSpecialForm(name, node) {
|
|
5021
|
+
switch (name) {
|
|
5022
|
+
case "LET":
|
|
5023
|
+
return this.evaluateLet(node);
|
|
5024
|
+
case "LAMBDA":
|
|
5025
|
+
return this.evaluateLambda(node);
|
|
5026
|
+
case "ISOMITTED": {
|
|
5027
|
+
const arg2 = node.args[0];
|
|
5028
|
+
if (_optionalChain([arg2, 'optionalAccess', _55 => _55.type]) !== "Name" || arg2.sheet !== void 0) return false;
|
|
5029
|
+
const variables = lookupEnvironment(this.env, arg2.name);
|
|
5030
|
+
return !!_optionalChain([variables, 'optionalAccess', _56 => _56.omitted, 'optionalAccess', _57 => _57.has, 'call', _58 => _58(arg2.name)]);
|
|
5031
|
+
}
|
|
5032
|
+
case "MAP":
|
|
5033
|
+
return this.evaluateMap(node);
|
|
5034
|
+
case "REDUCE":
|
|
5035
|
+
case "SCAN":
|
|
5036
|
+
return this.evaluateReduce(node, name === "SCAN");
|
|
5037
|
+
case "BYROW":
|
|
5038
|
+
case "BYCOL":
|
|
5039
|
+
return this.evaluateByLine(node, name === "BYROW");
|
|
5040
|
+
default:
|
|
5041
|
+
return this.evaluateMakeArray(node);
|
|
5042
|
+
}
|
|
5043
|
+
}
|
|
3485
5044
|
/** LET(name1, value1, [name2, value2, ...], calculation). */
|
|
3486
5045
|
evaluateLet(node) {
|
|
3487
5046
|
if (node.args.length % 2 === 0) {
|
|
3488
5047
|
return this.fail("#ERROR!", "LET needs name/value pairs followed by a calculation.");
|
|
3489
5048
|
}
|
|
3490
|
-
const
|
|
3491
|
-
this.
|
|
5049
|
+
const vars = /* @__PURE__ */ new Map();
|
|
5050
|
+
const savedEnv = this.env;
|
|
5051
|
+
this.env = { vars, parent: savedEnv };
|
|
3492
5052
|
try {
|
|
3493
5053
|
for (let i = 0; i < node.args.length - 1; i += 2) {
|
|
3494
5054
|
const nameNode = node.args[i];
|
|
3495
|
-
if (nameNode.type !== "Name") {
|
|
5055
|
+
if (nameNode.type !== "Name" || nameNode.sheet !== void 0) {
|
|
3496
5056
|
return this.fail("#ERROR!", `Argument ${i + 1} of LET must be a name (names can't look like cell references).`);
|
|
3497
5057
|
}
|
|
3498
|
-
|
|
5058
|
+
vars.set(nameNode.name, this.evaluateNode(node.args[i + 1]));
|
|
3499
5059
|
}
|
|
3500
5060
|
return this.evaluateNode(node.args[node.args.length - 1]);
|
|
3501
5061
|
} finally {
|
|
3502
|
-
this.
|
|
5062
|
+
this.env = savedEnv;
|
|
3503
5063
|
}
|
|
3504
5064
|
}
|
|
5065
|
+
/** LAMBDA([parameter1, ...], calculation): a function value closing over the current variables. */
|
|
5066
|
+
evaluateLambda(node) {
|
|
5067
|
+
const params = [];
|
|
5068
|
+
for (let i = 0; i < node.args.length - 1; i++) {
|
|
5069
|
+
const param = node.args[i];
|
|
5070
|
+
if (param.type !== "Name" || param.sheet !== void 0) {
|
|
5071
|
+
return this.fail("#VALUE!", `Parameter ${i + 1} of LAMBDA must be a name (names can't look like cell references).`);
|
|
5072
|
+
}
|
|
5073
|
+
if (params.includes(param.name)) {
|
|
5074
|
+
return this.fail("#VALUE!", `LAMBDA parameter ${param.name} is listed twice.`);
|
|
5075
|
+
}
|
|
5076
|
+
params.push(param.name);
|
|
5077
|
+
}
|
|
5078
|
+
const body = node.args[node.args.length - 1];
|
|
5079
|
+
if (body.type === "Missing") return this.fail("#VALUE!", "LAMBDA needs a calculation as its last argument.");
|
|
5080
|
+
return new FormulaLambda(params, body, this.env, this.defaultSheet);
|
|
5081
|
+
}
|
|
5082
|
+
/**
|
|
5083
|
+
* The function argument of a LAMBDA helper: a lambda, or a built-in
|
|
5084
|
+
* function given by name (`BYROW(A1:C3, SUM)`) unless a variable or defined
|
|
5085
|
+
* name has that name. Returns an error string when it is neither.
|
|
5086
|
+
*/
|
|
5087
|
+
lambdaArgument(fn, node, index) {
|
|
5088
|
+
if (!node) return this.fail("#VALUE!", `${fn} needs a LAMBDA function.`);
|
|
5089
|
+
if (node.type === "Name" && node.sheet === void 0 && !lookupEnvironment(this.env, node.name) && !this.lookupDefinedName(node.name)) {
|
|
5090
|
+
const def = getFormulaFunction(node.name);
|
|
5091
|
+
if (def && !SPECIAL_FORMS.has(def.name)) {
|
|
5092
|
+
return new FormulaLambda([], null, null, void 0, (values) => this.callBuiltinWithValues(def, values), def.name);
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
const value = this.evaluateNode(node);
|
|
5096
|
+
if (isFormulaLambda(value)) return value;
|
|
5097
|
+
if (isFormulaError(value)) return value;
|
|
5098
|
+
return this.fail("#VALUE!", `Argument ${index + 1} of ${fn} must be a LAMBDA function.`);
|
|
5099
|
+
}
|
|
5100
|
+
/** Calls a built-in function with already evaluated arguments. */
|
|
5101
|
+
callBuiltinWithValues(def, values) {
|
|
5102
|
+
const countError = checkFormulaArgumentCount(def, values.length);
|
|
5103
|
+
if (countError) return this.fail("#VALUE!", countError);
|
|
5104
|
+
const args = values.map((value, index) => ({ index, node: LITERAL_BLANK, value: () => value }));
|
|
5105
|
+
if (def.elementwise) {
|
|
5106
|
+
const lifted = this.liftOverArrays(def, args);
|
|
5107
|
+
if (lifted !== void 0) return lifted;
|
|
5108
|
+
}
|
|
5109
|
+
return this.finishCall(def, def.evaluate(this.createContext(def, args)));
|
|
5110
|
+
}
|
|
5111
|
+
/** Checks a lambda takes `count` parameters (native functions take any count). */
|
|
5112
|
+
checkArity(fn, lambda, count) {
|
|
5113
|
+
if (lambda.native || lambda.params.length === count) return null;
|
|
5114
|
+
return this.fail(
|
|
5115
|
+
"#VALUE!",
|
|
5116
|
+
`The LAMBDA passed to ${fn} must take ${count} parameter${count === 1 ? "" : "s"}, but it takes ${lambda.params.length}.`
|
|
5117
|
+
);
|
|
5118
|
+
}
|
|
5119
|
+
/** MAP(array1, [array2, ...], lambda): calls the lambda with each element. */
|
|
5120
|
+
evaluateMap(node) {
|
|
5121
|
+
const lambda = this.lambdaArgument("MAP", node.args[node.args.length - 1], node.args.length - 1);
|
|
5122
|
+
if (typeof lambda === "string") return lambda;
|
|
5123
|
+
const arrays = [];
|
|
5124
|
+
for (let i = 0; i < node.args.length - 1; i++) {
|
|
5125
|
+
const value = this.evaluateNode(node.args[i]);
|
|
5126
|
+
if (isFormulaError(value)) return value;
|
|
5127
|
+
arrays.push(value);
|
|
5128
|
+
}
|
|
5129
|
+
const arity = this.checkArity("MAP", lambda, arrays.length);
|
|
5130
|
+
if (arity) return arity;
|
|
5131
|
+
let rows = 1;
|
|
5132
|
+
let cols = 1;
|
|
5133
|
+
for (const array of arrays) {
|
|
5134
|
+
const shape = shapeOf(array);
|
|
5135
|
+
rows = Math.max(rows, shape.rows);
|
|
5136
|
+
cols = Math.max(cols, shape.cols);
|
|
5137
|
+
}
|
|
5138
|
+
if (rows * cols > MAX_FORMULA_ARRAY_SIZE) return this.fail("#NUM!", "The array result is too large.");
|
|
5139
|
+
return buildArray(
|
|
5140
|
+
rows,
|
|
5141
|
+
cols,
|
|
5142
|
+
(r, c) => elementValue(this.invokeWithValues(lambda, arrays.map((array) => broadcastGet(array, r, c))))
|
|
5143
|
+
);
|
|
5144
|
+
}
|
|
5145
|
+
/**
|
|
5146
|
+
* REDUCE([initial_value], array, lambda) / SCAN(...): folds the array row
|
|
5147
|
+
* by row with lambda(accumulator, value). SCAN returns every intermediate
|
|
5148
|
+
* value, in the array's shape.
|
|
5149
|
+
*/
|
|
5150
|
+
evaluateReduce(node, scan) {
|
|
5151
|
+
const fn = scan ? "SCAN" : "REDUCE";
|
|
5152
|
+
let acc = this.evaluateNode(node.args[0]);
|
|
5153
|
+
const arrayValue = this.evaluateNode(node.args[1]);
|
|
5154
|
+
if (isFormulaError(arrayValue)) return arrayValue;
|
|
5155
|
+
const lambda = this.lambdaArgument(fn, node.args[2], 2);
|
|
5156
|
+
if (typeof lambda === "string") return lambda;
|
|
5157
|
+
const arity = this.checkArity(fn, lambda, 2);
|
|
5158
|
+
if (arity) return arity;
|
|
5159
|
+
const range = asRange(arrayValue);
|
|
5160
|
+
const results = scan ? new Array(range.rows * range.cols) : [];
|
|
5161
|
+
for (let r = 0; r < range.rows; r++) {
|
|
5162
|
+
for (let c = 0; c < range.cols; c++) {
|
|
5163
|
+
acc = this.invokeWithValues(lambda, [acc, rangeGet(range, r, c)]);
|
|
5164
|
+
if (scan) results[c * range.rows + r] = elementValue(acc);
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5167
|
+
if (scan) return withRangeShape(results, range.rows, range.cols);
|
|
5168
|
+
return acc;
|
|
5169
|
+
}
|
|
5170
|
+
/** BYROW(array, lambda) / BYCOL(array, lambda): one result per row (a column) or per column (a row). */
|
|
5171
|
+
evaluateByLine(node, byRow) {
|
|
5172
|
+
const fn = byRow ? "BYROW" : "BYCOL";
|
|
5173
|
+
const arrayValue = this.evaluateNode(node.args[0]);
|
|
5174
|
+
if (isFormulaError(arrayValue)) return arrayValue;
|
|
5175
|
+
const lambda = this.lambdaArgument(fn, node.args[1], 1);
|
|
5176
|
+
if (typeof lambda === "string") return lambda;
|
|
5177
|
+
const arity = this.checkArity(fn, lambda, 1);
|
|
5178
|
+
if (arity) return arity;
|
|
5179
|
+
const range = asRange(arrayValue);
|
|
5180
|
+
const count = byRow ? range.rows : range.cols;
|
|
5181
|
+
const out = [];
|
|
5182
|
+
for (let i = 0; i < count; i++) {
|
|
5183
|
+
const line = [];
|
|
5184
|
+
const length = byRow ? range.cols : range.rows;
|
|
5185
|
+
for (let j = 0; j < length; j++) line.push(byRow ? rangeGet(range, i, j) : rangeGet(range, j, i));
|
|
5186
|
+
const slice = byRow ? withRangeShape(line, 1, length) : withRangeShape(line, length, 1);
|
|
5187
|
+
out.push(elementValue(this.invokeWithValues(lambda, [slice])));
|
|
5188
|
+
}
|
|
5189
|
+
return byRow ? withRangeShape(out, count, 1) : withRangeShape(out, 1, count);
|
|
5190
|
+
}
|
|
5191
|
+
/** MAKEARRAY(rows, columns, lambda): lambda(row, column) for every 1-based position. */
|
|
5192
|
+
evaluateMakeArray(node) {
|
|
5193
|
+
const size = (index) => {
|
|
5194
|
+
const value = firstValue(this.evaluateNode(node.args[index]));
|
|
5195
|
+
if (isFormulaError(value)) return value;
|
|
5196
|
+
const num = toNumber(value);
|
|
5197
|
+
if (isFormulaError(num)) return this.fail("#VALUE!", `Argument ${index + 1} of MAKEARRAY must be a number.`);
|
|
5198
|
+
return Math.trunc(num);
|
|
5199
|
+
};
|
|
5200
|
+
const rows = size(0);
|
|
5201
|
+
if (typeof rows === "string") return rows;
|
|
5202
|
+
const cols = size(1);
|
|
5203
|
+
if (typeof cols === "string") return cols;
|
|
5204
|
+
if (rows < 1 || cols < 1) return this.fail("#VALUE!", "MAKEARRAY needs at least one row and one column.");
|
|
5205
|
+
if (rows * cols > MAX_FORMULA_ARRAY_SIZE) return this.fail("#NUM!", "The array result is too large.");
|
|
5206
|
+
const lambda = this.lambdaArgument("MAKEARRAY", node.args[2], 2);
|
|
5207
|
+
if (typeof lambda === "string") return lambda;
|
|
5208
|
+
const arity = this.checkArity("MAKEARRAY", lambda, 2);
|
|
5209
|
+
if (arity) return arity;
|
|
5210
|
+
return buildArray(rows, cols, (r, c) => elementValue(this.invokeWithValues(lambda, [r + 1, c + 1])));
|
|
5211
|
+
}
|
|
3505
5212
|
createContext(def, args) {
|
|
3506
5213
|
const has = (index) => index >= 0 && index < args.length;
|
|
3507
5214
|
const value = (index) => has(index) ? args[index].value() : "";
|
|
@@ -3581,7 +5288,22 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3581
5288
|
}
|
|
3582
5289
|
};
|
|
3583
5290
|
}
|
|
3584
|
-
},
|
|
5291
|
+
}, _class3);
|
|
5292
|
+
|
|
5293
|
+
|
|
5294
|
+
|
|
5295
|
+
|
|
5296
|
+
|
|
5297
|
+
|
|
5298
|
+
|
|
5299
|
+
|
|
5300
|
+
|
|
5301
|
+
|
|
5302
|
+
|
|
5303
|
+
|
|
5304
|
+
|
|
5305
|
+
|
|
5306
|
+
|
|
3585
5307
|
|
|
3586
5308
|
|
|
3587
5309
|
|
|
@@ -3619,5 +5341,5 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
3619
5341
|
|
|
3620
5342
|
|
|
3621
5343
|
|
|
3622
|
-
exports.tokenizeFormula = tokenizeFormula; exports.WHOLE_COLUMN_END_ROW = WHOLE_COLUMN_END_ROW; exports.parseCellReference = parseCellReference; exports.parseRange = parseRange; exports.FormulaParser = FormulaParser; exports.parseFormula = parseFormula; exports.serialFromYmd = serialFromYmd; exports.parseCellInput = parseCellInput; exports.parseNumberText = parseNumberText; exports.FORMULA_ERRORS = FORMULA_ERRORS; exports.isFormulaError = isFormulaError; exports.numberToText = numberToText; exports.numericValue = numericValue; exports.compareNumbers = compareNumbers; exports.compareValues = compareValues; exports.sortCompareValues = sortCompareValues; exports.colNameToIndex = colNameToIndex; exports.indexToColName = indexToColName; exports.parseIsoDate = parseIsoDate; exports.wildcardToRegExp = wildcardToRegExp; exports.parseCriteria = parseCriteria; exports.
|
|
3623
|
-
//# sourceMappingURL=chunk-
|
|
5344
|
+
exports.tokenizeFormula = tokenizeFormula; exports.WHOLE_COLUMN_END_ROW = WHOLE_COLUMN_END_ROW; exports.parseCellReference = parseCellReference; exports.parseRange = parseRange; exports.FormulaParser = FormulaParser; exports.parseFormula = parseFormula; exports.FormulaLambda = FormulaLambda; exports.isFormulaLambda = isFormulaLambda; exports.MAX_LAMBDA_DEPTH = MAX_LAMBDA_DEPTH; exports.serialFromYmd = serialFromYmd; exports.parseCellInput = parseCellInput; exports.parseNumberText = parseNumberText; exports.FORMULA_ERRORS = FORMULA_ERRORS; exports.isFormulaError = isFormulaError; exports.isBlank = isBlank; exports.toBoolean = toBoolean; exports.toText = toText; exports.numberToText = numberToText; exports.numericValue = numericValue; exports.compareNumbers = compareNumbers; exports.compareValues = compareValues; exports.sortCompareValues = sortCompareValues; exports.colNameToIndex = colNameToIndex; exports.indexToColName = indexToColName; exports.parseIsoDate = parseIsoDate; exports.serialToDate = serialToDate; exports.wildcardToRegExp = wildcardToRegExp; exports.parseCriteria = parseCriteria; exports.formatGeneralFit = formatGeneralFit; exports.builtinNumberFormat = builtinNumberFormat; exports.builtinNumberFormatId = builtinNumberFormatId; exports.MAX_NUMBER_FORMAT_CODE_CHARS = MAX_NUMBER_FORMAT_CODE_CHARS; exports.NumberFormat = NumberFormat; exports.clearNumberFormatCache = clearNumberFormatCache; exports.dateValueToSerial = dateValueToSerial; exports.formatNumberFormatValue = formatNumberFormatValue; exports.formatTextFunction = formatTextFunction; exports.numberFormatColorToCss = numberFormatColorToCss; exports.registerFormulaFunction = registerFormulaFunction; exports.unregisterFormulaFunction = unregisterFormulaFunction; exports.getFormulaFunction = getFormulaFunction; exports.listFormulaFunctions = listFormulaFunctions; exports.searchFormulaFunctions = searchFormulaFunctions; exports.getFormulaArgumentSpecIndex = getFormulaArgumentSpecIndex; exports.checkFormulaArgumentCount = checkFormulaArgumentCount; exports.getFormulaSignatureParts = getFormulaSignatureParts; exports.getFormulaSignature = getFormulaSignature; exports.describeFormulaError = describeFormulaError; exports.MAX_FORMULA_ARRAY_SIZE = MAX_FORMULA_ARRAY_SIZE; exports.FormulaEvaluator = FormulaEvaluator;
|
|
5345
|
+
//# sourceMappingURL=chunk-C7P22VIP.cjs.map
|