@borgar/fx 4.13.0 → 5.0.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 (141) hide show
  1. package/dist/index-BMr6cTgc.d.cts +1444 -0
  2. package/dist/index-BMr6cTgc.d.ts +1444 -0
  3. package/dist/index.cjs +3054 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +2984 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/xlsx/index.cjs +3120 -0
  10. package/dist/xlsx/index.cjs.map +1 -0
  11. package/dist/xlsx/index.d.cts +55 -0
  12. package/dist/xlsx/index.d.ts +55 -0
  13. package/dist/xlsx/index.js +3049 -0
  14. package/dist/xlsx/index.js.map +1 -0
  15. package/docs/API.md +2959 -718
  16. package/docs/AST_format.md +2 -2
  17. package/eslint.config.mjs +40 -0
  18. package/lib/a1.spec.ts +32 -0
  19. package/lib/a1.ts +26 -0
  20. package/lib/addA1RangeBounds.ts +50 -0
  21. package/lib/addTokenMeta.spec.ts +166 -0
  22. package/lib/{addTokenMeta.js → addTokenMeta.ts} +53 -33
  23. package/lib/astTypes.ts +211 -0
  24. package/lib/cloneToken.ts +29 -0
  25. package/lib/{constants.js → constants.ts} +6 -3
  26. package/lib/fixRanges.spec.ts +220 -0
  27. package/lib/fixRanges.ts +260 -0
  28. package/lib/fromCol.spec.ts +15 -0
  29. package/lib/{fromCol.js → fromCol.ts} +1 -1
  30. package/lib/index.spec.ts +119 -0
  31. package/lib/index.ts +76 -0
  32. package/lib/isNodeType.ts +151 -0
  33. package/lib/isType.spec.ts +208 -0
  34. package/lib/{isType.js → isType.ts} +26 -25
  35. package/lib/lexers/{advRangeOp.js → advRangeOp.ts} +1 -1
  36. package/lib/lexers/{canEndRange.js → canEndRange.ts} +2 -2
  37. package/lib/lexers/{lexBoolean.js → lexBoolean.ts} +25 -6
  38. package/lib/lexers/{lexContext.js → lexContext.ts} +14 -6
  39. package/lib/lexers/{lexError.js → lexError.ts} +3 -3
  40. package/lib/lexers/{lexFunction.js → lexFunction.ts} +3 -2
  41. package/lib/lexers/lexNameFuncCntx.ts +112 -0
  42. package/lib/lexers/{lexNamed.js → lexNamed.ts} +4 -4
  43. package/lib/lexers/{lexNewLine.js → lexNewLine.ts} +3 -2
  44. package/lib/lexers/{lexNumber.js → lexNumber.ts} +4 -3
  45. package/lib/lexers/{lexOperator.js → lexOperator.ts} +5 -4
  46. package/lib/lexers/lexRange.ts +15 -0
  47. package/lib/lexers/{lexRangeA1.js → lexRangeA1.ts} +11 -7
  48. package/lib/lexers/{lexRangeR1C1.js → lexRangeR1C1.ts} +10 -6
  49. package/lib/lexers/{lexRangeTrim.js → lexRangeTrim.ts} +3 -2
  50. package/lib/lexers/{lexRefOp.js → lexRefOp.ts} +4 -3
  51. package/lib/lexers/{lexString.js → lexString.ts} +3 -3
  52. package/lib/lexers/{lexStructured.js → lexStructured.ts} +5 -5
  53. package/lib/lexers/{lexWhitespace.js → lexWhitespace.ts} +3 -2
  54. package/lib/lexers/sets.ts +51 -0
  55. package/lib/mergeRefTokens.spec.ts +141 -0
  56. package/lib/{mergeRefTokens.js → mergeRefTokens.ts} +14 -9
  57. package/lib/nodeTypes.ts +54 -0
  58. package/lib/parse.spec.ts +1410 -0
  59. package/lib/{parser.js → parse.ts} +81 -63
  60. package/lib/parseA1Range.spec.ts +233 -0
  61. package/lib/parseA1Range.ts +206 -0
  62. package/lib/parseA1Ref.spec.ts +337 -0
  63. package/lib/parseA1Ref.ts +115 -0
  64. package/lib/parseR1C1Range.ts +191 -0
  65. package/lib/parseR1C1Ref.spec.ts +323 -0
  66. package/lib/parseR1C1Ref.ts +127 -0
  67. package/lib/parseRef.spec.ts +90 -0
  68. package/lib/parseRef.ts +240 -0
  69. package/lib/{parseSRange.js → parseSRange.ts} +15 -10
  70. package/lib/parseStructRef.spec.ts +168 -0
  71. package/lib/parseStructRef.ts +76 -0
  72. package/lib/stringifyA1Range.spec.ts +72 -0
  73. package/lib/stringifyA1Range.ts +72 -0
  74. package/lib/stringifyA1Ref.spec.ts +64 -0
  75. package/lib/stringifyA1Ref.ts +59 -0
  76. package/lib/{stringifyPrefix.js → stringifyPrefix.ts} +17 -2
  77. package/lib/stringifyR1C1Range.spec.ts +92 -0
  78. package/lib/stringifyR1C1Range.ts +73 -0
  79. package/lib/stringifyR1C1Ref.spec.ts +63 -0
  80. package/lib/stringifyR1C1Ref.ts +67 -0
  81. package/lib/stringifyStructRef.spec.ts +124 -0
  82. package/lib/stringifyStructRef.ts +113 -0
  83. package/lib/stringifyTokens.ts +15 -0
  84. package/lib/toCol.spec.ts +11 -0
  85. package/lib/{toCol.js → toCol.ts} +4 -4
  86. package/lib/tokenTypes.ts +76 -0
  87. package/lib/tokenize-srefs.spec.ts +429 -0
  88. package/lib/tokenize.spec.ts +2103 -0
  89. package/lib/tokenize.ts +346 -0
  90. package/lib/translate.spec.ts +35 -0
  91. package/lib/translateToA1.spec.ts +247 -0
  92. package/lib/translateToA1.ts +231 -0
  93. package/lib/translateToR1C1.spec.ts +227 -0
  94. package/lib/translateToR1C1.ts +145 -0
  95. package/lib/types.ts +179 -0
  96. package/lib/xlsx/index.spec.ts +27 -0
  97. package/lib/xlsx/index.ts +32 -0
  98. package/package.json +45 -31
  99. package/tsconfig.json +28 -0
  100. package/typedoc-ignore-links.ts +17 -0
  101. package/typedoc.json +41 -0
  102. package/.eslintrc +0 -22
  103. package/benchmark/benchmark.js +0 -48
  104. package/benchmark/formulas.json +0 -15677
  105. package/dist/fx.d.ts +0 -823
  106. package/dist/fx.js +0 -2
  107. package/dist/package.json +0 -1
  108. package/lib/a1.js +0 -348
  109. package/lib/a1.spec.js +0 -458
  110. package/lib/addTokenMeta.spec.js +0 -153
  111. package/lib/astTypes.js +0 -96
  112. package/lib/extraTypes.js +0 -74
  113. package/lib/fixRanges.js +0 -104
  114. package/lib/fixRanges.spec.js +0 -171
  115. package/lib/fromCol.spec.js +0 -11
  116. package/lib/index.js +0 -134
  117. package/lib/index.spec.js +0 -67
  118. package/lib/isType.spec.js +0 -168
  119. package/lib/lexer-srefs.spec.js +0 -324
  120. package/lib/lexer.js +0 -264
  121. package/lib/lexer.spec.js +0 -1953
  122. package/lib/lexers/lexRange.js +0 -8
  123. package/lib/lexers/sets.js +0 -38
  124. package/lib/mergeRefTokens.spec.js +0 -121
  125. package/lib/package.json +0 -1
  126. package/lib/parseRef.js +0 -157
  127. package/lib/parseRef.spec.js +0 -71
  128. package/lib/parseStructRef.js +0 -48
  129. package/lib/parseStructRef.spec.js +0 -164
  130. package/lib/parser.spec.js +0 -1208
  131. package/lib/rc.js +0 -341
  132. package/lib/rc.spec.js +0 -403
  133. package/lib/stringifyStructRef.js +0 -80
  134. package/lib/stringifyStructRef.spec.js +0 -182
  135. package/lib/toCol.spec.js +0 -11
  136. package/lib/translate-toA1.spec.js +0 -214
  137. package/lib/translate-toRC.spec.js +0 -197
  138. package/lib/translate.js +0 -239
  139. package/lib/translate.spec.js +0 -21
  140. package/rollup.config.mjs +0 -22
  141. package/tsd.json +0 -12
@@ -0,0 +1,2103 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import {
3
+ FX_PREFIX, UNKNOWN,
4
+ OPERATOR, BOOLEAN, ERROR, NUMBER, FUNCTION, WHITESPACE, STRING,
5
+ REF_RANGE, REF_BEAM, REF_NAMED, REF_TERNARY, CONTEXT, CONTEXT_QUOTE, NEWLINE
6
+ } from './constants.ts';
7
+ import { tokenize, tokenizeXlsx } from './tokenize.ts';
8
+
9
+ function isTokens (expr: string, result: any[], opts?: any) {
10
+ expect(tokenize(expr, { negativeNumbers: false, ...opts })).toEqual(result);
11
+ }
12
+
13
+ function isTokensNeg (expr: string, result: any[], opts?: any) {
14
+ expect(tokenize(expr, { ...opts, negativeNumbers: true })).toEqual(result);
15
+ }
16
+
17
+ describe('lexer', () => {
18
+ describe('operators', () => {
19
+ test('basic comparison operators', () => {
20
+ isTokens('=1>1', [
21
+ { type: FX_PREFIX, value: '=' },
22
+ { type: NUMBER, value: '1' },
23
+ { type: OPERATOR, value: '>' },
24
+ { type: NUMBER, value: '1' }
25
+ ]);
26
+ isTokens('=1>=1', [
27
+ { type: FX_PREFIX, value: '=' },
28
+ { type: NUMBER, value: '1' },
29
+ { type: OPERATOR, value: '>=' },
30
+ { type: NUMBER, value: '1' }
31
+ ]);
32
+ isTokens('=1=1', [
33
+ { type: FX_PREFIX, value: '=' },
34
+ { type: NUMBER, value: '1' },
35
+ { type: OPERATOR, value: '=' },
36
+ { type: NUMBER, value: '1' }
37
+ ]);
38
+ isTokens('=1<>1', [
39
+ { type: FX_PREFIX, value: '=' },
40
+ { type: NUMBER, value: '1' },
41
+ { type: OPERATOR, value: '<>' },
42
+ { type: NUMBER, value: '1' }
43
+ ]);
44
+ isTokens('=1<=1', [
45
+ { type: FX_PREFIX, value: '=' },
46
+ { type: NUMBER, value: '1' },
47
+ { type: OPERATOR, value: '<=' },
48
+ { type: NUMBER, value: '1' }
49
+ ]);
50
+ isTokens('=1<1', [
51
+ { type: FX_PREFIX, value: '=' },
52
+ { type: NUMBER, value: '1' },
53
+ { type: OPERATOR, value: '<' },
54
+ { type: NUMBER, value: '1' }
55
+ ]);
56
+ });
57
+
58
+ test('arithmetic operators', () => {
59
+ isTokens('=1+1', [
60
+ { type: FX_PREFIX, value: '=' },
61
+ { type: NUMBER, value: '1' },
62
+ { type: OPERATOR, value: '+' },
63
+ { type: NUMBER, value: '1' }
64
+ ]);
65
+ isTokens('=1-1', [
66
+ { type: FX_PREFIX, value: '=' },
67
+ { type: NUMBER, value: '1' },
68
+ { type: OPERATOR, value: '-' },
69
+ { type: NUMBER, value: '1' }
70
+ ]);
71
+ isTokens('=1*1', [
72
+ { type: FX_PREFIX, value: '=' },
73
+ { type: NUMBER, value: '1' },
74
+ { type: OPERATOR, value: '*' },
75
+ { type: NUMBER, value: '1' }
76
+ ]);
77
+ isTokens('=1/1', [
78
+ { type: FX_PREFIX, value: '=' },
79
+ { type: NUMBER, value: '1' },
80
+ { type: OPERATOR, value: '/' },
81
+ { type: NUMBER, value: '1' }
82
+ ]);
83
+ isTokens('=1^1', [
84
+ { type: FX_PREFIX, value: '=' },
85
+ { type: NUMBER, value: '1' },
86
+ { type: OPERATOR, value: '^' },
87
+ { type: NUMBER, value: '1' }
88
+ ]);
89
+ isTokens('=1&1', [
90
+ { type: FX_PREFIX, value: '=' },
91
+ { type: NUMBER, value: '1' },
92
+ { type: OPERATOR, value: '&' },
93
+ { type: NUMBER, value: '1' }
94
+ ]);
95
+ });
96
+
97
+ test('string equality and references', () => {
98
+ isTokens('="A"="B"', [
99
+ { type: FX_PREFIX, value: '=' },
100
+ { type: STRING, value: '"A"' },
101
+ { type: OPERATOR, value: '=' },
102
+ { type: STRING, value: '"B"' }
103
+ ]);
104
+ isTokens('=A1:INDIRECT("B2",TRUE)', [
105
+ { type: FX_PREFIX, value: '=' },
106
+ { type: REF_RANGE, value: 'A1' },
107
+ { type: OPERATOR, value: ':' },
108
+ { type: FUNCTION, value: 'INDIRECT' },
109
+ { type: OPERATOR, value: '(' },
110
+ { type: STRING, value: '"B2"' },
111
+ { type: OPERATOR, value: ',' },
112
+ { type: BOOLEAN, value: 'TRUE' },
113
+ { type: OPERATOR, value: ')' }
114
+ ]);
115
+ });
116
+
117
+ test('percentage and sheet references', () => {
118
+ isTokens('=123%', [
119
+ { type: FX_PREFIX, value: '=' },
120
+ { type: NUMBER, value: '123' },
121
+ { type: OPERATOR, value: '%' }
122
+ ]);
123
+ isTokens('=Sheet1!A1', [
124
+ { type: FX_PREFIX, value: '=' },
125
+ { type: CONTEXT, value: 'Sheet1' },
126
+ { type: OPERATOR, value: '!' },
127
+ { type: REF_RANGE, value: 'A1' }
128
+ ], { mergeRefs: false });
129
+ isTokens('=Sheet1!A1', [
130
+ { type: FX_PREFIX, value: '=' },
131
+ { type: REF_RANGE, value: 'Sheet1!A1' }
132
+ ]);
133
+ });
134
+
135
+ test('range union and intersection', () => {
136
+ isTokens('=(A1:C1,A2:C2)', [
137
+ { type: FX_PREFIX, value: '=' },
138
+ { type: OPERATOR, value: '(' },
139
+ { type: REF_RANGE, value: 'A1' },
140
+ { type: OPERATOR, value: ':' },
141
+ { type: REF_RANGE, value: 'C1' },
142
+ { type: OPERATOR, value: ',' },
143
+ { type: REF_RANGE, value: 'A2' },
144
+ { type: OPERATOR, value: ':' },
145
+ { type: REF_RANGE, value: 'C2' },
146
+ { type: OPERATOR, value: ')' }
147
+ ], { mergeRefs: false });
148
+ isTokens('=(A1:C1,A2:C2)', [
149
+ { type: FX_PREFIX, value: '=' },
150
+ { type: OPERATOR, value: '(' },
151
+ { type: REF_RANGE, value: 'A1:C1' },
152
+ { type: OPERATOR, value: ',' },
153
+ { type: REF_RANGE, value: 'A2:C2' },
154
+ { type: OPERATOR, value: ')' }
155
+ ]);
156
+ isTokens('=(A1:C1 A2:C2)', [
157
+ { type: FX_PREFIX, value: '=' },
158
+ { type: OPERATOR, value: '(' },
159
+ { type: REF_RANGE, value: 'A1:C1' },
160
+ { type: WHITESPACE, value: ' ' }, // INTERSECT
161
+ { type: REF_RANGE, value: 'A2:C2' },
162
+ { type: OPERATOR, value: ')' }
163
+ ]);
164
+ isTokens('=(A1:C1 A2:C2)', [
165
+ { type: FX_PREFIX, value: '=' },
166
+ { type: OPERATOR, value: '(' },
167
+ { type: REF_RANGE, value: 'A1:C1' },
168
+ { type: WHITESPACE, value: ' ' }, // INTERSECT
169
+ { type: REF_RANGE, value: 'A2:C2' },
170
+ { type: OPERATOR, value: ')' }
171
+ ]);
172
+ });
173
+
174
+ test('array literals', () => {
175
+ isTokens('={1,2,3}', [
176
+ { type: FX_PREFIX, value: '=' },
177
+ { type: OPERATOR, value: '{' },
178
+ { type: NUMBER, value: '1' },
179
+ { type: OPERATOR, value: ',' },
180
+ { type: NUMBER, value: '2' },
181
+ { type: OPERATOR, value: ',' },
182
+ { type: NUMBER, value: '3' },
183
+ { type: OPERATOR, value: '}' }
184
+ ]);
185
+ isTokens('={1;2;3}', [
186
+ { type: FX_PREFIX, value: '=' },
187
+ { type: OPERATOR, value: '{' },
188
+ { type: NUMBER, value: '1' },
189
+ { type: OPERATOR, value: ';' },
190
+ { type: NUMBER, value: '2' },
191
+ { type: OPERATOR, value: ';' },
192
+ { type: NUMBER, value: '3' },
193
+ { type: OPERATOR, value: '}' }
194
+ ]);
195
+ isTokens('={1,2;3}', [
196
+ { type: FX_PREFIX, value: '=' },
197
+ { type: OPERATOR, value: '{' },
198
+ { type: NUMBER, value: '1' },
199
+ { type: OPERATOR, value: ',' },
200
+ { type: NUMBER, value: '2' },
201
+ { type: OPERATOR, value: ';' },
202
+ { type: NUMBER, value: '3' },
203
+ { type: OPERATOR, value: '}' }
204
+ ]);
205
+ isTokens('={"A",33;TRUE,123}', [
206
+ { type: FX_PREFIX, value: '=' },
207
+ { type: OPERATOR, value: '{' },
208
+ { type: STRING, value: '"A"' },
209
+ { type: OPERATOR, value: ',' },
210
+ { type: NUMBER, value: '33' },
211
+ { type: OPERATOR, value: ';' },
212
+ { type: BOOLEAN, value: 'TRUE' },
213
+ { type: OPERATOR, value: ',' },
214
+ { type: NUMBER, value: '123' },
215
+ { type: OPERATOR, value: '}' }
216
+ ]);
217
+ isTokens('={A1:B2}', [
218
+ { type: FX_PREFIX, value: '=' },
219
+ { type: OPERATOR, value: '{' },
220
+ { type: REF_RANGE, value: 'A1:B2' },
221
+ { type: OPERATOR, value: '}' }
222
+ ]);
223
+ isTokens('={A1:B2,C3:D4}', [
224
+ { type: FX_PREFIX, value: '=' },
225
+ { type: OPERATOR, value: '{' },
226
+ { type: REF_RANGE, value: 'A1:B2' },
227
+ { type: OPERATOR, value: ',' },
228
+ { type: REF_RANGE, value: 'C3:D4' },
229
+ { type: OPERATOR, value: '}' }
230
+ ]);
231
+ });
232
+ });
233
+
234
+ describe('functions', () => {
235
+ test('simple functions', () => {
236
+ isTokens('=TODAY()', [
237
+ { type: FX_PREFIX, value: '=' },
238
+ { type: FUNCTION, value: 'TODAY' },
239
+ { type: OPERATOR, value: '(' },
240
+ { type: OPERATOR, value: ')' }
241
+ ]);
242
+ isTokens('=ToDaY()', [
243
+ { type: FX_PREFIX, value: '=' },
244
+ { type: FUNCTION, value: 'ToDaY' },
245
+ { type: OPERATOR, value: '(' },
246
+ { type: OPERATOR, value: ')' }
247
+ ]);
248
+ isTokens('=SUM(1)', [
249
+ { type: FX_PREFIX, value: '=' },
250
+ { type: FUNCTION, value: 'SUM' },
251
+ { type: OPERATOR, value: '(' },
252
+ { type: NUMBER, value: '1' },
253
+ { type: OPERATOR, value: ')' }
254
+ ]);
255
+ isTokens('=N()', [
256
+ { type: FX_PREFIX, value: '=' },
257
+ { type: FUNCTION, value: 'N' },
258
+ { type: OPERATOR, value: '(' },
259
+ { type: OPERATOR, value: ')' }
260
+ ]);
261
+ });
262
+
263
+ test('boolean functions', () => {
264
+ isTokens('=TRUE()', [
265
+ { type: FX_PREFIX, value: '=' },
266
+ { type: FUNCTION, value: 'TRUE' },
267
+ { type: OPERATOR, value: '(' },
268
+ { type: OPERATOR, value: ')' }
269
+ ]);
270
+ isTokens('=FALSE()', [
271
+ { type: FX_PREFIX, value: '=' },
272
+ { type: FUNCTION, value: 'FALSE' },
273
+ { type: OPERATOR, value: '(' },
274
+ { type: OPERATOR, value: ')' }
275
+ ]);
276
+ });
277
+
278
+ test('implicit intersection operator', () => {
279
+ isTokens('=@SUM(1)', [
280
+ { type: FX_PREFIX, value: '=' },
281
+ { type: OPERATOR, value: '@' },
282
+ { type: FUNCTION, value: 'SUM' },
283
+ { type: OPERATOR, value: '(' },
284
+ { type: NUMBER, value: '1' },
285
+ { type: OPERATOR, value: ')' }
286
+ ]);
287
+ });
288
+
289
+ test('function with arguments and whitespace', () => {
290
+ isTokens('=SUM(1, 2)', [
291
+ { type: FX_PREFIX, value: '=' },
292
+ { type: FUNCTION, value: 'SUM' },
293
+ { type: OPERATOR, value: '(' },
294
+ { type: NUMBER, value: '1' },
295
+ { type: OPERATOR, value: ',' },
296
+ { type: WHITESPACE, value: ' ' },
297
+ { type: NUMBER, value: '2' },
298
+ { type: OPERATOR, value: ')' }
299
+ ]);
300
+ isTokens('=SUM(1, SUM(2, 3))', [
301
+ { type: FX_PREFIX, value: '=' },
302
+ { type: FUNCTION, value: 'SUM' },
303
+ { type: OPERATOR, value: '(' },
304
+ { type: NUMBER, value: '1' },
305
+ { type: OPERATOR, value: ',' },
306
+ { type: WHITESPACE, value: ' ' },
307
+ { type: FUNCTION, value: 'SUM' },
308
+ { type: OPERATOR, value: '(' },
309
+ { type: NUMBER, value: '2' },
310
+ { type: OPERATOR, value: ',' },
311
+ { type: WHITESPACE, value: ' ' },
312
+ { type: NUMBER, value: '3' },
313
+ { type: OPERATOR, value: ')' },
314
+ { type: OPERATOR, value: ')' }
315
+ ]);
316
+ });
317
+
318
+ test('special function names', () => {
319
+ isTokens('=INDIRECT("A1",TRUE)', [
320
+ { type: FX_PREFIX, value: '=' },
321
+ { type: FUNCTION, value: 'INDIRECT' },
322
+ { type: OPERATOR, value: '(' },
323
+ { type: STRING, value: '"A1"' },
324
+ { type: OPERATOR, value: ',' },
325
+ { type: BOOLEAN, value: 'TRUE' },
326
+ { type: OPERATOR, value: ')' }
327
+ ]);
328
+ isTokens('=BINOM.DIST.REF_RANGE(1)', [
329
+ { type: FX_PREFIX, value: '=' },
330
+ { type: FUNCTION, value: 'BINOM.DIST.REF_RANGE' },
331
+ { type: OPERATOR, value: '(' },
332
+ { type: NUMBER, value: '1' },
333
+ { type: OPERATOR, value: ')' }
334
+ ]);
335
+ isTokens('=OCT2BIN(1)', [
336
+ { type: FX_PREFIX, value: '=' },
337
+ { type: FUNCTION, value: 'OCT2BIN' },
338
+ { type: OPERATOR, value: '(' },
339
+ { type: NUMBER, value: '1' },
340
+ { type: OPERATOR, value: ')' }
341
+ ]);
342
+ isTokens('=TEST_FUNC(1)', [
343
+ { type: FX_PREFIX, value: '=' },
344
+ { type: FUNCTION, value: 'TEST_FUNC' },
345
+ { type: OPERATOR, value: '(' },
346
+ { type: NUMBER, value: '1' },
347
+ { type: OPERATOR, value: ')' }
348
+ ]);
349
+ isTokens('=_xlfn.FOO(1)', [
350
+ { type: FX_PREFIX, value: '=' },
351
+ { type: FUNCTION, value: '_xlfn.FOO' },
352
+ { type: OPERATOR, value: '(' },
353
+ { type: NUMBER, value: '1' },
354
+ { type: OPERATOR, value: ')' }
355
+ ]);
356
+ isTokens('=_FOO(1)', [
357
+ { type: FX_PREFIX, value: '=' },
358
+ { type: FUNCTION, value: '_FOO' },
359
+ { type: OPERATOR, value: '(' },
360
+ { type: NUMBER, value: '1' },
361
+ { type: OPERATOR, value: ')' }
362
+ ]);
363
+ });
364
+
365
+ test('named range vs function disambiguation', () => {
366
+ isTokens('=\\FOO(1)', [
367
+ { type: FX_PREFIX, value: '=' },
368
+ { type: REF_NAMED, value: '\\FOO' },
369
+ { type: OPERATOR, value: '(' },
370
+ { type: NUMBER, value: '1' },
371
+ { type: OPERATOR, value: ')' }
372
+ ]);
373
+ isTokens('=9FOO(1)', [
374
+ { type: FX_PREFIX, value: '=' },
375
+ { type: NUMBER, value: '9' },
376
+ { type: FUNCTION, value: 'FOO' },
377
+ { type: OPERATOR, value: '(' },
378
+ { type: NUMBER, value: '1' },
379
+ { type: OPERATOR, value: ')' }
380
+ ]);
381
+ });
382
+ });
383
+
384
+ describe('numbers', () => {
385
+ test('integers', () => {
386
+ isTokens('=0', [
387
+ { type: FX_PREFIX, value: '=' },
388
+ { type: NUMBER, value: '0' }
389
+ ]);
390
+ isTokens('=+0', [
391
+ { type: FX_PREFIX, value: '=' },
392
+ { type: OPERATOR, value: '+' },
393
+ { type: NUMBER, value: '0' }
394
+ ]);
395
+ isTokens('=+1', [
396
+ { type: FX_PREFIX, value: '=' },
397
+ { type: OPERATOR, value: '+' },
398
+ { type: NUMBER, value: '1' }
399
+ ]);
400
+ isTokens('=-0', [
401
+ { type: FX_PREFIX, value: '=' },
402
+ { type: OPERATOR, value: '-' },
403
+ { type: NUMBER, value: '0' }
404
+ ]);
405
+ isTokens('=1123', [
406
+ { type: FX_PREFIX, value: '=' },
407
+ { type: NUMBER, value: '1123' }
408
+ ]);
409
+ isTokens('=-1123', [
410
+ { type: FX_PREFIX, value: '=' },
411
+ { type: OPERATOR, value: '-' },
412
+ { type: NUMBER, value: '1123' }
413
+ ]);
414
+ });
415
+
416
+ test('decimals', () => {
417
+ isTokens('=1.5', [
418
+ { type: FX_PREFIX, value: '=' },
419
+ { type: NUMBER, value: '1.5' }
420
+ ]);
421
+ isTokens('=-1.5', [
422
+ { type: FX_PREFIX, value: '=' },
423
+ { type: OPERATOR, value: '-' },
424
+ { type: NUMBER, value: '1.5' }
425
+ ]);
426
+ isTokens('=1234.5678', [
427
+ { type: FX_PREFIX, value: '=' },
428
+ { type: NUMBER, value: '1234.5678' }
429
+ ]);
430
+ isTokens('=-1234.5678', [
431
+ { type: FX_PREFIX, value: '=' },
432
+ { type: OPERATOR, value: '-' },
433
+ { type: NUMBER, value: '1234.5678' }
434
+ ]);
435
+ });
436
+
437
+ test('scientific notation', () => {
438
+ isTokens('=1E-1', [
439
+ { type: FX_PREFIX, value: '=' },
440
+ { type: NUMBER, value: '1E-1' }
441
+ ]);
442
+ isTokens('=1.5E-10', [
443
+ { type: FX_PREFIX, value: '=' },
444
+ { type: NUMBER, value: '1.5E-10' }
445
+ ]);
446
+ isTokens('=1.55E+100', [
447
+ { type: FX_PREFIX, value: '=' },
448
+ { type: NUMBER, value: '1.55E+100' }
449
+ ]);
450
+ isTokens('=1.55e+100', [
451
+ { type: FX_PREFIX, value: '=' },
452
+ { type: NUMBER, value: '1.55e+100' }
453
+ ]);
454
+ });
455
+ });
456
+
457
+ describe('negative numbers', () => {
458
+ test('basic negative numbers', () => {
459
+ isTokensNeg('=-0', [
460
+ { type: FX_PREFIX, value: '=' },
461
+ { type: NUMBER, value: '-0' }
462
+ ]);
463
+ isTokensNeg('=-1123', [
464
+ { type: FX_PREFIX, value: '=' },
465
+ { type: NUMBER, value: '-1123' }
466
+ ]);
467
+ isTokensNeg('=-1.5', [
468
+ { type: FX_PREFIX, value: '=' },
469
+ { type: NUMBER, value: '-1.5' }
470
+ ]);
471
+ isTokensNeg('=-1234.5678', [
472
+ { type: FX_PREFIX, value: '=' },
473
+ { type: NUMBER, value: '-1234.5678' }
474
+ ]);
475
+ });
476
+
477
+ test('negative scientific notation', () => {
478
+ isTokensNeg('=1E-1', [
479
+ { type: FX_PREFIX, value: '=' },
480
+ { type: NUMBER, value: '1E-1' }
481
+ ]);
482
+ isTokensNeg('=-1E-1', [
483
+ { type: FX_PREFIX, value: '=' },
484
+ { type: NUMBER, value: '-1E-1' }
485
+ ]);
486
+ isTokensNeg('=1.5E-10', [
487
+ { type: FX_PREFIX, value: '=' },
488
+ { type: NUMBER, value: '1.5E-10' }
489
+ ]);
490
+ isTokensNeg('=-1.5E-10', [
491
+ { type: FX_PREFIX, value: '=' },
492
+ { type: NUMBER, value: '-1.5E-10' }
493
+ ]);
494
+ isTokensNeg('-1', [
495
+ { type: NUMBER, value: '-1' }
496
+ ]);
497
+ });
498
+
499
+ test('negative number context sensitivity', () => {
500
+ isTokensNeg('=1-1', [
501
+ { type: FX_PREFIX, value: '=' },
502
+ { type: NUMBER, value: '1' },
503
+ { type: OPERATOR, value: '-' },
504
+ { type: NUMBER, value: '1' }
505
+ ]);
506
+ isTokensNeg('1--1', [
507
+ { type: NUMBER, value: '1' },
508
+ { type: OPERATOR, value: '-' },
509
+ { type: NUMBER, value: '-1' }
510
+ ]);
511
+ isTokensNeg('1 - -1', [
512
+ { type: NUMBER, value: '1' },
513
+ { type: WHITESPACE, value: ' ' },
514
+ { type: OPERATOR, value: '-' },
515
+ { type: WHITESPACE, value: ' ' },
516
+ { type: NUMBER, value: '-1' }
517
+ ]);
518
+ isTokensNeg('1 - - 1', [
519
+ { type: NUMBER, value: '1' },
520
+ { type: WHITESPACE, value: ' ' },
521
+ { type: OPERATOR, value: '-' },
522
+ { type: WHITESPACE, value: ' ' },
523
+ { type: OPERATOR, value: '-' },
524
+ { type: WHITESPACE, value: ' ' },
525
+ { type: NUMBER, value: '1' }
526
+ ]);
527
+ });
528
+
529
+ test('negative numbers with newlines', () => {
530
+ isTokensNeg('1 \n - \n -1', [
531
+ { type: NUMBER, value: '1' },
532
+ { type: WHITESPACE, value: ' ' },
533
+ { type: NEWLINE, value: '\n' },
534
+ { type: WHITESPACE, value: ' ' },
535
+ { type: OPERATOR, value: '-' },
536
+ { type: WHITESPACE, value: ' ' },
537
+ { type: NEWLINE, value: '\n' },
538
+ { type: WHITESPACE, value: ' ' },
539
+ { type: NUMBER, value: '-1' }
540
+ ]);
541
+ });
542
+
543
+ test('negative numbers in parentheses', () => {
544
+ isTokensNeg('-(-1)', [
545
+ { type: OPERATOR, value: '-' },
546
+ { type: OPERATOR, value: '(' },
547
+ { type: NUMBER, value: '-1' },
548
+ { type: OPERATOR, value: ')' }
549
+ ]);
550
+ isTokensNeg('-( -1 )', [
551
+ { type: OPERATOR, value: '-' },
552
+ { type: OPERATOR, value: '(' },
553
+ { type: WHITESPACE, value: ' ' },
554
+ { type: NUMBER, value: '-1' },
555
+ { type: WHITESPACE, value: ' ' },
556
+ { type: OPERATOR, value: ')' }
557
+ ]);
558
+ });
559
+
560
+ test('negative numbers after other tokens', () => {
561
+ isTokensNeg('=true-1', [
562
+ { type: FX_PREFIX, value: '=' },
563
+ { type: BOOLEAN, value: 'true' },
564
+ { type: OPERATOR, value: '-' },
565
+ { type: NUMBER, value: '1' }
566
+ ]);
567
+ isTokensNeg('=true -1', [
568
+ { type: FX_PREFIX, value: '=' },
569
+ { type: BOOLEAN, value: 'true' },
570
+ { type: WHITESPACE, value: ' ' },
571
+ { type: OPERATOR, value: '-' },
572
+ { type: NUMBER, value: '1' }
573
+ ]);
574
+ isTokensNeg('=true - 1', [
575
+ { type: FX_PREFIX, value: '=' },
576
+ { type: BOOLEAN, value: 'true' },
577
+ { type: WHITESPACE, value: ' ' },
578
+ { type: OPERATOR, value: '-' },
579
+ { type: WHITESPACE, value: ' ' },
580
+ { type: NUMBER, value: '1' }
581
+ ]);
582
+ isTokensNeg('=#VALUE!-1', [
583
+ { type: FX_PREFIX, value: '=' },
584
+ { type: ERROR, value: '#VALUE!' },
585
+ { type: OPERATOR, value: '-' },
586
+ { type: NUMBER, value: '1' }
587
+ ]);
588
+ isTokensNeg('=#VALUE! -1', [
589
+ { type: FX_PREFIX, value: '=' },
590
+ { type: ERROR, value: '#VALUE!' },
591
+ { type: WHITESPACE, value: ' ' },
592
+ { type: OPERATOR, value: '-' },
593
+ { type: NUMBER, value: '1' }
594
+ ]);
595
+ });
596
+
597
+ test('negative numbers with functions and references', () => {
598
+ isTokensNeg('=SUM(-1) -1', [
599
+ { type: FX_PREFIX, value: '=' },
600
+ { type: FUNCTION, value: 'SUM' },
601
+ { type: OPERATOR, value: '(' },
602
+ { type: NUMBER, value: '-1' },
603
+ { type: OPERATOR, value: ')' },
604
+ { type: WHITESPACE, value: ' ' },
605
+ { type: OPERATOR, value: '-' },
606
+ { type: NUMBER, value: '1' }
607
+ ]);
608
+ isTokensNeg('=SUM( -1)-1', [
609
+ { type: FX_PREFIX, value: '=' },
610
+ { type: FUNCTION, value: 'SUM' },
611
+ { type: OPERATOR, value: '(' },
612
+ { type: WHITESPACE, value: ' ' },
613
+ { type: NUMBER, value: '-1' },
614
+ { type: OPERATOR, value: ')' },
615
+ { type: OPERATOR, value: '-' },
616
+ { type: NUMBER, value: '1' }
617
+ ]);
618
+ isTokensNeg('=A1-1', [
619
+ { type: FX_PREFIX, value: '=' },
620
+ { type: REF_RANGE, value: 'A1' },
621
+ { type: OPERATOR, value: '-' },
622
+ { type: NUMBER, value: '1' }
623
+ ]);
624
+ isTokensNeg('=A1 -1', [
625
+ { type: FX_PREFIX, value: '=' },
626
+ { type: REF_RANGE, value: 'A1' },
627
+ { type: WHITESPACE, value: ' ' },
628
+ { type: OPERATOR, value: '-' },
629
+ { type: NUMBER, value: '1' }
630
+ ]);
631
+ isTokensNeg('=foo-1', [
632
+ { type: FX_PREFIX, value: '=' },
633
+ { type: REF_NAMED, value: 'foo' },
634
+ { type: OPERATOR, value: '-' },
635
+ { type: NUMBER, value: '1' }
636
+ ]);
637
+ isTokensNeg('=foo -1', [
638
+ { type: FX_PREFIX, value: '=' },
639
+ { type: REF_NAMED, value: 'foo' },
640
+ { type: WHITESPACE, value: ' ' },
641
+ { type: OPERATOR, value: '-' },
642
+ { type: NUMBER, value: '1' }
643
+ ]);
644
+ isTokensNeg('="true"-1', [
645
+ { type: FX_PREFIX, value: '=' },
646
+ { type: STRING, value: '"true"' },
647
+ { type: OPERATOR, value: '-' },
648
+ { type: NUMBER, value: '1' }
649
+ ]);
650
+ isTokensNeg('="true" -1', [
651
+ { type: FX_PREFIX, value: '=' },
652
+ { type: STRING, value: '"true"' },
653
+ { type: WHITESPACE, value: ' ' },
654
+ { type: OPERATOR, value: '-' },
655
+ { type: NUMBER, value: '1' }
656
+ ]);
657
+ });
658
+
659
+ test('negative numbers with complex expressions', () => {
660
+ isTokensNeg('=SUM(1)-1', [
661
+ { type: FX_PREFIX, value: '=' },
662
+ { type: FUNCTION, value: 'SUM' },
663
+ { type: OPERATOR, value: '(' },
664
+ { type: NUMBER, value: '1' },
665
+ { type: OPERATOR, value: ')' },
666
+ { type: OPERATOR, value: '-' },
667
+ { type: NUMBER, value: '1' }
668
+ ]);
669
+ isTokensNeg('={1, 2, 3}-4', [
670
+ { type: FX_PREFIX, value: '=' },
671
+ { type: OPERATOR, value: '{' },
672
+ { type: NUMBER, value: '1' },
673
+ { type: OPERATOR, value: ',' },
674
+ { type: WHITESPACE, value: ' ' },
675
+ { type: NUMBER, value: '2' },
676
+ { type: OPERATOR, value: ',' },
677
+ { type: WHITESPACE, value: ' ' },
678
+ { type: NUMBER, value: '3' },
679
+ { type: OPERATOR, value: '}' },
680
+ { type: OPERATOR, value: '-' },
681
+ { type: NUMBER, value: '4' }
682
+ ]);
683
+ isTokensNeg('=10%-1', [
684
+ { type: FX_PREFIX, value: '=' },
685
+ { type: NUMBER, value: '10' },
686
+ { type: OPERATOR, value: '%' },
687
+ { type: OPERATOR, value: '-' },
688
+ { type: NUMBER, value: '1' }
689
+ ]);
690
+ isTokensNeg('=A1#-1', [
691
+ { type: FX_PREFIX, value: '=' },
692
+ { type: REF_RANGE, value: 'A1' },
693
+ { type: OPERATOR, value: '#' },
694
+ { type: OPERATOR, value: '-' },
695
+ { type: NUMBER, value: '1' }
696
+ ]);
697
+ });
698
+ });
699
+
700
+ describe('simple equations', () => {
701
+ test('basic arithmetic with spacing', () => {
702
+ isTokens('=1 + 2', [
703
+ { type: FX_PREFIX, value: '=' },
704
+ { type: NUMBER, value: '1' },
705
+ { type: WHITESPACE, value: ' ' },
706
+ { type: OPERATOR, value: '+' },
707
+ { type: WHITESPACE, value: ' ' },
708
+ { type: NUMBER, value: '2' }
709
+ ]);
710
+ isTokens('=1+2', [
711
+ { type: FX_PREFIX, value: '=' },
712
+ { type: NUMBER, value: '1' },
713
+ { type: OPERATOR, value: '+' },
714
+ { type: NUMBER, value: '2' }
715
+ ]);
716
+ isTokens('=1.1+2.2', [
717
+ { type: FX_PREFIX, value: '=' },
718
+ { type: NUMBER, value: '1.1' },
719
+ { type: OPERATOR, value: '+' },
720
+ { type: NUMBER, value: '2.2' }
721
+ ]);
722
+ });
723
+
724
+ test('parentheses and operator precedence', () => {
725
+ isTokens('=(1 + 2) - 3', [
726
+ { type: FX_PREFIX, value: '=' },
727
+ { type: OPERATOR, value: '(' },
728
+ { type: NUMBER, value: '1' },
729
+ { type: WHITESPACE, value: ' ' },
730
+ { type: OPERATOR, value: '+' },
731
+ { type: WHITESPACE, value: ' ' },
732
+ { type: NUMBER, value: '2' },
733
+ { type: OPERATOR, value: ')' },
734
+ { type: WHITESPACE, value: ' ' },
735
+ { type: OPERATOR, value: '-' },
736
+ { type: WHITESPACE, value: ' ' },
737
+ { type: NUMBER, value: '3' }
738
+ ]);
739
+ isTokens(' = ( 1.1+2 ) - 3 ', [
740
+ { type: WHITESPACE, value: ' ' },
741
+ { type: OPERATOR, value: '=' }, // FX_PREFIX?
742
+ { type: WHITESPACE, value: ' ' },
743
+ { type: OPERATOR, value: '(' },
744
+ { type: WHITESPACE, value: ' ' },
745
+ { type: NUMBER, value: '1.1' },
746
+ { type: OPERATOR, value: '+' },
747
+ { type: NUMBER, value: '2' },
748
+ { type: WHITESPACE, value: ' ' },
749
+ { type: OPERATOR, value: ')' },
750
+ { type: WHITESPACE, value: ' ' },
751
+ { type: OPERATOR, value: '-' },
752
+ { type: WHITESPACE, value: ' ' },
753
+ { type: NUMBER, value: '3' },
754
+ { type: WHITESPACE, value: ' ' }
755
+ ]);
756
+ });
757
+
758
+ test('multiplication and formula prefix', () => {
759
+ isTokens('=1+2*3', [
760
+ { type: FX_PREFIX, value: '=' },
761
+ { type: NUMBER, value: '1' },
762
+ { type: OPERATOR, value: '+' },
763
+ { type: NUMBER, value: '2' },
764
+ { type: OPERATOR, value: '*' },
765
+ { type: NUMBER, value: '3' }
766
+ ]);
767
+ isTokens('= 1+2*3', [
768
+ { type: FX_PREFIX, value: '=' },
769
+ { type: WHITESPACE, value: ' ' },
770
+ { type: NUMBER, value: '1' },
771
+ { type: OPERATOR, value: '+' },
772
+ { type: NUMBER, value: '2' },
773
+ { type: OPERATOR, value: '*' },
774
+ { type: NUMBER, value: '3' }
775
+ ]);
776
+ });
777
+
778
+ test('percentage operator', () => {
779
+ isTokens('=1%', [
780
+ { type: FX_PREFIX, value: '=' },
781
+ { type: NUMBER, value: '1' },
782
+ { type: OPERATOR, value: '%' }
783
+ ]);
784
+ isTokens('=-1%', [
785
+ { type: FX_PREFIX, value: '=' },
786
+ { type: OPERATOR, value: '-' },
787
+ { type: NUMBER, value: '1' },
788
+ { type: OPERATOR, value: '%' }
789
+ ]);
790
+ isTokens('=-(1 + 2)%', [
791
+ { type: FX_PREFIX, value: '=' },
792
+ { type: OPERATOR, value: '-' },
793
+ { type: OPERATOR, value: '(' },
794
+ { type: NUMBER, value: '1' },
795
+ { type: WHITESPACE, value: ' ' },
796
+ { type: OPERATOR, value: '+' },
797
+ { type: WHITESPACE, value: ' ' },
798
+ { type: NUMBER, value: '2' },
799
+ { type: OPERATOR, value: ')' },
800
+ { type: OPERATOR, value: '%' }
801
+ ]);
802
+ });
803
+ });
804
+
805
+ describe('R1C1 style references', () => {
806
+ test('basic row and column references', () => {
807
+ isTokens('=R', [
808
+ { type: FX_PREFIX, value: '=' },
809
+ { type: REF_BEAM, value: 'R' }
810
+ ], { r1c1: true });
811
+ isTokens('=R:R', [
812
+ { type: FX_PREFIX, value: '=' },
813
+ { type: REF_BEAM, value: 'R:R' }
814
+ ], { r1c1: true });
815
+
816
+ isTokens('=R1', [
817
+ { type: FX_PREFIX, value: '=' },
818
+ { type: REF_BEAM, value: 'R1' }
819
+ ], { r1c1: true });
820
+ isTokens('=R1:R1', [
821
+ { type: FX_PREFIX, value: '=' },
822
+ { type: REF_BEAM, value: 'R1:R1' }
823
+ ], { r1c1: true });
824
+
825
+ isTokens('=C', [
826
+ { type: FX_PREFIX, value: '=' },
827
+ { type: REF_BEAM, value: 'C' }
828
+ ], { r1c1: true });
829
+ isTokens('=C:C', [
830
+ { type: FX_PREFIX, value: '=' },
831
+ { type: REF_BEAM, value: 'C:C' }
832
+ ], { r1c1: true });
833
+
834
+ isTokens('=C1', [
835
+ { type: FX_PREFIX, value: '=' },
836
+ { type: REF_BEAM, value: 'C1' }
837
+ ], { r1c1: true });
838
+ isTokens('=C1:C1', [
839
+ { type: FX_PREFIX, value: '=' },
840
+ { type: REF_BEAM, value: 'C1:C1' }
841
+ ], { r1c1: true });
842
+ });
843
+
844
+ test('relative references with brackets', () => {
845
+ isTokens('=R[1]', [
846
+ { type: FX_PREFIX, value: '=' },
847
+ { type: REF_BEAM, value: 'R[1]' }
848
+ ], { r1c1: true });
849
+ isTokens('=R[1]:R[1]', [
850
+ { type: FX_PREFIX, value: '=' },
851
+ { type: REF_BEAM, value: 'R[1]:R[1]' }
852
+ ], { r1c1: true });
853
+
854
+ isTokens('=R[-1]', [
855
+ { type: FX_PREFIX, value: '=' },
856
+ { type: REF_BEAM, value: 'R[-1]' }
857
+ ], { r1c1: true });
858
+ isTokens('=R[-1]:R[-1]', [
859
+ { type: FX_PREFIX, value: '=' },
860
+ { type: REF_BEAM, value: 'R[-1]:R[-1]' }
861
+ ], { r1c1: true });
862
+
863
+ isTokens('=C[1]', [
864
+ { type: FX_PREFIX, value: '=' },
865
+ { type: REF_BEAM, value: 'C[1]' }
866
+ ], { r1c1: true });
867
+ isTokens('=C[1]:C[1]', [
868
+ { type: FX_PREFIX, value: '=' },
869
+ { type: REF_BEAM, value: 'C[1]:C[1]' }
870
+ ], { r1c1: true });
871
+
872
+ isTokens('=C[-1]', [
873
+ { type: FX_PREFIX, value: '=' },
874
+ { type: REF_BEAM, value: 'C[-1]' }
875
+ ], { r1c1: true });
876
+ isTokens('=C[-1]:C[-1]', [
877
+ { type: FX_PREFIX, value: '=' },
878
+ { type: REF_BEAM, value: 'C[-1]:C[-1]' }
879
+ ], { r1c1: true });
880
+ });
881
+
882
+ test('cell references', () => {
883
+ isTokens('=RC', [
884
+ { type: FX_PREFIX, value: '=' },
885
+ { type: REF_RANGE, value: 'RC' }
886
+ ], { r1c1: true });
887
+ isTokens('=RC:RC', [
888
+ { type: FX_PREFIX, value: '=' },
889
+ { type: REF_RANGE, value: 'RC:RC' }
890
+ ], { r1c1: true });
891
+
892
+ isTokens('=R1C1', [
893
+ { type: FX_PREFIX, value: '=' },
894
+ { type: REF_RANGE, value: 'R1C1' }
895
+ ], { r1c1: true });
896
+ isTokens('=R1C1:R1C1', [
897
+ { type: FX_PREFIX, value: '=' },
898
+ { type: REF_RANGE, value: 'R1C1:R1C1' }
899
+ ], { r1c1: true });
900
+ });
901
+
902
+ test('mixed absolute and relative references', () => {
903
+ isTokens('=R[2]C', [
904
+ { type: FX_PREFIX, value: '=' },
905
+ { type: REF_RANGE, value: 'R[2]C' }
906
+ ], { r1c1: true });
907
+ isTokens('=R[2]C:R[2]C', [
908
+ { type: FX_PREFIX, value: '=' },
909
+ { type: REF_RANGE, value: 'R[2]C:R[2]C' }
910
+ ], { r1c1: true });
911
+
912
+ isTokens('=R[-2]C', [
913
+ { type: FX_PREFIX, value: '=' },
914
+ { type: REF_RANGE, value: 'R[-2]C' }
915
+ ], { r1c1: true });
916
+ isTokens('=R[-2]C:R[-2]C', [
917
+ { type: FX_PREFIX, value: '=' },
918
+ { type: REF_RANGE, value: 'R[-2]C:R[-2]C' }
919
+ ], { r1c1: true });
920
+
921
+ isTokens('=RC[3]', [
922
+ { type: FX_PREFIX, value: '=' },
923
+ { type: REF_RANGE, value: 'RC[3]' }
924
+ ], { r1c1: true });
925
+ isTokens('=RC[3]:RC[3]', [
926
+ { type: FX_PREFIX, value: '=' },
927
+ { type: REF_RANGE, value: 'RC[3]:RC[3]' }
928
+ ], { r1c1: true });
929
+
930
+ isTokens('=RC[-3]', [
931
+ { type: FX_PREFIX, value: '=' },
932
+ { type: REF_RANGE, value: 'RC[-3]' }
933
+ ], { r1c1: true });
934
+ isTokens('=RC[-3]:RC[-3]', [
935
+ { type: FX_PREFIX, value: '=' },
936
+ { type: REF_RANGE, value: 'RC[-3]:RC[-3]' }
937
+ ], { r1c1: true });
938
+ });
939
+
940
+ test('complex relative references', () => {
941
+ isTokens('=R[2]C[2]', [
942
+ { type: FX_PREFIX, value: '=' },
943
+ { type: REF_RANGE, value: 'R[2]C[2]' }
944
+ ], { r1c1: true });
945
+ isTokens('=R[2]C[2]:R[2]C[2]', [
946
+ { type: FX_PREFIX, value: '=' },
947
+ { type: REF_RANGE, value: 'R[2]C[2]:R[2]C[2]' }
948
+ ], { r1c1: true });
949
+
950
+ isTokens('=R[-2]C[-2]', [
951
+ { type: FX_PREFIX, value: '=' },
952
+ { type: REF_RANGE, value: 'R[-2]C[-2]' }
953
+ ], { r1c1: true });
954
+ isTokens('=R[-2]C[-2]:R[-1]C[-1]', [
955
+ { type: FX_PREFIX, value: '=' },
956
+ { type: REF_RANGE, value: 'R[-2]C[-2]:R[-1]C[-1]' }
957
+ ], { r1c1: true });
958
+ });
959
+
960
+ test('external references', () => {
961
+ isTokens('=[filename]Sheetname!R[-2]C:R[-1]C', [
962
+ { type: FX_PREFIX, value: '=' },
963
+ { type: REF_RANGE, value: '[filename]Sheetname!R[-2]C:R[-1]C' }
964
+ ], { r1c1: true });
965
+
966
+ isTokens('=[filename]Sheetname!R[-2]C:R[-1]C', [
967
+ { type: FX_PREFIX, value: '=' },
968
+ { type: CONTEXT, value: '[filename]Sheetname' },
969
+ { type: OPERATOR, value: '!' },
970
+ { type: REF_RANGE, value: 'R[-2]C' },
971
+ { type: OPERATOR, value: ':' },
972
+ { type: REF_RANGE, value: 'R[-1]C' }
973
+ ], { mergeRefs: false, r1c1: true });
974
+ });
975
+
976
+ test('ranges and mixed types', () => {
977
+ isTokens('=R[-2]C[-2]:R[-1]C[-1]', [
978
+ { type: FX_PREFIX, value: '=' },
979
+ { type: REF_RANGE, value: 'R[-2]C[-2]:R[-1]C[-1]' }
980
+ ], { r1c1: true });
981
+ isTokens('=R[-2]:R1', [
982
+ { type: FX_PREFIX, value: '=' },
983
+ { type: REF_BEAM, value: 'R[-2]:R1' }
984
+ ], { r1c1: true });
985
+ });
986
+
987
+ test('incompatible range combinations', () => {
988
+ isTokens('=R:C', [
989
+ { type: FX_PREFIX, value: '=' },
990
+ { type: REF_BEAM, value: 'R' },
991
+ { type: OPERATOR, value: ':' },
992
+ { type: REF_BEAM, value: 'C' }
993
+ ], { r1c1: true });
994
+ isTokens('=C[1]:R[-2]', [
995
+ { type: FX_PREFIX, value: '=' },
996
+ { type: REF_BEAM, value: 'C[1]' },
997
+ { type: OPERATOR, value: ':' },
998
+ { type: REF_BEAM, value: 'R[-2]' }
999
+ ], { r1c1: true });
1000
+ isTokens('=R1:RC', [
1001
+ { type: FX_PREFIX, value: '=' },
1002
+ { type: REF_BEAM, value: 'R1' },
1003
+ { type: OPERATOR, value: ':' },
1004
+ { type: REF_RANGE, value: 'RC' }
1005
+ ], { r1c1: true });
1006
+ isTokens('=RC:C1', [
1007
+ { type: FX_PREFIX, value: '=' },
1008
+ { type: REF_RANGE, value: 'RC' },
1009
+ { type: OPERATOR, value: ':' },
1010
+ { type: REF_BEAM, value: 'C1' }
1011
+ ], { r1c1: true });
1012
+ });
1013
+ });
1014
+
1015
+ describe('A1 style references', () => {
1016
+ test('basic cell references', () => {
1017
+ isTokens('=A1', [
1018
+ { type: FX_PREFIX, value: '=' },
1019
+ { type: REF_RANGE, value: 'A1' }
1020
+ ]);
1021
+
1022
+ isTokens('=C1', [
1023
+ { type: FX_PREFIX, value: '=' },
1024
+ { type: REF_RANGE, value: 'C1' }
1025
+ ]);
1026
+
1027
+ isTokens('=R1', [
1028
+ { type: FX_PREFIX, value: '=' },
1029
+ { type: REF_RANGE, value: 'R1' }
1030
+ ]);
1031
+ });
1032
+
1033
+ test('absolute references', () => {
1034
+ isTokens('=$A$1', [
1035
+ { type: FX_PREFIX, value: '=' },
1036
+ { type: REF_RANGE, value: '$A$1' }
1037
+ ]);
1038
+
1039
+ isTokens('=A$1', [
1040
+ { type: FX_PREFIX, value: '=' },
1041
+ { type: REF_RANGE, value: 'A$1' }
1042
+ ]);
1043
+
1044
+ isTokens('=$A1', [
1045
+ { type: FX_PREFIX, value: '=' },
1046
+ { type: REF_RANGE, value: '$A1' }
1047
+ ]);
1048
+ });
1049
+
1050
+ test('ranges', () => {
1051
+ isTokens('=A10:A20', [
1052
+ { type: FX_PREFIX, value: '=' },
1053
+ { type: REF_RANGE, value: 'A10:A20' }
1054
+ ]);
1055
+
1056
+ isTokens('=A10:E20', [
1057
+ { type: FX_PREFIX, value: '=' },
1058
+ { type: REF_RANGE, value: 'A10:E20' }
1059
+ ]);
1060
+
1061
+ isTokens('=A1:C1', [
1062
+ { type: FX_PREFIX, value: '=' },
1063
+ { type: REF_RANGE, value: 'A1:C1' }
1064
+ ]);
1065
+ });
1066
+
1067
+ test('spill range syntax', () => {
1068
+ isTokens('=A10.:A20', [
1069
+ { type: FX_PREFIX, value: '=' },
1070
+ { type: REF_RANGE, value: 'A10.:A20' }
1071
+ ]);
1072
+
1073
+ isTokens('=A10:.A20', [
1074
+ { type: FX_PREFIX, value: '=' },
1075
+ { type: REF_RANGE, value: 'A10:.A20' }
1076
+ ]);
1077
+
1078
+ isTokens('=A10.:.A20', [
1079
+ { type: FX_PREFIX, value: '=' },
1080
+ { type: REF_RANGE, value: 'A10.:.A20' }
1081
+ ]);
1082
+ });
1083
+
1084
+ test('row and column references', () => {
1085
+ isTokens('=5:5', [
1086
+ { type: FX_PREFIX, value: '=' },
1087
+ { type: REF_BEAM, value: '5:5' }
1088
+ ]);
1089
+
1090
+ isTokens('=A:A', [
1091
+ { type: FX_PREFIX, value: '=' },
1092
+ { type: REF_BEAM, value: 'A:A' }
1093
+ ]);
1094
+
1095
+ isTokens('=$A:$A', [
1096
+ { type: FX_PREFIX, value: '=' },
1097
+ { type: REF_BEAM, value: '$A:$A' }
1098
+ ]);
1099
+
1100
+ isTokens('=1:5', [
1101
+ { type: FX_PREFIX, value: '=' },
1102
+ { type: REF_BEAM, value: '1:5' }
1103
+ ]);
1104
+
1105
+ isTokens('=A:E', [
1106
+ { type: FX_PREFIX, value: '=' },
1107
+ { type: REF_BEAM, value: 'A:E' }
1108
+ ]);
1109
+
1110
+ isTokens('=$1:$5', [
1111
+ { type: FX_PREFIX, value: '=' },
1112
+ { type: REF_BEAM, value: '$1:$5' }
1113
+ ]);
1114
+
1115
+ isTokens('=$A:$E', [
1116
+ { type: FX_PREFIX, value: '=' },
1117
+ { type: REF_BEAM, value: '$A:$E' }
1118
+ ]);
1119
+ });
1120
+
1121
+ test('sheet references', () => {
1122
+ isTokens('=Sheet1!A1', [
1123
+ { type: FX_PREFIX, value: '=' },
1124
+ { type: CONTEXT, value: 'Sheet1' },
1125
+ { type: OPERATOR, value: '!' },
1126
+ { type: REF_RANGE, value: 'A1' }
1127
+ ], { mergeRefs: false });
1128
+ isTokens('=Sheet1!A1', [
1129
+ { type: FX_PREFIX, value: '=' },
1130
+ { type: REF_RANGE, value: 'Sheet1!A1' }
1131
+ ]);
1132
+
1133
+ isTokens('=Sheet1!A1:B2', [
1134
+ { type: FX_PREFIX, value: '=' },
1135
+ { type: CONTEXT, value: 'Sheet1' },
1136
+ { type: OPERATOR, value: '!' },
1137
+ { type: REF_RANGE, value: 'A1' },
1138
+ { type: OPERATOR, value: ':' },
1139
+ { type: REF_RANGE, value: 'B2' }
1140
+ ], { mergeRefs: false });
1141
+ isTokens('=Sheet1!A1:B2', [
1142
+ { type: FX_PREFIX, value: '=' },
1143
+ { type: REF_RANGE, value: 'Sheet1!A1:B2' }
1144
+ ]);
1145
+ });
1146
+
1147
+ test('quoted sheet names', () => {
1148
+ isTokens("='Sheets'' name'!A1:B2", [
1149
+ { type: FX_PREFIX, value: '=' },
1150
+ { type: CONTEXT_QUOTE, value: "'Sheets'' name'" },
1151
+ { type: OPERATOR, value: '!' },
1152
+ { type: REF_RANGE, value: 'A1' },
1153
+ { type: OPERATOR, value: ':' },
1154
+ { type: REF_RANGE, value: 'B2' }
1155
+ ], { mergeRefs: false });
1156
+
1157
+ isTokens("='Run forest, run!'!A1", [
1158
+ { type: FX_PREFIX, value: '=' },
1159
+ { type: REF_RANGE, value: '\'Run forest, run!\'!A1' }
1160
+ ]);
1161
+
1162
+ isTokens("='Run forest, run!'!A1", [
1163
+ { type: FX_PREFIX, value: '=' },
1164
+ { type: CONTEXT_QUOTE, value: "'Run forest, run!'" },
1165
+ { type: OPERATOR, value: '!' },
1166
+ { type: REF_RANGE, value: 'A1' }
1167
+ ], { mergeRefs: false });
1168
+
1169
+ isTokens("='foo'''!A1", [
1170
+ { type: FX_PREFIX, value: '=' },
1171
+ { type: REF_RANGE, value: "'foo'''!A1" }
1172
+ ]);
1173
+
1174
+ isTokens("='foo'''!A1", [
1175
+ { type: FX_PREFIX, value: '=' },
1176
+ { type: CONTEXT_QUOTE, value: "'foo'''" },
1177
+ { type: OPERATOR, value: '!' },
1178
+ { type: REF_RANGE, value: 'A1' }
1179
+ ], { mergeRefs: false });
1180
+
1181
+ isTokens("='foo'''''!A1", [
1182
+ { type: FX_PREFIX, value: '=' },
1183
+ { type: REF_RANGE, value: "'foo'''''!A1" }
1184
+ ]);
1185
+
1186
+ isTokens("='foo'''''!A1", [
1187
+ { type: FX_PREFIX, value: '=' },
1188
+ { type: CONTEXT_QUOTE, value: "'foo'''''" },
1189
+ { type: OPERATOR, value: '!' },
1190
+ { type: REF_RANGE, value: 'A1' }
1191
+ ], { mergeRefs: false });
1192
+ });
1193
+
1194
+ test('external workbook references', () => {
1195
+ isTokens('=[filename]Sheetname!A1', [
1196
+ { type: FX_PREFIX, value: '=' },
1197
+ { type: REF_RANGE, value: '[filename]Sheetname!A1' }
1198
+ ]);
1199
+
1200
+ isTokens('=[filename]Sheetname!A1', [
1201
+ { type: FX_PREFIX, value: '=' },
1202
+ { type: CONTEXT, value: '[filename]Sheetname' },
1203
+ { type: OPERATOR, value: '!' },
1204
+ { type: REF_RANGE, value: 'A1' }
1205
+ ], { mergeRefs: false });
1206
+
1207
+ isTokens("='[filename]Sheets'' name'!A1:B2", [
1208
+ { type: FX_PREFIX, value: '=' },
1209
+ { type: REF_RANGE, value: "'[filename]Sheets'' name'!A1:B2" }
1210
+ ]);
1211
+
1212
+ isTokens("='[filename]Sheets'' name'!A1:B2", [
1213
+ { type: FX_PREFIX, value: '=' },
1214
+ { type: CONTEXT_QUOTE, value: "'[filename]Sheets'' name'" },
1215
+ { type: OPERATOR, value: '!' },
1216
+ { type: REF_RANGE, value: 'A1' },
1217
+ { type: OPERATOR, value: ':' },
1218
+ { type: REF_RANGE, value: 'B2' }
1219
+ ], { mergeRefs: false });
1220
+
1221
+ isTokens('=[15]Sheet32!X4', [
1222
+ { type: FX_PREFIX, value: '=' },
1223
+ { type: REF_RANGE, value: '[15]Sheet32!X4' }
1224
+ ]);
1225
+
1226
+ isTokens('=[15]Sheet32!X4', [
1227
+ { type: FX_PREFIX, value: '=' },
1228
+ { type: CONTEXT, value: '[15]Sheet32' },
1229
+ { type: OPERATOR, value: '!' },
1230
+ { type: REF_RANGE, value: 'X4' }
1231
+ ], { mergeRefs: false });
1232
+ });
1233
+
1234
+ test('illegal syntax handling', () => {
1235
+ isTokens('=[15]!named', [
1236
+ { type: FX_PREFIX, value: '=' },
1237
+ { type: UNKNOWN, value: '[' },
1238
+ { type: NUMBER, value: '15' },
1239
+ { type: UNKNOWN, value: ']' },
1240
+ { type: OPERATOR, value: '!' },
1241
+ { type: REF_NAMED, value: 'named' }
1242
+ ]);
1243
+
1244
+ isTokens('=filename!named', [
1245
+ { type: FX_PREFIX, value: '=' },
1246
+ { type: REF_NAMED, value: 'filename!named' }
1247
+ ]);
1248
+ isTokens('=filename!named', [
1249
+ { type: FX_PREFIX, value: '=' },
1250
+ { type: CONTEXT, value: 'filename' },
1251
+ { type: OPERATOR, value: '!' },
1252
+ { type: REF_NAMED, value: 'named' }
1253
+ ], { mergeRefs: false });
1254
+ });
1255
+
1256
+ test('maximum reference bounds', () => {
1257
+ isTokens('=XFD1048576', [
1258
+ { type: FX_PREFIX, value: '=' },
1259
+ { type: REF_RANGE, value: 'XFD1048576' }
1260
+ ]);
1261
+ isTokens('=XFD1048577', [
1262
+ { type: FX_PREFIX, value: '=' },
1263
+ { type: REF_NAMED, value: 'XFD1048577' }
1264
+ ]);
1265
+ isTokens('=XFE1048577', [
1266
+ { type: FX_PREFIX, value: '=' },
1267
+ { type: REF_NAMED, value: 'XFE1048577' }
1268
+ ]);
1269
+ isTokens('=pensioneligibilitypartner1', [
1270
+ { type: FX_PREFIX, value: '=' },
1271
+ { type: REF_NAMED, value: 'pensioneligibilitypartner1' }
1272
+ ]);
1273
+ });
1274
+
1275
+ test('file path references', () => {
1276
+ isTokens("='D:\\Reports\\Sales.xlsx'!namedrange", [
1277
+ { type: FX_PREFIX, value: '=' },
1278
+ { type: CONTEXT_QUOTE, value: "'D:\\Reports\\Sales.xlsx'" },
1279
+ { type: OPERATOR, value: '!' },
1280
+ { type: REF_NAMED, value: 'namedrange' }
1281
+ ], { mergeRefs: false });
1282
+ isTokens("='D:\\Reports\\Sales.xlsx'!namedrange", [
1283
+ { type: FX_PREFIX, value: '=' },
1284
+ { type: REF_NAMED, value: "'D:\\Reports\\Sales.xlsx'!namedrange" }
1285
+ ]);
1286
+
1287
+ isTokens('=Sales.xlsx!namedrange', [
1288
+ { type: FX_PREFIX, value: '=' },
1289
+ { type: CONTEXT, value: 'Sales.xlsx' },
1290
+ { type: OPERATOR, value: '!' },
1291
+ { type: REF_NAMED, value: 'namedrange' }
1292
+ ], { mergeRefs: false });
1293
+ isTokens('=Sales.xlsx!namedrange', [
1294
+ { type: FX_PREFIX, value: '=' },
1295
+ { type: REF_NAMED, value: 'Sales.xlsx!namedrange' }
1296
+ ]);
1297
+ });
1298
+
1299
+ test('column and row beam references with sheets', () => {
1300
+ isTokens('=Sheet1!A:A', [
1301
+ { type: FX_PREFIX, value: '=' },
1302
+ { type: REF_BEAM, value: 'Sheet1!A:A' }
1303
+ ]);
1304
+ isTokens('=Sheet1!A:A', [
1305
+ { type: FX_PREFIX, value: '=' },
1306
+ { type: CONTEXT, value: 'Sheet1' },
1307
+ { type: OPERATOR, value: '!' },
1308
+ { type: REF_BEAM, value: 'A:A' }
1309
+ ], { mergeRefs: false });
1310
+
1311
+ isTokens('=Sheet1!A:A:B:B', [
1312
+ { type: FX_PREFIX, value: '=' },
1313
+ { type: REF_BEAM, value: 'Sheet1!A:A' },
1314
+ { type: OPERATOR, value: ':' },
1315
+ { type: REF_BEAM, value: 'B:B' }
1316
+ ]);
1317
+ isTokens('=Sheet1!A:A:B:B', [
1318
+ { type: FX_PREFIX, value: '=' },
1319
+ { type: CONTEXT, value: 'Sheet1' },
1320
+ { type: OPERATOR, value: '!' },
1321
+ { type: REF_BEAM, value: 'A:A' },
1322
+ { type: OPERATOR, value: ':' },
1323
+ { type: REF_BEAM, value: 'B:B' }
1324
+ ], { mergeRefs: false });
1325
+
1326
+ isTokens('=Sheet1!A.:.A:B.:.B', [
1327
+ { type: FX_PREFIX, value: '=' },
1328
+ { type: CONTEXT, value: 'Sheet1' },
1329
+ { type: OPERATOR, value: '!' },
1330
+ { type: REF_BEAM, value: 'A.:.A' },
1331
+ { type: OPERATOR, value: ':' },
1332
+ { type: REF_BEAM, value: 'B.:.B' }
1333
+ ], { mergeRefs: false });
1334
+ });
1335
+
1336
+ test('error references', () => {
1337
+ isTokens('=Sheet1!#REF!:A1', [
1338
+ { type: FX_PREFIX, value: '=' },
1339
+ { type: CONTEXT, value: 'Sheet1' },
1340
+ { type: OPERATOR, value: '!' },
1341
+ { type: ERROR, value: '#REF!' },
1342
+ { type: OPERATOR, value: ':' },
1343
+ { type: REF_RANGE, value: 'A1' }
1344
+ ]);
1345
+ });
1346
+ });
1347
+
1348
+ describe('errors', () => {
1349
+ test('standard errors', () => {
1350
+ isTokens('=#NAME?', [
1351
+ { type: FX_PREFIX, value: '=' },
1352
+ { type: ERROR, value: '#NAME?' }
1353
+ ]);
1354
+ isTokens('=#VALUE!', [
1355
+ { type: FX_PREFIX, value: '=' },
1356
+ { type: ERROR, value: '#VALUE!' }
1357
+ ]);
1358
+ isTokens('=#REF!', [
1359
+ { type: FX_PREFIX, value: '=' },
1360
+ { type: ERROR, value: '#REF!' }
1361
+ ]);
1362
+ isTokens('=#DIV/0!', [
1363
+ { type: FX_PREFIX, value: '=' },
1364
+ { type: ERROR, value: '#DIV/0!' }
1365
+ ]);
1366
+ isTokens('=#NULL!', [
1367
+ { type: FX_PREFIX, value: '=' },
1368
+ { type: ERROR, value: '#NULL!' }
1369
+ ]);
1370
+ isTokens('=#NUM!', [
1371
+ { type: FX_PREFIX, value: '=' },
1372
+ { type: ERROR, value: '#NUM!' }
1373
+ ]);
1374
+ isTokens('=#N/A', [
1375
+ { type: FX_PREFIX, value: '=' },
1376
+ { type: ERROR, value: '#N/A' }
1377
+ ]);
1378
+ });
1379
+
1380
+ test('dynamic array and advanced errors', () => {
1381
+ isTokens('=#GETTING_DATA', [
1382
+ { type: FX_PREFIX, value: '=' },
1383
+ { type: ERROR, value: '#GETTING_DATA' }
1384
+ ]);
1385
+ isTokens('=#SPILL!', [
1386
+ { type: FX_PREFIX, value: '=' },
1387
+ { type: ERROR, value: '#SPILL!' }
1388
+ ]);
1389
+ isTokens('=#UNKNOWN!', [
1390
+ { type: FX_PREFIX, value: '=' },
1391
+ { type: ERROR, value: '#UNKNOWN!' }
1392
+ ]);
1393
+ isTokens('=#FIELD!', [
1394
+ { type: FX_PREFIX, value: '=' },
1395
+ { type: ERROR, value: '#FIELD!' }
1396
+ ]);
1397
+ isTokens('=#CALC!', [
1398
+ { type: FX_PREFIX, value: '=' },
1399
+ { type: ERROR, value: '#CALC!' }
1400
+ ]);
1401
+ isTokens('=#SYNTAX?', [
1402
+ { type: FX_PREFIX, value: '=' },
1403
+ { type: ERROR, value: '#SYNTAX?' }
1404
+ ]);
1405
+ isTokens('=#ERROR!', [
1406
+ { type: FX_PREFIX, value: '=' },
1407
+ { type: ERROR, value: '#ERROR!' }
1408
+ ]);
1409
+ isTokens('=#CONNECT!', [
1410
+ { type: FX_PREFIX, value: '=' },
1411
+ { type: ERROR, value: '#CONNECT!' }
1412
+ ]);
1413
+ isTokens('=#BLOCKED!', [
1414
+ { type: FX_PREFIX, value: '=' },
1415
+ { type: ERROR, value: '#BLOCKED!' }
1416
+ ]);
1417
+ isTokens('=#EXTERNAL!', [
1418
+ { type: FX_PREFIX, value: '=' },
1419
+ { type: ERROR, value: '#EXTERNAL!' }
1420
+ ]);
1421
+ });
1422
+
1423
+ test('unrecognized error syntax', () => {
1424
+ isTokens('=#NONSENSE!', [
1425
+ { type: FX_PREFIX, value: '=' },
1426
+ { type: OPERATOR, value: '#' },
1427
+ { type: CONTEXT, value: 'NONSENSE' },
1428
+ { type: OPERATOR, value: '!' }
1429
+ ]);
1430
+ });
1431
+ });
1432
+
1433
+ describe('booleans', () => {
1434
+ test('true values', () => {
1435
+ isTokens('=true', [
1436
+ { type: FX_PREFIX, value: '=' },
1437
+ { type: BOOLEAN, value: 'true' }
1438
+ ]);
1439
+ isTokens('=tRuE', [
1440
+ { type: FX_PREFIX, value: '=' },
1441
+ { type: BOOLEAN, value: 'tRuE' }
1442
+ ]);
1443
+ isTokens('=TRUE', [
1444
+ { type: FX_PREFIX, value: '=' },
1445
+ { type: BOOLEAN, value: 'TRUE' }
1446
+ ]);
1447
+ isTokens('true!A1', [
1448
+ { type: BOOLEAN, value: 'true' },
1449
+ { type: OPERATOR, value: '!' },
1450
+ { type: REF_RANGE, value: 'A1' }
1451
+ ]);
1452
+ isTokens('truesheet!A1', [
1453
+ { type: REF_RANGE, value: 'truesheet!A1' }
1454
+ ]);
1455
+ isTokens('true()', [
1456
+ { type: FUNCTION, value: 'true' },
1457
+ { type: OPERATOR, value: '(' },
1458
+ { type: OPERATOR, value: ')' }
1459
+ ]);
1460
+ });
1461
+
1462
+ test('false values', () => {
1463
+ isTokens('=false', [
1464
+ { type: FX_PREFIX, value: '=' },
1465
+ { type: BOOLEAN, value: 'false' }
1466
+ ]);
1467
+ isTokens('=fAlSe', [
1468
+ { type: FX_PREFIX, value: '=' },
1469
+ { type: BOOLEAN, value: 'fAlSe' }
1470
+ ]);
1471
+ isTokens('=FALSE', [
1472
+ { type: FX_PREFIX, value: '=' },
1473
+ { type: BOOLEAN, value: 'FALSE' }
1474
+ ]);
1475
+ isTokens('false!A1', [
1476
+ { type: BOOLEAN, value: 'false' },
1477
+ { type: OPERATOR, value: '!' },
1478
+ { type: REF_RANGE, value: 'A1' }
1479
+ ]);
1480
+ isTokens('falsesheet!A1', [
1481
+ { type: REF_RANGE, value: 'falsesheet!A1' }
1482
+ ]);
1483
+ isTokens('false()', [
1484
+ { type: FUNCTION, value: 'false' },
1485
+ { type: OPERATOR, value: '(' },
1486
+ { type: OPERATOR, value: ')' }
1487
+ ]);
1488
+ });
1489
+ });
1490
+
1491
+ describe('strings', () => {
1492
+ test('basic strings', () => {
1493
+ isTokens('=""', [
1494
+ { type: FX_PREFIX, value: '=' },
1495
+ { type: STRING, value: '""' }
1496
+ ]);
1497
+ isTokens('=""""', [
1498
+ { type: FX_PREFIX, value: '=' },
1499
+ { type: STRING, value: '""""' }
1500
+ ]);
1501
+ isTokens('="data"', [
1502
+ { type: FX_PREFIX, value: '=' },
1503
+ { type: STRING, value: '"data"' }
1504
+ ]);
1505
+ isTokens('="data""data"', [
1506
+ { type: FX_PREFIX, value: '=' },
1507
+ { type: STRING, value: '"data""data"' }
1508
+ ]);
1509
+ });
1510
+
1511
+ test('string concatenation', () => {
1512
+ isTokens('="data"&"data"', [
1513
+ { type: FX_PREFIX, value: '=' },
1514
+ { type: STRING, value: '"data"' },
1515
+ { type: OPERATOR, value: '&' },
1516
+ { type: STRING, value: '"data"' }
1517
+ ]);
1518
+ isTokens('="data"&"data"&"data"', [
1519
+ { type: FX_PREFIX, value: '=' },
1520
+ { type: STRING, value: '"data"' },
1521
+ { type: OPERATOR, value: '&' },
1522
+ { type: STRING, value: '"data"' },
1523
+ { type: OPERATOR, value: '&' },
1524
+ { type: STRING, value: '"data"' }
1525
+ ]);
1526
+ });
1527
+
1528
+ test('unterminated strings', () => {
1529
+ isTokens('="incomple', [
1530
+ { type: FX_PREFIX, value: '=' },
1531
+ { type: STRING, value: '"incomple', unterminated: true }
1532
+ ]);
1533
+
1534
+ isTokens('="', [
1535
+ { type: FX_PREFIX, value: '=' },
1536
+ { type: STRING, value: '"', unterminated: true }
1537
+ ]);
1538
+ isTokens('=""', [
1539
+ { type: FX_PREFIX, value: '=' },
1540
+ { type: STRING, value: '""' }
1541
+ ]);
1542
+ isTokens('="""', [
1543
+ { type: FX_PREFIX, value: '=' },
1544
+ { type: STRING, value: '"""', unterminated: true }
1545
+ ]);
1546
+ isTokens('=""""', [
1547
+ { type: FX_PREFIX, value: '=' },
1548
+ { type: STRING, value: '""""' }
1549
+ ]);
1550
+ isTokens('="""""', [
1551
+ { type: FX_PREFIX, value: '=' },
1552
+ { type: STRING, value: '"""""', unterminated: true }
1553
+ ]);
1554
+ isTokens('=""""""', [
1555
+ { type: FX_PREFIX, value: '=' },
1556
+ { type: STRING, value: '""""""' }
1557
+ ]);
1558
+ });
1559
+
1560
+ test('escaped quotes', () => {
1561
+ isTokens('="aa""ss', [
1562
+ { type: FX_PREFIX, value: '=' },
1563
+ { type: STRING, value: '"aa""ss', unterminated: true }
1564
+ ]);
1565
+ isTokens('="aa""ss"', [
1566
+ { type: FX_PREFIX, value: '=' },
1567
+ { type: STRING, value: '"aa""ss"' }
1568
+ ]);
1569
+ isTokens('="aa""', [
1570
+ { type: FX_PREFIX, value: '=' },
1571
+ { type: STRING, value: '"aa""', unterminated: true }
1572
+ ]);
1573
+ isTokens('="aa"""', [
1574
+ { type: FX_PREFIX, value: '=' },
1575
+ { type: STRING, value: '"aa"""' }
1576
+ ]);
1577
+ });
1578
+ });
1579
+
1580
+ describe('unknowns and location handling', () => {
1581
+ test('unknown tokens with location', () => {
1582
+ isTokens('=-1', [
1583
+ { type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
1584
+ { type: OPERATOR, value: '-', loc: [ 1, 2 ] },
1585
+ { type: NUMBER, value: '1', loc: [ 2, 3 ] }
1586
+ ], { withLocation: true });
1587
+ isTokens('=-1', [
1588
+ { type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
1589
+ { type: NUMBER, value: '-1', loc: [ 1, 3 ] }
1590
+ ], { withLocation: true, negativeNumbers: true });
1591
+
1592
+ isTokens('=$C', [
1593
+ { type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
1594
+ { type: UNKNOWN, value: '$C', loc: [ 1, 3 ] }
1595
+ ], { withLocation: true });
1596
+ isTokens('=$C.foo', [
1597
+ { type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
1598
+ { type: UNKNOWN, value: '$C.foo', loc: [ 1, 7 ] }
1599
+ ], { withLocation: true });
1600
+ });
1601
+ });
1602
+
1603
+ describe('named ranges and functions', () => {
1604
+ test('basic named ranges', () => {
1605
+ isTokens('=foo', [
1606
+ { type: FX_PREFIX, value: '=' },
1607
+ { type: REF_NAMED, value: 'foo' }
1608
+ ]);
1609
+ isTokens('=_foo', [
1610
+ { type: FX_PREFIX, value: '=' },
1611
+ { type: REF_NAMED, value: '_foo' }
1612
+ ]);
1613
+ isTokens('=\\foo', [
1614
+ { type: FX_PREFIX, value: '=' },
1615
+ { type: REF_NAMED, value: '\\foo' }
1616
+ ]);
1617
+ isTokens('=\\fo', [
1618
+ { type: FX_PREFIX, value: '=' },
1619
+ { type: REF_NAMED, value: '\\fo' }
1620
+ ]);
1621
+ });
1622
+
1623
+ test('unknown backslash syntax', () => {
1624
+ isTokens('=\\f', [
1625
+ { type: FX_PREFIX, value: '=' },
1626
+ { type: UNKNOWN, value: '\\f' }
1627
+ ]);
1628
+ isTokens('=\\', [
1629
+ { type: FX_PREFIX, value: '=' },
1630
+ { type: UNKNOWN, value: '\\' }
1631
+ ]);
1632
+ });
1633
+
1634
+ test('unicode named ranges', () => {
1635
+ isTokens('=æði', [
1636
+ { type: FX_PREFIX, value: '=' },
1637
+ { type: REF_NAMED, value: 'æði' }
1638
+ ]);
1639
+ isTokens('=らーめん', [
1640
+ { type: FX_PREFIX, value: '=' },
1641
+ { type: REF_NAMED, value: 'らーめん' }
1642
+ ]);
1643
+ isTokens('=¢mah¢', [
1644
+ { type: FX_PREFIX, value: '=' },
1645
+ { type: REF_NAMED, value: '¢mah¢' }
1646
+ ]);
1647
+ });
1648
+
1649
+ test('implicit intersection and named ranges', () => {
1650
+ isTokens('=@foo', [
1651
+ { type: FX_PREFIX, value: '=' },
1652
+ { type: OPERATOR, value: '@' },
1653
+ { type: REF_NAMED, value: 'foo' }
1654
+ ]);
1655
+ isTokens('=9æði', [
1656
+ { type: FX_PREFIX, value: '=' },
1657
+ { type: NUMBER, value: '9' },
1658
+ { type: REF_NAMED, value: 'æði' }
1659
+ ]);
1660
+ });
1661
+
1662
+ test('invalid characters in names', () => {
1663
+ isTokens('=~mah~', [
1664
+ { type: FX_PREFIX, value: '=' },
1665
+ { type: UNKNOWN, value: '~mah~' }
1666
+ ]);
1667
+ isTokens('=$foo', [
1668
+ { type: FX_PREFIX, value: '=' },
1669
+ { type: UNKNOWN, value: '$foo' }
1670
+ ]);
1671
+ isTokens('=$zzzz12', [
1672
+ { type: FX_PREFIX, value: '=' },
1673
+ { type: UNKNOWN, value: '$zzzz12' }
1674
+ ]);
1675
+ isTokens('=~zzzz12()', [
1676
+ { type: FX_PREFIX, value: '=' },
1677
+ { type: UNKNOWN, value: '~zzzz12' },
1678
+ { type: OPERATOR, value: '(' },
1679
+ { type: OPERATOR, value: ')' }
1680
+ ]);
1681
+ isTokens('=zzzz~12()', [
1682
+ { type: FX_PREFIX, value: '=' },
1683
+ { type: UNKNOWN, value: 'zzzz~' },
1684
+ { type: NUMBER, value: '12' },
1685
+ { type: OPERATOR, value: '(' },
1686
+ { type: OPERATOR, value: ')' }
1687
+ ]);
1688
+ });
1689
+ });
1690
+
1691
+ describe('partial ranges', () => {
1692
+ const opts = { allowTernary: true };
1693
+
1694
+ test('form 1: row:cell references', () => {
1695
+ isTokens('1:D$1', [
1696
+ { type: REF_TERNARY, value: '1:D$1' }
1697
+ ], opts);
1698
+
1699
+ isTokens('1:D$1', [
1700
+ { type: NUMBER, value: '1' },
1701
+ { type: OPERATOR, value: ':' },
1702
+ { type: REF_RANGE, value: 'D$1' }
1703
+ ]);
1704
+
1705
+ isTokens('B2:B', [
1706
+ { type: REF_TERNARY, value: 'B2:B' }
1707
+ ], opts);
1708
+ isTokens('B2:B', [
1709
+ { type: REF_RANGE, value: 'B2' },
1710
+ { type: OPERATOR, value: ':' },
1711
+ { type: REF_NAMED, value: 'B' }
1712
+ ]);
1713
+
1714
+ isTokens('1:A1', [
1715
+ { type: REF_TERNARY, value: '1:A1' }
1716
+ ], opts);
1717
+ isTokens('$1:A1', [
1718
+ { type: REF_TERNARY, value: '$1:A1' }
1719
+ ], opts);
1720
+ isTokens('1:$A1', [
1721
+ { type: REF_TERNARY, value: '1:$A1' }
1722
+ ], opts);
1723
+ isTokens('1:A$1', [
1724
+ { type: REF_TERNARY, value: '1:A$1' }
1725
+ ], opts);
1726
+ isTokens('1:$A$1', [
1727
+ { type: REF_TERNARY, value: '1:$A$1' }
1728
+ ], opts);
1729
+ isTokens('$1:A$1', [
1730
+ { type: REF_TERNARY, value: '$1:A$1' }
1731
+ ], opts);
1732
+ isTokens('$1:$A1', [
1733
+ { type: REF_TERNARY, value: '$1:$A1' }
1734
+ ], opts);
1735
+ isTokens('$1:$A$1', [
1736
+ { type: REF_TERNARY, value: '$1:$A$1' }
1737
+ ], opts);
1738
+ });
1739
+
1740
+ test('form 2: cell:row references', () => {
1741
+ isTokens('A1:1', [
1742
+ { type: REF_TERNARY, value: 'A1:1' }
1743
+ ], opts);
1744
+ isTokens('A1:$1', [
1745
+ { type: REF_TERNARY, value: 'A1:$1' }
1746
+ ], opts);
1747
+ isTokens('$A1:1', [
1748
+ { type: REF_TERNARY, value: '$A1:1' }
1749
+ ], opts);
1750
+ isTokens('A$1:1', [
1751
+ { type: REF_TERNARY, value: 'A$1:1' }
1752
+ ], opts);
1753
+ isTokens('$A$1:1', [
1754
+ { type: REF_TERNARY, value: '$A$1:1' }
1755
+ ], opts);
1756
+ isTokens('A$1:$1', [
1757
+ { type: REF_TERNARY, value: 'A$1:$1' }
1758
+ ], opts);
1759
+ isTokens('$A1:$1', [
1760
+ { type: REF_TERNARY, value: '$A1:$1' }
1761
+ ], opts);
1762
+ isTokens('$A$1:$1', [
1763
+ { type: REF_TERNARY, value: '$A$1:$1' }
1764
+ ], opts);
1765
+ });
1766
+
1767
+ test('form 3: column:cell references', () => {
1768
+ isTokens('A:A1', [
1769
+ { type: REF_TERNARY, value: 'A:A1' }
1770
+ ], opts);
1771
+ isTokens('$A:A1', [
1772
+ { type: REF_TERNARY, value: '$A:A1' }
1773
+ ], opts);
1774
+ isTokens('A:$A1', [
1775
+ { type: REF_TERNARY, value: 'A:$A1' }
1776
+ ], opts);
1777
+ isTokens('A:A$1', [
1778
+ { type: REF_TERNARY, value: 'A:A$1' }
1779
+ ], opts);
1780
+ isTokens('A:$A$1', [
1781
+ { type: REF_TERNARY, value: 'A:$A$1' }
1782
+ ], opts);
1783
+ isTokens('$A:A$1', [
1784
+ { type: REF_TERNARY, value: '$A:A$1' }
1785
+ ], opts);
1786
+ isTokens('$A:$A1', [
1787
+ { type: REF_TERNARY, value: '$A:$A1' }
1788
+ ], opts);
1789
+ isTokens('$A:$A$1', [
1790
+ { type: REF_TERNARY, value: '$A:$A$1' }
1791
+ ], opts);
1792
+ });
1793
+
1794
+ test('form 4: cell:column references', () => {
1795
+ isTokens('A1:A', [
1796
+ { type: REF_TERNARY, value: 'A1:A' }
1797
+ ], opts);
1798
+ isTokens('A1:$A', [
1799
+ { type: REF_TERNARY, value: 'A1:$A' }
1800
+ ], opts);
1801
+ isTokens('$A1:A', [
1802
+ { type: REF_TERNARY, value: '$A1:A' }
1803
+ ], opts);
1804
+ isTokens('A$1:A', [
1805
+ { type: REF_TERNARY, value: 'A$1:A' }
1806
+ ], opts);
1807
+ isTokens('$A$1:A', [
1808
+ { type: REF_TERNARY, value: '$A$1:A' }
1809
+ ], opts);
1810
+ isTokens('A$1:$A', [
1811
+ { type: REF_TERNARY, value: 'A$1:$A' }
1812
+ ], opts);
1813
+ isTokens('$A1:$A', [
1814
+ { type: REF_TERNARY, value: '$A1:$A' }
1815
+ ], opts);
1816
+ isTokens('$A$1:$A', [
1817
+ { type: REF_TERNARY, value: '$A$1:$A' }
1818
+ ], opts);
1819
+ });
1820
+
1821
+ test('complex partial range expressions', () => {
1822
+ isTokens('=A10:A+B1:2', [
1823
+ { type: FX_PREFIX, value: '=' },
1824
+ { type: REF_TERNARY, value: 'A10:A' },
1825
+ { type: OPERATOR, value: '+' },
1826
+ { type: REF_TERNARY, value: 'B1:2' }
1827
+ ], opts);
1828
+ isTokens('=SUM(A:A$10,3:B$2)', [
1829
+ { type: FX_PREFIX, value: '=' },
1830
+ { type: FUNCTION, value: 'SUM' },
1831
+ { type: OPERATOR, value: '(' },
1832
+ { type: REF_TERNARY, value: 'A:A$10' },
1833
+ { type: OPERATOR, value: ',' },
1834
+ { type: REF_TERNARY, value: '3:B$2' },
1835
+ { type: OPERATOR, value: ')' }
1836
+ ], opts);
1837
+ isTokens('$A$10:$12', [
1838
+ { type: REF_TERNARY, value: '$A$10:$12' }
1839
+ ], opts);
1840
+ isTokens('1:D$1', [
1841
+ { type: REF_TERNARY, value: '1:D$1' }
1842
+ ], opts);
1843
+ });
1844
+
1845
+ test('ambiguous range vs function cases', () => {
1846
+ isTokens('=A1:IF()', [
1847
+ { type: FX_PREFIX, value: '=' },
1848
+ { type: REF_RANGE, value: 'A1' },
1849
+ { type: OPERATOR, value: ':' },
1850
+ { type: FUNCTION, value: 'IF' },
1851
+ { type: OPERATOR, value: '(' },
1852
+ { type: OPERATOR, value: ')' }
1853
+ ], opts);
1854
+ isTokens('=A1:F.DIST()', [
1855
+ { type: FX_PREFIX, value: '=' },
1856
+ { type: REF_RANGE, value: 'A1' },
1857
+ { type: OPERATOR, value: ':' },
1858
+ { type: FUNCTION, value: 'F.DIST' },
1859
+ { type: OPERATOR, value: '(' },
1860
+ { type: OPERATOR, value: ')' }
1861
+ ], opts);
1862
+ });
1863
+
1864
+ test('invalid partial range syntax', () => {
1865
+ isTokens('=1:A1.', [
1866
+ { type: FX_PREFIX, value: '=' },
1867
+ { type: NUMBER, value: '1' },
1868
+ { type: OPERATOR, value: ':' },
1869
+ { type: REF_RANGE, value: 'A1' },
1870
+ { type: UNKNOWN, value: '.' }
1871
+ ], opts);
1872
+ isTokens('=A1:X$', [
1873
+ { type: FX_PREFIX, value: '=' },
1874
+ { type: REF_RANGE, value: 'A1' },
1875
+ { type: OPERATOR, value: ':' },
1876
+ { type: UNKNOWN, value: 'X$' }
1877
+ ], opts);
1878
+ });
1879
+
1880
+ test('external partial ranges', () => {
1881
+ isTokens('=[foo]Bar!A:A1', [
1882
+ { type: FX_PREFIX, value: '=' },
1883
+ { type: CONTEXT, value: '[foo]Bar' },
1884
+ { type: OPERATOR, value: '!' },
1885
+ { type: REF_TERNARY, value: 'A:A1' }
1886
+ ], { mergeRefs: false, allowTernary: true });
1887
+ });
1888
+ });
1889
+
1890
+ describe('external refs syntax from XLSX files', () => {
1891
+ const opts = { xlsx: true };
1892
+
1893
+ test('numeric workbook references', () => {
1894
+ isTokens('=[1]!A1', [
1895
+ { type: FX_PREFIX, value: '=' },
1896
+ { type: REF_RANGE, value: '[1]!A1' }
1897
+ ], opts);
1898
+ isTokens('=[1]Sheet1!A1', [
1899
+ { type: FX_PREFIX, value: '=' },
1900
+ { type: REF_RANGE, value: '[1]Sheet1!A1' }
1901
+ ], opts);
1902
+ isTokens('=[4]!name', [
1903
+ { type: FX_PREFIX, value: '=' },
1904
+ { type: REF_NAMED, value: '[4]!name' }
1905
+ ], opts);
1906
+ isTokens('=[16]Sheet1!name', [
1907
+ { type: FX_PREFIX, value: '=' },
1908
+ { type: REF_NAMED, value: '[16]Sheet1!name' }
1909
+ ], opts);
1910
+ });
1911
+
1912
+ test('quoted numeric workbook references', () => {
1913
+ isTokens("='[1]'!A1", [
1914
+ { type: FX_PREFIX, value: '=' },
1915
+ { type: REF_RANGE, value: "'[1]'!A1" }
1916
+ ], opts);
1917
+ isTokens("='[1]Sheet1'!A1", [
1918
+ { type: FX_PREFIX, value: '=' },
1919
+ { type: REF_RANGE, value: "'[1]Sheet1'!A1" }
1920
+ ], opts);
1921
+ isTokens("='[4]'!name", [
1922
+ { type: FX_PREFIX, value: '=' },
1923
+ { type: REF_NAMED, value: "'[4]'!name" }
1924
+ ], opts);
1925
+ isTokens("='[16]Sheet1'!name", [
1926
+ { type: FX_PREFIX, value: '=' },
1927
+ { type: REF_NAMED, value: "'[16]Sheet1'!name" }
1928
+ ], opts);
1929
+ });
1930
+
1931
+ test('named workbook references', () => {
1932
+ isTokens('=[Workbook.xlsx]!A1', [
1933
+ { type: FX_PREFIX, value: '=' },
1934
+ { type: REF_RANGE, value: '[Workbook.xlsx]!A1' }
1935
+ ], opts);
1936
+ isTokens('=[Workbook.xlsx]Sheet1!A1', [
1937
+ { type: FX_PREFIX, value: '=' },
1938
+ { type: REF_RANGE, value: '[Workbook.xlsx]Sheet1!A1' }
1939
+ ], opts);
1940
+ isTokens('=[Workbook.xlsx]!name', [
1941
+ { type: FX_PREFIX, value: '=' },
1942
+ { type: REF_NAMED, value: '[Workbook.xlsx]!name' }
1943
+ ], opts);
1944
+ isTokens('=[Workbook.xlsx]Sheet1!name', [
1945
+ { type: FX_PREFIX, value: '=' },
1946
+ { type: REF_NAMED, value: '[Workbook.xlsx]Sheet1!name' }
1947
+ ], opts);
1948
+ });
1949
+
1950
+ test('quoted named workbook references', () => {
1951
+ isTokens("='[Workbook.xlsx]'!A1", [
1952
+ { type: FX_PREFIX, value: '=' },
1953
+ { type: REF_RANGE, value: "'[Workbook.xlsx]'!A1" }
1954
+ ], opts);
1955
+ isTokens("='[Workbook.xlsx]Sheet1'!A1", [
1956
+ { type: FX_PREFIX, value: '=' },
1957
+ { type: REF_RANGE, value: "'[Workbook.xlsx]Sheet1'!A1" }
1958
+ ], opts);
1959
+ isTokens("='[Workbook.xlsx]'!name", [
1960
+ { type: FX_PREFIX, value: '=' },
1961
+ { type: REF_NAMED, value: "'[Workbook.xlsx]'!name" }
1962
+ ], opts);
1963
+ isTokens("='[Workbook.xlsx]Sheet1'!name", [
1964
+ { type: FX_PREFIX, value: '=' },
1965
+ { type: REF_NAMED, value: "'[Workbook.xlsx]Sheet1'!name" }
1966
+ ], opts);
1967
+ });
1968
+ });
1969
+
1970
+ describe('r and c as names within LET and LAMBDA calls', () => {
1971
+ test('r and c context sensitivity', () => {
1972
+ isTokens('=c*(LAMBDA(r,c,r*c)+r)+r', [
1973
+ { type: FX_PREFIX, value: '=' },
1974
+ { type: UNKNOWN, value: 'c' },
1975
+ { type: OPERATOR, value: '*' },
1976
+ { type: OPERATOR, value: '(' },
1977
+ { type: FUNCTION, value: 'LAMBDA' },
1978
+ { type: OPERATOR, value: '(' },
1979
+ { type: REF_NAMED, value: 'r' },
1980
+ { type: OPERATOR, value: ',' },
1981
+ { type: REF_NAMED, value: 'c' },
1982
+ { type: OPERATOR, value: ',' },
1983
+ { type: REF_NAMED, value: 'r' },
1984
+ { type: OPERATOR, value: '*' },
1985
+ { type: REF_NAMED, value: 'c' },
1986
+ { type: OPERATOR, value: ')' },
1987
+ { type: OPERATOR, value: '+' },
1988
+ { type: UNKNOWN, value: 'r' },
1989
+ { type: OPERATOR, value: ')' },
1990
+ { type: OPERATOR, value: '+' },
1991
+ { type: UNKNOWN, value: 'r' }
1992
+ ]);
1993
+ isTokens('=c*(LET(r,A1,c,B2,r*c)+r)+r', [
1994
+ { type: FX_PREFIX, value: '=' },
1995
+ { type: UNKNOWN, value: 'c' },
1996
+ { type: OPERATOR, value: '*' },
1997
+ { type: OPERATOR, value: '(' },
1998
+ { type: FUNCTION, value: 'LET' },
1999
+ { type: OPERATOR, value: '(' },
2000
+ { type: REF_NAMED, value: 'r' },
2001
+ { type: OPERATOR, value: ',' },
2002
+ { type: REF_RANGE, value: 'A1' },
2003
+ { type: OPERATOR, value: ',' },
2004
+ { type: REF_NAMED, value: 'c' },
2005
+ { type: OPERATOR, value: ',' },
2006
+ { type: REF_RANGE, value: 'B2' },
2007
+ { type: OPERATOR, value: ',' },
2008
+ { type: REF_NAMED, value: 'r' },
2009
+ { type: OPERATOR, value: '*' },
2010
+ { type: REF_NAMED, value: 'c' },
2011
+ { type: OPERATOR, value: ')' },
2012
+ { type: OPERATOR, value: '+' },
2013
+ { type: UNKNOWN, value: 'r' },
2014
+ { type: OPERATOR, value: ')' },
2015
+ { type: OPERATOR, value: '+' },
2016
+ { type: UNKNOWN, value: 'r' }
2017
+ ]);
2018
+ });
2019
+ });
2020
+
2021
+ describe('trim operators', () => {
2022
+ test('valid trim operators between ranges', () => {
2023
+ isTokens('=Sheet1!A1.:.B2', [
2024
+ { type: FX_PREFIX, value: '=' },
2025
+ { type: CONTEXT, value: 'Sheet1' },
2026
+ { type: OPERATOR, value: '!' },
2027
+ { type: REF_RANGE, value: 'A1' },
2028
+ { type: OPERATOR, value: '.:.' },
2029
+ { type: REF_RANGE, value: 'B2' }
2030
+ ], { mergeRefs: false });
2031
+ isTokens('A1:.B2', [
2032
+ { type: REF_RANGE, value: 'A1' },
2033
+ { type: OPERATOR, value: ':.' },
2034
+ { type: REF_RANGE, value: 'B2' }
2035
+ ], { mergeRefs: false });
2036
+ isTokens('A1.:B2', [
2037
+ { type: REF_RANGE, value: 'A1' },
2038
+ { type: OPERATOR, value: '.:' },
2039
+ { type: REF_RANGE, value: 'B2' }
2040
+ ], { mergeRefs: false });
2041
+ });
2042
+
2043
+ test('invalid trim operators outside literal ranges', () => {
2044
+ isTokens('=Sheet1!A.:.A.:.B.:.B', [
2045
+ { type: FX_PREFIX, value: '=' },
2046
+ { type: REF_BEAM, value: 'Sheet1!A.:.A' },
2047
+ { type: UNKNOWN, value: '.:.' },
2048
+ { type: REF_BEAM, value: 'B.:.B' }
2049
+ ]);
2050
+
2051
+ isTokens('=name1.:.name2', [
2052
+ { type: FX_PREFIX, value: '=' },
2053
+ { type: REF_NAMED, value: 'name1.' },
2054
+ { type: UNKNOWN, value: ':.name2' }
2055
+ ]);
2056
+
2057
+ isTokens('=OFFSET(A1,1,1).:.INDIRECT("A1")', [
2058
+ { type: FX_PREFIX, value: '=' },
2059
+ { type: FUNCTION, value: 'OFFSET' },
2060
+ { type: OPERATOR, value: '(' },
2061
+ { type: REF_RANGE, value: 'A1' },
2062
+ { type: OPERATOR, value: ',' },
2063
+ { type: NUMBER, value: '1' },
2064
+ { type: OPERATOR, value: ',' },
2065
+ { type: NUMBER, value: '1' },
2066
+ { type: OPERATOR, value: ')' },
2067
+ { type: UNKNOWN, value: '.:.INDIRECT' },
2068
+ { type: OPERATOR, value: '(' },
2069
+ { type: STRING, value: '"A1"' },
2070
+ { type: OPERATOR, value: ')' }
2071
+ ]);
2072
+ });
2073
+ });
2074
+
2075
+ describe('whitespace handling', () => {
2076
+ test('various whitespace types', () => {
2077
+ isTokens('\tA1\u00a0+\nB2\r', [
2078
+ { type: WHITESPACE, value: '\t' },
2079
+ { type: REF_RANGE, value: 'A1' },
2080
+ { type: WHITESPACE, value: '\u00a0' },
2081
+ { type: OPERATOR, value: '+' },
2082
+ { type: NEWLINE, value: '\n' },
2083
+ { type: REF_RANGE, value: 'B2' },
2084
+ { type: WHITESPACE, value: '\r' }
2085
+ ]);
2086
+ });
2087
+ });
2088
+
2089
+ test('xlsx vs non-xlsx modes work as exptected', () => {
2090
+ // to the tokenizer, the only difference between the two variants is
2091
+ // that [x]!A1 is forbidden in the default one
2092
+ expect(tokenize('[foo]bar!A1')).toEqual([ { type: REF_RANGE, value: '[foo]bar!A1' } ]);
2093
+ expect(tokenize('[foo]!A1')).toEqual([
2094
+ { type: UNKNOWN, value: '[foo]' },
2095
+ { type: OPERATOR, value: '!' },
2096
+ { type: REF_RANGE, value: 'A1' }
2097
+ ]);
2098
+ expect(tokenize('foo!A1')).toEqual([ { type: REF_RANGE, value: 'foo!A1' } ]);
2099
+ expect(tokenizeXlsx('[foo]bar!A1')).toEqual([ { type: REF_RANGE, value: '[foo]bar!A1' } ]);
2100
+ expect(tokenizeXlsx('[foo]!A1')).toEqual([ { type: REF_RANGE, value: '[foo]!A1' } ]);
2101
+ expect(tokenizeXlsx('foo!A1')).toEqual([ { type: REF_RANGE, value: 'foo!A1' } ]);
2102
+ });
2103
+ });