@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,2298 @@
1
+ "use strict";
2
+ /**
3
+ * Evaluator — Execute BoundExpr using the RuntimeValue system.
4
+ *
5
+ * The evaluator operates on BoundExpr (from the compile phase),
6
+ * WorkbookSnapshot (from the snapshot phase), and RuntimeValue
7
+ * (the value system).
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.EvalSession = void 0;
11
+ exports.evaluate = evaluate;
12
+ exports.evaluateFormula = evaluateFormula;
13
+ exports.evaluateFormulaRaw = evaluateFormulaRaw;
14
+ exports.implicitIntersect = implicitIntersect;
15
+ const address_utils_1 = require("../compile/address-utils");
16
+ const binder_1 = require("../compile/binder");
17
+ const structured_ref_utils_1 = require("../compile/structured-ref-utils");
18
+ const workbook_snapshot_1 = require("../integration/workbook-snapshot");
19
+ const parser_1 = require("../syntax/parser");
20
+ const token_types_1 = require("../syntax/token-types");
21
+ const tokenizer_1 = require("../syntax/tokenizer");
22
+ const function_registry_1 = require("./function-registry");
23
+ const values_1 = require("./values");
24
+ /**
25
+ * Per-calculation mutable state.
26
+ */
27
+ class EvalSession {
28
+ constructor() {
29
+ /** Cells currently on the evaluation call stack (circular-ref detection). */
30
+ this.evaluating = new Set();
31
+ /**
32
+ * Unified formula result cache.
33
+ * Each entry holds both the scalar form (for dependents) and the raw form
34
+ * (for materialize). This replaces the previous separate cache/rawCache
35
+ * pattern with a single, self-documenting structure.
36
+ */
37
+ this.resultCache = new Map();
38
+ /** Cache for runtime name resolution (defined names that need parsing). */
39
+ this.nameCache = new Map();
40
+ /** Fallback values for circular references during iterative calculation. */
41
+ this.circularFallback = new Map();
42
+ /**
43
+ * Live spill map: cell key → (masterKey, row-offset, col-offset).
44
+ *
45
+ * Populated as soon as a dynamic-array formula is evaluated and yields
46
+ * an array result. Downstream formulas that read a cell inside the
47
+ * spill region look the master's cached array up via this map and
48
+ * return the correct element — even before materialize has written
49
+ * the ghost cells to the snapshot.
50
+ *
51
+ * This is the fix for "first-pass `=SUM(A1:A5)` over a `=SEQUENCE(5)`
52
+ * spill" — without the live map, `getCellValue("S", 2, 1)` returned
53
+ * BLANK and SUM only counted the master cell.
54
+ */
55
+ this.liveSpills = new Map();
56
+ /**
57
+ * Runtime dependency recorder — tracks cell accesses made during evaluation.
58
+ *
59
+ * When a formula with `hasDynamicRefs` (INDIRECT/OFFSET) is being evaluated,
60
+ * every `getCellValue` / `buildRangeArray` call records the accessed cell/range
61
+ * key here. After evaluation, these dynamic edges can be merged with the
62
+ * compiled static dependency set to produce a complete dependency graph.
63
+ *
64
+ * Key: formula cell key being evaluated → Set of accessed cell keys.
65
+ * Only populated for formulas that have `hasDynamicRefs === true`.
66
+ */
67
+ this.dynamicDeps = new Map();
68
+ /**
69
+ * The formula cell key currently being recorded (null if recording is off).
70
+ * Set before evaluating a formula with dynamic refs, cleared after.
71
+ */
72
+ this.recordingKey = null;
73
+ /**
74
+ * Current LAMBDA invocation depth. Guards against unbounded recursion
75
+ * (e.g. `LAMBDA(x, x(x))(LAMBDA(x, x(x)))`) that would otherwise overflow
76
+ * the JS call stack. Excel documents a recursion limit of ~256.
77
+ */
78
+ this.lambdaDepth = 0;
79
+ /**
80
+ * AST cache for INDIRECT re-parsing. INDIRECT receives a runtime string
81
+ * describing a reference; re-parsing it per invocation would be wasted
82
+ * work, so we memoise the `bound` expression keyed on the reference
83
+ * text. This belongs to the session (per-calculation lifetime) rather
84
+ * than the snapshot because the bindings depend on the evaluation
85
+ * context.
86
+ */
87
+ this.indirectAstCache = new Map();
88
+ }
89
+ makeKey(sheet, row, col) {
90
+ return (0, workbook_snapshot_1.formulaCellKey)(sheet, row, col);
91
+ }
92
+ /**
93
+ * Record a cell access for the currently-recording formula.
94
+ */
95
+ recordAccess(sheet, row, col) {
96
+ if (this.recordingKey === null) {
97
+ return;
98
+ }
99
+ let deps = this.dynamicDeps.get(this.recordingKey);
100
+ if (!deps) {
101
+ deps = new Set();
102
+ this.dynamicDeps.set(this.recordingKey, deps);
103
+ }
104
+ deps.add((0, workbook_snapshot_1.formulaCellKey)(sheet, row, col));
105
+ }
106
+ }
107
+ exports.EvalSession = EvalSession;
108
+ // ============================================================================
109
+ // Main Evaluate Function
110
+ // ============================================================================
111
+ /**
112
+ * Exhaustiveness helper — TypeScript narrows `never` here to prove that
113
+ * every discriminated-union variant was handled. At runtime this should be
114
+ * unreachable; if a new variant is added without a case, compilation fails.
115
+ */
116
+ function assertNever(x) {
117
+ throw new Error(`unexpected variant: ${JSON.stringify(x)}`);
118
+ }
119
+ /**
120
+ * Evaluate a BoundExpr to produce a RuntimeValue.
121
+ */
122
+ function evaluate(expr, ctx, session) {
123
+ switch (expr.kind) {
124
+ case 1 /* BoundExprKind.Literal */:
125
+ return evaluateLiteral(expr);
126
+ case 2 /* BoundExprKind.CellRef */:
127
+ return evaluateCellRef(expr, ctx, session);
128
+ case 3 /* BoundExprKind.AreaRef */:
129
+ return evaluateAreaRef(expr, ctx, session);
130
+ case 4 /* BoundExprKind.ColRangeRef */:
131
+ return evaluateColRange(expr, ctx, session);
132
+ case 5 /* BoundExprKind.RowRangeRef */:
133
+ return evaluateRowRange(expr, ctx, session);
134
+ case 6 /* BoundExprKind.Ref3D */:
135
+ return evaluateRef3D(expr, ctx, session);
136
+ case 7 /* BoundExprKind.BinaryOp */:
137
+ return evaluateBinaryOp(expr.op, expr.left, expr.right, ctx, session);
138
+ case 8 /* BoundExprKind.UnaryOp */:
139
+ return evaluateUnaryOp(expr.op, expr.operand, ctx, session);
140
+ case 9 /* BoundExprKind.Percent */:
141
+ return evaluatePercent(expr.operand, ctx, session);
142
+ case 10 /* BoundExprKind.Call */:
143
+ return evaluateCall(expr, ctx, session);
144
+ case 11 /* BoundExprKind.SpecialCall */:
145
+ return evaluateSpecialCall(expr, ctx, session);
146
+ case 12 /* BoundExprKind.Array */:
147
+ return evaluateArrayLiteral(expr, ctx, session);
148
+ case 13 /* BoundExprKind.NameExpr */:
149
+ return evaluateNameExpr(expr, ctx, session);
150
+ case 14 /* BoundExprKind.Lambda */:
151
+ return evaluateLambdaExpr(expr, ctx);
152
+ case 15 /* BoundExprKind.StructuredRef */:
153
+ return evaluateStructuredRef(expr, ctx, session);
154
+ default:
155
+ return assertNever(expr);
156
+ }
157
+ }
158
+ // ============================================================================
159
+ // Literal
160
+ // ============================================================================
161
+ function evaluateLiteral(expr) {
162
+ if (expr.errorCode) {
163
+ return (0, values_1.rvError)(expr.errorCode);
164
+ }
165
+ if (expr.value === null) {
166
+ return values_1.BLANK;
167
+ }
168
+ if (typeof expr.value === "number") {
169
+ return (0, values_1.rvNumber)(expr.value);
170
+ }
171
+ if (typeof expr.value === "string") {
172
+ return (0, values_1.rvString)(expr.value);
173
+ }
174
+ if (typeof expr.value === "boolean") {
175
+ return (0, values_1.rvBoolean)(expr.value);
176
+ }
177
+ return values_1.BLANK;
178
+ }
179
+ // ============================================================================
180
+ // Cell Reference
181
+ // ============================================================================
182
+ function evaluateCellRef(expr, ctx, session) {
183
+ return (0, values_1.rvCellRef)(expr.sheet, expr.row, expr.col);
184
+ }
185
+ // ============================================================================
186
+ // Area Reference → ReferenceValue
187
+ // ============================================================================
188
+ function evaluateAreaRef(expr, ctx, session) {
189
+ return (0, values_1.rvRef)(expr.sheet, expr.top, expr.left, expr.bottom, expr.right);
190
+ }
191
+ function evaluateColRange(expr, ctx, session) {
192
+ const ws = ctx.snapshot.worksheetsByName.get(expr.sheet.toLowerCase());
193
+ if (!ws || !ws.dimensions) {
194
+ return (0, values_1.rvArray)([]);
195
+ }
196
+ return (0, values_1.rvRef)(expr.sheet, ws.dimensions.top, expr.leftCol, ws.dimensions.bottom, expr.rightCol);
197
+ }
198
+ function evaluateRowRange(expr, ctx, session) {
199
+ const ws = ctx.snapshot.worksheetsByName.get(expr.sheet.toLowerCase());
200
+ if (!ws || !ws.dimensions) {
201
+ return (0, values_1.rvArray)([]);
202
+ }
203
+ return (0, values_1.rvRef)(expr.sheet, expr.topRow, ws.dimensions.left, expr.bottomRow, ws.dimensions.right);
204
+ }
205
+ function evaluateRef3D(expr, ctx, session) {
206
+ const areas = [];
207
+ for (const sheet of expr.sheets) {
208
+ if (expr.inner.kind === 2 /* BoundExprKind.CellRef */) {
209
+ areas.push({
210
+ sheet,
211
+ top: expr.inner.row,
212
+ left: expr.inner.col,
213
+ bottom: expr.inner.row,
214
+ right: expr.inner.col
215
+ });
216
+ }
217
+ else {
218
+ // For AreaRef: if the inner range is a whole-column or whole-row
219
+ // reference (top = 1 & bottom = 1048576, or left = 1 & right =
220
+ // 16384), clamp it against each sheet's actual used dimensions.
221
+ // Without this clamp, a 3D reference like `Sheet1:Sheet3!A:A`
222
+ // would allocate 3 × 1M rows of BLANK values and spend seconds
223
+ // (or OOM) before SUM even starts. The non-3D path already does
224
+ // this clamp in `evaluateColRange`; parity with that is what we
225
+ // want here.
226
+ let top = expr.inner.top;
227
+ let left = expr.inner.left;
228
+ let bottom = expr.inner.bottom;
229
+ let right = expr.inner.right;
230
+ const isWholeCol = top === 1 && bottom === 1048576;
231
+ const isWholeRow = left === 1 && right === 16384;
232
+ if (isWholeCol || isWholeRow) {
233
+ const ws = ctx.snapshot.worksheetsByName.get(sheet.toLowerCase());
234
+ const dims = ws?.dimensions;
235
+ if (dims) {
236
+ if (isWholeCol) {
237
+ top = dims.top;
238
+ bottom = dims.bottom;
239
+ }
240
+ if (isWholeRow) {
241
+ left = dims.left;
242
+ right = dims.right;
243
+ }
244
+ }
245
+ }
246
+ areas.push({ sheet, top, left, bottom, right });
247
+ }
248
+ }
249
+ return { kind: 6 /* RVKind.Reference */, areas };
250
+ }
251
+ // ============================================================================
252
+ // Build Range Array from Snapshot
253
+ // ============================================================================
254
+ function buildRangeArray(ctx, session, sheet, top, left, bottom, right) {
255
+ // Hoist the worksheet lookup once for the entire range — avoids
256
+ // N redundant toLowerCase()/Map.get() calls inside the hot loop.
257
+ const ws = ctx.snapshot.worksheetsByName.get(sheet.toLowerCase());
258
+ const cells = ws?.cells;
259
+ const wsHiddenRows = ws?.hiddenRows;
260
+ const compiledFormulas = ctx.compiledFormulas;
261
+ const resultCache = session.resultCache;
262
+ // Hoist the recording guard — when not recording, skip recordAccess
263
+ // entirely in the loop instead of paying the function-call overhead.
264
+ const recording = session.recordingKey !== null;
265
+ const rows = [];
266
+ // Lazily-allocated masks: only materialized once we encounter a
267
+ // SUBTOTAL/AGGREGATE cell or a hidden row inside the range. For the
268
+ // common case (plain data range, no hidden rows) we never touch these
269
+ // and emit an ArrayValue without extra metadata.
270
+ let subtotalMask;
271
+ let hiddenRowMask;
272
+ const height = bottom - top + 1;
273
+ const width = right - left + 1;
274
+ for (let r = top; r <= bottom; r++) {
275
+ const row = [];
276
+ const ri = r - top;
277
+ // Record row visibility — SUBTOTAL 1xx / AGGREGATE opt 5/7 use it.
278
+ if (wsHiddenRows?.has(r)) {
279
+ if (!hiddenRowMask) {
280
+ hiddenRowMask = new Array(height).fill(false);
281
+ }
282
+ hiddenRowMask[ri] = true;
283
+ }
284
+ for (let c = left; c <= right; c++) {
285
+ if (recording) {
286
+ session.recordAccess(sheet, r, c);
287
+ }
288
+ // Missing worksheet: matches getCellValue's BLANK fallback.
289
+ if (!cells) {
290
+ row.push(values_1.BLANK);
291
+ continue;
292
+ }
293
+ const cell = cells.get((0, workbook_snapshot_1.snapshotCellKey)(r, c));
294
+ if (!cell) {
295
+ // No snapshot cell yet — might still be inside a live spill
296
+ // (e.g. reading A2..A5 while A1 = SEQUENCE(5) is still being
297
+ // materialized). See `readLiveSpill` for the lookup path.
298
+ const live = readLiveSpill(sheet, r, c, session);
299
+ row.push(live ? (0, values_1.topLeft)(live) : values_1.BLANK);
300
+ continue;
301
+ }
302
+ if (cell.formulaKind !== "none" && cell.formula) {
303
+ const fKey = (0, workbook_snapshot_1.formulaCellKey)(sheet, r, c);
304
+ const compiled = compiledFormulas.get(fKey);
305
+ // Mark SUBTOTAL/AGGREGATE output cells so an outer SUBTOTAL /
306
+ // AGGREGATE over this range knows to skip them (Excel's
307
+ // no-double-count semantics).
308
+ if (compiled?.isSubtotalOutput) {
309
+ if (!subtotalMask) {
310
+ subtotalMask = new Array(height);
311
+ for (let i = 0; i < height; i++) {
312
+ subtotalMask[i] = new Array(width).fill(false);
313
+ }
314
+ }
315
+ subtotalMask[ri][c - left] = true;
316
+ }
317
+ const cached = resultCache.get(fKey);
318
+ if (cached !== undefined) {
319
+ row.push((0, values_1.topLeft)(cached.scalar));
320
+ continue;
321
+ }
322
+ if (compiled) {
323
+ row.push((0, values_1.topLeft)(evaluateFormula(compiled, ctx, session)));
324
+ continue;
325
+ }
326
+ }
327
+ // Non-formula snapshot cell — but it might still be a ghost slot
328
+ // that a fresh dynamic-array spill is about to overwrite. Prefer
329
+ // the live value when a master is registered so this-pass SUM /
330
+ // LOOKUP / etc. see the new spill immediately.
331
+ const live = readLiveSpill(sheet, r, c, session);
332
+ if (live) {
333
+ row.push((0, values_1.topLeft)(live));
334
+ continue;
335
+ }
336
+ row.push((0, values_1.topLeft)((0, values_1.fromSnapshotValue)(cell.value)));
337
+ }
338
+ rows.push(row);
339
+ }
340
+ return (0, values_1.rvArray)(rows, top, left, subtotalMask, hiddenRowMask);
341
+ }
342
+ // ============================================================================
343
+ // Dereference: Reference → concrete value
344
+ // ============================================================================
345
+ /**
346
+ * Resolve a `ReferenceValue` to its concrete value (scalar or array).
347
+ * - Single-cell references (from CellRef nodes) resolve to a scalar.
348
+ * - Area references (even 1x1 from A1:A1) produce an array.
349
+ * - Multi-area references (from 3D refs) flatten all areas into one array.
350
+ * Non-reference values are returned unchanged.
351
+ */
352
+ function dereferenceValue(v, ctx, session) {
353
+ if (v.kind !== 6 /* RVKind.Reference */) {
354
+ return v;
355
+ }
356
+ if (v.areas.length === 0) {
357
+ return values_1.BLANK;
358
+ }
359
+ // Single-cell references (from CellRef nodes) resolve to scalar
360
+ if (v.singleCell) {
361
+ const area = v.areas[0];
362
+ return getCellValue(area.sheet, area.top, area.left, ctx, session);
363
+ }
364
+ // Single area — build range array
365
+ if (v.areas.length === 1) {
366
+ const area = v.areas[0];
367
+ return buildRangeArray(ctx, session, area.sheet, area.top, area.left, area.bottom, area.right);
368
+ }
369
+ // Multi-area (3D reference) — flatten all areas into one array
370
+ const allRows = [];
371
+ let mergedSubtotal;
372
+ let mergedHidden;
373
+ for (const area of v.areas) {
374
+ const arr = buildRangeArray(ctx, session, area.sheet, area.top, area.left, area.bottom, area.right);
375
+ const startRow = allRows.length;
376
+ for (const row of arr.rows) {
377
+ allRows.push([...row]);
378
+ }
379
+ // Merge masks from this area into the flattened output. Only
380
+ // allocate the merged masks when the first masked area shows up —
381
+ // most multi-area refs have no masks and should pay no overhead.
382
+ if (arr.subtotalMask || arr.hiddenRowMask) {
383
+ const height = arr.height;
384
+ if (arr.subtotalMask) {
385
+ if (!mergedSubtotal) {
386
+ mergedSubtotal = [];
387
+ for (let i = 0; i < startRow; i++) {
388
+ // Widths may differ across areas; mask rows use each area's
389
+ // own width so an outer SUBTOTAL reads the right positions.
390
+ mergedSubtotal.push([]);
391
+ }
392
+ }
393
+ for (let r = 0; r < height; r++) {
394
+ mergedSubtotal.push([...arr.subtotalMask[r]]);
395
+ }
396
+ }
397
+ else if (mergedSubtotal) {
398
+ // Pad with empty rows so indices stay aligned.
399
+ for (let r = 0; r < height; r++) {
400
+ mergedSubtotal.push([]);
401
+ }
402
+ }
403
+ if (arr.hiddenRowMask) {
404
+ if (!mergedHidden) {
405
+ mergedHidden = new Array(startRow).fill(false);
406
+ }
407
+ for (let r = 0; r < height; r++) {
408
+ mergedHidden.push(arr.hiddenRowMask[r] ?? false);
409
+ }
410
+ }
411
+ else if (mergedHidden) {
412
+ for (let r = 0; r < height; r++) {
413
+ mergedHidden.push(false);
414
+ }
415
+ }
416
+ }
417
+ else if (mergedSubtotal || mergedHidden) {
418
+ // An earlier area had masks; pad this area's rows with non-masked
419
+ // entries to keep row indices aligned.
420
+ const height = arr.height;
421
+ if (mergedSubtotal) {
422
+ for (let r = 0; r < height; r++) {
423
+ mergedSubtotal.push([]);
424
+ }
425
+ }
426
+ if (mergedHidden) {
427
+ for (let r = 0; r < height; r++) {
428
+ mergedHidden.push(false);
429
+ }
430
+ }
431
+ }
432
+ }
433
+ return (0, values_1.rvArray)(allRows, undefined, undefined, mergedSubtotal, mergedHidden);
434
+ }
435
+ // ============================================================================
436
+ // Get Cell Value from Snapshot
437
+ // ============================================================================
438
+ function getCellValue(sheetName, row, col, ctx, session) {
439
+ // Record this access for runtime dependency tracking. Inline guard
440
+ // avoids the function call overhead in the common case where no
441
+ // recording is active (formulas without dynamic refs).
442
+ if (session.recordingKey !== null) {
443
+ session.recordAccess(sheetName, row, col);
444
+ }
445
+ const ws = ctx.snapshot.worksheetsByName.get(sheetName.toLowerCase());
446
+ if (!ws) {
447
+ return values_1.BLANK;
448
+ }
449
+ const cellKey = (0, workbook_snapshot_1.snapshotCellKey)(row, col);
450
+ const cell = ws.cells.get(cellKey);
451
+ if (!cell) {
452
+ // The cell isn't in the snapshot, but we might still have a live
453
+ // spill value for it — look up the master formula and extract the
454
+ // right array element. Matters when a downstream formula like
455
+ // `SUM(A1:A5)` runs before materialize writes the ghost cells for
456
+ // `A1 = SEQUENCE(5)` into the snapshot.
457
+ return readLiveSpill(sheetName, row, col, session) ?? values_1.BLANK;
458
+ }
459
+ // If this cell has a formula, evaluate it
460
+ if (cell.formulaKind !== "none" && cell.formula) {
461
+ const fKey = (0, workbook_snapshot_1.formulaCellKey)(sheetName, row, col);
462
+ // Check cache — return scalar form for dependency resolution
463
+ const cached = session.resultCache.get(fKey);
464
+ if (cached !== undefined) {
465
+ return cached.scalar;
466
+ }
467
+ // Get compiled formula
468
+ const compiled = ctx.compiledFormulas.get(fKey);
469
+ if (compiled) {
470
+ return evaluateFormula(compiled, ctx, session);
471
+ }
472
+ }
473
+ // Non-formula cell — but it might also be a ghost for a live spill
474
+ // (e.g. a value that exists in the snapshot from a previous calc
475
+ // cycle but is about to be overwritten by a fresh spill). Prefer the
476
+ // live value when a master is registered.
477
+ const spill = readLiveSpill(sheetName, row, col, session);
478
+ if (spill) {
479
+ return spill;
480
+ }
481
+ return (0, values_1.fromSnapshotValue)(cell.value);
482
+ }
483
+ /**
484
+ * Retrieve the spill-target value at (sheetName, row, col) from an
485
+ * already-evaluated dynamic-array master. Returns `undefined` when no
486
+ * master is registered for that cell — callers fall back to the
487
+ * snapshot value or BLANK.
488
+ */
489
+ function readLiveSpill(sheetName, row, col, session) {
490
+ const key = session.makeKey(sheetName, row, col);
491
+ const spill = session.liveSpills.get(key);
492
+ if (!spill) {
493
+ return undefined;
494
+ }
495
+ const master = session.resultCache.get(spill.masterKey);
496
+ if (!master || master.raw.kind !== 5 /* RVKind.Array */) {
497
+ return undefined;
498
+ }
499
+ const arr = master.raw;
500
+ if (spill.rowOffset >= arr.height || spill.colOffset >= arr.width) {
501
+ return undefined;
502
+ }
503
+ return arr.rows[spill.rowOffset][spill.colOffset];
504
+ }
505
+ // ============================================================================
506
+ // Evaluate a Compiled Formula
507
+ // ============================================================================
508
+ /**
509
+ * Shared implementation for evaluateFormula and evaluateFormulaRaw.
510
+ *
511
+ * Handles key computation, cache lookup, circular reference detection,
512
+ * expression evaluation, and result caching.
513
+ */
514
+ function evaluateFormulaInner(compiled, ctx, session) {
515
+ const inst = compiled.instance;
516
+ const key = session.makeKey(inst.sheetName, inst.row, inst.col);
517
+ // Check cache
518
+ const cached = session.resultCache.get(key);
519
+ if (cached !== undefined) {
520
+ return cached;
521
+ }
522
+ // Circular reference detection. Under iterative calculation the driver
523
+ // (calculate-formulas-impl.ts) seeds `circularFallback` with the previous
524
+ // iteration's result so the re-entrant lookup receives a stable value.
525
+ // Outside of iterative mode the map is empty — we return 0 as the fallback,
526
+ // matching Excel's "iterate with 0 seed" convention. This keeps simple
527
+ // cycles like A1=A1+1 producing a number instead of an error, which is the
528
+ // established behaviour for this engine (tests depend on this). For strict
529
+ // circular-reference error reporting, enable iterative calculation and
530
+ // observe convergence failure, or configure a custom fallback value.
531
+ if (session.evaluating.has(key)) {
532
+ const fallback = session.circularFallback.get(key);
533
+ const val = fallback !== undefined ? fallback : (0, values_1.rvNumber)(0);
534
+ return { scalar: val, raw: val };
535
+ }
536
+ session.evaluating.add(key);
537
+ const prevAddress = ctx.currentAddress;
538
+ const prevSheet = ctx.currentSheet;
539
+ const prevRecording = session.recordingKey;
540
+ ctx.currentAddress = { sheet: inst.sheetName, row: inst.row, col: inst.col };
541
+ ctx.currentSheet = inst.sheetName;
542
+ // Enable runtime dependency recording for formulas with dynamic refs
543
+ if (compiled.hasDynamicRefs) {
544
+ session.recordingKey = key;
545
+ }
546
+ try {
547
+ const result = evaluate(compiled.bound, ctx, session);
548
+ const intersected = implicitIntersect(result, ctx);
549
+ const scalar = dereferenceValue(intersected, ctx, session);
550
+ const raw = dereferenceValue(result, ctx, session);
551
+ const entry = { scalar, raw };
552
+ session.resultCache.set(key, entry);
553
+ // Register the spill region if this is a dynamic-array formula
554
+ // whose result is a multi-cell array. Downstream formulas that
555
+ // read into the spill range (e.g. `=SUM(A1:A5)` over a
556
+ // `=SEQUENCE(5)` master) can now pick up the ghost-cell values
557
+ // before materialize writes them back to the snapshot.
558
+ const isDyn = compiled.instance.isDynamicArray || compiled.isDynamicArrayFunction;
559
+ if (isDyn && raw.kind === 5 /* RVKind.Array */ && (raw.height > 1 || raw.width > 1)) {
560
+ for (let r = 0; r < raw.height; r++) {
561
+ for (let c = 0; c < raw.width; c++) {
562
+ if (r === 0 && c === 0) {
563
+ continue; // master cell already points to its own cache entry
564
+ }
565
+ const targetKey = session.makeKey(inst.sheetName, inst.row + r, inst.col + c);
566
+ session.liveSpills.set(targetKey, {
567
+ masterKey: key,
568
+ rowOffset: r,
569
+ colOffset: c
570
+ });
571
+ }
572
+ }
573
+ }
574
+ return entry;
575
+ }
576
+ catch (err) {
577
+ // Cache a #CALC! sentinel so a re-entrant lookup for the same cell does
578
+ // not trigger repeated (exponentially growing) re-evaluation under
579
+ // iterative calc or dependent recomputation. The exception is re-thrown
580
+ // so the outer caller can still log / translate it into a sheet error.
581
+ const fallback = { scalar: values_1.ERRORS.CALC, raw: values_1.ERRORS.CALC };
582
+ session.resultCache.set(key, fallback);
583
+ throw err;
584
+ }
585
+ finally {
586
+ session.evaluating.delete(key);
587
+ ctx.currentAddress = prevAddress;
588
+ ctx.currentSheet = prevSheet;
589
+ session.recordingKey = prevRecording;
590
+ }
591
+ }
592
+ /**
593
+ * Evaluate a compiled formula and return its **scalar** result.
594
+ *
595
+ * This is the standard evaluation path for regular (non-array) formulas.
596
+ * The result is:
597
+ * 1. Evaluated from the bound expression tree
598
+ * 2. Implicit-intersected to a single value/reference
599
+ * 3. Dereferenced if it's a reference
600
+ * 4. Cached for subsequent lookups by dependent formulas
601
+ *
602
+ * Use `evaluateFormulaRaw` instead when the full array result is needed
603
+ * (dynamic array formulas, CSE formulas).
604
+ */
605
+ function evaluateFormula(compiled, ctx, session) {
606
+ return evaluateFormulaInner(compiled, ctx, session).scalar;
607
+ }
608
+ /**
609
+ * Evaluate a compiled formula and return the **raw** (possibly array) result.
610
+ *
611
+ * This is the evaluation path for dynamic array and CSE formulas where
612
+ * the full array shape must be preserved for spill/distribution.
613
+ *
614
+ * Semantics:
615
+ * - Both scalar and raw forms are stored in `session.resultCache` as a
616
+ * `CachedResult{scalar, raw}`. Dependent scalar formulas see the scalar
617
+ * form; the materialize layer retrieves the raw form.
618
+ * - The return value is the full dereferenced result — may be an ArrayValue
619
+ * with height > 1 or width > 1.
620
+ */
621
+ function evaluateFormulaRaw(compiled, ctx, session) {
622
+ return evaluateFormulaInner(compiled, ctx, session).raw;
623
+ }
624
+ // ============================================================================
625
+ // Binary Operations
626
+ // ============================================================================
627
+ function evaluateBinaryOp(op, leftExpr, rightExpr, ctx, session) {
628
+ // Intersection operator (whitespace between two refs). Must be handled
629
+ // BEFORE dereferencing so we can inspect the reference areas.
630
+ if (op === " ") {
631
+ return evaluateIntersection(leftExpr, rightExpr, ctx, session);
632
+ }
633
+ // Range operator `:` — union of two references into the bounding
634
+ // rectangle. Needed when one side is a function call (e.g.
635
+ // `B11:INDIRECT("B" & ROW()-1)`). Both sides must be references or
636
+ // coerce to references; otherwise Excel returns #REF!.
637
+ if (op === ":") {
638
+ return evaluateRangeUnion(leftExpr, rightExpr, ctx, session);
639
+ }
640
+ const left = dereferenceValue(evaluate(leftExpr, ctx, session), ctx, session);
641
+ const right = dereferenceValue(evaluate(rightExpr, ctx, session), ctx, session);
642
+ const lIsArr = left.kind === 5 /* RVKind.Array */;
643
+ const rIsArr = right.kind === 5 /* RVKind.Array */;
644
+ if (lIsArr || rIsArr) {
645
+ return broadcastBinaryOp(op, left, right);
646
+ }
647
+ return applyScalarBinaryOp(op, (0, values_1.topLeft)(left), (0, values_1.topLeft)(right));
648
+ }
649
+ /**
650
+ * Excel's intersection operator — a whitespace character separating two
651
+ * references (e.g. `A1:A10 B1:B10`).
652
+ *
653
+ * Semantics:
654
+ * - Both operands must evaluate to single-area references. Otherwise the
655
+ * result is `#VALUE!` (matches Excel's behaviour when non-refs or
656
+ * multi-area refs are intersected).
657
+ * - Intersection is the rectangle overlap of the two areas on the same
658
+ * sheet.
659
+ * - If the areas do not overlap (or are on different sheets) the result
660
+ * is `#NULL!`, Excel's canonical "empty intersection" error.
661
+ */
662
+ function evaluateIntersection(leftExpr, rightExpr, ctx, session) {
663
+ const left = evaluate(leftExpr, ctx, session);
664
+ const right = evaluate(rightExpr, ctx, session);
665
+ if ((0, values_1.isError)(left)) {
666
+ return left;
667
+ }
668
+ if ((0, values_1.isError)(right)) {
669
+ return right;
670
+ }
671
+ if (left.kind !== 6 /* RVKind.Reference */ || right.kind !== 6 /* RVKind.Reference */) {
672
+ return values_1.ERRORS.VALUE;
673
+ }
674
+ if (left.areas.length !== 1 || right.areas.length !== 1) {
675
+ return values_1.ERRORS.VALUE;
676
+ }
677
+ const la = left.areas[0];
678
+ const ra = right.areas[0];
679
+ if (la.sheet.toLowerCase() !== ra.sheet.toLowerCase()) {
680
+ return values_1.ERRORS.NULL;
681
+ }
682
+ const top = Math.max(la.top, ra.top);
683
+ const left_ = Math.max(la.left, ra.left);
684
+ const bottom = Math.min(la.bottom, ra.bottom);
685
+ const right_ = Math.min(la.right, ra.right);
686
+ if (top > bottom || left_ > right_) {
687
+ return values_1.ERRORS.NULL;
688
+ }
689
+ return (0, values_1.rvRef)(la.sheet, top, left_, bottom, right_);
690
+ }
691
+ /**
692
+ * Range operator `:` applied at runtime. Normally `A1:B2` is merged by
693
+ * the tokenizer, but patterns like `A1:INDIRECT("B5")` leave the colon
694
+ * as a standalone operator. Both operands must evaluate to references
695
+ * (single-cell or area); the result is the bounding rectangle of the
696
+ * two reference ranges on the same sheet.
697
+ *
698
+ * Semantics:
699
+ * - Both sides must be references. Literal numbers / strings → #VALUE!.
700
+ * - References must live on the same sheet → else #REF!.
701
+ * - Multi-area references on either side → #REF! (Excel behavior).
702
+ */
703
+ function evaluateRangeUnion(leftExpr, rightExpr, ctx, session) {
704
+ const left = evaluate(leftExpr, ctx, session);
705
+ const right = evaluate(rightExpr, ctx, session);
706
+ if ((0, values_1.isError)(left)) {
707
+ return left;
708
+ }
709
+ if ((0, values_1.isError)(right)) {
710
+ return right;
711
+ }
712
+ if (left.kind !== 6 /* RVKind.Reference */ || right.kind !== 6 /* RVKind.Reference */) {
713
+ return values_1.ERRORS.VALUE;
714
+ }
715
+ if (left.areas.length !== 1 || right.areas.length !== 1) {
716
+ return values_1.ERRORS.REF;
717
+ }
718
+ const la = left.areas[0];
719
+ const ra = right.areas[0];
720
+ if (la.sheet.toLowerCase() !== ra.sheet.toLowerCase()) {
721
+ return values_1.ERRORS.REF;
722
+ }
723
+ // Bounding rectangle that spans both areas — this is the union /
724
+ // range-op semantics (Excel), distinct from intersection which uses
725
+ // min/max in the opposite direction.
726
+ const top = Math.min(la.top, ra.top);
727
+ const left_ = Math.min(la.left, ra.left);
728
+ const bottom = Math.max(la.bottom, ra.bottom);
729
+ const right_ = Math.max(la.right, ra.right);
730
+ return (0, values_1.rvRef)(la.sheet, top, left_, bottom, right_);
731
+ }
732
+ function applyScalarBinaryOp(op, left, right) {
733
+ if ((0, values_1.isError)(left)) {
734
+ return left;
735
+ }
736
+ if ((0, values_1.isError)(right)) {
737
+ return right;
738
+ }
739
+ // Concatenation
740
+ if (op === "&") {
741
+ const lStr = (0, values_1.toStringRV)(left);
742
+ const rStr = (0, values_1.toStringRV)(right);
743
+ return (0, values_1.rvString)(lStr + rStr);
744
+ }
745
+ // Comparison
746
+ if (op === "=" || op === "<>" || op === "<" || op === ">" || op === "<=" || op === ">=") {
747
+ return (0, values_1.rvBoolean)(compareScalars(left, right, op));
748
+ }
749
+ // Arithmetic
750
+ const lNum = (0, values_1.toNumberRV)(left);
751
+ if ((0, values_1.isError)(lNum)) {
752
+ return lNum;
753
+ }
754
+ const rNum = (0, values_1.toNumberRV)(right);
755
+ if ((0, values_1.isError)(rNum)) {
756
+ return rNum;
757
+ }
758
+ let result;
759
+ switch (op) {
760
+ case "+":
761
+ result = lNum.value + rNum.value;
762
+ break;
763
+ case "-":
764
+ result = lNum.value - rNum.value;
765
+ break;
766
+ case "*":
767
+ result = lNum.value * rNum.value;
768
+ break;
769
+ case "/":
770
+ if (rNum.value === 0) {
771
+ return values_1.ERRORS.DIV0;
772
+ }
773
+ result = lNum.value / rNum.value;
774
+ break;
775
+ case "^":
776
+ // Excel distinguishes `0 ^ n` for n < 0 (→ #DIV/0!, since it's
777
+ // semantically 1/0) from other overflows (→ #NUM!). The generic
778
+ // `isFinite` check below loses that distinction, so route the
779
+ // division-by-zero case explicitly first. 0^0 is conventionally 1
780
+ // (matches Excel and POWER()).
781
+ if (lNum.value === 0) {
782
+ if (rNum.value < 0) {
783
+ return values_1.ERRORS.DIV0;
784
+ }
785
+ if (rNum.value === 0) {
786
+ return (0, values_1.rvNumber)(1);
787
+ }
788
+ }
789
+ result = Math.pow(lNum.value, rNum.value);
790
+ if (Number.isNaN(result)) {
791
+ // `Math.pow(-1, 0.5)` etc. — complex result; Excel reports #NUM!.
792
+ return values_1.ERRORS.NUM;
793
+ }
794
+ break;
795
+ default:
796
+ return values_1.ERRORS.VALUE;
797
+ }
798
+ return !isFinite(result) ? values_1.ERRORS.NUM : (0, values_1.rvNumber)(result);
799
+ }
800
+ function compareScalars(left, right, op) {
801
+ // Normalize blanks to a neutral form of the opposing kind so formulas like
802
+ // `"" = A1` (where A1 is blank) compare equal. Without this normalisation
803
+ // Excel would route us to the cross-type tiebreak below.
804
+ const l = left.kind === 0 /* RVKind.Blank */
805
+ ? right.kind === 2 /* RVKind.String */
806
+ ? (0, values_1.rvString)("")
807
+ : right.kind === 3 /* RVKind.Boolean */
808
+ ? (0, values_1.rvBoolean)(false)
809
+ : (0, values_1.rvNumber)(0)
810
+ : left;
811
+ const r = right.kind === 0 /* RVKind.Blank */
812
+ ? left.kind === 2 /* RVKind.String */
813
+ ? (0, values_1.rvString)("")
814
+ : left.kind === 3 /* RVKind.Boolean */
815
+ ? (0, values_1.rvBoolean)(false)
816
+ : (0, values_1.rvNumber)(0)
817
+ : right;
818
+ let cmp;
819
+ if (l.kind === r.kind) {
820
+ cmp = (0, values_1.compareScalarsSameKind)(l, r);
821
+ if (!Number.isFinite(cmp)) {
822
+ cmp = 0;
823
+ }
824
+ }
825
+ else {
826
+ // Excel orders scalar kinds: Number < String < Boolean < Error/Blank.
827
+ const order = (v) => {
828
+ if (v.kind === 1 /* RVKind.Number */) {
829
+ return 0;
830
+ }
831
+ if (v.kind === 2 /* RVKind.String */) {
832
+ return 1;
833
+ }
834
+ if (v.kind === 3 /* RVKind.Boolean */) {
835
+ return 2;
836
+ }
837
+ return 3;
838
+ };
839
+ cmp = order(l) - order(r);
840
+ }
841
+ switch (op) {
842
+ case "=":
843
+ return cmp === 0;
844
+ case "<>":
845
+ return cmp !== 0;
846
+ case "<":
847
+ return cmp < 0;
848
+ case ">":
849
+ return cmp > 0;
850
+ case "<=":
851
+ return cmp <= 0;
852
+ case ">=":
853
+ return cmp >= 0;
854
+ default:
855
+ return false;
856
+ }
857
+ }
858
+ function broadcastBinaryOp(op, left, right) {
859
+ const lArr = left.kind === 5 /* RVKind.Array */ ? left : null;
860
+ const rArr = right.kind === 5 /* RVKind.Array */ ? right : null;
861
+ const lRows = lArr ? lArr.height : 1;
862
+ const lCols = lArr ? lArr.width : 1;
863
+ const rRows = rArr ? rArr.height : 1;
864
+ const rCols = rArr ? rArr.width : 1;
865
+ const outRows = Math.max(lRows, rRows);
866
+ const outCols = Math.max(lCols, rCols);
867
+ if ((lRows !== 1 && rRows !== 1 && lRows !== rRows) ||
868
+ (lCols !== 1 && rCols !== 1 && lCols !== rCols)) {
869
+ return values_1.ERRORS.VALUE;
870
+ }
871
+ // Guard against pathological broadcasts (e.g. A:A * 1:1 = ~17B cells).
872
+ // 10M cells is well beyond any legitimate array use case.
873
+ if (outRows * outCols > 10000000) {
874
+ return values_1.ERRORS.CALC;
875
+ }
876
+ // Precompute scalar-broadcast values once outside the hot cell loop.
877
+ // When one side is a non-array `RuntimeValue`, it expands to the same
878
+ // `ScalarValue` for every (r, c); repeating `topLeft(left)` inside the
879
+ // inner loop (outRows × outCols calls) was pure overhead.
880
+ const lScalarFallback = lArr ? undefined : (0, values_1.topLeft)(left);
881
+ const rScalarFallback = rArr ? undefined : (0, values_1.topLeft)(right);
882
+ const rows = [];
883
+ for (let r = 0; r < outRows; r++) {
884
+ const row = [];
885
+ for (let c = 0; c < outCols; c++) {
886
+ const lR = lRows === 1 ? 0 : r;
887
+ const lC = lCols === 1 ? 0 : c;
888
+ const rR = rRows === 1 ? 0 : r;
889
+ const rC = rCols === 1 ? 0 : c;
890
+ // Array values are rectangular (normalised by rvArray) so direct
891
+ // indexing is safe; the previous `?? BLANK` fallback was defensive
892
+ // code that never triggered in practice but cost an optional chain
893
+ // per cell in a hot loop.
894
+ const lVal = lArr ? lArr.rows[lR][lC] : lScalarFallback;
895
+ const rVal = rArr ? rArr.rows[rR][rC] : rScalarFallback;
896
+ row.push(applyScalarBinaryOp(op, lVal, rVal));
897
+ }
898
+ rows.push(row);
899
+ }
900
+ // Propagate origin metadata
901
+ const originRow = lArr?.originRow ?? rArr?.originRow;
902
+ const originCol = lArr?.originCol ?? rArr?.originCol;
903
+ return (0, values_1.rvArray)(rows, originRow, originCol);
904
+ }
905
+ // ============================================================================
906
+ // Unary Operations
907
+ // ============================================================================
908
+ function evaluateUnaryOp(op, operandExpr, ctx, session) {
909
+ const rawVal = evaluate(operandExpr, ctx, session);
910
+ // @ implicit intersection
911
+ if (op === "@") {
912
+ const intersected = implicitIntersect(rawVal, ctx);
913
+ return dereferenceValue(intersected, ctx, session);
914
+ }
915
+ const val = dereferenceValue(rawVal, ctx, session);
916
+ if (val.kind === 5 /* RVKind.Array */) {
917
+ const rows = [];
918
+ for (const row of val.rows) {
919
+ rows.push(row.map(cell => applyScalarUnary(op, cell)));
920
+ }
921
+ return (0, values_1.rvArray)(rows, val.originRow, val.originCol);
922
+ }
923
+ return applyScalarUnary(op, (0, values_1.topLeft)(val));
924
+ }
925
+ function applyScalarUnary(op, val) {
926
+ if ((0, values_1.isError)(val)) {
927
+ return val;
928
+ }
929
+ const n = (0, values_1.toNumberRV)(val);
930
+ if ((0, values_1.isError)(n)) {
931
+ return n;
932
+ }
933
+ switch (op) {
934
+ case "-":
935
+ return (0, values_1.rvNumber)(-n.value);
936
+ case "+":
937
+ return n;
938
+ default:
939
+ return values_1.ERRORS.VALUE;
940
+ }
941
+ }
942
+ // ============================================================================
943
+ // Percent
944
+ // ============================================================================
945
+ function evaluatePercent(operandExpr, ctx, session) {
946
+ const val = dereferenceValue(evaluate(operandExpr, ctx, session), ctx, session);
947
+ if (val.kind === 5 /* RVKind.Array */) {
948
+ const rows = [];
949
+ for (const row of val.rows) {
950
+ rows.push(row.map(cell => {
951
+ if ((0, values_1.isError)(cell)) {
952
+ return cell;
953
+ }
954
+ const n = (0, values_1.toNumberRV)(cell);
955
+ if ((0, values_1.isError)(n)) {
956
+ return n;
957
+ }
958
+ return (0, values_1.rvNumber)(n.value / 100);
959
+ }));
960
+ }
961
+ return (0, values_1.rvArray)(rows);
962
+ }
963
+ const scalar = (0, values_1.topLeft)(val);
964
+ if ((0, values_1.isError)(scalar)) {
965
+ return scalar;
966
+ }
967
+ const n = (0, values_1.toNumberRV)(scalar);
968
+ if ((0, values_1.isError)(n)) {
969
+ return n;
970
+ }
971
+ return (0, values_1.rvNumber)(n.value / 100);
972
+ }
973
+ // ============================================================================
974
+ // Function Call (Eager)
975
+ // ============================================================================
976
+ function evaluateCall(expr, ctx, session) {
977
+ // Reference functions: ROW, COLUMN, ROWS, COLUMNS
978
+ // (Accept _XLFN. prefixed names transparently.)
979
+ const canonical = (0, token_types_1.stripFunctionPrefix)(expr.name);
980
+ const refResult = tryEvaluateRefFunction(canonical, expr.args, ctx);
981
+ if (refResult !== undefined) {
982
+ return refResult;
983
+ }
984
+ // Reference-producing functions like INDIRECT/OFFSET yield a
985
+ // ReferenceValue at runtime. ROW/COLUMN/ROWS/COLUMNS need to inspect
986
+ // the resulting reference's address rather than its dereferenced value,
987
+ // so we evaluate the argument *without* dereferencing and extract the
988
+ // geometry directly. Only the 1-arg reference-only forms go down this
989
+ // path; scalar/array arguments still fall through to the eager fallback
990
+ // below, which returns #VALUE! for ROW/COLUMN and the correct count for
991
+ // ROWS/COLUMNS.
992
+ if (expr.args.length === 1 && isSimpleRefFunction(canonical)) {
993
+ const raw = evaluate(expr.args[0], ctx, session);
994
+ if (raw.kind === 6 /* RVKind.Reference */ && raw.areas.length > 0) {
995
+ const area = raw.areas[0];
996
+ switch (canonical) {
997
+ case "ROW":
998
+ return (0, values_1.rvNumber)(area.top);
999
+ case "COLUMN":
1000
+ return (0, values_1.rvNumber)(area.left);
1001
+ case "ROWS":
1002
+ return (0, values_1.rvNumber)(area.bottom - area.top + 1);
1003
+ case "COLUMNS":
1004
+ return (0, values_1.rvNumber)(area.right - area.left + 1);
1005
+ }
1006
+ }
1007
+ }
1008
+ // ── Reference-aware functions (ISREF, CELL) ──
1009
+ // These inspect the argument's reference-ness rather than its dereferenced
1010
+ // value. Handled here so the raw BoundExpr / ReferenceValue is visible.
1011
+ if (canonical === "ISREF") {
1012
+ return evaluateISREF(expr.args, ctx, session);
1013
+ }
1014
+ if (canonical === "CELL") {
1015
+ return evaluateCELL(expr.args, ctx, session);
1016
+ }
1017
+ // Evaluate all arguments eagerly and dereference references
1018
+ const args = expr.args.map(arg => dereferenceValue(evaluate(arg, ctx, session), ctx, session));
1019
+ // Look up function
1020
+ const desc = (0, function_registry_1.lookupFunction)(expr.name);
1021
+ if (desc) {
1022
+ // Validate arity — produce #VALUE! for wrong argument count
1023
+ if (args.length < desc.minArity || args.length > desc.maxArity) {
1024
+ return values_1.ERRORS.VALUE;
1025
+ }
1026
+ // Context-aware overrides for functions that need evaluator state
1027
+ switch (canonical) {
1028
+ case "SHEET": {
1029
+ // SHEET() → current sheet number; SHEET(ref) → sheet number of ref
1030
+ if (args.length === 0) {
1031
+ const idx = ctx.snapshot.worksheets.findIndex(ws => ws.name.toLowerCase() === ctx.currentSheet.toLowerCase());
1032
+ return (0, values_1.rvNumber)(idx >= 0 ? idx + 1 : 1);
1033
+ }
1034
+ return desc.invoke(args);
1035
+ }
1036
+ case "SHEETS": {
1037
+ // SHEETS() → total sheet count
1038
+ if (args.length === 0) {
1039
+ return (0, values_1.rvNumber)(ctx.snapshot.worksheets.length);
1040
+ }
1041
+ return desc.invoke(args);
1042
+ }
1043
+ case "ISFORMULA": {
1044
+ // ISFORMULA requires a reference argument. When the raw argument is
1045
+ // a CellRef/AreaRef we can look up the underlying cell's formulaKind.
1046
+ // Any other shape (literal, computed value, etc.) yields #N/A per
1047
+ // Excel's behavior for non-reference arguments.
1048
+ const raw = expr.args[0];
1049
+ if (raw && raw.kind === 2 /* BoundExprKind.CellRef */) {
1050
+ const ws = ctx.snapshot.worksheetsByName.get(raw.sheet.toLowerCase());
1051
+ if (!ws) {
1052
+ return values_1.ERRORS.REF;
1053
+ }
1054
+ const cell = ws.cells.get((0, workbook_snapshot_1.snapshotCellKey)(raw.row, raw.col));
1055
+ return (0, values_1.rvBoolean)(cell !== undefined && cell.formulaKind !== "none");
1056
+ }
1057
+ if (raw && raw.kind === 3 /* BoundExprKind.AreaRef */) {
1058
+ const ws = ctx.snapshot.worksheetsByName.get(raw.sheet.toLowerCase());
1059
+ if (!ws) {
1060
+ return values_1.ERRORS.REF;
1061
+ }
1062
+ // ISFORMULA on an area ref inspects the top-left cell.
1063
+ const cell = ws.cells.get((0, workbook_snapshot_1.snapshotCellKey)(raw.top, raw.left));
1064
+ return (0, values_1.rvBoolean)(cell !== undefined && cell.formulaKind !== "none");
1065
+ }
1066
+ return values_1.ERRORS.NA;
1067
+ }
1068
+ case "FORMULATEXT": {
1069
+ // FORMULATEXT returns the formula source text at the referenced cell,
1070
+ // or #N/A if the cell has no formula. Non-reference arguments also
1071
+ // yield #N/A.
1072
+ const raw = expr.args[0];
1073
+ if (raw && raw.kind === 2 /* BoundExprKind.CellRef */) {
1074
+ const ws = ctx.snapshot.worksheetsByName.get(raw.sheet.toLowerCase());
1075
+ if (!ws) {
1076
+ return values_1.ERRORS.REF;
1077
+ }
1078
+ const cell = ws.cells.get((0, workbook_snapshot_1.snapshotCellKey)(raw.row, raw.col));
1079
+ if (cell && cell.formulaKind !== "none" && cell.formula !== undefined) {
1080
+ return (0, values_1.rvString)(`=${cell.formula}`);
1081
+ }
1082
+ return values_1.ERRORS.NA;
1083
+ }
1084
+ if (raw && raw.kind === 3 /* BoundExprKind.AreaRef */) {
1085
+ const ws = ctx.snapshot.worksheetsByName.get(raw.sheet.toLowerCase());
1086
+ if (!ws) {
1087
+ return values_1.ERRORS.REF;
1088
+ }
1089
+ const cell = ws.cells.get((0, workbook_snapshot_1.snapshotCellKey)(raw.top, raw.left));
1090
+ if (cell && cell.formulaKind !== "none" && cell.formula !== undefined) {
1091
+ return (0, values_1.rvString)(`=${cell.formula}`);
1092
+ }
1093
+ return values_1.ERRORS.NA;
1094
+ }
1095
+ return values_1.ERRORS.NA;
1096
+ }
1097
+ default:
1098
+ return desc.invoke(args);
1099
+ }
1100
+ }
1101
+ // Check if name resolves to a lambda (defined name or local binding)
1102
+ const lambda = resolveLambdaName(expr.name, args, ctx, session);
1103
+ if (lambda !== undefined) {
1104
+ return lambda;
1105
+ }
1106
+ return values_1.ERRORS.NAME;
1107
+ }
1108
+ // ============================================================================
1109
+ // Special Form Call (Lazy)
1110
+ // ============================================================================
1111
+ function evaluateSpecialCall(expr, ctx, session) {
1112
+ switch (expr.name) {
1113
+ case "IF":
1114
+ return evaluateIF(expr.args, ctx, session);
1115
+ case "IFERROR":
1116
+ return evaluateIFERROR(expr.args, ctx, session);
1117
+ case "IFNA":
1118
+ return evaluateIFNA(expr.args, ctx, session);
1119
+ case "IFS":
1120
+ return evaluateIFS(expr.args, ctx, session);
1121
+ case "SWITCH":
1122
+ return evaluateSWITCH(expr.args, ctx, session);
1123
+ case "CHOOSE":
1124
+ return evaluateCHOOSE(expr.args, ctx, session);
1125
+ case "LET":
1126
+ return evaluateLET(expr.args, ctx, session);
1127
+ case "LAMBDA":
1128
+ return evaluateLAMBDA(expr.args, ctx);
1129
+ case "INDIRECT":
1130
+ return evaluateINDIRECT(expr.args, ctx, session);
1131
+ case "OFFSET":
1132
+ return evaluateOFFSET(expr.args, ctx, session);
1133
+ case "MAP":
1134
+ case "REDUCE":
1135
+ case "SCAN":
1136
+ case "MAKEARRAY":
1137
+ case "BYROW":
1138
+ case "BYCOL":
1139
+ return evaluateHigherOrder(expr.name, expr.args, ctx, session);
1140
+ default:
1141
+ return values_1.ERRORS.VALUE;
1142
+ }
1143
+ }
1144
+ // ============================================================================
1145
+ // Special Forms Implementation
1146
+ // ============================================================================
1147
+ function evaluateIF(args, ctx, session) {
1148
+ if (args.length < 2) {
1149
+ return values_1.ERRORS.VALUE;
1150
+ }
1151
+ const condRaw = evalDeref(args[0], ctx, session);
1152
+ // Array condition: element-wise IF. Excel's dynamic-array mode makes
1153
+ // `IF({TRUE,FALSE,TRUE}, "Y", "N")` return `{"Y","N","Y"}`. The branches
1154
+ // are evaluated eagerly — Excel does this too because array broadcasting
1155
+ // requires both shapes to be known — and each cell in the output picks
1156
+ // from the corresponding cell of the chosen branch (with scalar branches
1157
+ // broadcasting to fill the condition array's shape).
1158
+ if (condRaw.kind === 5 /* RVKind.Array */) {
1159
+ const trueVal = evalDeref(args[1], ctx, session);
1160
+ const falseVal = args.length > 2 ? evalDeref(args[2], ctx, session) : (0, values_1.rvBoolean)(false);
1161
+ const rows = [];
1162
+ for (let r = 0; r < condRaw.height; r++) {
1163
+ const outRow = [];
1164
+ for (let c = 0; c < condRaw.width; c++) {
1165
+ const cell = condRaw.rows[r][c];
1166
+ if (cell.kind === 4 /* RVKind.Error */) {
1167
+ outRow.push(cell);
1168
+ continue;
1169
+ }
1170
+ const b = (0, values_1.toBooleanRV)(cell);
1171
+ if (b.kind === 4 /* RVKind.Error */) {
1172
+ outRow.push(b);
1173
+ continue;
1174
+ }
1175
+ const branch = b.value ? trueVal : falseVal;
1176
+ outRow.push(pickCellBroadcast(branch, r, c));
1177
+ }
1178
+ rows.push(outRow);
1179
+ }
1180
+ return (0, values_1.rvArray)(rows);
1181
+ }
1182
+ const cond = (0, values_1.topLeft)(condRaw);
1183
+ if ((0, values_1.isError)(cond)) {
1184
+ return cond;
1185
+ }
1186
+ const bool = (0, values_1.toBooleanRV)(cond);
1187
+ if ((0, values_1.isError)(bool)) {
1188
+ return bool;
1189
+ }
1190
+ if (bool.value) {
1191
+ return evaluate(args[1], ctx, session);
1192
+ }
1193
+ return args.length > 2 ? evaluate(args[2], ctx, session) : (0, values_1.rvBoolean)(false);
1194
+ }
1195
+ /**
1196
+ * Pick a scalar from `branch` corresponding to grid position (r, c), with
1197
+ * broadcasting: scalar branches are repeated; smaller arrays are indexed
1198
+ * modulo their bounds (out-of-range → BLANK) matching Excel's array
1199
+ * alignment rules for IF/IFS/etc.
1200
+ */
1201
+ function pickCellBroadcast(branch, r, c) {
1202
+ if (branch.kind !== 5 /* RVKind.Array */) {
1203
+ return (0, values_1.topLeft)(branch);
1204
+ }
1205
+ const row = r < branch.height ? r : branch.height === 1 ? 0 : -1;
1206
+ const col = c < branch.width ? c : branch.width === 1 ? 0 : -1;
1207
+ if (row < 0 || col < 0) {
1208
+ // Misaligned array branch (smaller than the condition, and not a
1209
+ // broadcastable 1-row / 1-column shape). Excel fills the gaps with
1210
+ // `#N/A` rather than BLANK, so downstream consumers can distinguish
1211
+ // "branch didn't cover this cell" from "branch actually returned
1212
+ // empty". (R6-P1-7)
1213
+ return values_1.ERRORS.NA;
1214
+ }
1215
+ return branch.rows[row][col];
1216
+ }
1217
+ /**
1218
+ * Shared array-aware error replacement used by IFERROR and IFNA. Scans `val`
1219
+ * for cells matching `isMatch`; if any are found, replaces each with the
1220
+ * top-left scalar of `replacement` (lazily evaluated). Scalar inputs follow
1221
+ * the same match-or-pass-through logic.
1222
+ */
1223
+ function replaceErrorsIn(val, args, ctx, session, isMatch) {
1224
+ if (val.kind === 5 /* RVKind.Array */) {
1225
+ let anyMatch = false;
1226
+ for (const row of val.rows) {
1227
+ for (const cell of row) {
1228
+ if (cell.kind === 4 /* RVKind.Error */ && isMatch(cell)) {
1229
+ anyMatch = true;
1230
+ break;
1231
+ }
1232
+ }
1233
+ if (anyMatch) {
1234
+ break;
1235
+ }
1236
+ }
1237
+ if (!anyMatch) {
1238
+ return val;
1239
+ }
1240
+ const replaceScalar = (0, values_1.topLeft)(evalDeref(args[1], ctx, session));
1241
+ const rows = [];
1242
+ for (const row of val.rows) {
1243
+ const newRow = [];
1244
+ for (const cell of row) {
1245
+ newRow.push(cell.kind === 4 /* RVKind.Error */ && isMatch(cell) ? replaceScalar : cell);
1246
+ }
1247
+ rows.push(newRow);
1248
+ }
1249
+ return (0, values_1.rvArray)(rows);
1250
+ }
1251
+ return (0, values_1.isError)(val) && isMatch(val) ? evalDeref(args[1], ctx, session) : val;
1252
+ }
1253
+ function evaluateIFERROR(args, ctx, session) {
1254
+ if (args.length < 2) {
1255
+ return values_1.ERRORS.VALUE;
1256
+ }
1257
+ const val = evalDeref(args[0], ctx, session);
1258
+ return replaceErrorsIn(val, args, ctx, session, () => true);
1259
+ }
1260
+ function evaluateIFNA(args, ctx, session) {
1261
+ if (args.length < 2) {
1262
+ return values_1.ERRORS.VALUE;
1263
+ }
1264
+ const val = evalDeref(args[0], ctx, session);
1265
+ return replaceErrorsIn(val, args, ctx, session, err => err.code === "#N/A");
1266
+ }
1267
+ function evaluateIFS(args, ctx, session) {
1268
+ if (args.length < 2) {
1269
+ return values_1.ERRORS.VALUE;
1270
+ }
1271
+ // Excel requires IFS args to come in test/value pairs. Odd-length
1272
+ // arg lists imply a trailing test with no value and are #N/A.
1273
+ if (args.length % 2 !== 0) {
1274
+ return values_1.ERRORS.NA;
1275
+ }
1276
+ for (let i = 0; i < args.length - 1; i += 2) {
1277
+ const cond = (0, values_1.topLeft)(evalDeref(args[i], ctx, session));
1278
+ if ((0, values_1.isError)(cond)) {
1279
+ return cond;
1280
+ }
1281
+ const bool = (0, values_1.toBooleanRV)(cond);
1282
+ if ((0, values_1.isError)(bool)) {
1283
+ return bool;
1284
+ }
1285
+ if (bool.value) {
1286
+ return evaluate(args[i + 1], ctx, session);
1287
+ }
1288
+ }
1289
+ return values_1.ERRORS.NA;
1290
+ }
1291
+ function evaluateSWITCH(args, ctx, session) {
1292
+ if (args.length < 3) {
1293
+ return values_1.ERRORS.VALUE;
1294
+ }
1295
+ const expr = (0, values_1.topLeft)(evalDeref(args[0], ctx, session));
1296
+ if ((0, values_1.isError)(expr)) {
1297
+ return expr;
1298
+ }
1299
+ for (let i = 1; i < args.length - 1; i += 2) {
1300
+ const caseVal = (0, values_1.topLeft)(evalDeref(args[i], ctx, session));
1301
+ if ((0, values_1.scalarEquals)(expr, caseVal)) {
1302
+ return evaluate(args[i + 1], ctx, session);
1303
+ }
1304
+ }
1305
+ if (args.length % 2 === 0) {
1306
+ return evaluate(args[args.length - 1], ctx, session);
1307
+ }
1308
+ return values_1.ERRORS.NA;
1309
+ }
1310
+ function evaluateCHOOSE(args, ctx, session) {
1311
+ if (args.length < 2) {
1312
+ return values_1.ERRORS.VALUE;
1313
+ }
1314
+ const idxVal = (0, values_1.topLeft)(evalDeref(args[0], ctx, session));
1315
+ if ((0, values_1.isError)(idxVal)) {
1316
+ return idxVal;
1317
+ }
1318
+ const num = (0, values_1.toNumberRV)(idxVal);
1319
+ if ((0, values_1.isError)(num)) {
1320
+ return num;
1321
+ }
1322
+ const idx = Math.floor(num.value);
1323
+ if (idx < 1 || idx >= args.length) {
1324
+ return values_1.ERRORS.VALUE;
1325
+ }
1326
+ return evaluate(args[idx], ctx, session);
1327
+ }
1328
+ function evaluateLET(args, ctx, session) {
1329
+ if (args.length < 3 || args.length % 2 !== 1) {
1330
+ return values_1.ERRORS.VALUE;
1331
+ }
1332
+ const prevBindings = ctx.localBindings;
1333
+ const newBindings = new Map(prevBindings);
1334
+ try {
1335
+ const pairCount = (args.length - 1) / 2;
1336
+ for (let i = 0; i < pairCount; i++) {
1337
+ const nameExpr = args[i * 2];
1338
+ const valueExpr = args[i * 2 + 1];
1339
+ if (nameExpr.kind !== 13 /* BoundExprKind.NameExpr */) {
1340
+ return values_1.ERRORS.VALUE;
1341
+ }
1342
+ ctx.localBindings = newBindings;
1343
+ const val = evaluate(valueExpr, ctx, session);
1344
+ newBindings.set(nameExpr.upperName, val);
1345
+ }
1346
+ ctx.localBindings = newBindings;
1347
+ return evaluate(args[args.length - 1], ctx, session);
1348
+ }
1349
+ finally {
1350
+ ctx.localBindings = prevBindings;
1351
+ }
1352
+ }
1353
+ function evaluateLAMBDA(args, ctx) {
1354
+ if (args.length < 1) {
1355
+ return values_1.ERRORS.VALUE;
1356
+ }
1357
+ const paramExprs = args.slice(0, -1);
1358
+ const bodyExpr = args[args.length - 1];
1359
+ const params = [];
1360
+ for (const p of paramExprs) {
1361
+ if (p.kind !== 13 /* BoundExprKind.NameExpr */) {
1362
+ return values_1.ERRORS.VALUE;
1363
+ }
1364
+ params.push(p.upperName);
1365
+ }
1366
+ return (0, values_1.rvLambda)(params, bodyExpr, ctx.localBindings ? new Map(ctx.localBindings) : undefined);
1367
+ }
1368
+ function evaluateINDIRECT(args, ctx, session) {
1369
+ if (args.length < 1) {
1370
+ return values_1.ERRORS.VALUE;
1371
+ }
1372
+ const refArg = evalDeref(args[0], ctx, session);
1373
+ const refText = (0, values_1.toStringRV)((0, values_1.topLeft)(refArg));
1374
+ if (!refText) {
1375
+ return values_1.ERRORS.REF;
1376
+ }
1377
+ let a1 = true;
1378
+ if (args.length >= 2) {
1379
+ const a1Val = (0, values_1.topLeft)(evalDeref(args[1], ctx, session));
1380
+ a1 =
1381
+ !(a1Val.kind === 3 /* RVKind.Boolean */ && !a1Val.value) &&
1382
+ !(a1Val.kind === 1 /* RVKind.Number */ && a1Val.value === 0);
1383
+ }
1384
+ if (!a1) {
1385
+ // R1C1 — delegate to runtime R1C1 parser
1386
+ return resolveR1C1(refText, ctx, session);
1387
+ }
1388
+ // A1 style — parse and bind at runtime
1389
+ try {
1390
+ // Use NUL (U+0000) as the separator so sheet names containing `__`
1391
+ // can't collide with distinct INDIRECT call sites. Neither formula
1392
+ // text nor an Excel sheet name is allowed to contain `\0`, so the
1393
+ // key is unambiguous. (R6-P1-12)
1394
+ const cacheKey = `${ctx.currentSheet}\u0000${refText}`;
1395
+ let bound = session.indirectAstCache.get(cacheKey);
1396
+ if (bound) {
1397
+ // LRU touch: delete-then-set moves the hit entry to the Map's
1398
+ // insertion-order tail so the oldest entry is always `keys().next()`.
1399
+ session.indirectAstCache.delete(cacheKey);
1400
+ session.indirectAstCache.set(cacheKey, bound);
1401
+ }
1402
+ else {
1403
+ const tokens = (0, tokenizer_1.tokenize)(refText);
1404
+ const ast = (0, parser_1.parse)(tokens);
1405
+ const bindCtx = { snapshot: ctx.snapshot, currentSheet: ctx.currentSheet };
1406
+ bound = (0, binder_1.bind)(ast, bindCtx);
1407
+ // Bound the cache so an adversarial formula that generates a fresh
1408
+ // INDIRECT string every call can't grow session memory unbounded.
1409
+ // The cap matches `astCache` in calculate-formulas-impl.ts. (R6
1410
+ // architectural note #6)
1411
+ if (session.indirectAstCache.size >= 10000) {
1412
+ const oldestKey = session.indirectAstCache.keys().next().value;
1413
+ if (oldestKey !== undefined) {
1414
+ session.indirectAstCache.delete(oldestKey);
1415
+ }
1416
+ }
1417
+ session.indirectAstCache.set(cacheKey, bound);
1418
+ }
1419
+ return evaluate(bound, ctx, session);
1420
+ }
1421
+ catch {
1422
+ return values_1.ERRORS.REF;
1423
+ }
1424
+ }
1425
+ function evaluateOFFSET(args, ctx, session) {
1426
+ if (args.length < 3) {
1427
+ return values_1.ERRORS.VALUE;
1428
+ }
1429
+ const refExpr = args[0];
1430
+ let baseRow;
1431
+ let baseCol;
1432
+ let baseSheet;
1433
+ // Remember the base reference's shape — Excel's OFFSET uses it as the
1434
+ // default height/width when those optional arguments are omitted.
1435
+ let baseHeight;
1436
+ let baseWidth;
1437
+ if (refExpr.kind === 2 /* BoundExprKind.CellRef */) {
1438
+ baseRow = refExpr.row;
1439
+ baseCol = refExpr.col;
1440
+ baseSheet = refExpr.sheet;
1441
+ baseHeight = 1;
1442
+ baseWidth = 1;
1443
+ }
1444
+ else if (refExpr.kind === 3 /* BoundExprKind.AreaRef */) {
1445
+ baseRow = refExpr.top;
1446
+ baseCol = refExpr.left;
1447
+ baseSheet = refExpr.sheet;
1448
+ baseHeight = refExpr.bottom - refExpr.top + 1;
1449
+ baseWidth = refExpr.right - refExpr.left + 1;
1450
+ }
1451
+ else {
1452
+ return values_1.ERRORS.VALUE;
1453
+ }
1454
+ const rowsVal = (0, values_1.topLeft)(evalDeref(args[1], ctx, session));
1455
+ const rowsNum = (0, values_1.toNumberRV)(rowsVal);
1456
+ if ((0, values_1.isError)(rowsNum)) {
1457
+ return rowsNum;
1458
+ }
1459
+ const colsVal = (0, values_1.topLeft)(evalDeref(args[2], ctx, session));
1460
+ const colsNum = (0, values_1.toNumberRV)(colsVal);
1461
+ if ((0, values_1.isError)(colsNum)) {
1462
+ return colsNum;
1463
+ }
1464
+ // Excel truncates fractional rows/cols toward zero (not floor). Without
1465
+ // the `Math.trunc`, `OFFSET(A5, -0.7, 0)` would resolve to row 4.3 and
1466
+ // then fail the Map lookup silently, returning BLANK instead of A5.
1467
+ const newRow = baseRow + Math.trunc(rowsNum.value);
1468
+ const newCol = baseCol + Math.trunc(colsNum.value);
1469
+ if (newRow < 1 || newCol < 1 || newRow > 1048576 || newCol > 16384) {
1470
+ return values_1.ERRORS.REF;
1471
+ }
1472
+ // Default height / width come from the base reference itself. OFFSET
1473
+ // only shrinks/expands when the caller passes an explicit non-missing
1474
+ // fourth/fifth argument. A `MissingNode` (compile-time "omitted
1475
+ // argument") binds to a `null`-valued literal; we treat that the same
1476
+ // as "no argument provided" so `OFFSET(A1:C3, 0, 0, , )` keeps the
1477
+ // 3-row × 3-col span instead of collapsing to #REF! with `height = 0`.
1478
+ const isOmitted = (a) => a.kind === 1 /* BoundExprKind.Literal */ && a.value === null && a.errorCode === undefined;
1479
+ let height = baseHeight;
1480
+ let width = baseWidth;
1481
+ if (args.length > 3 && !isOmitted(args[3])) {
1482
+ const h = (0, values_1.toNumberRV)((0, values_1.topLeft)(evalDeref(args[3], ctx, session)));
1483
+ if ((0, values_1.isError)(h)) {
1484
+ return h;
1485
+ }
1486
+ height = Math.trunc(h.value);
1487
+ if (height === 0) {
1488
+ return values_1.ERRORS.REF;
1489
+ }
1490
+ }
1491
+ if (args.length > 4 && !isOmitted(args[4])) {
1492
+ const w = (0, values_1.toNumberRV)((0, values_1.topLeft)(evalDeref(args[4], ctx, session)));
1493
+ if ((0, values_1.isError)(w)) {
1494
+ return w;
1495
+ }
1496
+ width = Math.trunc(w.value);
1497
+ if (width === 0) {
1498
+ return values_1.ERRORS.REF;
1499
+ }
1500
+ }
1501
+ // Resolve range coordinates — negative height/width extend upward/leftward
1502
+ let top = newRow;
1503
+ let bottom = newRow + height - 1;
1504
+ if (height < 0) {
1505
+ top = newRow + height + 1;
1506
+ bottom = newRow;
1507
+ }
1508
+ let left = newCol;
1509
+ let right = newCol + width - 1;
1510
+ if (width < 0) {
1511
+ left = newCol + width + 1;
1512
+ right = newCol;
1513
+ }
1514
+ if (top < 1 || left < 1 || bottom > 1048576 || right > 16384) {
1515
+ return values_1.ERRORS.REF;
1516
+ }
1517
+ if (top === bottom && left === right) {
1518
+ return getCellValue(baseSheet, top, left, ctx, session);
1519
+ }
1520
+ return buildRangeArray(ctx, session, baseSheet, top, left, bottom, right);
1521
+ }
1522
+ // ============================================================================
1523
+ // Higher-Order Functions
1524
+ // ============================================================================
1525
+ function evaluateHigherOrder(name, args, ctx, session) {
1526
+ switch (name) {
1527
+ case "MAP":
1528
+ return evaluateMAP(args, ctx, session);
1529
+ case "REDUCE":
1530
+ return evaluateREDUCE(args, ctx, session);
1531
+ case "SCAN":
1532
+ return evaluateSCAN(args, ctx, session);
1533
+ case "MAKEARRAY":
1534
+ return evaluateMAKEARRAY(args, ctx, session);
1535
+ case "BYROW":
1536
+ return evaluateBYROW(args, ctx, session);
1537
+ case "BYCOL":
1538
+ return evaluateBYCOL(args, ctx, session);
1539
+ default:
1540
+ return values_1.ERRORS.VALUE;
1541
+ }
1542
+ }
1543
+ function invokeLambda(lambda, lambdaArgs, ctx, session) {
1544
+ if (lambdaArgs.length !== lambda.params.length) {
1545
+ return values_1.ERRORS.VALUE;
1546
+ }
1547
+ if (session.lambdaDepth >= 256) {
1548
+ return values_1.ERRORS.NUM;
1549
+ }
1550
+ session.lambdaDepth++;
1551
+ try {
1552
+ const prevBindings = ctx.localBindings;
1553
+ const newBindings = new Map(lambda.closureBindings);
1554
+ for (let i = 0; i < lambda.params.length; i++) {
1555
+ newBindings.set(lambda.params[i], lambdaArgs[i]);
1556
+ }
1557
+ ctx.localBindings = newBindings;
1558
+ try {
1559
+ return dereferenceValue(evaluate(lambda.body, ctx, session), ctx, session);
1560
+ }
1561
+ finally {
1562
+ ctx.localBindings = prevBindings;
1563
+ }
1564
+ }
1565
+ finally {
1566
+ session.lambdaDepth--;
1567
+ }
1568
+ }
1569
+ function evaluateMAP(args, ctx, session) {
1570
+ if (args.length < 2) {
1571
+ return values_1.ERRORS.VALUE;
1572
+ }
1573
+ const arrVal = evalDeref(args[0], ctx, session);
1574
+ const lambdaVal = evalDeref(args[args.length - 1], ctx, session);
1575
+ if (!(0, values_1.isLambda)(lambdaVal)) {
1576
+ return values_1.ERRORS.VALUE;
1577
+ }
1578
+ if (arrVal.kind !== 5 /* RVKind.Array */) {
1579
+ return invokeLambda(lambdaVal, [arrVal], ctx, session);
1580
+ }
1581
+ const rows = [];
1582
+ for (const row of arrVal.rows) {
1583
+ const outRow = [];
1584
+ for (const cell of row) {
1585
+ outRow.push((0, values_1.topLeft)(invokeLambda(lambdaVal, [cell], ctx, session)));
1586
+ }
1587
+ rows.push(outRow);
1588
+ }
1589
+ return (0, values_1.rvArray)(rows);
1590
+ }
1591
+ function evaluateREDUCE(args, ctx, session) {
1592
+ if (args.length < 3) {
1593
+ return values_1.ERRORS.VALUE;
1594
+ }
1595
+ let acc = evalDeref(args[0], ctx, session);
1596
+ const arrVal = evalDeref(args[1], ctx, session);
1597
+ const lambdaVal = evalDeref(args[2], ctx, session);
1598
+ if (!(0, values_1.isLambda)(lambdaVal)) {
1599
+ return values_1.ERRORS.VALUE;
1600
+ }
1601
+ if (arrVal.kind === 5 /* RVKind.Array */) {
1602
+ for (const row of arrVal.rows) {
1603
+ for (const cell of row) {
1604
+ acc = invokeLambda(lambdaVal, [(0, values_1.topLeft)(acc), cell], ctx, session);
1605
+ }
1606
+ }
1607
+ }
1608
+ else {
1609
+ // Scalar input: Excel treats the scalar as a 1×1 array and invokes the
1610
+ // reducer exactly once. Previously we returned `init` unchanged, which
1611
+ // silently broke `REDUCE(0, some_scalar, lambda)` callers.
1612
+ acc = invokeLambda(lambdaVal, [(0, values_1.topLeft)(acc), (0, values_1.topLeft)(arrVal)], ctx, session);
1613
+ }
1614
+ return acc;
1615
+ }
1616
+ function evaluateSCAN(args, ctx, session) {
1617
+ if (args.length < 3) {
1618
+ return values_1.ERRORS.VALUE;
1619
+ }
1620
+ let acc = evalDeref(args[0], ctx, session);
1621
+ const arrVal = evalDeref(args[1], ctx, session);
1622
+ const lambdaVal = evalDeref(args[2], ctx, session);
1623
+ if (!(0, values_1.isLambda)(lambdaVal)) {
1624
+ return values_1.ERRORS.VALUE;
1625
+ }
1626
+ const rows = [];
1627
+ if (arrVal.kind === 5 /* RVKind.Array */) {
1628
+ for (const row of arrVal.rows) {
1629
+ const outRow = [];
1630
+ for (const cell of row) {
1631
+ acc = invokeLambda(lambdaVal, [(0, values_1.topLeft)(acc), cell], ctx, session);
1632
+ outRow.push((0, values_1.topLeft)(acc));
1633
+ }
1634
+ rows.push(outRow);
1635
+ }
1636
+ return rows.length > 0 ? (0, values_1.rvArray)(rows) : values_1.ERRORS.CALC;
1637
+ }
1638
+ // Scalar input: emit a single-cell array containing the one accumulated
1639
+ // value. Previously we returned #CALC! here, which was an artefact of the
1640
+ // array-only implementation path.
1641
+ const result = invokeLambda(lambdaVal, [(0, values_1.topLeft)(acc), (0, values_1.topLeft)(arrVal)], ctx, session);
1642
+ return (0, values_1.rvArray)([[(0, values_1.topLeft)(result)]]);
1643
+ }
1644
+ function evaluateMAKEARRAY(args, ctx, session) {
1645
+ if (args.length < 3) {
1646
+ return values_1.ERRORS.VALUE;
1647
+ }
1648
+ const rowsNum = (0, values_1.toNumberRV)((0, values_1.topLeft)(evalDeref(args[0], ctx, session)));
1649
+ if ((0, values_1.isError)(rowsNum)) {
1650
+ return rowsNum;
1651
+ }
1652
+ const colsNum = (0, values_1.toNumberRV)((0, values_1.topLeft)(evalDeref(args[1], ctx, session)));
1653
+ if ((0, values_1.isError)(colsNum)) {
1654
+ return colsNum;
1655
+ }
1656
+ const lambdaVal = evalDeref(args[2], ctx, session);
1657
+ if (!(0, values_1.isLambda)(lambdaVal)) {
1658
+ return values_1.ERRORS.VALUE;
1659
+ }
1660
+ // Truncate toward zero and reject non-positive / overflow sizes.
1661
+ // Without the cell-count cap the engine can silently allocate billions
1662
+ // of scalars before blowing the heap; matching the broadcast limit
1663
+ // keeps MAKEARRAY in line with the rest of the array pipeline.
1664
+ const rCount = Math.trunc(rowsNum.value);
1665
+ const cCount = Math.trunc(colsNum.value);
1666
+ if (rCount < 1 || cCount < 1) {
1667
+ return values_1.ERRORS.VALUE;
1668
+ }
1669
+ if (rCount * cCount > 10000000) {
1670
+ return values_1.ERRORS.NUM;
1671
+ }
1672
+ const rows = [];
1673
+ for (let r = 1; r <= rCount; r++) {
1674
+ const outRow = [];
1675
+ for (let c = 1; c <= cCount; c++) {
1676
+ outRow.push((0, values_1.topLeft)(invokeLambda(lambdaVal, [(0, values_1.rvNumber)(r), (0, values_1.rvNumber)(c)], ctx, session)));
1677
+ }
1678
+ rows.push(outRow);
1679
+ }
1680
+ return (0, values_1.rvArray)(rows);
1681
+ }
1682
+ function evaluateBYROW(args, ctx, session) {
1683
+ if (args.length < 2) {
1684
+ return values_1.ERRORS.VALUE;
1685
+ }
1686
+ const arrVal = evalDeref(args[0], ctx, session);
1687
+ const lambdaVal = evalDeref(args[1], ctx, session);
1688
+ if (!(0, values_1.isLambda)(lambdaVal)) {
1689
+ return values_1.ERRORS.VALUE;
1690
+ }
1691
+ if (arrVal.kind !== 5 /* RVKind.Array */) {
1692
+ return invokeLambda(lambdaVal, [(0, values_1.rvArray)([[(0, values_1.topLeft)(arrVal)]])], ctx, session);
1693
+ }
1694
+ const rows = [];
1695
+ for (const row of arrVal.rows) {
1696
+ const rowArr = (0, values_1.rvArray)([row.map(c => c)]);
1697
+ rows.push([(0, values_1.topLeft)(invokeLambda(lambdaVal, [rowArr], ctx, session))]);
1698
+ }
1699
+ return (0, values_1.rvArray)(rows);
1700
+ }
1701
+ function evaluateBYCOL(args, ctx, session) {
1702
+ if (args.length < 2) {
1703
+ return values_1.ERRORS.VALUE;
1704
+ }
1705
+ const arrVal = evalDeref(args[0], ctx, session);
1706
+ const lambdaVal = evalDeref(args[1], ctx, session);
1707
+ if (!(0, values_1.isLambda)(lambdaVal)) {
1708
+ return values_1.ERRORS.VALUE;
1709
+ }
1710
+ if (arrVal.kind !== 5 /* RVKind.Array */) {
1711
+ return invokeLambda(lambdaVal, [(0, values_1.rvArray)([[(0, values_1.topLeft)(arrVal)]])], ctx, session);
1712
+ }
1713
+ const numCols = arrVal.width;
1714
+ const outRow = [];
1715
+ for (let c = 0; c < numCols; c++) {
1716
+ const colArr = (0, values_1.rvArray)(arrVal.rows.map(row => [row[c]]));
1717
+ outRow.push((0, values_1.topLeft)(invokeLambda(lambdaVal, [colArr], ctx, session)));
1718
+ }
1719
+ return (0, values_1.rvArray)([outRow]);
1720
+ }
1721
+ // ============================================================================
1722
+ // Reference Functions (ROW, COLUMN, ROWS, COLUMNS)
1723
+ // ============================================================================
1724
+ /** Whether `name` is one of the four "inspect a reference's geometry" builtins. */
1725
+ function isSimpleRefFunction(name) {
1726
+ return name === "ROW" || name === "COLUMN" || name === "ROWS" || name === "COLUMNS";
1727
+ }
1728
+ function tryEvaluateRefFunction(name, args, ctx) {
1729
+ switch (name) {
1730
+ case "ROW":
1731
+ if (args.length === 0) {
1732
+ return ctx.currentAddress ? (0, values_1.rvNumber)(ctx.currentAddress.row) : values_1.ERRORS.VALUE;
1733
+ }
1734
+ if (args[0].kind === 2 /* BoundExprKind.CellRef */) {
1735
+ return (0, values_1.rvNumber)(args[0].row);
1736
+ }
1737
+ if (args[0].kind === 3 /* BoundExprKind.AreaRef */) {
1738
+ return (0, values_1.rvNumber)(args[0].top);
1739
+ }
1740
+ return undefined;
1741
+ case "COLUMN":
1742
+ if (args.length === 0) {
1743
+ return ctx.currentAddress ? (0, values_1.rvNumber)(ctx.currentAddress.col) : values_1.ERRORS.VALUE;
1744
+ }
1745
+ if (args[0].kind === 2 /* BoundExprKind.CellRef */) {
1746
+ return (0, values_1.rvNumber)(args[0].col);
1747
+ }
1748
+ if (args[0].kind === 3 /* BoundExprKind.AreaRef */) {
1749
+ return (0, values_1.rvNumber)(args[0].left);
1750
+ }
1751
+ return undefined;
1752
+ case "ROWS":
1753
+ if (args.length > 0 && args[0].kind === 3 /* BoundExprKind.AreaRef */) {
1754
+ return (0, values_1.rvNumber)(args[0].bottom - args[0].top + 1);
1755
+ }
1756
+ if (args.length > 0 && args[0].kind === 2 /* BoundExprKind.CellRef */) {
1757
+ return (0, values_1.rvNumber)(1);
1758
+ }
1759
+ return undefined;
1760
+ case "COLUMNS":
1761
+ if (args.length > 0 && args[0].kind === 3 /* BoundExprKind.AreaRef */) {
1762
+ return (0, values_1.rvNumber)(args[0].right - args[0].left + 1);
1763
+ }
1764
+ if (args.length > 0 && args[0].kind === 2 /* BoundExprKind.CellRef */) {
1765
+ return (0, values_1.rvNumber)(1);
1766
+ }
1767
+ return undefined;
1768
+ default:
1769
+ return undefined;
1770
+ }
1771
+ }
1772
+ // ============================================================================
1773
+ // Reference-aware: ISREF
1774
+ // ============================================================================
1775
+ /**
1776
+ * ISREF(value) → TRUE if `value` is a reference; FALSE otherwise.
1777
+ *
1778
+ * Excel's rule is syntactic + runtime: any `CellRef` / `AreaRef` / 3-D ref /
1779
+ * `ColRangeRef` / `RowRangeRef` is a reference, and any call that *produces*
1780
+ * a `ReferenceValue` (INDIRECT, OFFSET) is also a reference. Errors in the
1781
+ * sub-expression are suppressed (Excel returns FALSE for `ISREF(INDIRECT("xx"))`
1782
+ * where INDIRECT returns `#REF!`).
1783
+ */
1784
+ function evaluateISREF(args, ctx, session) {
1785
+ if (args.length !== 1) {
1786
+ return values_1.ERRORS.VALUE;
1787
+ }
1788
+ const arg = args[0];
1789
+ // Purely syntactic reference forms — always TRUE without evaluating.
1790
+ if (arg.kind === 2 /* BoundExprKind.CellRef */ ||
1791
+ arg.kind === 3 /* BoundExprKind.AreaRef */ ||
1792
+ arg.kind === 4 /* BoundExprKind.ColRangeRef */ ||
1793
+ arg.kind === 5 /* BoundExprKind.RowRangeRef */ ||
1794
+ arg.kind === 6 /* BoundExprKind.Ref3D */) {
1795
+ return (0, values_1.rvBoolean)(true);
1796
+ }
1797
+ // Otherwise evaluate without dereferencing. INDIRECT/OFFSET yield a
1798
+ // ReferenceValue when successful; anything else (error, scalar, array)
1799
+ // is not a reference. Per Excel, ISREF suppresses errors to FALSE.
1800
+ const raw = evaluate(arg, ctx, session);
1801
+ return (0, values_1.rvBoolean)(raw.kind === 6 /* RVKind.Reference */);
1802
+ }
1803
+ // ============================================================================
1804
+ // Reference-aware: CELL
1805
+ // ============================================================================
1806
+ /**
1807
+ * Resolve a CELL(..., ref) argument to a concrete {sheet,row,col} triple.
1808
+ * CELL always inspects the *top-left* cell of the referenced area.
1809
+ * Returns an error value if the argument cannot be resolved to a reference.
1810
+ */
1811
+ function resolveCellRefArg(arg, ctx, session) {
1812
+ // Syntactic reference forms — extract top-left directly.
1813
+ if (arg.kind === 2 /* BoundExprKind.CellRef */) {
1814
+ return { sheet: arg.sheet, row: arg.row, col: arg.col };
1815
+ }
1816
+ if (arg.kind === 3 /* BoundExprKind.AreaRef */) {
1817
+ return { sheet: arg.sheet, row: arg.top, col: arg.left };
1818
+ }
1819
+ if (arg.kind === 4 /* BoundExprKind.ColRangeRef */) {
1820
+ const ws = ctx.snapshot.worksheetsByName.get(arg.sheet.toLowerCase());
1821
+ const top = ws?.dimensions?.top ?? 1;
1822
+ return { sheet: arg.sheet, row: top, col: arg.leftCol };
1823
+ }
1824
+ if (arg.kind === 5 /* BoundExprKind.RowRangeRef */) {
1825
+ const ws = ctx.snapshot.worksheetsByName.get(arg.sheet.toLowerCase());
1826
+ const left = ws?.dimensions?.left ?? 1;
1827
+ return { sheet: arg.sheet, row: arg.topRow, col: left };
1828
+ }
1829
+ if (arg.kind === 6 /* BoundExprKind.Ref3D */) {
1830
+ const first = arg.sheets[0];
1831
+ if (first === undefined) {
1832
+ return values_1.ERRORS.VALUE;
1833
+ }
1834
+ if (arg.inner.kind === 2 /* BoundExprKind.CellRef */) {
1835
+ return { sheet: first, row: arg.inner.row, col: arg.inner.col };
1836
+ }
1837
+ return { sheet: first, row: arg.inner.top, col: arg.inner.left };
1838
+ }
1839
+ // Fall back to evaluating — INDIRECT/OFFSET etc. may produce a ReferenceValue.
1840
+ const raw = evaluate(arg, ctx, session);
1841
+ if (raw.kind === 4 /* RVKind.Error */) {
1842
+ return raw;
1843
+ }
1844
+ if (raw.kind === 6 /* RVKind.Reference */ && raw.areas.length > 0) {
1845
+ const area = raw.areas[0];
1846
+ return { sheet: area.sheet, row: area.top, col: area.left };
1847
+ }
1848
+ // Non-reference argument — Excel returns #VALUE! for CELL.
1849
+ return values_1.ERRORS.VALUE;
1850
+ }
1851
+ /** Convert a 1-based column number to its letter form (1 → "A", 27 → "AA"). */
1852
+ function colNumberToLetter(colNum) {
1853
+ let col = "";
1854
+ let cv = colNum;
1855
+ while (cv > 0) {
1856
+ cv--;
1857
+ col = String.fromCharCode(65 + (cv % 26)) + col;
1858
+ cv = Math.floor(cv / 26);
1859
+ }
1860
+ return col;
1861
+ }
1862
+ /**
1863
+ * CELL(info_type, [reference]) — limited, workbook-internal subset.
1864
+ *
1865
+ * Supported info types:
1866
+ * - `"address"` → "$A$1"-style absolute reference (no sheet name)
1867
+ * - `"row"` → 1-based row number of the top-left cell
1868
+ * - `"col"` → 1-based column number of the top-left cell
1869
+ * - `"contents"` → value of the top-left cell
1870
+ * - `"type"` → "b" (blank), "l" (label/text), "v" (value/other)
1871
+ * - `"width"` → 8 (column width is not tracked in the snapshot)
1872
+ * - `"filename"` → "" (no file path available)
1873
+ *
1874
+ * Any other info type yields `#N/A`, matching Excel's treatment of
1875
+ * workbook-state-dependent info in contexts where the data is unavailable.
1876
+ *
1877
+ * When `reference` is omitted, the current formula's own cell is used —
1878
+ * if that cannot be determined, `#VALUE!` is returned.
1879
+ */
1880
+ function evaluateCELL(args, ctx, session) {
1881
+ if (args.length < 1 || args.length > 2) {
1882
+ return values_1.ERRORS.VALUE;
1883
+ }
1884
+ // Resolve info_type — this is a plain string expression, evaluate normally.
1885
+ const infoRV = dereferenceValue(evaluate(args[0], ctx, session), ctx, session);
1886
+ if (infoRV.kind === 4 /* RVKind.Error */) {
1887
+ return infoRV;
1888
+ }
1889
+ const infoScalar = (0, values_1.topLeft)(infoRV);
1890
+ if (infoScalar.kind === 4 /* RVKind.Error */) {
1891
+ return infoScalar;
1892
+ }
1893
+ if (infoScalar.kind !== 2 /* RVKind.String */) {
1894
+ return values_1.ERRORS.VALUE;
1895
+ }
1896
+ const info = infoScalar.value.toLowerCase();
1897
+ // Resolve reference: explicit arg, or the current formula cell.
1898
+ let target;
1899
+ if (args.length === 2) {
1900
+ const resolved = resolveCellRefArg(args[1], ctx, session);
1901
+ if ("kind" in resolved) {
1902
+ return resolved;
1903
+ }
1904
+ target = resolved;
1905
+ }
1906
+ else {
1907
+ if (!ctx.currentAddress) {
1908
+ return values_1.ERRORS.VALUE;
1909
+ }
1910
+ target = {
1911
+ sheet: ctx.currentSheet,
1912
+ row: ctx.currentAddress.row,
1913
+ col: ctx.currentAddress.col
1914
+ };
1915
+ }
1916
+ switch (info) {
1917
+ case "address": {
1918
+ return (0, values_1.rvString)(`$${colNumberToLetter(target.col)}$${target.row}`);
1919
+ }
1920
+ case "row":
1921
+ return (0, values_1.rvNumber)(target.row);
1922
+ case "col":
1923
+ case "column":
1924
+ return (0, values_1.rvNumber)(target.col);
1925
+ case "contents": {
1926
+ return getCellValue(target.sheet, target.row, target.col, ctx, session);
1927
+ }
1928
+ case "type": {
1929
+ const val = getCellValue(target.sheet, target.row, target.col, ctx, session);
1930
+ if (val.kind === 0 /* RVKind.Blank */) {
1931
+ return (0, values_1.rvString)("b");
1932
+ }
1933
+ if (val.kind === 2 /* RVKind.String */) {
1934
+ return (0, values_1.rvString)("l");
1935
+ }
1936
+ // Numbers, booleans, errors — all classified as "value".
1937
+ return (0, values_1.rvString)("v");
1938
+ }
1939
+ case "width":
1940
+ // Column width is not captured in the snapshot — return Excel's default.
1941
+ return (0, values_1.rvNumber)(8);
1942
+ case "filename":
1943
+ // No file path is available to the calculation engine.
1944
+ return (0, values_1.rvString)("");
1945
+ default:
1946
+ return values_1.ERRORS.NA;
1947
+ }
1948
+ }
1949
+ // ============================================================================
1950
+ // Name Expression
1951
+ // ============================================================================
1952
+ function evaluateNameExpr(expr, ctx, session) {
1953
+ // Check local bindings first (LET variables)
1954
+ if (ctx.localBindings?.has(expr.upperName)) {
1955
+ return ctx.localBindings.get(expr.upperName);
1956
+ }
1957
+ // Try snapshot defined name resolution (for formula-based names).
1958
+ // Respects scope precedence: sheet-local > workbook-global.
1959
+ const dn = (0, workbook_snapshot_1.resolveDefinedName)(ctx.snapshot.definedNames, expr.name, ctx.currentSheet);
1960
+ if (dn && dn.ranges.length > 0) {
1961
+ if (dn.ranges.length > 1) {
1962
+ return values_1.ERRORS.VALUE;
1963
+ }
1964
+ const rangeStr = dn.ranges[0];
1965
+ const parsed = (0, address_utils_1.parseDefinedNameRange)(rangeStr);
1966
+ if (parsed) {
1967
+ if (parsed.startRow === parsed.endRow && parsed.startCol === parsed.endCol) {
1968
+ return getCellValue(parsed.sheet, parsed.startRow, parsed.startCol, ctx, session);
1969
+ }
1970
+ return buildRangeArray(ctx, session, parsed.sheet, Math.min(parsed.startRow, parsed.endRow), Math.min(parsed.startCol, parsed.endCol), Math.max(parsed.startRow, parsed.endRow), Math.max(parsed.startCol, parsed.endCol));
1971
+ }
1972
+ // Formula expression — parse and evaluate via shared helper
1973
+ const nameResult = evaluateFormulaName(expr.upperName, rangeStr, ctx, session);
1974
+ return nameResult ?? values_1.ERRORS.NAME;
1975
+ }
1976
+ return values_1.ERRORS.NAME;
1977
+ }
1978
+ // ============================================================================
1979
+ // Formula-based Defined Name Evaluation (shared helper)
1980
+ // ============================================================================
1981
+ /**
1982
+ * Evaluate a formula-based defined name expression, with caching.
1983
+ * Cache key includes name + sheet + cell address to handle position-dependent
1984
+ * formulas like ROW()/COLUMN().
1985
+ *
1986
+ * Returns the evaluated result, or undefined if parsing/evaluation fails.
1987
+ */
1988
+ function evaluateFormulaName(upperName, formulaExpr, ctx, session) {
1989
+ const addr = ctx.currentAddress;
1990
+ const cacheKey = addr
1991
+ ? `__NAME__${upperName}__${ctx.currentSheet}__${addr.row}:${addr.col}`
1992
+ : `__NAME__${upperName}__${ctx.currentSheet}`;
1993
+ const cached = session.nameCache.get(cacheKey);
1994
+ if (cached !== undefined) {
1995
+ return cached;
1996
+ }
1997
+ // Guard against recursion through a defined name that references itself.
1998
+ // Uses a dedicated prefix so it cannot collide with formula-cell guard keys.
1999
+ const guardKey = `__NAMEEVAL__${upperName}`;
2000
+ if (session.evaluating.has(guardKey)) {
2001
+ return values_1.ERRORS.CALC;
2002
+ }
2003
+ session.evaluating.add(guardKey);
2004
+ try {
2005
+ const tokens = (0, tokenizer_1.tokenize)(formulaExpr);
2006
+ const ast = (0, parser_1.parse)(tokens);
2007
+ const bindCtx = { snapshot: ctx.snapshot, currentSheet: ctx.currentSheet };
2008
+ const bound = (0, binder_1.bind)(ast, bindCtx);
2009
+ const result = evaluate(bound, ctx, session);
2010
+ session.nameCache.set(cacheKey, result);
2011
+ return result;
2012
+ }
2013
+ catch {
2014
+ return undefined;
2015
+ }
2016
+ finally {
2017
+ session.evaluating.delete(guardKey);
2018
+ }
2019
+ }
2020
+ // ============================================================================
2021
+ // Lambda Expression
2022
+ // ============================================================================
2023
+ function evaluateLambdaExpr(expr, ctx) {
2024
+ return (0, values_1.rvLambda)([...expr.params], expr.body, ctx.localBindings ? new Map(ctx.localBindings) : undefined);
2025
+ }
2026
+ // ============================================================================
2027
+ // Structured Reference (runtime resolution)
2028
+ // ============================================================================
2029
+ function evaluateStructuredRef(expr, ctx, session) {
2030
+ const snapshot = ctx.snapshot;
2031
+ const addr = ctx.currentAddress;
2032
+ // Find the table
2033
+ let tableName = expr.tableName;
2034
+ let tableSheet = null;
2035
+ let tableInfo = null;
2036
+ if (tableName === "") {
2037
+ // Implicit table — find the table containing the current cell
2038
+ if (!addr) {
2039
+ return values_1.ERRORS.REF;
2040
+ }
2041
+ // Sheet names are case-insensitive in Excel. Comparing the literal
2042
+ // `ws.name !== addr.sheet` would miss tables when the formula-cell's
2043
+ // address records its sheet in a different case than the workbook's
2044
+ // canonical name (possible after rename / import flows).
2045
+ const addrSheetLower = addr.sheet.toLowerCase();
2046
+ for (const ws of snapshot.worksheets) {
2047
+ if (ws.name.toLowerCase() !== addrSheetLower) {
2048
+ continue;
2049
+ }
2050
+ for (const t of ws.tables) {
2051
+ const g = (0, structured_ref_utils_1.buildTableGeometry)(t);
2052
+ const width = t.columns.length;
2053
+ if (addr.row >= g.dataRowStart &&
2054
+ addr.row <= g.dataRowEnd &&
2055
+ addr.col >= t.topLeft.col &&
2056
+ addr.col < t.topLeft.col + width) {
2057
+ tableInfo = t;
2058
+ tableSheet = ws.name;
2059
+ tableName = t.name;
2060
+ break;
2061
+ }
2062
+ }
2063
+ if (tableInfo) {
2064
+ break;
2065
+ }
2066
+ }
2067
+ }
2068
+ else {
2069
+ // Named table — use the pre-built index for O(1) lookup
2070
+ const resolved = snapshot.tablesByName.get(tableName.toLowerCase());
2071
+ if (resolved) {
2072
+ tableInfo = resolved.table;
2073
+ tableSheet = resolved.sheetName;
2074
+ }
2075
+ }
2076
+ if (!tableInfo || !tableSheet) {
2077
+ return values_1.ERRORS.REF;
2078
+ }
2079
+ const geo = (0, structured_ref_utils_1.buildTableGeometry)(tableInfo);
2080
+ // Strict column resolution — unknown column names surface as #REF!
2081
+ const colRange = (0, structured_ref_utils_1.resolveStructuredRefColumns)(expr.columns, tableInfo, "strict");
2082
+ if (colRange === "error") {
2083
+ return values_1.ERRORS.REF;
2084
+ }
2085
+ const rowRange = (0, structured_ref_utils_1.resolveStructuredRefRows)(expr.specials, geo);
2086
+ let rowTop;
2087
+ let rowBottom;
2088
+ if (rowRange === "error") {
2089
+ return values_1.ERRORS.REF;
2090
+ }
2091
+ else if (rowRange === "thisRow") {
2092
+ if (addr) {
2093
+ rowTop = addr.row;
2094
+ rowBottom = addr.row;
2095
+ }
2096
+ else {
2097
+ return values_1.ERRORS.VALUE;
2098
+ }
2099
+ }
2100
+ else {
2101
+ rowTop = rowRange.rowTop;
2102
+ rowBottom = rowRange.rowBottom;
2103
+ }
2104
+ // Single cell — return as single-cell ReferenceValue
2105
+ if (rowTop === rowBottom && colRange.colLeft === colRange.colRight) {
2106
+ return (0, values_1.rvCellRef)(tableSheet, rowTop, colRange.colLeft);
2107
+ }
2108
+ // Range — return as area ReferenceValue
2109
+ return (0, values_1.rvRef)(tableSheet, rowTop, colRange.colLeft, rowBottom, colRange.colRight);
2110
+ }
2111
+ // ============================================================================
2112
+ // Array Literal
2113
+ // ============================================================================
2114
+ function evaluateArrayLiteral(expr, ctx, session) {
2115
+ const rows = [];
2116
+ for (const row of expr.rows) {
2117
+ const evalRow = [];
2118
+ for (const elem of row) {
2119
+ evalRow.push((0, values_1.topLeft)(evalDeref(elem, ctx, session)));
2120
+ }
2121
+ rows.push(evalRow);
2122
+ }
2123
+ return (0, values_1.rvArray)(rows);
2124
+ }
2125
+ // ============================================================================
2126
+ // Implicit Intersection
2127
+ // ============================================================================
2128
+ function implicitIntersect(val, ctx) {
2129
+ if ((0, values_1.isScalar)(val)) {
2130
+ return val;
2131
+ }
2132
+ if (val.kind === 7 /* RVKind.Lambda */) {
2133
+ return val;
2134
+ }
2135
+ if (val.kind === 6 /* RVKind.Reference */) {
2136
+ // Implicit intersection on a reference: resolve using formula cell's row/col
2137
+ if (val.areas.length === 0) {
2138
+ return values_1.BLANK;
2139
+ }
2140
+ const area = val.areas[0];
2141
+ const isSingleCell = area.top === area.bottom && area.left === area.right;
2142
+ if (isSingleCell) {
2143
+ return val; // Single cell ref — keep as-is, will be dereferenced at use site
2144
+ }
2145
+ // Multi-cell reference — apply implicit intersection
2146
+ const addr = ctx.currentAddress;
2147
+ if (!addr) {
2148
+ return val;
2149
+ }
2150
+ // Single column — pick row
2151
+ if (area.left === area.right && addr.row >= area.top && addr.row <= area.bottom) {
2152
+ return (0, values_1.rvCellRef)(area.sheet, addr.row, area.left);
2153
+ }
2154
+ // Single row — pick column
2155
+ if (area.top === area.bottom && addr.col >= area.left && addr.col <= area.right) {
2156
+ return (0, values_1.rvCellRef)(area.sheet, area.top, addr.col);
2157
+ }
2158
+ // Both row and col
2159
+ if (addr.row >= area.top &&
2160
+ addr.row <= area.bottom &&
2161
+ addr.col >= area.left &&
2162
+ addr.col <= area.right) {
2163
+ return (0, values_1.rvCellRef)(area.sheet, addr.row, addr.col);
2164
+ }
2165
+ return val;
2166
+ }
2167
+ if (val.kind !== 5 /* RVKind.Array */) {
2168
+ return val;
2169
+ }
2170
+ const arr = val;
2171
+ if (arr.height === 0 || arr.width === 0) {
2172
+ return values_1.BLANK;
2173
+ }
2174
+ if (arr.height === 1 && arr.width === 1) {
2175
+ return arr.rows[0][0];
2176
+ }
2177
+ const addr = ctx.currentAddress;
2178
+ if (!addr) {
2179
+ return arr.rows[0][0];
2180
+ }
2181
+ // Single row — pick column by offset
2182
+ if (arr.height === 1) {
2183
+ if (arr.originCol !== undefined) {
2184
+ const colIdx = addr.col - arr.originCol;
2185
+ if (colIdx >= 0 && colIdx < arr.width) {
2186
+ return arr.rows[0][colIdx];
2187
+ }
2188
+ }
2189
+ return arr.rows[0][0];
2190
+ }
2191
+ // Single column — pick row by offset
2192
+ if (arr.width === 1) {
2193
+ if (arr.originRow !== undefined) {
2194
+ const rowIdx = addr.row - arr.originRow;
2195
+ if (rowIdx >= 0 && rowIdx < arr.height) {
2196
+ return arr.rows[rowIdx][0];
2197
+ }
2198
+ }
2199
+ return arr.rows[0][0];
2200
+ }
2201
+ // Multi-row, multi-column
2202
+ if (arr.originRow !== undefined && arr.originCol !== undefined) {
2203
+ const rowIdx = addr.row - arr.originRow;
2204
+ const colIdx = addr.col - arr.originCol;
2205
+ if (rowIdx >= 0 && rowIdx < arr.height && colIdx >= 0 && colIdx < arr.width) {
2206
+ return arr.rows[rowIdx][colIdx];
2207
+ }
2208
+ }
2209
+ return arr.rows[0][0];
2210
+ }
2211
+ // ============================================================================
2212
+ // R1C1 Reference Resolution
2213
+ // ============================================================================
2214
+ function resolveR1C1(refText, ctx, session) {
2215
+ const upper = refText.toUpperCase().trim();
2216
+ // Check for range separator
2217
+ let depth = 0;
2218
+ let sepIdx = -1;
2219
+ for (let i = 0; i < upper.length; i++) {
2220
+ if (upper[i] === "[") {
2221
+ depth++;
2222
+ }
2223
+ else if (upper[i] === "]") {
2224
+ depth--;
2225
+ }
2226
+ else if (upper[i] === ":" && depth === 0) {
2227
+ sepIdx = i;
2228
+ break;
2229
+ }
2230
+ }
2231
+ if (sepIdx !== -1) {
2232
+ const s = parseR1C1Single(upper.slice(0, sepIdx), ctx);
2233
+ const e = parseR1C1Single(upper.slice(sepIdx + 1), ctx);
2234
+ if (!s || !e) {
2235
+ return values_1.ERRORS.REF;
2236
+ }
2237
+ const top = Math.min(s.row, e.row);
2238
+ const bottom = Math.max(s.row, e.row);
2239
+ const left = Math.min(s.col, e.col);
2240
+ const right = Math.max(s.col, e.col);
2241
+ return (0, values_1.rvRef)(ctx.currentSheet, top, left, bottom, right);
2242
+ }
2243
+ const ref = parseR1C1Single(upper, ctx);
2244
+ if (!ref || ref.row < 1 || ref.col < 1) {
2245
+ return values_1.ERRORS.REF;
2246
+ }
2247
+ return (0, values_1.rvCellRef)(ctx.currentSheet, ref.row, ref.col);
2248
+ }
2249
+ function parseR1C1Single(text, ctx) {
2250
+ const re = /^R(\[(-?\d+)\]|(\d+))C(\[(-?\d+)\]|(\d+))$/;
2251
+ const m = re.exec(text);
2252
+ if (!m) {
2253
+ return null;
2254
+ }
2255
+ const addr = ctx.currentAddress;
2256
+ const row = m[2] !== undefined ? (addr?.row ?? 1) + parseInt(m[2], 10) : parseInt(m[3], 10);
2257
+ const col = m[5] !== undefined ? (addr?.col ?? 1) + parseInt(m[5], 10) : parseInt(m[6], 10);
2258
+ return { row, col };
2259
+ }
2260
+ // ============================================================================
2261
+ // Helpers
2262
+ // ============================================================================
2263
+ /**
2264
+ * Evaluate a BoundExpr and dereference any resulting ReferenceValue.
2265
+ * Use this whenever a concrete (non-reference) value is needed.
2266
+ */
2267
+ function evalDeref(expr, ctx, session) {
2268
+ return dereferenceValue(evaluate(expr, ctx, session), ctx, session);
2269
+ }
2270
+ function resolveLambdaName(name, args, ctx, session) {
2271
+ // Check local bindings
2272
+ if (ctx.localBindings?.has(name)) {
2273
+ const val = ctx.localBindings.get(name);
2274
+ if ((0, values_1.isLambda)(val)) {
2275
+ return invokeLambda(val, args, ctx, session);
2276
+ }
2277
+ }
2278
+ // Check defined names that resolve to lambdas (via snapshot, scope-aware)
2279
+ const dn = (0, workbook_snapshot_1.resolveDefinedName)(ctx.snapshot.definedNames, name, ctx.currentSheet);
2280
+ if (dn && dn.ranges.length === 1) {
2281
+ const rangeStr = dn.ranges[0];
2282
+ const parsed = (0, address_utils_1.parseDefinedNameRange)(rangeStr);
2283
+ if (parsed && parsed.startRow === parsed.endRow && parsed.startCol === parsed.endCol) {
2284
+ const cellVal = getCellValue(parsed.sheet, parsed.startRow, parsed.startCol, ctx, session);
2285
+ if ((0, values_1.isLambda)(cellVal)) {
2286
+ return invokeLambda(cellVal, args, ctx, session);
2287
+ }
2288
+ }
2289
+ // Formula-based name — evaluate via shared helper
2290
+ if (!parsed) {
2291
+ const nameVal = evaluateFormulaName(name.toUpperCase(), rangeStr, ctx, session);
2292
+ if (nameVal !== undefined && (0, values_1.isLambda)(nameVal)) {
2293
+ return invokeLambda(nameVal, args, ctx, session);
2294
+ }
2295
+ }
2296
+ }
2297
+ return undefined;
2298
+ }