@cj-tech-master/excelts 9.2.1 → 9.3.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 (383) hide show
  1. package/README.md +25 -2
  2. package/README_zh.md +29 -6
  3. package/dist/browser/index.browser.d.ts +1 -1
  4. package/dist/browser/index.browser.js +4 -0
  5. package/dist/browser/index.d.ts +1 -1
  6. package/dist/browser/index.js +4 -0
  7. package/dist/browser/modules/excel/cell.d.ts +17 -3
  8. package/dist/browser/modules/excel/cell.js +170 -22
  9. package/dist/browser/modules/excel/defined-names.d.ts +96 -1
  10. package/dist/browser/modules/excel/defined-names.js +411 -21
  11. package/dist/browser/modules/excel/image.d.ts +11 -0
  12. package/dist/browser/modules/excel/image.js +24 -1
  13. package/dist/browser/modules/excel/stream/workbook-reader.browser.d.ts +9 -3
  14. package/dist/browser/modules/excel/stream/workbook-reader.browser.js +14 -0
  15. package/dist/browser/modules/excel/stream/workbook-reader.d.ts +2 -1
  16. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +39 -5
  17. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +48 -1
  18. package/dist/browser/modules/excel/stream/workbook-writer.d.ts +3 -2
  19. package/dist/browser/modules/excel/stream/worksheet-reader.js +17 -1
  20. package/dist/browser/modules/excel/stream/worksheet-writer.d.ts +39 -6
  21. package/dist/browser/modules/excel/stream/worksheet-writer.js +45 -5
  22. package/dist/browser/modules/excel/table.js +15 -2
  23. package/dist/browser/modules/excel/types.d.ts +133 -2
  24. package/dist/browser/modules/excel/utils/col-cache.d.ts +1 -0
  25. package/dist/browser/modules/excel/utils/col-cache.js +15 -0
  26. package/dist/browser/modules/excel/utils/drawing-utils.d.ts +3 -3
  27. package/dist/browser/modules/excel/utils/drawing-utils.js +4 -0
  28. package/dist/browser/modules/excel/utils/external-link-formula.d.ts +76 -0
  29. package/dist/browser/modules/excel/utils/external-link-formula.js +208 -0
  30. package/dist/browser/modules/excel/utils/iterate-stream.d.ts +9 -3
  31. package/dist/browser/modules/excel/utils/iterate-stream.js +3 -1
  32. package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +19 -0
  33. package/dist/browser/modules/excel/utils/ooxml-paths.js +37 -2
  34. package/dist/browser/modules/excel/utils/shared-strings.d.ts +8 -3
  35. package/dist/browser/modules/excel/utils/shared-strings.js +21 -2
  36. package/dist/browser/modules/excel/utils/workbook-protection.d.ts +30 -0
  37. package/dist/browser/modules/excel/utils/workbook-protection.js +30 -0
  38. package/dist/browser/modules/excel/workbook.browser.d.ts +257 -6
  39. package/dist/browser/modules/excel/workbook.browser.js +318 -34
  40. package/dist/browser/modules/excel/workbook.d.ts +1 -1
  41. package/dist/browser/modules/excel/worksheet.d.ts +3 -1
  42. package/dist/browser/modules/excel/worksheet.js +21 -2
  43. package/dist/browser/modules/excel/xlsx/rel-type.d.ts +15 -0
  44. package/dist/browser/modules/excel/xlsx/rel-type.js +16 -1
  45. package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +6 -5
  46. package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
  47. package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.d.ts +84 -0
  48. package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.js +330 -0
  49. package/dist/browser/modules/excel/xlsx/xform/book/external-reference-xform.d.ts +17 -0
  50. package/dist/browser/modules/excel/xlsx/xform/book/external-reference-xform.js +24 -0
  51. package/dist/browser/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.d.ts +3 -0
  52. package/dist/browser/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
  53. package/dist/browser/modules/excel/xlsx/xform/book/workbook-protection-xform.d.ts +20 -0
  54. package/dist/browser/modules/excel/xlsx/xform/book/workbook-protection-xform.js +66 -0
  55. package/dist/browser/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
  56. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +19 -1
  57. package/dist/browser/modules/excel/xlsx/xform/core/metadata-xform.d.ts +56 -0
  58. package/dist/browser/modules/excel/xlsx/xform/core/metadata-xform.js +158 -0
  59. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.d.ts +26 -0
  60. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +105 -0
  61. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
  62. package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
  63. package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.d.ts +1 -1
  64. package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
  65. package/dist/browser/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
  66. package/dist/browser/modules/excel/xlsx/xform/sheet/ignored-errors-xform.d.ts +21 -0
  67. package/dist/browser/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +80 -0
  68. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
  69. package/dist/browser/modules/excel/xlsx/xform/style/border-xform.js +4 -1
  70. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +172 -13
  71. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +410 -20
  72. package/dist/browser/modules/excel/xlsx/xlsx.d.ts +7 -4
  73. package/dist/browser/modules/excel/xlsx/xlsx.js +4 -5
  74. package/dist/browser/modules/formula/compile/address-utils.d.ts +62 -0
  75. package/dist/browser/modules/formula/compile/address-utils.js +83 -0
  76. package/dist/browser/modules/formula/compile/binder.d.ts +42 -0
  77. package/dist/browser/modules/formula/compile/binder.js +487 -0
  78. package/dist/browser/modules/formula/compile/bound-ast.d.ts +230 -0
  79. package/dist/browser/modules/formula/compile/bound-ast.js +80 -0
  80. package/dist/browser/modules/formula/compile/compiled-formula.d.ts +137 -0
  81. package/dist/browser/modules/formula/compile/compiled-formula.js +383 -0
  82. package/dist/browser/modules/formula/compile/dependency-analysis.d.ts +93 -0
  83. package/dist/browser/modules/formula/compile/dependency-analysis.js +432 -0
  84. package/dist/browser/modules/formula/compile/structured-ref-utils.d.ts +93 -0
  85. package/dist/browser/modules/formula/compile/structured-ref-utils.js +136 -0
  86. package/dist/browser/modules/formula/default-syntax-probe.d.ts +79 -0
  87. package/dist/browser/modules/formula/default-syntax-probe.js +83 -0
  88. package/dist/browser/modules/formula/functions/_date-context.d.ts +4 -0
  89. package/dist/browser/modules/formula/functions/_date-context.js +29 -0
  90. package/dist/browser/modules/formula/functions/_shared.d.ts +121 -0
  91. package/dist/browser/modules/formula/functions/_shared.js +381 -0
  92. package/dist/browser/modules/formula/functions/conditional.d.ts +27 -0
  93. package/dist/browser/modules/formula/functions/conditional.js +343 -0
  94. package/dist/browser/modules/formula/functions/database.d.ts +37 -0
  95. package/dist/browser/modules/formula/functions/database.js +274 -0
  96. package/dist/browser/modules/formula/functions/date.d.ts +61 -0
  97. package/dist/browser/modules/formula/functions/date.js +855 -0
  98. package/dist/browser/modules/formula/functions/dynamic-array.d.ts +23 -0
  99. package/dist/browser/modules/formula/functions/dynamic-array.js +860 -0
  100. package/dist/browser/modules/formula/functions/engineering.d.ts +57 -0
  101. package/dist/browser/modules/formula/functions/engineering.js +1128 -0
  102. package/dist/browser/modules/formula/functions/financial.d.ts +202 -0
  103. package/dist/browser/modules/formula/functions/financial.js +2296 -0
  104. package/dist/browser/modules/formula/functions/lookup.d.ts +18 -0
  105. package/dist/browser/modules/formula/functions/lookup.js +886 -0
  106. package/dist/browser/modules/formula/functions/math.d.ts +114 -0
  107. package/dist/browser/modules/formula/functions/math.js +1406 -0
  108. package/dist/browser/modules/formula/functions/statistical.d.ts +193 -0
  109. package/dist/browser/modules/formula/functions/statistical.js +3390 -0
  110. package/dist/browser/modules/formula/functions/text.d.ts +86 -0
  111. package/dist/browser/modules/formula/functions/text.js +1845 -0
  112. package/dist/browser/modules/formula/host-registry.d.ts +53 -0
  113. package/dist/browser/modules/formula/host-registry.js +69 -0
  114. package/dist/browser/modules/formula/index.d.ts +39 -0
  115. package/dist/browser/modules/formula/index.js +49 -0
  116. package/dist/browser/modules/formula/install.d.ts +62 -0
  117. package/dist/browser/modules/formula/install.js +88 -0
  118. package/dist/browser/modules/formula/integration/apply-writeback-plan.d.ts +26 -0
  119. package/dist/browser/modules/formula/integration/apply-writeback-plan.js +210 -0
  120. package/dist/browser/modules/formula/integration/calculate-formulas-impl.d.ts +30 -0
  121. package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +616 -0
  122. package/dist/browser/modules/formula/integration/calculate-formulas.d.ts +67 -0
  123. package/dist/browser/modules/formula/integration/calculate-formulas.js +68 -0
  124. package/dist/browser/modules/formula/integration/formula-instance.d.ts +64 -0
  125. package/dist/browser/modules/formula/integration/formula-instance.js +79 -0
  126. package/dist/browser/modules/formula/integration/workbook-adapter.d.ts +26 -0
  127. package/dist/browser/modules/formula/integration/workbook-adapter.js +324 -0
  128. package/dist/browser/modules/formula/integration/workbook-snapshot.d.ts +267 -0
  129. package/dist/browser/modules/formula/integration/workbook-snapshot.js +77 -0
  130. package/dist/browser/modules/formula/materialize/build-writeback-plan.d.ts +34 -0
  131. package/dist/browser/modules/formula/materialize/build-writeback-plan.js +473 -0
  132. package/dist/browser/modules/formula/materialize/spill-engine.d.ts +9 -0
  133. package/dist/browser/modules/formula/materialize/spill-engine.js +38 -0
  134. package/dist/browser/modules/formula/materialize/types.d.ts +179 -0
  135. package/dist/browser/modules/formula/materialize/types.js +29 -0
  136. package/dist/browser/modules/formula/materialize/writeback-plan.d.ts +167 -0
  137. package/dist/browser/modules/formula/materialize/writeback-plan.js +27 -0
  138. package/dist/browser/modules/formula/runtime/evaluator.d.ts +151 -0
  139. package/dist/browser/modules/formula/runtime/evaluator.js +2291 -0
  140. package/dist/browser/modules/formula/runtime/function-registry.d.ts +47 -0
  141. package/dist/browser/modules/formula/runtime/function-registry.js +840 -0
  142. package/dist/browser/modules/formula/runtime/values.d.ts +211 -0
  143. package/dist/browser/modules/formula/runtime/values.js +385 -0
  144. package/dist/browser/modules/formula/syntax/ast.d.ts +129 -0
  145. package/dist/browser/modules/formula/syntax/ast.js +28 -0
  146. package/dist/browser/modules/formula/syntax/parser.d.ts +18 -0
  147. package/dist/browser/modules/formula/syntax/parser.js +439 -0
  148. package/dist/browser/modules/formula/syntax/token-types.d.ts +153 -0
  149. package/dist/browser/modules/formula/syntax/token-types.js +59 -0
  150. package/dist/browser/modules/formula/syntax/tokenizer.d.ts +10 -0
  151. package/dist/browser/modules/formula/syntax/tokenizer.js +1074 -0
  152. package/dist/browser/modules/pdf/excel-bridge.js +9 -0
  153. package/dist/cjs/index.js +4 -0
  154. package/dist/cjs/modules/excel/cell.js +170 -22
  155. package/dist/cjs/modules/excel/defined-names.js +411 -21
  156. package/dist/cjs/modules/excel/image.js +24 -1
  157. package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +14 -0
  158. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +48 -1
  159. package/dist/cjs/modules/excel/stream/worksheet-reader.js +17 -1
  160. package/dist/cjs/modules/excel/stream/worksheet-writer.js +45 -5
  161. package/dist/cjs/modules/excel/table.js +15 -2
  162. package/dist/cjs/modules/excel/utils/col-cache.js +15 -0
  163. package/dist/cjs/modules/excel/utils/drawing-utils.js +4 -0
  164. package/dist/cjs/modules/excel/utils/external-link-formula.js +212 -0
  165. package/dist/cjs/modules/excel/utils/iterate-stream.js +3 -1
  166. package/dist/cjs/modules/excel/utils/ooxml-paths.js +42 -2
  167. package/dist/cjs/modules/excel/utils/shared-strings.js +21 -2
  168. package/dist/cjs/modules/excel/utils/workbook-protection.js +33 -0
  169. package/dist/cjs/modules/excel/workbook.browser.js +318 -34
  170. package/dist/cjs/modules/excel/worksheet.js +20 -1
  171. package/dist/cjs/modules/excel/xlsx/rel-type.js +16 -1
  172. package/dist/cjs/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
  173. package/dist/cjs/modules/excel/xlsx/xform/book/external-link-xform.js +333 -0
  174. package/dist/cjs/modules/excel/xlsx/xform/book/external-reference-xform.js +27 -0
  175. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
  176. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-protection-xform.js +69 -0
  177. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
  178. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +18 -0
  179. package/dist/cjs/modules/excel/xlsx/xform/core/metadata-xform.js +161 -0
  180. package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +108 -0
  181. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
  182. package/dist/cjs/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
  183. package/dist/cjs/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
  184. package/dist/cjs/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
  185. package/dist/cjs/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +83 -0
  186. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
  187. package/dist/cjs/modules/excel/xlsx/xform/style/border-xform.js +4 -1
  188. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +408 -18
  189. package/dist/cjs/modules/excel/xlsx/xlsx.js +4 -5
  190. package/dist/cjs/modules/formula/compile/address-utils.js +89 -0
  191. package/dist/cjs/modules/formula/compile/binder.js +489 -0
  192. package/dist/cjs/modules/formula/compile/bound-ast.js +68 -0
  193. package/dist/cjs/modules/formula/compile/compiled-formula.js +387 -0
  194. package/dist/cjs/modules/formula/compile/dependency-analysis.js +437 -0
  195. package/dist/cjs/modules/formula/compile/structured-ref-utils.js +141 -0
  196. package/dist/cjs/modules/formula/default-syntax-probe.js +87 -0
  197. package/dist/cjs/modules/formula/functions/_date-context.js +33 -0
  198. package/dist/cjs/modules/formula/functions/_shared.js +396 -0
  199. package/dist/cjs/modules/formula/functions/conditional.js +354 -0
  200. package/dist/cjs/modules/formula/functions/database.js +288 -0
  201. package/dist/cjs/modules/formula/functions/date.js +883 -0
  202. package/dist/cjs/modules/formula/functions/dynamic-array.js +881 -0
  203. package/dist/cjs/modules/formula/functions/engineering.js +1183 -0
  204. package/dist/cjs/modules/formula/functions/financial.js +2348 -0
  205. package/dist/cjs/modules/formula/functions/lookup.js +902 -0
  206. package/dist/cjs/modules/formula/functions/math.js +1487 -0
  207. package/dist/cjs/modules/formula/functions/statistical.js +3488 -0
  208. package/dist/cjs/modules/formula/functions/text.js +1889 -0
  209. package/dist/cjs/modules/formula/host-registry.js +75 -0
  210. package/dist/cjs/modules/formula/index.js +58 -0
  211. package/dist/cjs/modules/formula/install.js +93 -0
  212. package/dist/cjs/modules/formula/integration/apply-writeback-plan.js +213 -0
  213. package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +619 -0
  214. package/dist/cjs/modules/formula/integration/calculate-formulas.js +71 -0
  215. package/dist/cjs/modules/formula/integration/formula-instance.js +82 -0
  216. package/dist/cjs/modules/formula/integration/workbook-adapter.js +327 -0
  217. package/dist/cjs/modules/formula/integration/workbook-snapshot.js +84 -0
  218. package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +475 -0
  219. package/dist/cjs/modules/formula/materialize/spill-engine.js +42 -0
  220. package/dist/cjs/modules/formula/materialize/types.js +32 -0
  221. package/dist/cjs/modules/formula/materialize/writeback-plan.js +28 -0
  222. package/dist/cjs/modules/formula/runtime/evaluator.js +2298 -0
  223. package/dist/cjs/modules/formula/runtime/function-registry.js +846 -0
  224. package/dist/cjs/modules/formula/runtime/values.js +385 -0
  225. package/dist/cjs/modules/formula/syntax/ast.js +8 -0
  226. package/dist/cjs/modules/formula/syntax/parser.js +440 -0
  227. package/dist/cjs/modules/formula/syntax/token-types.js +32 -0
  228. package/dist/cjs/modules/formula/syntax/tokenizer.js +1076 -0
  229. package/dist/cjs/modules/pdf/excel-bridge.js +9 -0
  230. package/dist/esm/index.browser.js +4 -0
  231. package/dist/esm/index.js +4 -0
  232. package/dist/esm/modules/excel/cell.js +170 -22
  233. package/dist/esm/modules/excel/defined-names.js +411 -21
  234. package/dist/esm/modules/excel/image.js +24 -1
  235. package/dist/esm/modules/excel/stream/workbook-reader.browser.js +14 -0
  236. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +48 -1
  237. package/dist/esm/modules/excel/stream/worksheet-reader.js +17 -1
  238. package/dist/esm/modules/excel/stream/worksheet-writer.js +45 -5
  239. package/dist/esm/modules/excel/table.js +15 -2
  240. package/dist/esm/modules/excel/utils/col-cache.js +15 -0
  241. package/dist/esm/modules/excel/utils/drawing-utils.js +4 -0
  242. package/dist/esm/modules/excel/utils/external-link-formula.js +208 -0
  243. package/dist/esm/modules/excel/utils/iterate-stream.js +3 -1
  244. package/dist/esm/modules/excel/utils/ooxml-paths.js +37 -2
  245. package/dist/esm/modules/excel/utils/shared-strings.js +21 -2
  246. package/dist/esm/modules/excel/utils/workbook-protection.js +30 -0
  247. package/dist/esm/modules/excel/workbook.browser.js +318 -34
  248. package/dist/esm/modules/excel/worksheet.js +21 -2
  249. package/dist/esm/modules/excel/xlsx/rel-type.js +16 -1
  250. package/dist/esm/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
  251. package/dist/esm/modules/excel/xlsx/xform/book/external-link-xform.js +330 -0
  252. package/dist/esm/modules/excel/xlsx/xform/book/external-reference-xform.js +24 -0
  253. package/dist/esm/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
  254. package/dist/esm/modules/excel/xlsx/xform/book/workbook-protection-xform.js +66 -0
  255. package/dist/esm/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
  256. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +19 -1
  257. package/dist/esm/modules/excel/xlsx/xform/core/metadata-xform.js +158 -0
  258. package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +105 -0
  259. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
  260. package/dist/esm/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
  261. package/dist/esm/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
  262. package/dist/esm/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
  263. package/dist/esm/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +80 -0
  264. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
  265. package/dist/esm/modules/excel/xlsx/xform/style/border-xform.js +4 -1
  266. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +410 -20
  267. package/dist/esm/modules/excel/xlsx/xlsx.js +4 -5
  268. package/dist/esm/modules/formula/compile/address-utils.js +83 -0
  269. package/dist/esm/modules/formula/compile/binder.js +487 -0
  270. package/dist/esm/modules/formula/compile/bound-ast.js +80 -0
  271. package/dist/esm/modules/formula/compile/compiled-formula.js +383 -0
  272. package/dist/esm/modules/formula/compile/dependency-analysis.js +432 -0
  273. package/dist/esm/modules/formula/compile/structured-ref-utils.js +136 -0
  274. package/dist/esm/modules/formula/default-syntax-probe.js +83 -0
  275. package/dist/esm/modules/formula/functions/_date-context.js +29 -0
  276. package/dist/esm/modules/formula/functions/_shared.js +381 -0
  277. package/dist/esm/modules/formula/functions/conditional.js +343 -0
  278. package/dist/esm/modules/formula/functions/database.js +274 -0
  279. package/dist/esm/modules/formula/functions/date.js +855 -0
  280. package/dist/esm/modules/formula/functions/dynamic-array.js +860 -0
  281. package/dist/esm/modules/formula/functions/engineering.js +1128 -0
  282. package/dist/esm/modules/formula/functions/financial.js +2296 -0
  283. package/dist/esm/modules/formula/functions/lookup.js +886 -0
  284. package/dist/esm/modules/formula/functions/math.js +1406 -0
  285. package/dist/esm/modules/formula/functions/statistical.js +3390 -0
  286. package/dist/esm/modules/formula/functions/text.js +1845 -0
  287. package/dist/esm/modules/formula/host-registry.js +69 -0
  288. package/dist/esm/modules/formula/index.js +49 -0
  289. package/dist/esm/modules/formula/install.js +88 -0
  290. package/dist/esm/modules/formula/integration/apply-writeback-plan.js +210 -0
  291. package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +616 -0
  292. package/dist/esm/modules/formula/integration/calculate-formulas.js +68 -0
  293. package/dist/esm/modules/formula/integration/formula-instance.js +79 -0
  294. package/dist/esm/modules/formula/integration/workbook-adapter.js +324 -0
  295. package/dist/esm/modules/formula/integration/workbook-snapshot.js +77 -0
  296. package/dist/esm/modules/formula/materialize/build-writeback-plan.js +473 -0
  297. package/dist/esm/modules/formula/materialize/spill-engine.js +38 -0
  298. package/dist/esm/modules/formula/materialize/types.js +29 -0
  299. package/dist/esm/modules/formula/materialize/writeback-plan.js +27 -0
  300. package/dist/esm/modules/formula/runtime/evaluator.js +2291 -0
  301. package/dist/esm/modules/formula/runtime/function-registry.js +840 -0
  302. package/dist/esm/modules/formula/runtime/values.js +385 -0
  303. package/dist/esm/modules/formula/syntax/ast.js +28 -0
  304. package/dist/esm/modules/formula/syntax/parser.js +439 -0
  305. package/dist/esm/modules/formula/syntax/token-types.js +59 -0
  306. package/dist/esm/modules/formula/syntax/tokenizer.js +1074 -0
  307. package/dist/esm/modules/pdf/excel-bridge.js +9 -0
  308. package/dist/iife/excelts.iife.js +2302 -373
  309. package/dist/iife/excelts.iife.js.map +1 -1
  310. package/dist/iife/excelts.iife.min.js +34 -34
  311. package/dist/types/index.browser.d.ts +1 -1
  312. package/dist/types/index.d.ts +1 -1
  313. package/dist/types/modules/excel/cell.d.ts +17 -3
  314. package/dist/types/modules/excel/defined-names.d.ts +96 -1
  315. package/dist/types/modules/excel/image.d.ts +11 -0
  316. package/dist/types/modules/excel/stream/workbook-reader.browser.d.ts +9 -3
  317. package/dist/types/modules/excel/stream/workbook-reader.d.ts +2 -1
  318. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +39 -5
  319. package/dist/types/modules/excel/stream/workbook-writer.d.ts +3 -2
  320. package/dist/types/modules/excel/stream/worksheet-writer.d.ts +39 -6
  321. package/dist/types/modules/excel/types.d.ts +133 -2
  322. package/dist/types/modules/excel/utils/col-cache.d.ts +1 -0
  323. package/dist/types/modules/excel/utils/drawing-utils.d.ts +3 -3
  324. package/dist/types/modules/excel/utils/external-link-formula.d.ts +76 -0
  325. package/dist/types/modules/excel/utils/iterate-stream.d.ts +9 -3
  326. package/dist/types/modules/excel/utils/ooxml-paths.d.ts +19 -0
  327. package/dist/types/modules/excel/utils/shared-strings.d.ts +8 -3
  328. package/dist/types/modules/excel/utils/workbook-protection.d.ts +30 -0
  329. package/dist/types/modules/excel/workbook.browser.d.ts +257 -6
  330. package/dist/types/modules/excel/workbook.d.ts +1 -1
  331. package/dist/types/modules/excel/worksheet.d.ts +3 -1
  332. package/dist/types/modules/excel/xlsx/rel-type.d.ts +15 -0
  333. package/dist/types/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +6 -5
  334. package/dist/types/modules/excel/xlsx/xform/book/external-link-xform.d.ts +84 -0
  335. package/dist/types/modules/excel/xlsx/xform/book/external-reference-xform.d.ts +17 -0
  336. package/dist/types/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.d.ts +3 -0
  337. package/dist/types/modules/excel/xlsx/xform/book/workbook-protection-xform.d.ts +20 -0
  338. package/dist/types/modules/excel/xlsx/xform/core/metadata-xform.d.ts +56 -0
  339. package/dist/types/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.d.ts +26 -0
  340. package/dist/types/modules/excel/xlsx/xform/sheet/cell-xform.d.ts +1 -1
  341. package/dist/types/modules/excel/xlsx/xform/sheet/ignored-errors-xform.d.ts +21 -0
  342. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +172 -13
  343. package/dist/types/modules/excel/xlsx/xlsx.d.ts +7 -4
  344. package/dist/types/modules/formula/compile/address-utils.d.ts +62 -0
  345. package/dist/types/modules/formula/compile/binder.d.ts +42 -0
  346. package/dist/types/modules/formula/compile/bound-ast.d.ts +230 -0
  347. package/dist/types/modules/formula/compile/compiled-formula.d.ts +137 -0
  348. package/dist/types/modules/formula/compile/dependency-analysis.d.ts +93 -0
  349. package/dist/types/modules/formula/compile/structured-ref-utils.d.ts +93 -0
  350. package/dist/types/modules/formula/default-syntax-probe.d.ts +79 -0
  351. package/dist/types/modules/formula/functions/_date-context.d.ts +4 -0
  352. package/dist/types/modules/formula/functions/_shared.d.ts +121 -0
  353. package/dist/types/modules/formula/functions/conditional.d.ts +27 -0
  354. package/dist/types/modules/formula/functions/database.d.ts +37 -0
  355. package/dist/types/modules/formula/functions/date.d.ts +61 -0
  356. package/dist/types/modules/formula/functions/dynamic-array.d.ts +23 -0
  357. package/dist/types/modules/formula/functions/engineering.d.ts +57 -0
  358. package/dist/types/modules/formula/functions/financial.d.ts +202 -0
  359. package/dist/types/modules/formula/functions/lookup.d.ts +18 -0
  360. package/dist/types/modules/formula/functions/math.d.ts +114 -0
  361. package/dist/types/modules/formula/functions/statistical.d.ts +193 -0
  362. package/dist/types/modules/formula/functions/text.d.ts +86 -0
  363. package/dist/types/modules/formula/host-registry.d.ts +53 -0
  364. package/dist/types/modules/formula/index.d.ts +39 -0
  365. package/dist/types/modules/formula/install.d.ts +62 -0
  366. package/dist/types/modules/formula/integration/apply-writeback-plan.d.ts +26 -0
  367. package/dist/types/modules/formula/integration/calculate-formulas-impl.d.ts +30 -0
  368. package/dist/types/modules/formula/integration/calculate-formulas.d.ts +67 -0
  369. package/dist/types/modules/formula/integration/formula-instance.d.ts +64 -0
  370. package/dist/types/modules/formula/integration/workbook-adapter.d.ts +26 -0
  371. package/dist/types/modules/formula/integration/workbook-snapshot.d.ts +267 -0
  372. package/dist/types/modules/formula/materialize/build-writeback-plan.d.ts +34 -0
  373. package/dist/types/modules/formula/materialize/spill-engine.d.ts +9 -0
  374. package/dist/types/modules/formula/materialize/types.d.ts +179 -0
  375. package/dist/types/modules/formula/materialize/writeback-plan.d.ts +167 -0
  376. package/dist/types/modules/formula/runtime/evaluator.d.ts +151 -0
  377. package/dist/types/modules/formula/runtime/function-registry.d.ts +47 -0
  378. package/dist/types/modules/formula/runtime/values.d.ts +211 -0
  379. package/dist/types/modules/formula/syntax/ast.d.ts +129 -0
  380. package/dist/types/modules/formula/syntax/parser.d.ts +18 -0
  381. package/dist/types/modules/formula/syntax/token-types.d.ts +153 -0
  382. package/dist/types/modules/formula/syntax/tokenizer.d.ts +10 -0
  383. package/package.json +28 -28
@@ -0,0 +1,1074 @@
1
+ /**
2
+ * Formula Tokenizer
3
+ *
4
+ * Converts an Excel formula string into a stream of tokens.
5
+ * Supports cell references (A1, $A$1), ranges (A1:B10),
6
+ * cross-sheet references (Sheet1!A1), operators, function calls,
7
+ * numbers, strings, booleans, and error literals.
8
+ */
9
+ import { TokenType } from "./token-types.js";
10
+ // ============================================================================
11
+ // Character Helpers
12
+ // ============================================================================
13
+ function isDigit(ch) {
14
+ return ch >= "0" && ch <= "9";
15
+ }
16
+ // Matches a single Unicode letter (any script). Used by `isAlpha` for the
17
+ // non-ASCII path so that defined names in Chinese, Japanese, Korean, Cyrillic,
18
+ // Greek, etc. tokenise correctly (e.g. `=销售额+10`).
19
+ //
20
+ // Note: JavaScript strings are UTF-16, so `formula[i]` yields a single code
21
+ // unit. This regex correctly identifies any BMP letter. Astral-plane letters
22
+ // (U+10000+) require surrogate-pair handling which would be more invasive;
23
+ // BMP coverage is sufficient for virtually all real-world named ranges.
24
+ const UNICODE_LETTER = /\p{L}/u;
25
+ function isAlpha(ch) {
26
+ // ASCII fast path — the overwhelming majority of Excel formulas.
27
+ if ((ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z")) {
28
+ return true;
29
+ }
30
+ // Any other ASCII character (digits, punctuation, control) is not a letter.
31
+ if (ch.charCodeAt(0) < 128) {
32
+ return false;
33
+ }
34
+ // Non-ASCII: defer to Unicode letter classification.
35
+ return UNICODE_LETTER.test(ch);
36
+ }
37
+ function isAlphaNumOrUnderscore(ch) {
38
+ return isAlpha(ch) || isDigit(ch) || ch === "_" || ch === ".";
39
+ }
40
+ /**
41
+ * ASCII-only alpha check. Cell column letters are A–Z only — a Unicode
42
+ * identifier like `销售额` must never be misread as a column. Use this inside
43
+ * `parseCellRef` and other strictly-A1-notation parsers; use `isAlpha` for
44
+ * general identifier lexing where Unicode letters are valid.
45
+ */
46
+ function isAsciiAlpha(ch) {
47
+ return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z");
48
+ }
49
+ /**
50
+ * Cheap lookahead: does position `i` in `s` start a plausible cell or
51
+ * range reference? We only need to distinguish "this is a ref" from
52
+ * "this is anything else" — good enough to disambiguate 3D sheet
53
+ * references (`Q1:Q4!A1`) from name references.
54
+ *
55
+ * Accepts: `$?[A-Z]+$?\d+`-shaped prefixes, whole-column `$?[A-Z]+`,
56
+ * and whole-row `$?\d+`. Anything else (identifiers, punctuation,
57
+ * quotes) returns false.
58
+ */
59
+ function looksLikeCellRefStart(s, i) {
60
+ const len = s.length;
61
+ let j = i;
62
+ if (j < len && s[j] === "$") {
63
+ j++;
64
+ }
65
+ // Either letters first (cell / col) or digits first (whole row)
66
+ if (j < len && isAsciiAlpha(s[j])) {
67
+ // Consume up to 3 letters (Excel column max "XFD"). More than 3
68
+ // consecutive letters means it's not a column ref.
69
+ let letters = 0;
70
+ while (j < len && isAsciiAlpha(s[j]) && letters < 4) {
71
+ j++;
72
+ letters++;
73
+ }
74
+ if (letters === 0 || letters > 3) {
75
+ return false;
76
+ }
77
+ // Optional $digits for an A1-style cell ref; pure letters are a
78
+ // whole-column ref, also valid.
79
+ if (j < len && s[j] === "$") {
80
+ j++;
81
+ }
82
+ // At minimum the following char should be a digit or a delimiter
83
+ // that closes a whole-column ref (`:` / `,` / `)` / operators).
84
+ if (j < len && s[j] >= "0" && s[j] <= "9") {
85
+ return true;
86
+ }
87
+ return true; // whole-column ref, e.g. `A:A` or bare `A`
88
+ }
89
+ if (j < len && s[j] >= "0" && s[j] <= "9") {
90
+ // Whole-row ref (e.g. `1:5`)
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+ /**
96
+ * Advance past a run of spaces and tabs. Used inside bracketed structured
97
+ * reference parsing where only horizontal whitespace is significant (newlines
98
+ * and carriage returns are not expected inside `[...]` tokens).
99
+ */
100
+ function skipHorizontalWhitespace(s, i, len) {
101
+ while (i < len && (s[i] === " " || s[i] === "\t")) {
102
+ i++;
103
+ }
104
+ return i;
105
+ }
106
+ // ============================================================================
107
+ // Error Literal Detection
108
+ // ============================================================================
109
+ /**
110
+ * Known Excel error literals. The constant below is sorted longest-first at
111
+ * module load so greedy longest-match in `matchErrorLiteral` is always
112
+ * correct — future entries can be added in any order.
113
+ */
114
+ const ERROR_LITERALS = [
115
+ "#GETTING_DATA",
116
+ "#BLOCKED!",
117
+ "#CONNECT!",
118
+ "#UNKNOWN!",
119
+ "#SPILL!",
120
+ "#VALUE!",
121
+ "#FIELD!",
122
+ "#DIV/0!",
123
+ "#NAME?",
124
+ "#NULL!",
125
+ "#CALC!",
126
+ "#BUSY!",
127
+ "#REF!",
128
+ "#NUM!",
129
+ "#N/A"
130
+ ].sort((a, b) => b.length - a.length);
131
+ /**
132
+ * Try to match a known Excel error literal at `pos` (which must point at `#`).
133
+ * Matches case-insensitively and greedily (longest list entry wins).
134
+ * Returns the canonical (upper-cased) value and end index, or null on no match.
135
+ */
136
+ function matchErrorLiteral(formula, pos) {
137
+ for (const lit of ERROR_LITERALS) {
138
+ const end = pos + lit.length;
139
+ if (end > formula.length) {
140
+ continue;
141
+ }
142
+ const slice = formula.slice(pos, end);
143
+ if (slice.toUpperCase() === lit) {
144
+ return { value: lit, end };
145
+ }
146
+ }
147
+ return null;
148
+ }
149
+ // ============================================================================
150
+ // Cell Reference Detection
151
+ // ============================================================================
152
+ /**
153
+ * Check if a string is a valid cell reference (like A1, $B$2, XFD1048576).
154
+ * Returns null if not a cell ref, otherwise returns parsed info.
155
+ */
156
+ function parseCellRef(s) {
157
+ let i = 0;
158
+ let colAbsolute = false;
159
+ let rowAbsolute = false;
160
+ if (i < s.length && s[i] === "$") {
161
+ colAbsolute = true;
162
+ i++;
163
+ }
164
+ const colStart = i;
165
+ while (i < s.length && isAsciiAlpha(s[i])) {
166
+ i++;
167
+ }
168
+ const colPart = s.slice(colStart, i).toUpperCase();
169
+ if (colPart.length === 0 || colPart.length > 3) {
170
+ return null;
171
+ }
172
+ // Validate column is <= XFD
173
+ if (colPart.length === 3 && colPart > "XFD") {
174
+ return null;
175
+ }
176
+ if (i < s.length && s[i] === "$") {
177
+ rowAbsolute = true;
178
+ i++;
179
+ }
180
+ const rowStart = i;
181
+ while (i < s.length && isDigit(s[i])) {
182
+ i++;
183
+ }
184
+ const rowPart = s.slice(rowStart, i);
185
+ if (rowPart.length === 0 || i !== s.length) {
186
+ return null;
187
+ }
188
+ const rowNum = parseInt(rowPart, 10);
189
+ if (rowNum < 1 || rowNum > 1048576) {
190
+ return null;
191
+ }
192
+ return { col: colPart, row: rowPart, colAbsolute, rowAbsolute };
193
+ }
194
+ // ============================================================================
195
+ // Structured Reference Bracket Parser
196
+ // ============================================================================
197
+ /** Valid special items in structured references */
198
+ const STRUCTURED_REF_SPECIALS = new Set(["#All", "#Data", "#Headers", "#Totals", "#This Row"]);
199
+ /**
200
+ * Parse the bracketed portion of a structured reference starting at position `pos`.
201
+ * `pos` must point to the opening `[`.
202
+ *
203
+ * Handles:
204
+ * [Column] → simple column
205
+ * [#Headers] → special item
206
+ * [[#Headers],[Column]] → nested: special + column
207
+ * [[#This Row],[Column]] → nested: special + column
208
+ * [[Col1]:[Col2]] → column range
209
+ * [@Column] → shorthand for [#This Row],[Column]
210
+ * [@[Column]] → alternative shorthand
211
+ *
212
+ * Returns parsed specials, columns, and the position after the closing `]`.
213
+ */
214
+ function parseStructuredRefBrackets(formula, pos) {
215
+ const len = formula.length;
216
+ if (pos >= len || formula[pos] !== "[") {
217
+ throw new Error("Expected '[' at position " + pos);
218
+ }
219
+ const specials = [];
220
+ const columns = [];
221
+ let i = pos + 1; // skip opening [
222
+ // Skip whitespace inside brackets
223
+ i = skipHorizontalWhitespace(formula, i, len);
224
+ // Check for @ shorthand: [@Column] or [@[Column]]
225
+ if (i < len && formula[i] === "@") {
226
+ specials.push("#This Row");
227
+ i++; // skip @
228
+ // Skip whitespace
229
+ i = skipHorizontalWhitespace(formula, i, len);
230
+ if (i < len && formula[i] === "[") {
231
+ // [@[Column]] form — inner bracketed column name
232
+ i++; // skip [
233
+ const colName = readBracketedItem(formula, i);
234
+ columns.push(colName.value);
235
+ i = colName.end; // after inner ]
236
+ }
237
+ else if (i < len && formula[i] !== "]") {
238
+ // [@Column] form — unbracketed column name until ]
239
+ const start = i;
240
+ while (i < len && formula[i] !== "]") {
241
+ i++;
242
+ }
243
+ const name = formula.slice(start, i).trim();
244
+ if (name.length > 0) {
245
+ columns.push(name);
246
+ }
247
+ }
248
+ // Skip whitespace before closing ]
249
+ i = skipHorizontalWhitespace(formula, i, len);
250
+ if (i < len && formula[i] === "]") {
251
+ i++; // skip closing ]
252
+ }
253
+ return { specials, columns, end: i };
254
+ }
255
+ // Check for nested brackets: [[...],[...]] or [[Col1]:[Col2]]
256
+ if (i < len && formula[i] === "[") {
257
+ // Parse comma-separated inner bracket items
258
+ while (i < len && formula[i] === "[") {
259
+ const item = readBracketedItem(formula, i + 1);
260
+ const value = item.value;
261
+ i = item.end; // after the inner ]
262
+ if (STRUCTURED_REF_SPECIALS.has(value)) {
263
+ specials.push(value);
264
+ }
265
+ else {
266
+ columns.push(value);
267
+ }
268
+ // Skip whitespace
269
+ i = skipHorizontalWhitespace(formula, i, len);
270
+ // Check for : (column range) or , (next item)
271
+ if (i < len && formula[i] === ":") {
272
+ i++; // skip :
273
+ // Skip whitespace
274
+ i = skipHorizontalWhitespace(formula, i, len);
275
+ // Next must be a [Column]
276
+ if (i < len && formula[i] === "[") {
277
+ const item2 = readBracketedItem(formula, i + 1);
278
+ columns.push(item2.value);
279
+ i = item2.end;
280
+ }
281
+ // Skip whitespace
282
+ i = skipHorizontalWhitespace(formula, i, len);
283
+ }
284
+ else if (i < len && formula[i] === ",") {
285
+ i++; // skip ,
286
+ // Skip whitespace
287
+ i = skipHorizontalWhitespace(formula, i, len);
288
+ }
289
+ }
290
+ // Skip whitespace before closing ]
291
+ i = skipHorizontalWhitespace(formula, i, len);
292
+ if (i < len && formula[i] === "]") {
293
+ i++; // skip outer closing ]
294
+ }
295
+ return { specials, columns, end: i };
296
+ }
297
+ // Check for special item: [#Headers], [#Data], etc.
298
+ if (i < len && formula[i] === "#") {
299
+ const start = i;
300
+ while (i < len && formula[i] !== "]") {
301
+ i++;
302
+ }
303
+ const value = formula.slice(start, i).trim();
304
+ if (STRUCTURED_REF_SPECIALS.has(value)) {
305
+ specials.push(value);
306
+ }
307
+ else {
308
+ // Unknown `[#Something]` is an error, not a silent no-op. Stash
309
+ // the invalid string so the binder can route it to #NAME?; doing
310
+ // this the other way (returning an empty specials+columns) would
311
+ // alias to the default data range and silently mis-evaluate. We
312
+ // use a sentinel prefix that can never match a real special so
313
+ // downstream code distinguishes it unambiguously.
314
+ specials.push(`#__INVALID__:${value}`);
315
+ }
316
+ if (i < len && formula[i] === "]") {
317
+ i++; // skip closing ]
318
+ }
319
+ return { specials, columns, end: i };
320
+ }
321
+ // Simple column reference: [Column]
322
+ const start = i;
323
+ while (i < len && formula[i] !== "]") {
324
+ i++;
325
+ }
326
+ const name = formula.slice(start, i).trim();
327
+ if (name.length > 0) {
328
+ columns.push(name);
329
+ }
330
+ if (i < len && formula[i] === "]") {
331
+ i++; // skip closing ]
332
+ }
333
+ return { specials, columns, end: i };
334
+ }
335
+ /**
336
+ * Read a single bracketed item starting after the opening `[`.
337
+ * Returns the text content and position after the closing `]`.
338
+ * Handles nested `'` escaping within bracket content (Excel uses `'` to escape
339
+ * special chars like `]`, `[`, `#` inside column names — but this is rare).
340
+ */
341
+ function readBracketedItem(formula, pos) {
342
+ const len = formula.length;
343
+ let i = pos;
344
+ let result = "";
345
+ while (i < len && formula[i] !== "]") {
346
+ if (formula[i] === "'" && i + 1 < len) {
347
+ // Escape: the next character is literal
348
+ result += formula[i + 1];
349
+ i += 2;
350
+ }
351
+ else {
352
+ result += formula[i];
353
+ i++;
354
+ }
355
+ }
356
+ if (i < len && formula[i] === "]") {
357
+ i++; // skip ]
358
+ }
359
+ return { value: result.trim(), end: i };
360
+ }
361
+ // ============================================================================
362
+ // Tokenizer
363
+ // ============================================================================
364
+ export function tokenize(formula) {
365
+ const tokens = [];
366
+ let i = 0;
367
+ const len = formula.length;
368
+ // Track whether previous token could produce a value
369
+ // (used to distinguish unary vs binary +/-)
370
+ function lastTokenIsValue() {
371
+ if (tokens.length === 0) {
372
+ return false;
373
+ }
374
+ const last = tokens[tokens.length - 1];
375
+ return (last.type === TokenType.Number ||
376
+ last.type === TokenType.String ||
377
+ last.type === TokenType.Boolean ||
378
+ last.type === TokenType.CellRef ||
379
+ last.type === TokenType.Range ||
380
+ last.type === TokenType.CloseParen ||
381
+ last.type === TokenType.CloseBrace ||
382
+ last.type === TokenType.Percent ||
383
+ last.type === TokenType.Name ||
384
+ last.type === TokenType.Error ||
385
+ last.type === TokenType.ColRange ||
386
+ last.type === TokenType.RowRange ||
387
+ last.type === TokenType.StructuredRef);
388
+ }
389
+ /**
390
+ * True if the previous token produces a reference (cell / range / area).
391
+ * Used to detect Excel's intersection operator — a whitespace character
392
+ * that separates two refs (e.g. `A1:A10 B1:B10`).
393
+ *
394
+ * Intentionally narrower than {@link lastTokenIsValue}: scalars (numbers,
395
+ * strings, booleans, errors) are not references, so whitespace that
396
+ * follows them must not be treated as an intersection operator.
397
+ */
398
+ function lastTokenIsRef() {
399
+ if (tokens.length === 0) {
400
+ return false;
401
+ }
402
+ const last = tokens[tokens.length - 1];
403
+ return (last.type === TokenType.CellRef ||
404
+ last.type === TokenType.Range ||
405
+ last.type === TokenType.ColRange ||
406
+ last.type === TokenType.RowRange ||
407
+ last.type === TokenType.StructuredRef ||
408
+ last.type === TokenType.CloseParen ||
409
+ last.type === TokenType.Name);
410
+ }
411
+ /**
412
+ * True if `ch` can start a reference-producing token: a cell ref, range,
413
+ * sheet-qualified ref (`Sheet!`, `'Sheet'!`), named range, external ref,
414
+ * or a parenthesised ref expression.
415
+ */
416
+ function couldStartRef(ch) {
417
+ return isAlpha(ch) || ch === "$" || ch === "_" || ch === "'" || ch === "[" || ch === "(";
418
+ }
419
+ while (i < len) {
420
+ const ch = formula[i];
421
+ // Whitespace — normally ignored, but between two refs it is Excel's
422
+ // intersection operator (e.g. `A1:A10 B1:B10` → intersection of areas).
423
+ if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") {
424
+ // Consume every consecutive whitespace character in one go.
425
+ while (i < len) {
426
+ const c = formula[i];
427
+ if (c === " " || c === "\t" || c === "\n" || c === "\r") {
428
+ i++;
429
+ }
430
+ else {
431
+ break;
432
+ }
433
+ }
434
+ // Emit an intersection token only when the previous token is a ref
435
+ // AND the next character can start another ref. Emits at most one
436
+ // token per whitespace run.
437
+ if (i < len && lastTokenIsRef() && couldStartRef(formula[i])) {
438
+ tokens.push({ type: TokenType.Intersect });
439
+ }
440
+ continue;
441
+ }
442
+ // String literals
443
+ if (ch === '"') {
444
+ i++; // skip opening quote
445
+ let str = "";
446
+ let closed = false;
447
+ while (i < len) {
448
+ if (formula[i] === '"') {
449
+ if (i + 1 < len && formula[i + 1] === '"') {
450
+ // Escaped quote
451
+ str += '"';
452
+ i += 2;
453
+ }
454
+ else {
455
+ i++; // skip closing quote
456
+ closed = true;
457
+ break;
458
+ }
459
+ }
460
+ else {
461
+ str += formula[i];
462
+ i++;
463
+ }
464
+ }
465
+ if (!closed) {
466
+ // Unterminated string literal — reject at tokenize time so we
467
+ // never hand the parser a truncated value that could alias to a
468
+ // different formula. Excel rejects this outright.
469
+ throw new Error(`Unterminated string literal at position ${i}`);
470
+ }
471
+ tokens.push({ type: TokenType.String, value: str });
472
+ continue;
473
+ }
474
+ // Error literals (#N/A, #REF!, #DIV/0!, etc.)
475
+ // Match greedily against the known Excel error literals (case-insensitive),
476
+ // preferring the longest match so that "#N/A" isn't parsed as "#N" + "/A".
477
+ if (ch === "#") {
478
+ const matched = matchErrorLiteral(formula, i);
479
+ if (matched) {
480
+ tokens.push({ type: TokenType.Error, value: matched.value });
481
+ i = matched.end;
482
+ continue;
483
+ }
484
+ // Fallback: consume through any unknown `#...` token and emit it
485
+ // as a #NAME? error. Unknown error literals (`#FOOBAR!`) are what
486
+ // Excel itself surfaces as #NAME?, and consumers downstream
487
+ // whitelist a fixed set of error codes — letting arbitrary strings
488
+ // like `#FOOBAR!` or bare `#` leak through breaks their enum
489
+ // checks. Normalising to #NAME? here keeps error propagation safe.
490
+ i++;
491
+ while (i < len &&
492
+ formula[i] !== " " &&
493
+ formula[i] !== "," &&
494
+ formula[i] !== ")" &&
495
+ formula[i] !== "+" &&
496
+ formula[i] !== "-" &&
497
+ formula[i] !== "*" &&
498
+ formula[i] !== "/" &&
499
+ formula[i] !== "^" &&
500
+ formula[i] !== "&" &&
501
+ formula[i] !== "=" &&
502
+ formula[i] !== "<" &&
503
+ formula[i] !== ">" &&
504
+ formula[i] !== "}") {
505
+ i++;
506
+ }
507
+ tokens.push({ type: TokenType.Error, value: "#NAME?" });
508
+ continue;
509
+ }
510
+ // External workbook reference: [Book.xlsx]Sheet!A1
511
+ // When [ appears and is NOT preceded by a value token (not array literal context),
512
+ // consume the bracketed workbook name and subsequent sheet!ref as a Name token.
513
+ // This allows the engine to fall back to cached results gracefully.
514
+ //
515
+ // But: a bare [...] can also be a standalone structured reference
516
+ // ([@Col], [#Headers], [[#This Row],[Col]], [Column Name]). Only treat it
517
+ // as an external ref when it really is followed by a `Sheet!` suffix;
518
+ // otherwise fall through to the structured-reference branch below.
519
+ if (ch === "[" && !lastTokenIsValue()) {
520
+ // Peek at first non-whitespace char after [ — @ or # always means a
521
+ // structured reference, never an external workbook reference.
522
+ const peek = skipHorizontalWhitespace(formula, i + 1, len);
523
+ const firstCh = peek < len ? formula[peek] : "";
524
+ const looksStructured = firstCh === "@" || firstCh === "#" || firstCh === "[";
525
+ if (!looksStructured) {
526
+ // Try to parse as an external ref: [WorkbookName]Sheet!...
527
+ let scan = i + 1;
528
+ let hasClose = false;
529
+ while (scan < len) {
530
+ if (formula[scan] === "]") {
531
+ hasClose = true;
532
+ scan++;
533
+ break;
534
+ }
535
+ scan++;
536
+ }
537
+ // Validate: after the closing ], we must see a sheet name (or quoted
538
+ // sheet name) followed by `!`. Otherwise this isn't an external ref.
539
+ let isExternal = false;
540
+ if (hasClose && scan < len) {
541
+ let p = scan;
542
+ if (formula[p] === "'") {
543
+ // Quoted sheet name — scan to matching '
544
+ p++;
545
+ while (p < len) {
546
+ if (formula[p] === "'") {
547
+ if (p + 1 < len && formula[p + 1] === "'") {
548
+ p += 2;
549
+ }
550
+ else {
551
+ p++;
552
+ break;
553
+ }
554
+ }
555
+ else {
556
+ p++;
557
+ }
558
+ }
559
+ if (p < len && formula[p] === "!") {
560
+ isExternal = true;
561
+ }
562
+ }
563
+ else {
564
+ // Unquoted sheet name — identifier chars, then !
565
+ const sheetStart = p;
566
+ while (p < len && (isAlphaNumOrUnderscore(formula[p]) || formula[p] === "$")) {
567
+ p++;
568
+ }
569
+ if (p > sheetStart && p < len && formula[p] === "!") {
570
+ isExternal = true;
571
+ }
572
+ }
573
+ }
574
+ if (isExternal) {
575
+ // External workbook references are unsupported — consume the full
576
+ // ref syntax (including any `:CELL` range suffix) so downstream
577
+ // tokens aren't polluted, then emit a single `#REF!` error token.
578
+ // The boundary logic preserved here matches the pre-simplification
579
+ // tokenizer exactly so the range of characters consumed is stable.
580
+ i = scan;
581
+ while (i < len &&
582
+ formula[i] !== "+" &&
583
+ formula[i] !== "-" &&
584
+ formula[i] !== "*" &&
585
+ formula[i] !== "/" &&
586
+ formula[i] !== "^" &&
587
+ formula[i] !== "&" &&
588
+ formula[i] !== "=" &&
589
+ formula[i] !== "<" &&
590
+ formula[i] !== ">" &&
591
+ formula[i] !== ")" &&
592
+ formula[i] !== "," &&
593
+ formula[i] !== " " &&
594
+ formula[i] !== "}" &&
595
+ formula[i] !== ":" &&
596
+ formula[i] !== "%" &&
597
+ formula[i] !== ";" &&
598
+ formula[i] !== "{") {
599
+ i++;
600
+ }
601
+ // Extend consumption through `:CELL` if this is a range form
602
+ // like [Book]Sheet!A1:A5. Without this, `:` would split the
603
+ // external ref and pollute downstream tokens.
604
+ if (i < len && formula[i] === ":") {
605
+ let q = i + 1;
606
+ // Optional $ absolute markers, column letters, optional $, row digits
607
+ if (q < len && formula[q] === "$") {
608
+ q++;
609
+ }
610
+ const colStart = q;
611
+ while (q < len && isAsciiAlpha(formula[q])) {
612
+ q++;
613
+ }
614
+ if (q > colStart) {
615
+ if (q < len && formula[q] === "$") {
616
+ q++;
617
+ }
618
+ const rowStart = q;
619
+ while (q < len && isDigit(formula[q])) {
620
+ q++;
621
+ }
622
+ if (q > rowStart) {
623
+ // Valid `:CELL` suffix — consume it as part of the ref
624
+ i = q;
625
+ }
626
+ }
627
+ }
628
+ tokens.push({ type: TokenType.Error, value: "#REF!" });
629
+ continue;
630
+ }
631
+ // Not an external ref — fall through to the structured-reference
632
+ // branch below, which handles bare [Column Name] etc.
633
+ }
634
+ }
635
+ // Array constant braces
636
+ if (ch === "{") {
637
+ tokens.push({ type: TokenType.OpenBrace });
638
+ i++;
639
+ continue;
640
+ }
641
+ if (ch === "}") {
642
+ tokens.push({ type: TokenType.CloseBrace });
643
+ i++;
644
+ continue;
645
+ }
646
+ // Semicolons (array row separator)
647
+ if (ch === ";") {
648
+ tokens.push({ type: TokenType.Semicolon });
649
+ i++;
650
+ continue;
651
+ }
652
+ // Numbers (and potential whole-row ranges like 1:5)
653
+ if (isDigit(ch) || (ch === "." && i + 1 < len && isDigit(formula[i + 1]))) {
654
+ const start = i;
655
+ while (i < len && isDigit(formula[i])) {
656
+ i++;
657
+ }
658
+ // Check for whole-row range: pure integer followed by : and another integer
659
+ const isInteger = i > start && formula[start] !== "." && i < len && formula[i] === ":";
660
+ if (isInteger) {
661
+ const row1Str = formula.slice(start, i);
662
+ const row1 = parseInt(row1Str, 10);
663
+ if (row1 >= 1 && row1 <= 1048576) {
664
+ const colonPos = i;
665
+ i++; // skip :
666
+ // Optional $ before second row number
667
+ const start2 = i;
668
+ if (i < len && formula[i] === "$") {
669
+ i++;
670
+ }
671
+ const rowStart2 = i;
672
+ while (i < len && isDigit(formula[i])) {
673
+ i++;
674
+ }
675
+ if (i > rowStart2) {
676
+ const row2Str = formula.slice(rowStart2, i);
677
+ const row2 = parseInt(row2Str, 10);
678
+ if (row2 >= 1 && row2 <= 1048576) {
679
+ tokens.push({
680
+ type: TokenType.RowRange,
681
+ value: row1Str + ":" + formula.slice(start2, i)
682
+ });
683
+ continue;
684
+ }
685
+ }
686
+ // Not a valid row range, backtrack
687
+ i = colonPos;
688
+ }
689
+ }
690
+ // Fractional part — only consume the `.` when at least one digit
691
+ // follows. This prevents "1..2" from being tokenised as "1." + ".2"
692
+ // with the first `.` silently absorbed into the integer number.
693
+ if (i < len && formula[i] === "." && i + 1 < len && isDigit(formula[i + 1])) {
694
+ i++;
695
+ while (i < len && isDigit(formula[i])) {
696
+ i++;
697
+ }
698
+ }
699
+ // Scientific notation. Only commit to consuming `e[+-]?` when at least
700
+ // one digit actually follows — otherwise restore position and stop the
701
+ // number here, so `1e` or `1e+` don't parse as the number `1`.
702
+ if (i < len && (formula[i] === "E" || formula[i] === "e")) {
703
+ const expStart = i;
704
+ let j = i + 1;
705
+ if (j < len && (formula[j] === "+" || formula[j] === "-")) {
706
+ j++;
707
+ }
708
+ if (j < len && isDigit(formula[j])) {
709
+ // Commit: consume e, optional sign, and the digit run.
710
+ i = j;
711
+ while (i < len && isDigit(formula[i])) {
712
+ i++;
713
+ }
714
+ }
715
+ else {
716
+ // Not a valid exponent — leave the `e`/`E` for the identifier lexer.
717
+ i = expStart;
718
+ }
719
+ }
720
+ tokens.push({ type: TokenType.Number, value: formula.slice(start, i) });
721
+ continue;
722
+ }
723
+ // Quoted sheet name: 'Sheet Name'! or 3D ref 'Sheet1:Sheet3'!
724
+ if (ch === "'") {
725
+ i++; // skip opening quote
726
+ let sheetName = "";
727
+ while (i < len) {
728
+ if (formula[i] === "'") {
729
+ if (i + 1 < len && formula[i + 1] === "'") {
730
+ sheetName += "'";
731
+ i += 2;
732
+ }
733
+ else {
734
+ i++; // skip closing quote
735
+ break;
736
+ }
737
+ }
738
+ else {
739
+ sheetName += formula[i];
740
+ i++;
741
+ }
742
+ }
743
+ // Expect ! after
744
+ if (i < len && formula[i] === "!") {
745
+ i++; // skip !
746
+ }
747
+ // Check for 3D reference: sheetName contains an unquoted colon separating two sheet names
748
+ // e.g. 'Sheet1:Sheet3' → startSheet="Sheet1", endSheet="Sheet3"
749
+ const colonIdx = sheetName.indexOf(":");
750
+ if (colonIdx > 0 && colonIdx < sheetName.length - 1) {
751
+ const startSheet = sheetName.slice(0, colonIdx);
752
+ const endSheet = sheetName.slice(colonIdx + 1);
753
+ tokens.push({
754
+ type: TokenType.SheetRef,
755
+ sheetName: startSheet,
756
+ endSheetName: endSheet
757
+ });
758
+ }
759
+ else {
760
+ tokens.push({
761
+ type: TokenType.SheetRef,
762
+ sheetName
763
+ });
764
+ }
765
+ continue;
766
+ }
767
+ // Standalone structured reference: [@Column] or [[#This Row],[Column]]
768
+ if (ch === "[") {
769
+ const sr = parseStructuredRefBrackets(formula, i);
770
+ tokens.push({
771
+ type: TokenType.StructuredRef,
772
+ tableName: "",
773
+ columns: sr.columns,
774
+ specials: sr.specials
775
+ });
776
+ i = sr.end;
777
+ continue;
778
+ }
779
+ // Identifiers: cell refs, function names, booleans, named ranges, sheet refs
780
+ if (isAlpha(ch) || ch === "$" || ch === "_") {
781
+ const start = i;
782
+ // Collect $-prefixed or alpha-numeric identifier
783
+ while (i < len && (isAlphaNumOrUnderscore(formula[i]) || formula[i] === "$")) {
784
+ i++;
785
+ }
786
+ const word = formula.slice(start, i);
787
+ // Check for sheet reference: WORD!
788
+ if (i < len && formula[i] === "!") {
789
+ i++; // skip !
790
+ tokens.push({
791
+ type: TokenType.SheetRef,
792
+ sheetName: word
793
+ });
794
+ continue;
795
+ }
796
+ // Check for 3D sheet reference: WORD:WORD!<ref>
797
+ // (e.g. Sheet1:Sheet3!A1, Q1:Q4!A1).
798
+ //
799
+ // Priority rule: when we see `WORD:WORD!` followed by an actual
800
+ // cell/range reference, that whole prefix is a 3D SheetRef —
801
+ // even when the first WORD looks like a cell reference on its
802
+ // own (sheet names like "Q1" are common). Previously we bailed
803
+ // out of this branch whenever `parseCellRef(word) !== null`,
804
+ // which left workbooks with sheets named Q1..Q4 unable to use
805
+ // 3D formulas.
806
+ //
807
+ // The fallback path below (RangeRef for `A1:B2`-shaped inputs)
808
+ // still fires when there is NO `!` — the lookahead disambiguates
809
+ // the two cases cleanly.
810
+ if (i < len && formula[i] === ":") {
811
+ const colonPos3D = i;
812
+ let j = i + 1;
813
+ // Collect the second sheet name (alphanumeric + underscore + .)
814
+ while (j < len && (isAlphaNumOrUnderscore(formula[j]) || formula[j] === "$")) {
815
+ j++;
816
+ }
817
+ if (j > colonPos3D + 1 && j < len && formula[j] === "!") {
818
+ const endSheet = formula.slice(colonPos3D + 1, j);
819
+ // Peek past `!` — accept the 3D reading only if a recognisable
820
+ // cell or range reference follows. Otherwise fall through
821
+ // so `A1:A3` (no trailing `!`) keeps its RangeRef reading.
822
+ const afterBang = j + 1;
823
+ if (afterBang < len && looksLikeCellRefStart(formula, afterBang)) {
824
+ i = afterBang; // skip past !
825
+ tokens.push({
826
+ type: TokenType.SheetRef,
827
+ sheetName: word,
828
+ endSheetName: endSheet
829
+ });
830
+ continue;
831
+ }
832
+ }
833
+ }
834
+ // Check for structured reference: WORD[...]
835
+ if (i < len && formula[i] === "[") {
836
+ const sr = parseStructuredRefBrackets(formula, i);
837
+ tokens.push({
838
+ type: TokenType.StructuredRef,
839
+ tableName: word,
840
+ columns: sr.columns,
841
+ specials: sr.specials
842
+ });
843
+ i = sr.end;
844
+ continue;
845
+ }
846
+ // Check for function call: WORD(
847
+ if (i < len && formula[i] === "(") {
848
+ tokens.push({ type: TokenType.Function, name: word.toUpperCase() });
849
+ // Don't consume the '(' — it will be consumed in the next iteration
850
+ continue;
851
+ }
852
+ // Check for boolean literals
853
+ const upper = word.toUpperCase();
854
+ if (upper === "TRUE" || upper === "FALSE") {
855
+ tokens.push({ type: TokenType.Boolean, value: upper });
856
+ continue;
857
+ }
858
+ // Check for cell reference
859
+ const ref = parseCellRef(word);
860
+ if (ref) {
861
+ const cellToken = {
862
+ type: TokenType.CellRef,
863
+ col: ref.col,
864
+ row: ref.row,
865
+ colAbsolute: ref.colAbsolute,
866
+ rowAbsolute: ref.rowAbsolute
867
+ };
868
+ tokens.push(cellToken);
869
+ // Check if this is part of a range (A1:B2)
870
+ if (i < len && formula[i] === ":") {
871
+ // Peek ahead for another cell ref
872
+ const colonPos = i;
873
+ i++; // skip :
874
+ const rangeStart = i;
875
+ while (i < len && (isAlphaNumOrUnderscore(formula[i]) || formula[i] === "$")) {
876
+ i++;
877
+ }
878
+ const secondWord = formula.slice(rangeStart, i);
879
+ const ref2 = parseCellRef(secondWord);
880
+ if (ref2) {
881
+ // It's a range — replace the last CellRef token with a Range token
882
+ tokens[tokens.length - 1] = {
883
+ type: TokenType.Range,
884
+ value: (ref.colAbsolute ? "$" : "") +
885
+ ref.col +
886
+ (ref.rowAbsolute ? "$" : "") +
887
+ ref.row +
888
+ ":" +
889
+ (ref2.colAbsolute ? "$" : "") +
890
+ ref2.col +
891
+ (ref2.rowAbsolute ? "$" : "") +
892
+ ref2.row
893
+ };
894
+ }
895
+ else {
896
+ // Not a valid range, push : as operator and backtrack
897
+ i = colonPos; // backtrack — let : be handled normally
898
+ }
899
+ }
900
+ continue;
901
+ }
902
+ // Otherwise it's a named range / defined name — but check for whole-column range first (A:B, $A:$B)
903
+ // A whole-column ref looks like: [pure-alpha or $+alpha] followed by ':'
904
+ if (i < len && formula[i] === ":") {
905
+ // Check if 'word' is a pure column reference (optional $ + letters only, no digits)
906
+ const colRefMatch = /^(\$?)([A-Za-z]{1,3})$/.exec(word);
907
+ if (colRefMatch) {
908
+ const col1Abs = colRefMatch[1] === "$";
909
+ const col1 = colRefMatch[2].toUpperCase();
910
+ // Validate column <= XFD
911
+ if (col1.length <= 3 && (col1.length < 3 || col1 <= "XFD")) {
912
+ const colonPos = i;
913
+ i++; // skip :
914
+ // Read the second column ref
915
+ const start2 = i;
916
+ if (i < len && formula[i] === "$") {
917
+ i++;
918
+ }
919
+ const colStart2 = i;
920
+ while (i < len && isAsciiAlpha(formula[i])) {
921
+ i++;
922
+ }
923
+ // Ensure it's ONLY letters (no digits follow = not a cell ref)
924
+ const afterLetters = i;
925
+ const isFollowedByDigit = i < len && isDigit(formula[i]);
926
+ if (!isFollowedByDigit && afterLetters > colStart2) {
927
+ const part2 = formula.slice(start2, i);
928
+ const col2Match = /^(\$?)([A-Za-z]{1,3})$/.exec(part2);
929
+ if (col2Match) {
930
+ const col2 = col2Match[2].toUpperCase();
931
+ if (col2.length <= 3 && (col2.length < 3 || col2 <= "XFD")) {
932
+ tokens.push({
933
+ type: TokenType.ColRange,
934
+ value: (col1Abs ? "$" : "") + col1 + ":" + part2.toUpperCase()
935
+ });
936
+ continue;
937
+ }
938
+ }
939
+ }
940
+ // Not a valid column range, backtrack
941
+ i = colonPos;
942
+ }
943
+ }
944
+ // Check for absolute whole-row range: $1:$5
945
+ const absRowMatch = /^(\$)(\d+)$/.exec(word);
946
+ if (absRowMatch) {
947
+ const row1 = parseInt(absRowMatch[2], 10);
948
+ if (row1 >= 1 && row1 <= 1048576) {
949
+ const colonPos = i;
950
+ i++; // skip :
951
+ const start2 = i;
952
+ if (i < len && formula[i] === "$") {
953
+ i++;
954
+ }
955
+ const rowStart2 = i;
956
+ while (i < len && isDigit(formula[i])) {
957
+ i++;
958
+ }
959
+ if (i > rowStart2) {
960
+ const row2 = parseInt(formula.slice(rowStart2, i), 10);
961
+ if (row2 >= 1 && row2 <= 1048576) {
962
+ tokens.push({
963
+ type: TokenType.RowRange,
964
+ value: word + ":" + formula.slice(start2, i)
965
+ });
966
+ continue;
967
+ }
968
+ }
969
+ i = colonPos;
970
+ }
971
+ }
972
+ }
973
+ tokens.push({ type: TokenType.Name, value: word });
974
+ continue;
975
+ }
976
+ // Operators and punctuation
977
+ if (ch === "(") {
978
+ tokens.push({ type: TokenType.OpenParen });
979
+ i++;
980
+ continue;
981
+ }
982
+ if (ch === ")") {
983
+ tokens.push({ type: TokenType.CloseParen });
984
+ i++;
985
+ continue;
986
+ }
987
+ if (ch === ",") {
988
+ tokens.push({ type: TokenType.Comma });
989
+ i++;
990
+ continue;
991
+ }
992
+ if (ch === ":") {
993
+ tokens.push({ type: TokenType.Colon });
994
+ i++;
995
+ continue;
996
+ }
997
+ if (ch === "%") {
998
+ tokens.push({ type: TokenType.Percent });
999
+ i++;
1000
+ continue;
1001
+ }
1002
+ // Multi-character operators
1003
+ if (ch === "<") {
1004
+ if (i + 1 < len && formula[i + 1] === "=") {
1005
+ tokens.push({ type: TokenType.Operator, value: "<=" });
1006
+ i += 2;
1007
+ continue;
1008
+ }
1009
+ if (i + 1 < len && formula[i + 1] === ">") {
1010
+ tokens.push({ type: TokenType.Operator, value: "<>" });
1011
+ i += 2;
1012
+ continue;
1013
+ }
1014
+ tokens.push({ type: TokenType.Operator, value: "<" });
1015
+ i++;
1016
+ continue;
1017
+ }
1018
+ if (ch === ">") {
1019
+ if (i + 1 < len && formula[i + 1] === "=") {
1020
+ tokens.push({ type: TokenType.Operator, value: ">=" });
1021
+ i += 2;
1022
+ continue;
1023
+ }
1024
+ tokens.push({ type: TokenType.Operator, value: ">" });
1025
+ i++;
1026
+ continue;
1027
+ }
1028
+ if (ch === "=") {
1029
+ tokens.push({ type: TokenType.Operator, value: "=" });
1030
+ i++;
1031
+ continue;
1032
+ }
1033
+ if (ch === "&") {
1034
+ tokens.push({ type: TokenType.Operator, value: "&" });
1035
+ i++;
1036
+ continue;
1037
+ }
1038
+ if (ch === "^") {
1039
+ tokens.push({ type: TokenType.Operator, value: "^" });
1040
+ i++;
1041
+ continue;
1042
+ }
1043
+ if (ch === "*") {
1044
+ tokens.push({ type: TokenType.Operator, value: "*" });
1045
+ i++;
1046
+ continue;
1047
+ }
1048
+ if (ch === "/") {
1049
+ tokens.push({ type: TokenType.Operator, value: "/" });
1050
+ i++;
1051
+ continue;
1052
+ }
1053
+ // + and - : unary or binary
1054
+ if (ch === "+" || ch === "-") {
1055
+ if (!lastTokenIsValue()) {
1056
+ tokens.push({ type: TokenType.UnaryPrefix, value: ch });
1057
+ }
1058
+ else {
1059
+ tokens.push({ type: TokenType.Operator, value: ch });
1060
+ }
1061
+ i++;
1062
+ continue;
1063
+ }
1064
+ // @ implicit intersection prefix (Excel 365)
1065
+ if (ch === "@") {
1066
+ tokens.push({ type: TokenType.AtSign });
1067
+ i++;
1068
+ continue;
1069
+ }
1070
+ // Unknown character — skip
1071
+ i++;
1072
+ }
1073
+ return tokens;
1074
+ }