@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.
Files changed (187) hide show
  1. package/README.md +167 -2
  2. package/THIRD_PARTY_NOTICES.md +80 -0
  3. package/dist/agent/index.cjs +10 -8
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +4 -3
  6. package/dist/agent/index.d.ts +4 -3
  7. package/dist/agent/index.js +4 -2
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/canvas/index.cjs +4437 -411
  10. package/dist/canvas/index.cjs.map +1 -1
  11. package/dist/canvas/index.d.cts +934 -4
  12. package/dist/canvas/index.d.ts +934 -4
  13. package/dist/canvas/index.js +4309 -283
  14. package/dist/canvas/index.js.map +1 -1
  15. package/dist/chunk-4BVP64JJ.cjs +1406 -0
  16. package/dist/chunk-4BVP64JJ.cjs.map +1 -0
  17. package/dist/chunk-664LQYFJ.js +115 -0
  18. package/dist/chunk-664LQYFJ.js.map +1 -0
  19. package/dist/chunk-7WISYZFO.cjs +740 -0
  20. package/dist/chunk-7WISYZFO.cjs.map +1 -0
  21. package/dist/chunk-AL6JMGWW.cjs +783 -0
  22. package/dist/chunk-AL6JMGWW.cjs.map +1 -0
  23. package/dist/chunk-BVP3TSLH.cjs +69 -0
  24. package/dist/chunk-BVP3TSLH.cjs.map +1 -0
  25. package/dist/{chunk-RIKBJLS2.cjs → chunk-C7P22VIP.cjs} +2001 -279
  26. package/dist/chunk-C7P22VIP.cjs.map +1 -0
  27. package/dist/chunk-D6FH2YAX.cjs +131 -0
  28. package/dist/chunk-D6FH2YAX.cjs.map +1 -0
  29. package/dist/chunk-ECCCYKGL.js +740 -0
  30. package/dist/chunk-ECCCYKGL.js.map +1 -0
  31. package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
  32. package/dist/chunk-FD4LPFAE.cjs.map +1 -0
  33. package/dist/{chunk-QBJ22YZR.js → chunk-FH6F67DN.js} +1999 -277
  34. package/dist/chunk-FH6F67DN.js.map +1 -0
  35. package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
  36. package/dist/chunk-FPENXAEJ.js.map +1 -0
  37. package/dist/chunk-HLIMTC6N.js +1 -0
  38. package/dist/chunk-HLIMTC6N.js.map +1 -0
  39. package/dist/chunk-HRMEDT2L.js +1406 -0
  40. package/dist/chunk-HRMEDT2L.js.map +1 -0
  41. package/dist/{chunk-QGHGJ56O.js → chunk-IXHSYOVY.js} +2518 -122
  42. package/dist/chunk-IXHSYOVY.js.map +1 -0
  43. package/dist/chunk-MKA3AHLV.cjs +256 -0
  44. package/dist/chunk-MKA3AHLV.cjs.map +1 -0
  45. package/dist/chunk-NAXVVS5I.js +783 -0
  46. package/dist/chunk-NAXVVS5I.js.map +1 -0
  47. package/dist/chunk-O5RYH2PG.js +69 -0
  48. package/dist/chunk-O5RYH2PG.js.map +1 -0
  49. package/dist/chunk-OXSV64JX.js +689 -0
  50. package/dist/chunk-OXSV64JX.js.map +1 -0
  51. package/dist/chunk-P3RQ5UJE.cjs +689 -0
  52. package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
  53. package/dist/chunk-P6ODPZ4W.js +256 -0
  54. package/dist/chunk-P6ODPZ4W.js.map +1 -0
  55. package/dist/chunk-PL4JB3BF.cjs +1 -0
  56. package/dist/chunk-PL4JB3BF.cjs.map +1 -0
  57. package/dist/{chunk-GVSMRPHB.cjs → chunk-PO6A6QQH.cjs} +2607 -211
  58. package/dist/chunk-PO6A6QQH.cjs.map +1 -0
  59. package/dist/{chunk-JJDLPEWL.js → chunk-QGYDWPRM.js} +2 -2
  60. package/dist/chunk-RR7F5GUM.js +5726 -0
  61. package/dist/chunk-RR7F5GUM.js.map +1 -0
  62. package/dist/chunk-TNNJXF46.cjs +115 -0
  63. package/dist/chunk-TNNJXF46.cjs.map +1 -0
  64. package/dist/chunk-U5HZSFCS.js +131 -0
  65. package/dist/chunk-U5HZSFCS.js.map +1 -0
  66. package/dist/{chunk-MJZBNWG7.js → chunk-UAKDG62Z.js} +309 -581
  67. package/dist/chunk-UAKDG62Z.js.map +1 -0
  68. package/dist/{chunk-CL37JGFW.cjs → chunk-V3CHHWOE.cjs} +4 -4
  69. package/dist/{chunk-CL37JGFW.cjs.map → chunk-V3CHHWOE.cjs.map} +1 -1
  70. package/dist/chunk-WSQKHXW7.cjs +5726 -0
  71. package/dist/chunk-WSQKHXW7.cjs.map +1 -0
  72. package/dist/{chunk-AA4ZEVCC.cjs → chunk-X4N4WUKB.cjs} +336 -608
  73. package/dist/chunk-X4N4WUKB.cjs.map +1 -0
  74. package/dist/commands/index.cjs +74 -1
  75. package/dist/commands/index.cjs.map +1 -1
  76. package/dist/commands/index.d.cts +38 -353
  77. package/dist/commands/index.d.ts +38 -353
  78. package/dist/commands/index.js +73 -0
  79. package/dist/commands/index.js.map +1 -1
  80. package/dist/comment-store-BEMgVlAA.d.cts +135 -0
  81. package/dist/comment-store-BEMgVlAA.d.ts +135 -0
  82. package/dist/compare/index.cjs +15 -0
  83. package/dist/compare/index.cjs.map +1 -0
  84. package/dist/compare/index.d.cts +58 -0
  85. package/dist/compare/index.d.ts +58 -0
  86. package/dist/compare/index.js +15 -0
  87. package/dist/compare/index.js.map +1 -0
  88. package/dist/controller-BIJiNoVs.d.cts +1842 -0
  89. package/dist/controller-BIJiNoVs.d.ts +1842 -0
  90. package/dist/documents-CHT1U5Vs.d.ts +86 -0
  91. package/dist/documents-Gp7wb0Me.d.cts +86 -0
  92. package/dist/extensions/index.cjs +100 -5
  93. package/dist/extensions/index.cjs.map +1 -1
  94. package/dist/extensions/index.d.cts +16 -4
  95. package/dist/extensions/index.d.ts +16 -4
  96. package/dist/extensions/index.js +100 -5
  97. package/dist/generation/index.cjs +2317 -0
  98. package/dist/generation/index.cjs.map +1 -0
  99. package/dist/generation/index.d.cts +1015 -0
  100. package/dist/generation/index.d.ts +1015 -0
  101. package/dist/generation/index.js +2317 -0
  102. package/dist/generation/index.js.map +1 -0
  103. package/dist/grammar/index.cjs +48 -3
  104. package/dist/grammar/index.cjs.map +1 -1
  105. package/dist/grammar/index.d.cts +322 -100
  106. package/dist/grammar/index.d.ts +322 -100
  107. package/dist/grammar/index.js +54 -9
  108. package/dist/image-editor/index.cjs +4392 -481
  109. package/dist/image-editor/index.cjs.map +1 -1
  110. package/dist/image-editor/index.d.cts +1249 -97
  111. package/dist/image-editor/index.d.ts +1249 -97
  112. package/dist/image-editor/index.js +4281 -370
  113. package/dist/image-editor/index.js.map +1 -1
  114. package/dist/index-7a6bHTLy.d.ts +1754 -0
  115. package/dist/index-Csd5qI8u.d.cts +1754 -0
  116. package/dist/index.cjs +1682 -99
  117. package/dist/index.cjs.map +1 -1
  118. package/dist/index.d.cts +118 -21
  119. package/dist/index.d.ts +118 -21
  120. package/dist/index.js +1630 -47
  121. package/dist/index.js.map +1 -1
  122. package/dist/mention-text-CDnCFd3c.d.cts +53 -0
  123. package/dist/mention-text-CDnCFd3c.d.ts +53 -0
  124. package/dist/migrate-iTlm3a8h.d.cts +112 -0
  125. package/dist/migrate-iTlm3a8h.d.ts +112 -0
  126. package/dist/model-C1zApQM8.d.cts +219 -0
  127. package/dist/model-C1zApQM8.d.ts +219 -0
  128. package/dist/persistence/index.cjs +676 -0
  129. package/dist/persistence/index.cjs.map +1 -0
  130. package/dist/persistence/index.d.cts +230 -0
  131. package/dist/persistence/index.d.ts +230 -0
  132. package/dist/persistence/index.js +676 -0
  133. package/dist/persistence/index.js.map +1 -0
  134. package/dist/{registry-D5-R90p0.d.cts → registry-BDe-vlv3.d.ts} +2 -40
  135. package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.cts} +12 -1
  136. package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.ts} +12 -1
  137. package/dist/registry-D32LpIc4.d.cts +354 -0
  138. package/dist/{registry-D5-R90p0.d.ts → registry-DnzpgY27.d.cts} +2 -40
  139. package/dist/registry-evKTP4ls.d.ts +354 -0
  140. package/dist/schema-DYDGdYL9.d.cts +41 -0
  141. package/dist/schema-DYDGdYL9.d.ts +41 -0
  142. package/dist/serializers/index.cjs +12 -3
  143. package/dist/serializers/index.cjs.map +1 -1
  144. package/dist/serializers/index.d.cts +40 -12
  145. package/dist/serializers/index.d.ts +40 -12
  146. package/dist/serializers/index.js +12 -3
  147. package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
  148. package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
  149. package/dist/spreadsheet/index.cjs +161 -3
  150. package/dist/spreadsheet/index.cjs.map +1 -1
  151. package/dist/spreadsheet/index.d.cts +611 -20
  152. package/dist/spreadsheet/index.d.ts +611 -20
  153. package/dist/spreadsheet/index.js +168 -10
  154. package/dist/storage-BpFfspHm.d.cts +120 -0
  155. package/dist/storage-BpFfspHm.d.ts +120 -0
  156. package/dist/tokens/index.cjs +115 -0
  157. package/dist/tokens/index.cjs.map +1 -0
  158. package/dist/tokens/index.d.cts +212 -0
  159. package/dist/tokens/index.d.ts +212 -0
  160. package/dist/tokens/index.js +115 -0
  161. package/dist/tokens/index.js.map +1 -0
  162. package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
  163. package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
  164. package/dist/{track-changes-BSF58RK0.d.cts → track-changes-D_VAPELi.d.cts} +110 -144
  165. package/dist/{track-changes-BSF58RK0.d.ts → track-changes-Db_fGEvx.d.ts} +110 -144
  166. package/package.json +21 -1
  167. package/dist/chunk-4SUNJZWB.cjs.map +0 -1
  168. package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
  169. package/dist/chunk-AXV2ZW34.cjs +0 -118
  170. package/dist/chunk-AXV2ZW34.cjs.map +0 -1
  171. package/dist/chunk-B2ZRLN4S.js +0 -118
  172. package/dist/chunk-B2ZRLN4S.js.map +0 -1
  173. package/dist/chunk-GVSMRPHB.cjs.map +0 -1
  174. package/dist/chunk-K3R5NG2D.js +0 -3100
  175. package/dist/chunk-K3R5NG2D.js.map +0 -1
  176. package/dist/chunk-MJZBNWG7.js.map +0 -1
  177. package/dist/chunk-OYXSKQFR.js.map +0 -1
  178. package/dist/chunk-QBJ22YZR.js.map +0 -1
  179. package/dist/chunk-QGHGJ56O.js.map +0 -1
  180. package/dist/chunk-RIKBJLS2.cjs.map +0 -1
  181. package/dist/chunk-XDMXXC4Z.cjs +0 -3100
  182. package/dist/chunk-XDMXXC4Z.cjs.map +0 -1
  183. package/dist/controller-pePCJLT1.d.cts +0 -959
  184. package/dist/controller-pePCJLT1.d.ts +0 -959
  185. package/dist/index-CCr_z9kF.d.cts +0 -899
  186. package/dist/index-Ck6ajwXh.d.ts +0 -899
  187. /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 (/[a-zA-Z_$]/.test(char)) {
147
+ if (isWordStart(char)) {
113
148
  const start = i;
114
- while (i < input.length && /[a-zA-Z0-9_$.]/.test(input[i])) {
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 && /[a-zA-Z0-9_$.]/.test(input[i])) {
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
- const upper = rawWord.toUpperCase();
138
- if (upper === "TRUE" || upper === "FALSE") {
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
- return this.primary();
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
- this.consume("LPAREN", `Expected '(' after function ${name}`);
329
- const args = [];
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("RPAREN", `Expected ')' after function arguments`);
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
- return { type: "Name", name };
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', _2 => _2(value), 'optionalAccess', _3 => _3.getTime, 'call', _4 => _4()]), () => ( null));
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/formula/format.ts
986
- function formatCellDisplay(cell) {
987
- if (!cell) return "";
988
- if (cell.error) return cell.error;
989
- const { value, numberFormat } = cell;
990
- if (numberFormat && value !== "" && value !== null && value !== void 0 && typeof value !== "boolean") {
991
- if (/^\[h\]/i.test(numberFormat) && typeof value === "number") return formatElapsedHours(value);
992
- if (numberFormat === "# ?/?" && typeof value === "number") return formatFraction(value);
993
- const formatted = formatValueWithCode(value, numberFormat);
994
- if (formatted !== null) return formatted;
995
- }
996
- return toText(value);
997
- }
998
- function formatFraction(value) {
999
- const sign = value < 0 ? "-" : "";
1000
- const abs = Math.abs(value);
1001
- let whole = Math.floor(abs);
1002
- const frac = abs - whole;
1003
- let best = { num: 0, den: 1, err: frac };
1004
- for (let den = 2; den <= 9; den++) {
1005
- const num = Math.round(frac * den);
1006
- const err = Math.abs(frac - num / den);
1007
- if (err < best.err - 1e-12) best = { num, den, err };
1008
- }
1009
- if (best.num === best.den) {
1010
- whole += 1;
1011
- best = { num: 0, den: 1, err: 0 };
1012
- }
1013
- if (best.num === 0) return `${sign}${whole}`;
1014
- return `${sign}${whole === 0 ? "" : `${whole} `}${best.num}/${best.den}`;
1015
- }
1016
- function formatElapsedHours(days) {
1017
- const total = Math.round(Math.abs(days) * 86400);
1018
- const p = (n) => String(n).padStart(2, "0");
1019
- const text = `${Math.floor(total / 3600)}:${p(Math.floor(total / 60) % 60)}:${p(total % 60)}`;
1020
- return days < 0 ? `-${text}` : text;
1021
- }
1022
- function formatValueWithCode(value, format) {
1023
- if (isDateFormat(format)) {
1024
- const date = toDate(value);
1025
- return date ? formatDate(date, format) : null;
1026
- }
1027
- const num = typeof value === "boolean" ? null : numericValue(value);
1028
- return num === null ? toText(value) : formatNumber(num, format);
1029
- }
1030
- function tokenize(format) {
1031
- const out = [];
1032
- const push = (literal, text) => {
1033
- const last = out[out.length - 1];
1034
- if (last && last.literal === literal) last.text += text;
1035
- else out.push({ literal, text });
1036
- };
1037
- for (let i = 0; i < format.length; i++) {
1038
- const char = format[i];
1039
- if (char === '"') {
1040
- const end = format.indexOf('"', i + 1);
1041
- const stop = end === -1 ? format.length : end;
1042
- push(true, format.slice(i + 1, stop));
1043
- i = stop;
1044
- } else if (char === "\\" && i + 1 < format.length) {
1045
- push(true, format[++i]);
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
- push(false, char);
1374
+ out[cur] += c;
1048
1375
  }
1049
1376
  }
1050
1377
  return out;
1051
1378
  }
1052
- var codeText = (format) => tokenize(format).filter((t) => !t.literal).map((t) => t.text).join("");
1053
- function isDateFormat(format) {
1054
- const code = codeText(format);
1055
- if (/[0#?]/.test(code)) return false;
1056
- return /[ydhs]|m/i.test(code);
1057
- }
1058
- function formatNumber(num, format) {
1059
- const sections = splitSections(format);
1060
- let section = sections[0];
1061
- let value = num;
1062
- let sign = num < 0 ? "-" : "";
1063
- if (num < 0 && sections.length > 1) {
1064
- section = sections[1];
1065
- value = Math.abs(num);
1066
- sign = "";
1067
- } else if (num === 0 && sections.length > 2) {
1068
- section = sections[2];
1069
- }
1070
- let prefix = "";
1071
- let suffix = "";
1072
- let pattern = "";
1073
- for (const token of tokenize(section)) {
1074
- if (token.literal) {
1075
- if (pattern) suffix += token.text;
1076
- else prefix += token.text;
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
- for (let i = 0; i < token.text.length; i++) {
1080
- const char = token.text[i];
1081
- const isExponent = /e/i.test(char) && /[+-]/.test(_nullishCoalesce(token.text[i + 1], () => ( "")));
1082
- const isExponentSign = /[+-]/.test(char) && /e$/i.test(pattern);
1083
- if (/[0#?.,]/.test(char) || pattern && (isExponent || isExponentSign)) {
1084
- pattern += char;
1085
- } else if (pattern) {
1086
- suffix += char;
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
- prefix += char;
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 (char === ";" && !quoted) sections.push("");
1130
- else sections[sections.length - 1] += char;
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 sections;
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 DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1136
- var pad3 = (n) => String(n).padStart(2, "0");
1137
- function formatDate(date, format) {
1138
- const parts = [];
1139
- for (const token of tokenize(format)) {
1140
- if (token.literal) {
1141
- parts.push(token);
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
- const re = /AM\/PM|am\/pm|A\/P|a\/p|y+|m+|d+|h+|s+|[^ymdhsaA]+|./gi;
1145
- for (const match of _nullishCoalesce(token.text.match(re), () => ( []))) {
1146
- const isCode = /^(AM\/PM|A\/P|y+|m+|d+|h+|s+)$/i.test(match);
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
- const codes = parts.map((p, i) => ({ ...p, i })).filter((p) => !p.literal);
1151
- const twelveHour = codes.some((p) => /^(AM\/PM|A\/P)$/i.test(p.text));
1152
- return parts.map((part, index) => {
1153
- if (part.literal) return part.text;
1154
- const code = part.text;
1155
- const lower = code.toLowerCase();
1156
- if (/^(am\/pm|a\/p)$/.test(lower)) {
1157
- const pm = date.getHours() >= 12;
1158
- const full = pm ? "PM" : "AM";
1159
- const text = lower === "a/p" ? full[0] : full;
1160
- return code[0] === code[0].toLowerCase() ? text.toLowerCase() : text;
1161
- }
1162
- if (lower.startsWith("y")) {
1163
- return lower.length <= 2 ? pad3(date.getFullYear() % 100) : String(date.getFullYear());
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
- if (lower.startsWith("d")) {
1166
- if (lower.length >= 4) return DAYS[date.getDay()];
1167
- if (lower.length === 3) return DAYS[date.getDay()].slice(0, 3);
1168
- return lower.length === 2 ? pad3(date.getDate()) : String(date.getDate());
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
- if (lower.startsWith("h")) {
1171
- let hours = date.getHours();
1172
- if (twelveHour) hours = hours % 12 || 12;
1173
- return lower.length >= 2 ? pad3(hours) : String(hours);
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
- if (lower.startsWith("s")) {
1176
- return lower.length >= 2 ? pad3(date.getSeconds()) : String(date.getSeconds());
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
- const position = codes.findIndex((c) => c.i === index);
1179
- const prev = _nullishCoalesce(_optionalChain([codes, 'access', _5 => _5[position - 1], 'optionalAccess', _6 => _6.text, 'access', _7 => _7.toLowerCase, 'call', _8 => _8()]), () => ( ""));
1180
- const next = _nullishCoalesce(_optionalChain([codes, 'access', _9 => _9[position + 1], 'optionalAccess', _10 => _10.text, 'access', _11 => _11.toLowerCase, 'call', _12 => _12()]), () => ( ""));
1181
- if (lower.length <= 2 && (prev.startsWith("h") || next.startsWith("s"))) {
1182
- return lower.length === 2 ? pad3(date.getMinutes()) : String(date.getMinutes());
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 (lower.length >= 4) return MONTHS[date.getMonth()];
1185
- if (lower.length === 3) return MONTHS[date.getMonth()].slice(0, 3);
1186
- return lower.length === 2 ? pad3(date.getMonth() + 1) : String(date.getMonth() + 1);
1187
- }).join("");
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 a format code such as "0.00", "#,##0", "0%" or "yyyy-mm-dd".',
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 = formatValueWithCode(value, format);
2208
- return _nullishCoalesce(result, () => ( ctx.argError(0, "#VALUE!", `isn't a date, so it can't use the format "${format}".`)));
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 approximate = ctx.boolean(3, true);
2274
- if (failed(approximate)) return approximate;
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, approximate ? "sorted-asc" : "wildcard");
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 < fixed.length) return 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 = fixed.filter((arg2) => !arg2.optional).length;
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 : fixed.length;
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 > fixed.length) {
3069
- const extra = (count - fixed.length) % group.length;
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
- fixed.forEach((arg2, i) => pushArg(arg2.optional ? `[${arg2.name}]` : arg2.name, i));
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 FormulaEvaluator = (_class2 = class {
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
- __init3() {this.diagnostics = []}
3157
- // LET variables, innermost scope last.
3158
- __init4() {this.scopes = []}
3159
- constructor(context = {}) {;_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);
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
- return this.evaluateNode(ast);
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 savedScopes = this.scopes;
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.scopes = [];
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', _13 => _13.message]), () => ( describeFormulaError(code)))
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
- return { value: "#ERROR!", error: "#ERROR!", message: _optionalChain([err, 'optionalAccess', _14 => _14.message]) || "Formula error" };
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.scopes = savedScopes;
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
- const value = this.context.resolveCell(node.col, node.row);
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 ${node.ref} is part of a circular reference.` : `Cell ${node.ref} contains an error (${value}).`
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
- for (let i = this.scopes.length - 1; i >= 0; i--) {
3237
- if (this.scopes[i].has(node.name)) return this.scopes[i].get(node.name);
3238
- }
3239
- return this.fail("#NAME?", "This formula contains an unknown name.");
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', _15 => _15.context, 'access', _16 => _16.getFormulaCell, 'optionalCall', _17 => _17()]);
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
- const spill = this.context.resolveSpill(node.col, node.row);
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
- const values = this.context.resolveRange(node.startCol, node.startRow, node.endCol, node.endRow);
3420
- if (!Array.isArray(values)) return values;
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) return this.fail("#NAME?", `Unknown function "${node.name.toUpperCase()}".`);
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 === "LET") return this.evaluateLet(node);
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', _18 => _18.args, 'access', _19 => _19[getFormulaArgumentSpecIndex(def, index)], 'optionalAccess', _20 => _20.type]) !== "range";
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 scope = /* @__PURE__ */ new Map();
3491
- this.scopes.push(scope);
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
- scope.set(nameNode.name, this.evaluateNode(node.args[i + 1]));
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.scopes.pop();
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
- }, _class2);
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.formatCellDisplay = formatCellDisplay; exports.formatValueWithCode = formatValueWithCode; 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;
3623
- //# sourceMappingURL=chunk-RIKBJLS2.cjs.map
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