@trebco/treb 36.1.4 → 37.0.1

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 (552) hide show
  1. package/api-config.json +1 -1
  2. package/build/package.json +119 -0
  3. package/build/treb-base-types/src/api_types.d.ts +11 -0
  4. package/build/treb-base-types/src/api_types.js +22 -0
  5. package/build/treb-base-types/src/api_types.js.map +1 -0
  6. package/build/treb-base-types/src/area-utils.d.ts +9 -0
  7. package/build/treb-base-types/src/area-utils.js +50 -0
  8. package/build/treb-base-types/src/area-utils.js.map +1 -0
  9. package/build/treb-base-types/src/area.d.ts +182 -0
  10. package/build/treb-base-types/src/area.js +715 -0
  11. package/build/treb-base-types/src/area.js.map +1 -0
  12. package/build/treb-base-types/src/basic_types.d.ts +20 -0
  13. package/build/treb-base-types/src/basic_types.js +22 -0
  14. package/build/treb-base-types/src/basic_types.js.map +1 -0
  15. package/build/treb-base-types/src/cell.d.ts +167 -0
  16. package/build/treb-base-types/src/cell.js +432 -0
  17. package/build/treb-base-types/src/cell.js.map +1 -0
  18. package/build/treb-base-types/src/cells.d.ts +251 -0
  19. package/build/treb-base-types/src/cells.js +1136 -0
  20. package/build/treb-base-types/src/cells.js.map +1 -0
  21. package/build/treb-base-types/src/color.d.ts +35 -0
  22. package/build/treb-base-types/src/color.js +162 -0
  23. package/build/treb-base-types/src/color.js.map +1 -0
  24. package/build/treb-base-types/src/dom-utilities.d.ts +70 -0
  25. package/build/treb-base-types/src/dom-utilities.js +144 -0
  26. package/build/treb-base-types/src/dom-utilities.js.map +1 -0
  27. package/build/treb-base-types/src/evaluate-options.d.ts +35 -0
  28. package/build/treb-base-types/src/evaluate-options.js +22 -0
  29. package/build/treb-base-types/src/evaluate-options.js.map +1 -0
  30. package/build/treb-base-types/src/font-stack.d.ts +37 -0
  31. package/build/treb-base-types/src/font-stack.js +93 -0
  32. package/build/treb-base-types/src/font-stack.js.map +1 -0
  33. package/build/treb-base-types/src/gradient.d.ts +18 -0
  34. package/build/treb-base-types/src/gradient.js +86 -0
  35. package/build/treb-base-types/src/gradient.js.map +1 -0
  36. package/build/treb-base-types/src/import.d.ts +48 -0
  37. package/build/treb-base-types/src/import.js +22 -0
  38. package/build/treb-base-types/src/import.js.map +1 -0
  39. package/build/treb-base-types/src/index-standalone.d.ts +6 -0
  40. package/build/treb-base-types/src/index-standalone.js +27 -0
  41. package/build/treb-base-types/src/index-standalone.js.map +1 -0
  42. package/build/treb-base-types/src/index.d.ts +22 -0
  43. package/build/treb-base-types/src/index.js +45 -0
  44. package/build/treb-base-types/src/index.js.map +1 -0
  45. package/build/treb-base-types/src/layout.d.ts +22 -0
  46. package/build/treb-base-types/src/layout.js +22 -0
  47. package/build/treb-base-types/src/layout.js.map +1 -0
  48. package/build/treb-base-types/src/localization.d.ts +37 -0
  49. package/build/treb-base-types/src/localization.js +157 -0
  50. package/build/treb-base-types/src/localization.js.map +1 -0
  51. package/build/treb-base-types/src/rectangle.d.ts +51 -0
  52. package/build/treb-base-types/src/rectangle.js +123 -0
  53. package/build/treb-base-types/src/rectangle.js.map +1 -0
  54. package/build/treb-base-types/src/render_text.d.ts +34 -0
  55. package/build/treb-base-types/src/render_text.js +22 -0
  56. package/build/treb-base-types/src/render_text.js.map +1 -0
  57. package/build/treb-base-types/src/style.d.ts +214 -0
  58. package/build/treb-base-types/src/style.js +373 -0
  59. package/build/treb-base-types/src/style.js.map +1 -0
  60. package/build/treb-base-types/src/table.d.ts +58 -0
  61. package/build/treb-base-types/src/table.js +27 -0
  62. package/build/treb-base-types/src/table.js.map +1 -0
  63. package/build/treb-base-types/src/text_part.d.ts +26 -0
  64. package/build/treb-base-types/src/text_part.js +47 -0
  65. package/build/treb-base-types/src/text_part.js.map +1 -0
  66. package/build/treb-base-types/src/theme.d.ts +120 -0
  67. package/build/treb-base-types/src/theme.js +460 -0
  68. package/build/treb-base-types/src/theme.js.map +1 -0
  69. package/build/treb-base-types/src/union.d.ts +73 -0
  70. package/build/treb-base-types/src/union.js +61 -0
  71. package/build/treb-base-types/src/union.js.map +1 -0
  72. package/build/treb-base-types/src/value-type.d.ts +86 -0
  73. package/build/treb-base-types/src/value-type.js +168 -0
  74. package/build/treb-base-types/src/value-type.js.map +1 -0
  75. package/build/treb-base-types/src/worker-proxy.d.ts +95 -0
  76. package/build/treb-base-types/src/worker-proxy.js +221 -0
  77. package/build/treb-base-types/src/worker-proxy.js.map +1 -0
  78. package/build/treb-calculator/src/calculator.d.ts +249 -0
  79. package/build/treb-calculator/src/calculator.js +2755 -0
  80. package/build/treb-calculator/src/calculator.js.map +1 -0
  81. package/build/treb-calculator/src/complex-math.d.ts +75 -0
  82. package/build/treb-calculator/src/complex-math.js +559 -0
  83. package/build/treb-calculator/src/complex-math.js.map +1 -0
  84. package/build/treb-calculator/src/dag/array-vertex.d.ts +71 -0
  85. package/build/treb-calculator/src/dag/array-vertex.js +156 -0
  86. package/build/treb-calculator/src/dag/array-vertex.js.map +1 -0
  87. package/build/treb-calculator/src/dag/calculation_leaf_vertex.d.ts +48 -0
  88. package/build/treb-calculator/src/dag/calculation_leaf_vertex.js +84 -0
  89. package/build/treb-calculator/src/dag/calculation_leaf_vertex.js.map +1 -0
  90. package/build/treb-calculator/src/dag/graph.d.ts +134 -0
  91. package/build/treb-calculator/src/dag/graph.js +842 -0
  92. package/build/treb-calculator/src/dag/graph.js.map +1 -0
  93. package/build/treb-calculator/src/dag/spreadsheet_vertex.d.ts +58 -0
  94. package/build/treb-calculator/src/dag/spreadsheet_vertex.js +232 -0
  95. package/build/treb-calculator/src/dag/spreadsheet_vertex.js.map +1 -0
  96. package/build/treb-calculator/src/dag/spreadsheet_vertex_base.d.ts +20 -0
  97. package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js +25 -0
  98. package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js.map +1 -0
  99. package/build/treb-calculator/src/dag/state_leaf_vertex.d.ts +43 -0
  100. package/build/treb-calculator/src/dag/state_leaf_vertex.js +81 -0
  101. package/build/treb-calculator/src/dag/state_leaf_vertex.js.map +1 -0
  102. package/build/treb-calculator/src/dag/vertex.d.ts +71 -0
  103. package/build/treb-calculator/src/dag/vertex.js +274 -0
  104. package/build/treb-calculator/src/dag/vertex.js.map +1 -0
  105. package/build/treb-calculator/src/descriptors.d.ts +189 -0
  106. package/build/treb-calculator/src/descriptors.js +22 -0
  107. package/build/treb-calculator/src/descriptors.js.map +1 -0
  108. package/build/treb-calculator/src/expression-calculator.d.ts +127 -0
  109. package/build/treb-calculator/src/expression-calculator.js +1033 -0
  110. package/build/treb-calculator/src/expression-calculator.js.map +1 -0
  111. package/build/treb-calculator/src/function-error.d.ts +35 -0
  112. package/build/treb-calculator/src/function-error.js +85 -0
  113. package/build/treb-calculator/src/function-error.js.map +1 -0
  114. package/build/treb-calculator/src/function-library.d.ts +22 -0
  115. package/build/treb-calculator/src/function-library.js +96 -0
  116. package/build/treb-calculator/src/function-library.js.map +1 -0
  117. package/build/treb-calculator/src/functions/base-functions.d.ts +7 -0
  118. package/build/treb-calculator/src/functions/base-functions.js +2611 -0
  119. package/build/treb-calculator/src/functions/base-functions.js.map +1 -0
  120. package/build/treb-calculator/src/functions/beta.d.ts +17 -0
  121. package/build/treb-calculator/src/functions/beta.js +201 -0
  122. package/build/treb-calculator/src/functions/beta.js.map +1 -0
  123. package/build/treb-calculator/src/functions/checkbox.d.ts +3 -0
  124. package/build/treb-calculator/src/functions/checkbox.js +128 -0
  125. package/build/treb-calculator/src/functions/checkbox.js.map +1 -0
  126. package/build/treb-calculator/src/functions/complex-functions.d.ts +2 -0
  127. package/build/treb-calculator/src/functions/complex-functions.js +217 -0
  128. package/build/treb-calculator/src/functions/complex-functions.js.map +1 -0
  129. package/build/treb-calculator/src/functions/date-utils.d.ts +3 -0
  130. package/build/treb-calculator/src/functions/date-utils.js +59 -0
  131. package/build/treb-calculator/src/functions/date-utils.js.map +1 -0
  132. package/build/treb-calculator/src/functions/finance-functions.d.ts +2 -0
  133. package/build/treb-calculator/src/functions/finance-functions.js +547 -0
  134. package/build/treb-calculator/src/functions/finance-functions.js.map +1 -0
  135. package/build/treb-calculator/src/functions/fp.d.ts +2 -0
  136. package/build/treb-calculator/src/functions/fp.js +463 -0
  137. package/build/treb-calculator/src/functions/fp.js.map +1 -0
  138. package/build/treb-calculator/src/functions/function-utilities.d.ts +2 -0
  139. package/build/treb-calculator/src/functions/function-utilities.js +36 -0
  140. package/build/treb-calculator/src/functions/function-utilities.js.map +1 -0
  141. package/build/treb-calculator/src/functions/gamma.d.ts +20 -0
  142. package/build/treb-calculator/src/functions/gamma.js +142 -0
  143. package/build/treb-calculator/src/functions/gamma.js.map +1 -0
  144. package/build/treb-calculator/src/functions/information-functions.d.ts +2 -0
  145. package/build/treb-calculator/src/functions/information-functions.js +71 -0
  146. package/build/treb-calculator/src/functions/information-functions.js.map +1 -0
  147. package/build/treb-calculator/src/functions/lambda-functions.d.ts +2 -0
  148. package/build/treb-calculator/src/functions/lambda-functions.js +85 -0
  149. package/build/treb-calculator/src/functions/lambda-functions.js.map +1 -0
  150. package/build/treb-calculator/src/functions/matrix-functions.d.ts +2 -0
  151. package/build/treb-calculator/src/functions/matrix-functions.js +144 -0
  152. package/build/treb-calculator/src/functions/matrix-functions.js.map +1 -0
  153. package/build/treb-calculator/src/functions/normal.d.ts +2 -0
  154. package/build/treb-calculator/src/functions/normal.js +32 -0
  155. package/build/treb-calculator/src/functions/normal.js.map +1 -0
  156. package/build/treb-calculator/src/functions/regex-functions.d.ts +2 -0
  157. package/build/treb-calculator/src/functions/regex-functions.js +188 -0
  158. package/build/treb-calculator/src/functions/regex-functions.js.map +1 -0
  159. package/build/treb-calculator/src/functions/sparkline.d.ts +37 -0
  160. package/build/treb-calculator/src/functions/sparkline.js +264 -0
  161. package/build/treb-calculator/src/functions/sparkline.js.map +1 -0
  162. package/build/treb-calculator/src/functions/statistics-functions.d.ts +6 -0
  163. package/build/treb-calculator/src/functions/statistics-functions.js +989 -0
  164. package/build/treb-calculator/src/functions/statistics-functions.js.map +1 -0
  165. package/build/treb-calculator/src/functions/students-t.d.ts +3 -0
  166. package/build/treb-calculator/src/functions/students-t.js +64 -0
  167. package/build/treb-calculator/src/functions/students-t.js.map +1 -0
  168. package/build/treb-calculator/src/functions/text-functions.d.ts +3 -0
  169. package/build/treb-calculator/src/functions/text-functions.js +320 -0
  170. package/build/treb-calculator/src/functions/text-functions.js.map +1 -0
  171. package/build/treb-calculator/src/index.d.ts +2 -0
  172. package/build/treb-calculator/src/index.js +22 -0
  173. package/build/treb-calculator/src/index.js.map +1 -0
  174. package/build/treb-calculator/src/notifier-types.d.ts +26 -0
  175. package/build/treb-calculator/src/notifier-types.js +22 -0
  176. package/build/treb-calculator/src/notifier-types.js.map +1 -0
  177. package/build/treb-calculator/src/primitives.d.ts +15 -0
  178. package/build/treb-calculator/src/primitives.js +398 -0
  179. package/build/treb-calculator/src/primitives.js.map +1 -0
  180. package/build/treb-calculator/src/utilities.d.ts +68 -0
  181. package/build/treb-calculator/src/utilities.js +324 -0
  182. package/build/treb-calculator/src/utilities.js.map +1 -0
  183. package/build/treb-charts/src/chart-functions.d.ts +8 -0
  184. package/build/treb-charts/src/chart-functions.js +209 -0
  185. package/build/treb-charts/src/chart-functions.js.map +1 -0
  186. package/build/treb-charts/src/chart-types.d.ts +233 -0
  187. package/build/treb-charts/src/chart-types.js +57 -0
  188. package/build/treb-charts/src/chart-types.js.map +1 -0
  189. package/build/treb-charts/src/chart-utils.d.ts +106 -0
  190. package/build/treb-charts/src/chart-utils.js +1060 -0
  191. package/build/treb-charts/src/chart-utils.js.map +1 -0
  192. package/build/treb-charts/src/chart.d.ts +23 -0
  193. package/build/treb-charts/src/chart.js +94 -0
  194. package/build/treb-charts/src/chart.js.map +1 -0
  195. package/build/treb-charts/src/default-chart-renderer.d.ts +16 -0
  196. package/build/treb-charts/src/default-chart-renderer.js +533 -0
  197. package/build/treb-charts/src/default-chart-renderer.js.map +1 -0
  198. package/build/treb-charts/src/index.d.ts +5 -0
  199. package/build/treb-charts/src/index.js +24 -0
  200. package/build/treb-charts/src/index.js.map +1 -0
  201. package/build/treb-charts/src/main.d.ts +1 -0
  202. package/build/treb-charts/src/main.js +34 -0
  203. package/build/treb-charts/src/main.js.map +1 -0
  204. package/build/treb-charts/src/quicksort.d.ts +1 -0
  205. package/build/treb-charts/src/quicksort.js +49 -0
  206. package/build/treb-charts/src/quicksort.js.map +1 -0
  207. package/build/treb-charts/src/rectangle.d.ts +18 -0
  208. package/build/treb-charts/src/rectangle.js +41 -0
  209. package/build/treb-charts/src/rectangle.js.map +1 -0
  210. package/build/treb-charts/src/renderer-type.d.ts +24 -0
  211. package/build/treb-charts/src/renderer-type.js +22 -0
  212. package/build/treb-charts/src/renderer-type.js.map +1 -0
  213. package/build/treb-charts/src/renderer.d.ts +127 -0
  214. package/build/treb-charts/src/renderer.js +1518 -0
  215. package/build/treb-charts/src/renderer.js.map +1 -0
  216. package/build/treb-charts/src/util.d.ts +18 -0
  217. package/build/treb-charts/src/util.js +71 -0
  218. package/build/treb-charts/src/util.js.map +1 -0
  219. package/build/treb-data-model/src/annotation.d.ts +167 -0
  220. package/build/treb-data-model/src/annotation.js +120 -0
  221. package/build/treb-data-model/src/annotation.js.map +1 -0
  222. package/build/treb-data-model/src/conditional_format.d.ts +155 -0
  223. package/build/treb-data-model/src/conditional_format.js +62 -0
  224. package/build/treb-data-model/src/conditional_format.js.map +1 -0
  225. package/build/treb-data-model/src/data-validation.d.ts +28 -0
  226. package/build/treb-data-model/src/data-validation.js +22 -0
  227. package/build/treb-data-model/src/data-validation.js.map +1 -0
  228. package/build/treb-data-model/src/data_model.d.ts +173 -0
  229. package/build/treb-data-model/src/data_model.js +637 -0
  230. package/build/treb-data-model/src/data_model.js.map +1 -0
  231. package/build/treb-data-model/src/index.d.ts +13 -0
  232. package/build/treb-data-model/src/index.js +28 -0
  233. package/build/treb-data-model/src/index.js.map +1 -0
  234. package/build/treb-data-model/src/language-model.d.ts +22 -0
  235. package/build/treb-data-model/src/language-model.js +22 -0
  236. package/build/treb-data-model/src/language-model.js.map +1 -0
  237. package/build/treb-data-model/src/named.d.ts +124 -0
  238. package/build/treb-data-model/src/named.js +372 -0
  239. package/build/treb-data-model/src/named.js.map +1 -0
  240. package/build/treb-data-model/src/serialize_options.d.ts +49 -0
  241. package/build/treb-data-model/src/serialize_options.js +22 -0
  242. package/build/treb-data-model/src/serialize_options.js.map +1 -0
  243. package/build/treb-data-model/src/sheet.d.ts +499 -0
  244. package/build/treb-data-model/src/sheet.js +2904 -0
  245. package/build/treb-data-model/src/sheet.js.map +1 -0
  246. package/build/treb-data-model/src/sheet_collection.d.ts +58 -0
  247. package/build/treb-data-model/src/sheet_collection.js +112 -0
  248. package/build/treb-data-model/src/sheet_collection.js.map +1 -0
  249. package/build/treb-data-model/src/sheet_selection.d.ts +42 -0
  250. package/build/treb-data-model/src/sheet_selection.js +39 -0
  251. package/build/treb-data-model/src/sheet_selection.js.map +1 -0
  252. package/build/treb-data-model/src/sheet_types.d.ts +104 -0
  253. package/build/treb-data-model/src/sheet_types.js +22 -0
  254. package/build/treb-data-model/src/sheet_types.js.map +1 -0
  255. package/build/treb-data-model/src/types.d.ts +59 -0
  256. package/build/treb-data-model/src/types.js +22 -0
  257. package/build/treb-data-model/src/types.js.map +1 -0
  258. package/build/treb-embed/src/custom-element/spreadsheet-constructor.d.ts +75 -0
  259. package/build/treb-embed/src/custom-element/spreadsheet-constructor.js +1144 -0
  260. package/build/treb-embed/src/custom-element/spreadsheet-constructor.js.map +1 -0
  261. package/build/treb-embed/src/custom-element/treb-global.d.ts +36 -0
  262. package/build/treb-embed/src/custom-element/treb-global.js +64 -0
  263. package/build/treb-embed/src/custom-element/treb-global.js.map +1 -0
  264. package/build/treb-embed/src/custom-element/treb-spreadsheet-element.d.ts +1 -0
  265. package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js +61 -0
  266. package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js.map +1 -0
  267. package/build/treb-embed/src/embedded-spreadsheet.d.ts +1358 -0
  268. package/build/treb-embed/src/embedded-spreadsheet.js +5205 -0
  269. package/build/treb-embed/src/embedded-spreadsheet.js.map +1 -0
  270. package/build/treb-embed/src/index.d.ts +12 -0
  271. package/build/treb-embed/src/index.js +34 -0
  272. package/build/treb-embed/src/index.js.map +1 -0
  273. package/build/treb-embed/src/options.d.ts +266 -0
  274. package/build/treb-embed/src/options.js +56 -0
  275. package/build/treb-embed/src/options.js.map +1 -0
  276. package/build/treb-embed/src/plugin.d.ts +9 -0
  277. package/build/treb-embed/src/plugin.js +22 -0
  278. package/build/treb-embed/src/plugin.js.map +1 -0
  279. package/build/treb-embed/src/progress-dialog.d.ts +49 -0
  280. package/build/treb-embed/src/progress-dialog.js +178 -0
  281. package/build/treb-embed/src/progress-dialog.js.map +1 -0
  282. package/build/treb-embed/src/selection-state.d.ts +15 -0
  283. package/build/treb-embed/src/selection-state.js +22 -0
  284. package/build/treb-embed/src/selection-state.js.map +1 -0
  285. package/build/treb-embed/src/spinner.d.ts +8 -0
  286. package/build/treb-embed/src/spinner.js +40 -0
  287. package/build/treb-embed/src/spinner.js.map +1 -0
  288. package/build/treb-embed/src/toolbar-message.d.ts +72 -0
  289. package/build/treb-embed/src/toolbar-message.js +22 -0
  290. package/build/treb-embed/src/toolbar-message.js.map +1 -0
  291. package/build/treb-embed/src/types.d.ts +185 -0
  292. package/build/treb-embed/src/types.js +45 -0
  293. package/build/treb-embed/src/types.js.map +1 -0
  294. package/build/treb-embed/tsconfig.tsbuildinfo +1 -0
  295. package/build/treb-export/src/address-type.d.ts +34 -0
  296. package/build/treb-export/src/address-type.js +53 -0
  297. package/build/treb-export/src/address-type.js.map +1 -0
  298. package/build/treb-export/src/base-template.d.ts +1 -0
  299. package/build/treb-export/src/base-template.js +22 -0
  300. package/build/treb-export/src/base-template.js.map +1 -0
  301. package/build/treb-export/src/column-width.d.ts +2 -0
  302. package/build/treb-export/src/column-width.js +80 -0
  303. package/build/treb-export/src/column-width.js.map +1 -0
  304. package/build/treb-export/src/drawing/bubble-chart-template.d.ts +514 -0
  305. package/build/treb-export/src/drawing/bubble-chart-template.js +544 -0
  306. package/build/treb-export/src/drawing/bubble-chart-template.js.map +1 -0
  307. package/build/treb-export/src/drawing/chart-template-components2.d.ts +365 -0
  308. package/build/treb-export/src/drawing/chart-template-components2.js +386 -0
  309. package/build/treb-export/src/drawing/chart-template-components2.js.map +1 -0
  310. package/build/treb-export/src/drawing/chart.d.ts +26 -0
  311. package/build/treb-export/src/drawing/chart.js +247 -0
  312. package/build/treb-export/src/drawing/chart.js.map +1 -0
  313. package/build/treb-export/src/drawing/column-chart-template2.d.ts +490 -0
  314. package/build/treb-export/src/drawing/column-chart-template2.js +518 -0
  315. package/build/treb-export/src/drawing/column-chart-template2.js.map +1 -0
  316. package/build/treb-export/src/drawing/donut-chart-template2.d.ts +272 -0
  317. package/build/treb-export/src/drawing/donut-chart-template2.js +293 -0
  318. package/build/treb-export/src/drawing/donut-chart-template2.js.map +1 -0
  319. package/build/treb-export/src/drawing/drawing.d.ts +49 -0
  320. package/build/treb-export/src/drawing/drawing.js +193 -0
  321. package/build/treb-export/src/drawing/drawing.js.map +1 -0
  322. package/build/treb-export/src/drawing/embedded-image.d.ts +12 -0
  323. package/build/treb-export/src/drawing/embedded-image.js +54 -0
  324. package/build/treb-export/src/drawing/embedded-image.js.map +1 -0
  325. package/build/treb-export/src/drawing/scatter-chart-template2.d.ts +520 -0
  326. package/build/treb-export/src/drawing/scatter-chart-template2.js +551 -0
  327. package/build/treb-export/src/drawing/scatter-chart-template2.js.map +1 -0
  328. package/build/treb-export/src/export.d.ts +72 -0
  329. package/build/treb-export/src/export.js +2039 -0
  330. package/build/treb-export/src/export.js.map +1 -0
  331. package/build/treb-export/src/import-export-messages.d.ts +31 -0
  332. package/build/treb-export/src/import-export-messages.js +22 -0
  333. package/build/treb-export/src/import-export-messages.js.map +1 -0
  334. package/build/treb-export/src/import.d.ts +33 -0
  335. package/build/treb-export/src/import.js +1258 -0
  336. package/build/treb-export/src/import.js.map +1 -0
  337. package/build/treb-export/src/index.worker.d.ts +1 -0
  338. package/build/treb-export/src/index.worker.js +93 -0
  339. package/build/treb-export/src/index.worker.js.map +1 -0
  340. package/build/treb-export/src/metadata.d.ts +51 -0
  341. package/build/treb-export/src/metadata.js +153 -0
  342. package/build/treb-export/src/metadata.js.map +1 -0
  343. package/build/treb-export/src/ooxml.d.ts +7 -0
  344. package/build/treb-export/src/ooxml.js +41 -0
  345. package/build/treb-export/src/ooxml.js.map +1 -0
  346. package/build/treb-export/src/relationship.d.ts +8 -0
  347. package/build/treb-export/src/relationship.js +27 -0
  348. package/build/treb-export/src/relationship.js.map +1 -0
  349. package/build/treb-export/src/shared-strings.d.ts +11 -0
  350. package/build/treb-export/src/shared-strings.js +105 -0
  351. package/build/treb-export/src/shared-strings.js.map +1 -0
  352. package/build/treb-export/src/template-2.d.ts +1 -0
  353. package/build/treb-export/src/template-2.js +22 -0
  354. package/build/treb-export/src/template-2.js.map +1 -0
  355. package/build/treb-export/src/unescape_xml.d.ts +1 -0
  356. package/build/treb-export/src/unescape_xml.js +61 -0
  357. package/build/treb-export/src/unescape_xml.js.map +1 -0
  358. package/build/treb-export/src/workbook-sheet.d.ts +75 -0
  359. package/build/treb-export/src/workbook-sheet.js +128 -0
  360. package/build/treb-export/src/workbook-sheet.js.map +1 -0
  361. package/build/treb-export/src/workbook-style.d.ts +110 -0
  362. package/build/treb-export/src/workbook-style.js +1134 -0
  363. package/build/treb-export/src/workbook-style.js.map +1 -0
  364. package/build/treb-export/src/workbook-theme.d.ts +13 -0
  365. package/build/treb-export/src/workbook-theme.js +85 -0
  366. package/build/treb-export/src/workbook-theme.js.map +1 -0
  367. package/build/treb-export/src/workbook.d.ts +123 -0
  368. package/build/treb-export/src/workbook.js +644 -0
  369. package/build/treb-export/src/workbook.js.map +1 -0
  370. package/build/treb-export/src/xml-test.d.ts +9 -0
  371. package/build/treb-export/src/xml-test.js +52 -0
  372. package/build/treb-export/src/xml-test.js.map +1 -0
  373. package/build/treb-export/src/xml-utils.d.ts +76 -0
  374. package/build/treb-export/src/xml-utils.js +223 -0
  375. package/build/treb-export/src/xml-utils.js.map +1 -0
  376. package/build/treb-export/src/zip-wrapper.d.ts +22 -0
  377. package/build/treb-export/src/zip-wrapper.js +93 -0
  378. package/build/treb-export/src/zip-wrapper.js.map +1 -0
  379. package/build/treb-format/src/format.d.ts +130 -0
  380. package/build/treb-format/src/format.js +805 -0
  381. package/build/treb-format/src/format.js.map +1 -0
  382. package/build/treb-format/src/format_cache.d.ts +55 -0
  383. package/build/treb-format/src/format_cache.js +166 -0
  384. package/build/treb-format/src/format_cache.js.map +1 -0
  385. package/build/treb-format/src/format_parser.d.ts +70 -0
  386. package/build/treb-format/src/format_parser.js +618 -0
  387. package/build/treb-format/src/format_parser.js.map +1 -0
  388. package/build/treb-format/src/index.d.ts +4 -0
  389. package/build/treb-format/src/index.js +25 -0
  390. package/build/treb-format/src/index.js.map +1 -0
  391. package/build/treb-format/src/number_format_section.d.ts +58 -0
  392. package/build/treb-format/src/number_format_section.js +78 -0
  393. package/build/treb-format/src/number_format_section.js.map +1 -0
  394. package/build/treb-format/src/value_parser.d.ts +48 -0
  395. package/build/treb-format/src/value_parser.js +244 -0
  396. package/build/treb-format/src/value_parser.js.map +1 -0
  397. package/build/treb-grid/src/editors/autocomplete.d.ts +39 -0
  398. package/build/treb-grid/src/editors/autocomplete.js +316 -0
  399. package/build/treb-grid/src/editors/autocomplete.js.map +1 -0
  400. package/build/treb-grid/src/editors/autocomplete_matcher.d.ts +74 -0
  401. package/build/treb-grid/src/editors/autocomplete_matcher.js +212 -0
  402. package/build/treb-grid/src/editors/autocomplete_matcher.js.map +1 -0
  403. package/build/treb-grid/src/editors/editor.d.ts +214 -0
  404. package/build/treb-grid/src/editors/editor.js +879 -0
  405. package/build/treb-grid/src/editors/editor.js.map +1 -0
  406. package/build/treb-grid/src/editors/external_editor.d.ts +11 -0
  407. package/build/treb-grid/src/editors/external_editor.js +118 -0
  408. package/build/treb-grid/src/editors/external_editor.js.map +1 -0
  409. package/build/treb-grid/src/editors/formula_bar.d.ts +85 -0
  410. package/build/treb-grid/src/editors/formula_bar.js +444 -0
  411. package/build/treb-grid/src/editors/formula_bar.js.map +1 -0
  412. package/build/treb-grid/src/editors/overlay_editor.d.ts +85 -0
  413. package/build/treb-grid/src/editors/overlay_editor.js +353 -0
  414. package/build/treb-grid/src/editors/overlay_editor.js.map +1 -0
  415. package/build/treb-grid/src/index.d.ts +12 -0
  416. package/build/treb-grid/src/index.js +28 -0
  417. package/build/treb-grid/src/index.js.map +1 -0
  418. package/build/treb-grid/src/layout/base_layout.d.ts +346 -0
  419. package/build/treb-grid/src/layout/base_layout.js +2050 -0
  420. package/build/treb-grid/src/layout/base_layout.js.map +1 -0
  421. package/build/treb-grid/src/layout/grid_layout.d.ts +19 -0
  422. package/build/treb-grid/src/layout/grid_layout.js +235 -0
  423. package/build/treb-grid/src/layout/grid_layout.js.map +1 -0
  424. package/build/treb-grid/src/layout/mock-layout.d.ts +10 -0
  425. package/build/treb-grid/src/layout/mock-layout.js +37 -0
  426. package/build/treb-grid/src/layout/mock-layout.js.map +1 -0
  427. package/build/treb-grid/src/render/selection-renderer.d.ts +97 -0
  428. package/build/treb-grid/src/render/selection-renderer.js +315 -0
  429. package/build/treb-grid/src/render/selection-renderer.js.map +1 -0
  430. package/build/treb-grid/src/render/svg_header_overlay.d.ts +20 -0
  431. package/build/treb-grid/src/render/svg_header_overlay.js +76 -0
  432. package/build/treb-grid/src/render/svg_header_overlay.js.map +1 -0
  433. package/build/treb-grid/src/render/svg_selection_block.d.ts +27 -0
  434. package/build/treb-grid/src/render/svg_selection_block.js +106 -0
  435. package/build/treb-grid/src/render/svg_selection_block.js.map +1 -0
  436. package/build/treb-grid/src/render/tile_renderer.d.ts +121 -0
  437. package/build/treb-grid/src/render/tile_renderer.js +1609 -0
  438. package/build/treb-grid/src/render/tile_renderer.js.map +1 -0
  439. package/build/treb-grid/src/types/border_constants.d.ts +9 -0
  440. package/build/treb-grid/src/types/border_constants.js +34 -0
  441. package/build/treb-grid/src/types/border_constants.js.map +1 -0
  442. package/build/treb-grid/src/types/clipboard_data.d.ts +11 -0
  443. package/build/treb-grid/src/types/clipboard_data.js +22 -0
  444. package/build/treb-grid/src/types/clipboard_data.js.map +1 -0
  445. package/build/treb-grid/src/types/clipboard_data2.d.ts +46 -0
  446. package/build/treb-grid/src/types/clipboard_data2.js +22 -0
  447. package/build/treb-grid/src/types/clipboard_data2.js.map +1 -0
  448. package/build/treb-grid/src/types/drag_mask.d.ts +10 -0
  449. package/build/treb-grid/src/types/drag_mask.js +78 -0
  450. package/build/treb-grid/src/types/drag_mask.js.map +1 -0
  451. package/build/treb-grid/src/types/external_editor_config.d.ts +33 -0
  452. package/build/treb-grid/src/types/external_editor_config.js +22 -0
  453. package/build/treb-grid/src/types/external_editor_config.js.map +1 -0
  454. package/build/treb-grid/src/types/grid.d.ts +806 -0
  455. package/build/treb-grid/src/types/grid.js +6410 -0
  456. package/build/treb-grid/src/types/grid.js.map +1 -0
  457. package/build/treb-grid/src/types/grid_base.d.ts +442 -0
  458. package/build/treb-grid/src/types/grid_base.js +3523 -0
  459. package/build/treb-grid/src/types/grid_base.js.map +1 -0
  460. package/build/treb-grid/src/types/grid_command.d.ts +408 -0
  461. package/build/treb-grid/src/types/grid_command.js +75 -0
  462. package/build/treb-grid/src/types/grid_command.js.map +1 -0
  463. package/build/treb-grid/src/types/grid_events.d.ts +93 -0
  464. package/build/treb-grid/src/types/grid_events.js +36 -0
  465. package/build/treb-grid/src/types/grid_events.js.map +1 -0
  466. package/build/treb-grid/src/types/grid_options.d.ts +50 -0
  467. package/build/treb-grid/src/types/grid_options.js +34 -0
  468. package/build/treb-grid/src/types/grid_options.js.map +1 -0
  469. package/build/treb-grid/src/types/scale-control.d.ts +21 -0
  470. package/build/treb-grid/src/types/scale-control.js +148 -0
  471. package/build/treb-grid/src/types/scale-control.js.map +1 -0
  472. package/build/treb-grid/src/types/set_range_options.d.ts +24 -0
  473. package/build/treb-grid/src/types/set_range_options.js +22 -0
  474. package/build/treb-grid/src/types/set_range_options.js.map +1 -0
  475. package/build/treb-grid/src/types/tab_bar.d.ts +84 -0
  476. package/build/treb-grid/src/types/tab_bar.js +426 -0
  477. package/build/treb-grid/src/types/tab_bar.js.map +1 -0
  478. package/build/treb-grid/src/types/tile.d.ts +29 -0
  479. package/build/treb-grid/src/types/tile.js +22 -0
  480. package/build/treb-grid/src/types/tile.js.map +1 -0
  481. package/build/treb-grid/src/types/update_flags.d.ts +48 -0
  482. package/build/treb-grid/src/types/update_flags.js +22 -0
  483. package/build/treb-grid/src/types/update_flags.js.map +1 -0
  484. package/build/treb-grid/src/util/fontmetrics.d.ts +21 -0
  485. package/build/treb-grid/src/util/fontmetrics.js +82 -0
  486. package/build/treb-grid/src/util/fontmetrics.js.map +1 -0
  487. package/build/treb-grid/src/util/ua.d.ts +33 -0
  488. package/build/treb-grid/src/util/ua.js +86 -0
  489. package/build/treb-grid/src/util/ua.js.map +1 -0
  490. package/build/treb-parser/src/csv-parser.d.ts +13 -0
  491. package/build/treb-parser/src/csv-parser.js +107 -0
  492. package/build/treb-parser/src/csv-parser.js.map +1 -0
  493. package/build/treb-parser/src/index.d.ts +4 -0
  494. package/build/treb-parser/src/index.js +25 -0
  495. package/build/treb-parser/src/index.js.map +1 -0
  496. package/build/treb-parser/src/md-parser.d.ts +97 -0
  497. package/build/treb-parser/src/md-parser.js +403 -0
  498. package/build/treb-parser/src/md-parser.js.map +1 -0
  499. package/build/treb-parser/src/parser-types.d.ts +345 -0
  500. package/build/treb-parser/src/parser-types.js +53 -0
  501. package/build/treb-parser/src/parser-types.js.map +1 -0
  502. package/build/treb-parser/src/parser.d.ts +422 -0
  503. package/build/treb-parser/src/parser.js +2418 -0
  504. package/build/treb-parser/src/parser.js.map +1 -0
  505. package/build/treb-utils/src/event_source.d.ts +34 -0
  506. package/build/treb-utils/src/event_source.js +110 -0
  507. package/build/treb-utils/src/event_source.js.map +1 -0
  508. package/build/treb-utils/src/ievent_source.d.ts +9 -0
  509. package/build/treb-utils/src/ievent_source.js +22 -0
  510. package/build/treb-utils/src/ievent_source.js.map +1 -0
  511. package/build/treb-utils/src/index.d.ts +6 -0
  512. package/build/treb-utils/src/index.js +30 -0
  513. package/build/treb-utils/src/index.js.map +1 -0
  514. package/build/treb-utils/src/measurement.d.ts +42 -0
  515. package/build/treb-utils/src/measurement.js +145 -0
  516. package/build/treb-utils/src/measurement.js.map +1 -0
  517. package/build/treb-utils/src/scale.d.ts +16 -0
  518. package/build/treb-utils/src/scale.js +106 -0
  519. package/build/treb-utils/src/scale.js.map +1 -0
  520. package/build/treb-utils/src/serialize_html.d.ts +5 -0
  521. package/build/treb-utils/src/serialize_html.js +128 -0
  522. package/build/treb-utils/src/serialize_html.js.map +1 -0
  523. package/build/treb-utils/src/validate_uri.d.ts +20 -0
  524. package/build/treb-utils/src/validate_uri.js +55 -0
  525. package/build/treb-utils/src/validate_uri.js.map +1 -0
  526. package/dist/{chunk-Z4XFMZ2X.mjs → chunk-E35ONJUS.mjs} +1 -1
  527. package/dist/treb-export-worker.mjs +2 -2
  528. package/dist/treb-spreadsheet.mjs +4 -4
  529. package/dist/treb.d.ts +1 -1
  530. package/esbuild-composite.mjs +5 -1
  531. package/package.json +67 -3
  532. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +7 -3
  533. package/treb-embed/src/embedded-spreadsheet.ts +1 -1
  534. package/treb-grid/src/types/grid_options.ts +1 -1
  535. package/tsproject.json +1 -2
  536. package/dist/chunk-43DLP2OX.mjs +0 -11
  537. package/dist/chunk-4CKS56PE.mjs +0 -11
  538. package/dist/chunk-75PARUQE.mjs +0 -11
  539. package/dist/chunk-7QD63AZS.mjs +0 -24601
  540. package/dist/chunk-A55ARVRD.mjs +0 -11
  541. package/dist/chunk-DESAKYW4.mjs +0 -11
  542. package/dist/chunk-EQ2R5W6P.mjs +0 -24565
  543. package/dist/chunk-IYJU2J6D.mjs +0 -24601
  544. package/dist/chunk-KSJFPGXT.mjs +0 -11
  545. package/dist/chunk-MQK4DNXI.mjs +0 -11
  546. package/dist/chunk-ORQFKLXM.mjs +0 -24601
  547. package/dist/chunk-SFDNNDHY.mjs +0 -11
  548. package/dist/chunk-T47DX5MI.mjs +0 -11
  549. package/dist/chunk-T6ILBVEX.mjs +0 -11
  550. package/dist/chunk-TPRCDYYG.mjs +0 -11
  551. package/dist/chunk-YAHNOOHO.mjs +0 -11
  552. package/dist/chunk-YLCFKX2G.mjs +0 -24601
@@ -0,0 +1,2755 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2026 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+ import { Localization, Area, ValueType, IsCellAddress } from 'treb-base-types';
22
+ import { Parser, DecimalMarkType, QuotedSheetNameRegex } from 'treb-parser';
23
+ import { Graph } from './dag/graph';
24
+ import { ExpressionCalculator, UnionIsMetadata } from './expression-calculator';
25
+ import * as Utilities from './utilities';
26
+ import { StringUnion } from './utilities';
27
+ import { FunctionLibrary } from './function-library';
28
+ // import * as Utils from './utilities';
29
+ import { AltFunctionLibrary, BaseFunctionLibrary } from './functions/base-functions';
30
+ import { FinanceFunctionLibrary } from './functions/finance-functions';
31
+ import { TextFunctionLibrary, TextFunctionAliases } from './functions/text-functions';
32
+ import { InformationFunctionLibrary } from './functions/information-functions';
33
+ import { StatisticsFunctionLibrary, StatisticsFunctionAliases } from './functions/statistics-functions';
34
+ import { ComplexFunctionLibrary } from './functions/complex-functions';
35
+ import { MatrixFunctionLibrary } from './functions/matrix-functions';
36
+ import { RegexFunctionLibrary } from './functions/regex-functions';
37
+ import { LambdaFunctionLibrary } from './functions/lambda-functions';
38
+ import { FPFunctionLibrary } from './functions/fp';
39
+ import { Variance } from './functions/statistics-functions';
40
+ import * as Primitives from './primitives';
41
+ import { ArgumentError, ReferenceError, UnknownError, ValueError, ExpressionError, NAError, DivideByZeroError, NotImplError } from './function-error';
42
+ import { StateLeafVertex } from './dag/state_leaf_vertex';
43
+ import { CalculationLeafVertex } from './dag/calculation_leaf_vertex';
44
+ import { Sheet } from 'treb-data-model';
45
+ import { ValueParser } from 'treb-format';
46
+ /**
47
+ * breaking this out so we can use it for export (TODO)
48
+ *
49
+ * @param type
50
+ * @returns
51
+ */
52
+ const TranslateSubtotalType = (type) => {
53
+ if (typeof type === 'string') {
54
+ type = type.toUpperCase();
55
+ switch (type) {
56
+ case 'AVERAGE':
57
+ case 'MEAN':
58
+ type = 101;
59
+ break;
60
+ case 'COUNT':
61
+ type = 102;
62
+ break;
63
+ case 'COUNTA':
64
+ type = 103;
65
+ break;
66
+ case 'MAX':
67
+ type = 104;
68
+ break;
69
+ case 'MIN':
70
+ type = 105;
71
+ break;
72
+ case 'PRODUCT':
73
+ type = 106;
74
+ break;
75
+ case 'STDEV':
76
+ type = 107;
77
+ break;
78
+ case 'STDEVP':
79
+ type = 108;
80
+ break;
81
+ case 'SUM':
82
+ type = 109;
83
+ break;
84
+ case 'VAR':
85
+ type = 110;
86
+ break;
87
+ case 'VARP':
88
+ type = 111;
89
+ break;
90
+ default:
91
+ type = 0;
92
+ break;
93
+ }
94
+ }
95
+ return type;
96
+ };
97
+ const default_calculator_options = {
98
+ complex_numbers: 'off',
99
+ spill: false,
100
+ };
101
+ /**
102
+ * Calculator now extends graph. there's a 1-1 relationship between the
103
+ * two, and we wind up passing a lot of operations from one to the other.
104
+ * this also simplifies the callback structure, as we can use local methods.
105
+ *
106
+ * NOTE: graph vertices hold references to cells. while that makes lookups
107
+ * more efficient, it causes problems if you mutate the sheet (adding or
108
+ * removing rows or columns).
109
+ *
110
+ * in that event, you need to flush the graph to force rebuilding references
111
+ * (TODO: just rebuild references). after mutating the sheet, call
112
+ * ```
113
+ * Calculator.Reset();
114
+ * ```
115
+ *
116
+ */
117
+ export class Calculator extends Graph {
118
+ model;
119
+ /**
120
+ * localized parser instance. we're sharing.
121
+ * FIXME: remove local references so we can remove this accessor
122
+ */
123
+ get parser() {
124
+ return this.model.parser;
125
+ }
126
+ library = new FunctionLibrary();
127
+ registered_libraries = {};
128
+ expression_calculator;
129
+ /** the next calculation must do a full rebuild -- set on reset */
130
+ full_rebuild_required = false;
131
+ options;
132
+ async_resource_init = false;
133
+ /**
134
+ * this is a flag we're using to communicate back to the embedded
135
+ * sheet, when the grid has expanded as a result of a calculation
136
+ * (because of a spill).
137
+ */
138
+ grid_expanded = false;
139
+ constructor(model, calculator_options = {}) {
140
+ super();
141
+ this.model = model;
142
+ this.expression_calculator = new ExpressionCalculator(this.model, this.library, this.parser);
143
+ // at the moment options are only used here; in the future
144
+ // we may need to extend handling.
145
+ this.options = {
146
+ ...default_calculator_options,
147
+ ...calculator_options,
148
+ };
149
+ if (this.options.complex_numbers === 'on') {
150
+ // complex number handling: we need to change SQRT, POWER and ^
151
+ for (const key of Object.keys(AltFunctionLibrary)) {
152
+ BaseFunctionLibrary[key] = AltFunctionLibrary[key];
153
+ }
154
+ Primitives.UseComplex();
155
+ }
156
+ // FIXME: why is this called here, if model now owns it?
157
+ // TODO: move to model
158
+ this.UpdateLocale(); // for parser
159
+ // base functions
160
+ this.library.Register(BaseFunctionLibrary, TextFunctionLibrary, // we split out text functions
161
+ StatisticsFunctionLibrary, // also stats (wip)
162
+ FinanceFunctionLibrary, // also this (wip)
163
+ InformationFunctionLibrary, // etc
164
+ ComplexFunctionLibrary, MatrixFunctionLibrary, RegexFunctionLibrary, LambdaFunctionLibrary, FPFunctionLibrary);
165
+ // aliases
166
+ for (const key of Object.keys(StatisticsFunctionAliases)) {
167
+ this.library.Alias(key, StatisticsFunctionAliases[key]);
168
+ }
169
+ for (const key of Object.keys(TextFunctionAliases)) {
170
+ this.library.Alias(key, TextFunctionAliases[key]);
171
+ }
172
+ // special functions... need reference to the graph (this)
173
+ // moving countif here so we can reference it in COUNTIFS...
174
+ /*
175
+ const FlattenBooleans = (value: ArrayUnion) => {
176
+ const result: boolean[] = [];
177
+ for (const col of value.value) {
178
+ for (const entry of col) {
179
+ result.push(entry.type === ValueType.boolean && entry.value);
180
+ }
181
+ }
182
+ return result;
183
+ };
184
+ */
185
+ /**
186
+ * this is a function that does sumif/averageif/countif.
187
+ * args is one or more sets of [criteria_range, criteria]
188
+ */
189
+ const XIf = (type, value_range, ...args) => {
190
+ // there's a bug here if the value range is a single value?
191
+ // that happens if countif passes in a one-cell range... we should
192
+ // handle this in the caller, or here?
193
+ // NOTE we also have to address this in the set of
194
+ // arguments, in which each pair could have a single
195
+ // value as the criterion
196
+ if (!Array.isArray(value_range)) {
197
+ value_range = [[value_range]];
198
+ }
199
+ const filter = [];
200
+ for (let i = 0; i < args.length; i += 2) {
201
+ let criteria_range = args[i];
202
+ if (!Array.isArray(criteria_range)) {
203
+ criteria_range = [[criteria_range]];
204
+ }
205
+ { // if (Array.isArray(args[i])) {
206
+ const step = CountIfInternal(criteria_range, args[i + 1]);
207
+ if (step.type !== ValueType.array) {
208
+ return step;
209
+ }
210
+ for (const [r, cell] of step.value[0].entries()) {
211
+ filter[r] = (!!cell.value && (filter[r] !== false));
212
+ }
213
+ }
214
+ }
215
+ const values = Utilities.FlattenCellValues(value_range, true); // keep undefineds
216
+ let count = 0;
217
+ let sum = 0;
218
+ for (const [index, test] of filter.entries()) {
219
+ if (test) {
220
+ count++;
221
+ const value = values[index];
222
+ if (typeof value === 'number') {
223
+ sum += value;
224
+ }
225
+ }
226
+ }
227
+ switch (type) {
228
+ case 'count':
229
+ return { type: ValueType.number, value: count };
230
+ case 'sum':
231
+ return { type: ValueType.number, value: sum };
232
+ case 'average':
233
+ if (count === 0) {
234
+ return DivideByZeroError();
235
+ }
236
+ return { type: ValueType.number, value: sum / count };
237
+ }
238
+ };
239
+ const CountIfInternal = (range, criteria) => {
240
+ // do we really need parser/calculator for this? I think
241
+ // we've maybe gone overboard here, could we just use valueparser
242
+ // on the criteria and then calculate normally? I think we might...
243
+ // in any event there are no dynamic dependencies with this
244
+ // function.
245
+ const data = Utilities.FlattenCellValues(range, true); // keep undefineds, important for mapping
246
+ let parse_result;
247
+ let expression;
248
+ // we'll handle operator and operand separately
249
+ let operator = '=';
250
+ // handle wildcards first. if we have a wildcard we use a
251
+ // matching function so we can centralize.
252
+ if (typeof criteria === 'string') {
253
+ // normalize first, pull out operator
254
+ criteria = criteria.trim();
255
+ const match = criteria.match(/^([=<>]+)/);
256
+ if (match) {
257
+ operator = match[1];
258
+ criteria = criteria.substring(operator.length);
259
+ }
260
+ const value_parser_result = ValueParser.TryParse(criteria);
261
+ if (value_parser_result?.type === ValueType.string) {
262
+ criteria = `"${value_parser_result.value}"`;
263
+ }
264
+ else {
265
+ criteria = value_parser_result?.value?.toString() || '';
266
+ }
267
+ // console.info({operator, criteria});
268
+ // check for wildcards (this will false-positive on escaped
269
+ // wildcards, which will not break but will waste cycles. we
270
+ // could check. TOOD/FIXME)
271
+ if (/[?*]/.test(criteria)) {
272
+ // NOTE: we're not specifying an argument separator when writing
273
+ // functions, because that might break numbers passed as strings.
274
+ // so we write the function based on the current separator.
275
+ const separator = this.parser.argument_separator;
276
+ if (operator === '=' || operator === '<>') {
277
+ parse_result = this.parser.Parse(`=WildcardMatch({}${separator} ${criteria}${separator} ${operator === '<>'})`);
278
+ expression = parse_result.expression;
279
+ if (parse_result.error || !expression) {
280
+ return ExpressionError();
281
+ }
282
+ if (expression?.type === 'call' && expression.args[0]?.type === 'array') {
283
+ expression.args[0].values = [Utilities.FilterIntrinsics(data, true)];
284
+ }
285
+ }
286
+ }
287
+ }
288
+ else {
289
+ // if it's not a string, by definition it doesn't have an
290
+ // operator so use equality (default). it does not need
291
+ // escaping.
292
+ criteria = (criteria || 0).toString();
293
+ }
294
+ if (!parse_result) {
295
+ parse_result = this.parser.Parse('{}' + operator + criteria);
296
+ expression = parse_result.expression;
297
+ if (parse_result.error || !expression) {
298
+ return ExpressionError();
299
+ }
300
+ if (expression.type !== 'binary') {
301
+ console.warn('invalid expression [1]', expression);
302
+ return ExpressionError();
303
+ }
304
+ if (expression.left.type !== 'array') {
305
+ console.warn('invalid expression [1]', expression);
306
+ return ExpressionError();
307
+ }
308
+ // this is only going to work for binary left/right. it won't
309
+ // work if we change this to a function (wildcard match)
310
+ // this will not happen anymore, we can remove
311
+ if (expression.right.type === 'identifier') {
312
+ console.warn('will never happen');
313
+ expression.right = {
314
+ ...expression.right,
315
+ type: 'literal',
316
+ value: expression.right.name,
317
+ };
318
+ }
319
+ expression.left.values = [Utilities.FilterIntrinsics(data, true)];
320
+ }
321
+ if (!expression) {
322
+ return ValueError();
323
+ }
324
+ const result = this.CalculateExpression(expression);
325
+ return result;
326
+ };
327
+ this.library.Register({
328
+ /**
329
+ * this function is here because it checks whether rows are hidden or
330
+ * not. cell dependencies don't track that, so we need to do it here.
331
+ * and it needs to be volatile. this is an ugly, ugly function.
332
+ */
333
+ Subtotal: {
334
+ arguments: [
335
+ { name: 'type' },
336
+ { name: 'range', metadata: true, }
337
+ ],
338
+ fn: (type, ...args) => {
339
+ type = TranslateSubtotalType(type);
340
+ // validate, I guess
341
+ if (type > 100) {
342
+ type -= 100;
343
+ }
344
+ if (type < 1 || type > 11) {
345
+ return ArgumentError();
346
+ }
347
+ // any number of ranges are allowed, they will inherit
348
+ // the properties of the last argument so they will all
349
+ // return metadata
350
+ const flat = Utilities.FlattenBoxed(args);
351
+ // values is the set of values from the arguments that
352
+ // are numbers -- not strings, not errors -- and are not
353
+ // hidden. that last thing is the hard part.
354
+ // there's one other thing we care about which is non-empty,
355
+ // for COUNTA -- we can do that separately
356
+ const values = [];
357
+ let counta = 0;
358
+ let sum = 0;
359
+ let sheet;
360
+ for (const entry of flat) {
361
+ // where is the metadata type? sigh
362
+ const address = (entry.value?.address);
363
+ if (!address) {
364
+ return ReferenceError();
365
+ }
366
+ if (!sheet || sheet.id !== address.sheet_id) {
367
+ if (!address.sheet_id) {
368
+ console.warn('invalid reference in metadata');
369
+ return ReferenceError();
370
+ }
371
+ sheet = this.model.sheets.Find(address.sheet_id);
372
+ if (!sheet) {
373
+ console.warn('invalid sheet in metadata');
374
+ return ReferenceError();
375
+ }
376
+ }
377
+ const height = sheet.GetRowHeight(address.row);
378
+ if (!height) {
379
+ continue;
380
+ }
381
+ const entry_value = entry.value?.value;
382
+ // counta includes empty strings
383
+ if (typeof entry_value === 'undefined') {
384
+ continue;
385
+ }
386
+ counta++;
387
+ if (typeof entry_value === 'number') {
388
+ sum += entry_value;
389
+ values.push(entry_value);
390
+ }
391
+ }
392
+ let value = 0;
393
+ switch (type) {
394
+ case 1: // average
395
+ if (values.length === 0) {
396
+ return DivideByZeroError();
397
+ }
398
+ value = sum / values.length;
399
+ break;
400
+ case 2: // count
401
+ value = values.length;
402
+ break;
403
+ case 3: // counta
404
+ value = counta;
405
+ break;
406
+ case 4: // max
407
+ if (values.length === 0) {
408
+ return ValueError();
409
+ }
410
+ value = Math.max.apply(0, values);
411
+ break;
412
+ case 5: // min
413
+ if (values.length === 0) {
414
+ return ValueError();
415
+ }
416
+ value = Math.min.apply(0, values);
417
+ break;
418
+ case 6: // product
419
+ if (values.length === 0) {
420
+ return ValueError();
421
+ }
422
+ value = 1;
423
+ for (const entry of values) {
424
+ value *= entry;
425
+ }
426
+ break;
427
+ case 7: // stdev.s
428
+ if (values.length < 2) {
429
+ return DivideByZeroError();
430
+ }
431
+ value = Math.sqrt(Variance(values, true));
432
+ break;
433
+ case 8: // stdev.p
434
+ if (values.length === 0) {
435
+ return DivideByZeroError();
436
+ }
437
+ value = Math.sqrt(Variance(values, false));
438
+ break;
439
+ case 9: // sum
440
+ value = sum;
441
+ break;
442
+ case 10: // var.s
443
+ if (values.length < 2) {
444
+ return DivideByZeroError();
445
+ }
446
+ value = Variance(values, true);
447
+ break;
448
+ case 11: // var.p
449
+ if (values.length === 0) {
450
+ return DivideByZeroError();
451
+ }
452
+ value = Variance(values, false);
453
+ break;
454
+ }
455
+ // console.info({type, args, flat, values});
456
+ return {
457
+ type: ValueType.number,
458
+ value,
459
+ };
460
+ },
461
+ },
462
+ Cell: {
463
+ description: 'Returns data about a cell',
464
+ arguments: [
465
+ { name: 'type', description: 'Type of data to return', unroll: true, },
466
+ { name: 'reference', description: 'Cell reference', metadata: true, unroll: true, },
467
+ ],
468
+ // there's no concept of "structure volatile", and structure events
469
+ // don't trigger recalc, so this is not helpful -- we may need to
470
+ // think about both of those things
471
+ // volatile: true,
472
+ fn: (type, reference) => {
473
+ if (!UnionIsMetadata(reference)) {
474
+ return ReferenceError();
475
+ }
476
+ if (type) {
477
+ switch (type.toString().toLowerCase()) {
478
+ case 'format':
479
+ return reference.value.format ? // || ReferenceError;
480
+ { type: ValueType.string, value: reference.value.format } : ReferenceError();
481
+ case 'address':
482
+ {
483
+ let sheet_name = '';
484
+ if (reference.value.address.sheet_id) {
485
+ const sheet = this.model.sheets.Find(reference.value.address.sheet_id);
486
+ sheet_name = sheet?.name || '';
487
+ }
488
+ if (sheet_name) {
489
+ if (QuotedSheetNameRegex.test(sheet_name)) {
490
+ sheet_name = `'${sheet_name}'`;
491
+ }
492
+ sheet_name += '!';
493
+ }
494
+ return {
495
+ type: ValueType.string,
496
+ value: '[]' + sheet_name + reference.value.address.label.replace(/\$/g, ''),
497
+ };
498
+ }
499
+ }
500
+ }
501
+ return { type: ValueType.error, value: NotImplError.error };
502
+ },
503
+ },
504
+ Address: {
505
+ arguments: [
506
+ { name: 'row' },
507
+ { name: 'column' },
508
+ { name: 'absolute' },
509
+ { name: 'a1' },
510
+ { name: 'sheet name' }
511
+ ],
512
+ fn: (row = 1, column = 1, absolute = 1, a1 = true, sheet_name) => {
513
+ const address = {
514
+ type: 'address',
515
+ id: 0, position: 0, label: '',
516
+ row: row - 1,
517
+ column: column - 1,
518
+ };
519
+ switch (absolute) {
520
+ case 2:
521
+ address.absolute_row = true;
522
+ break;
523
+ case 3:
524
+ address.absolute_column = true;
525
+ break;
526
+ case 4:
527
+ break;
528
+ default:
529
+ address.absolute_column = true;
530
+ address.absolute_row = true;
531
+ }
532
+ if (sheet_name) {
533
+ address.sheet = sheet_name;
534
+ }
535
+ return StringUnion(this.parser.Render(address, { r1c1: !a1 }));
536
+ },
537
+ },
538
+ /**
539
+ * anything I said about COUNTIF applies here, but worse.
540
+ * COUNTIFS is an AND operation across separate COUNTIFs.
541
+ * presumably they have to be the same shape.
542
+ */
543
+ CountIfs: {
544
+ arguments: [
545
+ { name: 'range', },
546
+ { name: 'criteria', },
547
+ { name: 'range', },
548
+ { name: 'criteria', }
549
+ ],
550
+ fn: (...args) => {
551
+ return XIf('count', args[0], ...args);
552
+ },
553
+ },
554
+ /** @see CountIf */
555
+ AverageIf: {
556
+ arguments: [
557
+ { name: 'range', },
558
+ { name: 'criteria', },
559
+ ],
560
+ fn: (range, criteria, average_range) => {
561
+ return XIf('average', average_range || range, range, criteria);
562
+ },
563
+ },
564
+ /** @see CountIf */
565
+ AverageIfs: {
566
+ arguments: [
567
+ { name: 'value range', },
568
+ { name: 'criteria range', },
569
+ { name: 'criteria', },
570
+ { name: 'criteria range', },
571
+ { name: 'criteria', },
572
+ ],
573
+ fn: (range, ...args) => {
574
+ return XIf('average', range, ...args);
575
+ },
576
+ },
577
+ /** @see CountIf */
578
+ SumIf: {
579
+ arguments: [
580
+ { name: 'range', },
581
+ { name: 'criteria', },
582
+ ],
583
+ fn: (range, criteria, sum_range) => {
584
+ return XIf('sum', sum_range || range, range, criteria);
585
+ },
586
+ },
587
+ /** @see CountIf */
588
+ SumIfs: {
589
+ arguments: [
590
+ { name: 'value range', },
591
+ { name: 'criteria range', },
592
+ { name: 'criteria', },
593
+ { name: 'criteria range', },
594
+ { name: 'criteria', },
595
+ ],
596
+ fn: (range, ...args) => {
597
+ return XIf('sum', range, ...args);
598
+ },
599
+ },
600
+ /**
601
+ * this function is here so it has access to the parser.
602
+ * this is crazy expensive. is there a way to reduce cost?
603
+ *
604
+ * we could, in theory, consider that there are only a few
605
+ * valid operations here -- all binary. instead of using a
606
+ * generic call to the CalculateExpression routine, we could
607
+ * short-cut and call the binary method.
608
+ *
609
+ * OTOH that makes it more fragile, and might not really
610
+ * provide that much in the way of savings. still, it would
611
+ * be good if we could somehow cache some of the effort,
612
+ * particularly if the list data changes but not the expression.
613
+ *
614
+ */
615
+ CountIf: {
616
+ arguments: [
617
+ { name: 'range', },
618
+ { name: 'criteria', }
619
+ ],
620
+ fn: (range, criteria) => {
621
+ return XIf('count', range, range, criteria);
622
+ },
623
+ },
624
+ /** like indirect, this creates dependencies at calc time */
625
+ Offset: {
626
+ arguments: [{
627
+ name: 'reference', description: 'Base reference', metadata: true,
628
+ }, {
629
+ name: 'rows', description: 'number of rows to offset'
630
+ }, {
631
+ name: 'columns', description: 'number of columns to offset'
632
+ }, {
633
+ name: 'height',
634
+ }, {
635
+ name: 'width',
636
+ },
637
+ ],
638
+ return_type: 'reference',
639
+ volatile: true,
640
+ fn: ((reference, rows = 0, columns = 0, height, width) => {
641
+ if (!reference) {
642
+ return ArgumentError();
643
+ }
644
+ // const parse_result = this.parser.Parse(reference);
645
+ // if (parse_result.error || !parse_result.expression) {
646
+ // return ReferenceError;
647
+ //}
648
+ if (reference.type === ValueType.array) {
649
+ // subset array. this is constructed, so we can take ownership
650
+ // and modify it, although it would be safer to copy. also, what's
651
+ // the cost of functional vs imperative loops these days?
652
+ const end_row = typeof height === 'number' ? (rows + height) : undefined;
653
+ const end_column = typeof width === 'number' ? (columns + width) : undefined;
654
+ const result = {
655
+ type: ValueType.array,
656
+ value: reference.value.slice(rows, end_row).map(row => row.slice(columns, end_column)),
657
+ };
658
+ return result;
659
+ }
660
+ // we need a proper type for this... also it might be a range
661
+ if (!UnionIsMetadata(reference)) {
662
+ console.info('e2', { reference });
663
+ return ReferenceError();
664
+ }
665
+ const check_result = this.DynamicDependencies(reference.value.address, this.expression_calculator.context.address, true, rows, columns, width, height);
666
+ if (!check_result) {
667
+ console.info('e1', { check_result });
668
+ return ReferenceError();
669
+ }
670
+ if (check_result.dirty) {
671
+ const current_vertex = this.GetVertex(this.expression_calculator.context.address, true);
672
+ current_vertex.short_circuit = true;
673
+ return { type: ValueType.undefined, value: undefined };
674
+ }
675
+ if (check_result.area) {
676
+ const start = {
677
+ type: 'address', ...check_result.area.start,
678
+ label: '', position: 0,
679
+ // id: parse_result.expression.id,
680
+ id: 0,
681
+ };
682
+ const end = {
683
+ type: 'address', ...check_result.area.end,
684
+ label: '', position: 0,
685
+ // id: parse_result.expression.id,
686
+ id: 0,
687
+ };
688
+ const expression = check_result.area.count === 1 ? start : {
689
+ type: 'range', start, end,
690
+ label: '', position: 0,
691
+ // id: parse_result.expression.id,
692
+ id: 0,
693
+ };
694
+ // return this.CalculateExpression(expression, undefined, true);
695
+ // return expression;
696
+ return { type: ValueType.object, value: expression };
697
+ }
698
+ return ValueError();
699
+ }).bind(this),
700
+ },
701
+ Indirect: {
702
+ arguments: [
703
+ { name: 'reference', description: 'Cell reference (string)' },
704
+ ],
705
+ return_type: 'reference',
706
+ volatile: true, // necessary?
707
+ fn: ((reference) => {
708
+ if (!reference || (typeof reference !== 'string')) {
709
+ return ArgumentError();
710
+ }
711
+ const parse_result = this.parser.Parse(reference);
712
+ if (parse_result.error || !parse_result.expression ||
713
+ (parse_result.expression.type !== 'address' && parse_result.expression.type !== 'range')) {
714
+ return ReferenceError();
715
+ }
716
+ const check_result = this.DynamicDependencies(parse_result.expression, this.expression_calculator.context.address);
717
+ if (!check_result) {
718
+ return ReferenceError();
719
+ }
720
+ if (check_result.dirty) {
721
+ const current_vertex = this.GetVertex(this.expression_calculator.context.address, true);
722
+ current_vertex.short_circuit = true;
723
+ return { type: ValueType.undefined, value: undefined };
724
+ }
725
+ return { type: ValueType.object, value: parse_result.expression };
726
+ }).bind(this),
727
+ },
728
+ /**
729
+ * FIXME: there are cases we are not handling
730
+ *
731
+ * match seems to return either the matching row, in a column set,
732
+ * or matching column, in a row set. you can't search a 2d array.
733
+ * match also supports inexact matching but assumes data is ordered.
734
+ * (TODO).
735
+ *
736
+ * FIXME: we also need to icase match strings
737
+ *
738
+ */
739
+ Match: {
740
+ arguments: [
741
+ { name: 'value', boxed: true },
742
+ { name: 'range', boxed: true },
743
+ { name: 'type', },
744
+ ],
745
+ fn: (value, range, type = 0) => {
746
+ if (type) {
747
+ console.warn('inexact match not supported', { value, range, type });
748
+ return NAError();
749
+ }
750
+ else {
751
+ // I suppose you can match on a single value
752
+ if (range.type === ValueType.array) {
753
+ if (range.value.length === 1) {
754
+ const arr = range.value[0];
755
+ for (let i = 0; i < arr.length; i++) {
756
+ if (value.type == arr[i].type && value.value === arr[i].value) {
757
+ return { type: ValueType.number, value: i + 1 };
758
+ }
759
+ }
760
+ }
761
+ else {
762
+ for (let i = 0; i < range.value.length; i++) {
763
+ const arr = range.value[i];
764
+ if (arr.length !== 1) {
765
+ return NAError();
766
+ }
767
+ if (value.type == arr[0].type && value.value === arr[0].value) {
768
+ return { type: ValueType.number, value: i + 1 };
769
+ }
770
+ }
771
+ }
772
+ return NAError();
773
+ }
774
+ else {
775
+ if (value.type === range.type && value.value === range.value) {
776
+ return {
777
+ type: ValueType.number, value: 1,
778
+ };
779
+ }
780
+ return NAError();
781
+ }
782
+ }
783
+ return ArgumentError();
784
+ },
785
+ },
786
+ /**
787
+ * FIXME: there are cases we are not handling
788
+ *
789
+ * update to return a reference so you can use it as part of a
790
+ * range. we're not handling literal arrays atm.
791
+ */
792
+ Index: {
793
+ return_type: 'reference',
794
+ arguments: [
795
+ { name: 'range', metadata: true, },
796
+ { name: 'row', },
797
+ { name: 'column', }
798
+ ],
799
+ // volatile: true, // not sure this is necessary bc input is the range
800
+ volatile: false,
801
+ // FIXME: handle full row, full column calls
802
+ fn: (range, row, column) => {
803
+ if (!range) {
804
+ return ArgumentError();
805
+ }
806
+ // this is illegal, although we could just default to zeros
807
+ if (row === undefined && column === undefined) {
808
+ return ArgumentError();
809
+ }
810
+ row = row || 0;
811
+ column = column || 0;
812
+ let arr = [];
813
+ // NOTE: could be a single cell. in that case it won't be passed
814
+ // as an array so we'll need a second branch (or convert it)
815
+ if (range.type === ValueType.array) {
816
+ // FIXME: validate these are addresses (shouldn't be necessary
817
+ // if we're marking the argument as metadata? what about literals?)
818
+ // check rows and columns. we might need to return an array.
819
+ arr = range.value;
820
+ }
821
+ else if (range.type === ValueType.object) {
822
+ const metadata = range.value;
823
+ if (metadata.type === 'metadata' && metadata.address) {
824
+ arr.push([range]);
825
+ }
826
+ }
827
+ const columns = arr.length;
828
+ const rows = arr[0]?.length || 0;
829
+ if (rows <= 0 || columns <= 0 || row > rows || column > columns || row < 0 || column < 0) {
830
+ return ArgumentError();
831
+ }
832
+ // return everything if arguments are (0, 0)
833
+ if (column === 0 && row === 0) {
834
+ // because of the way we're structured this we might be
835
+ // returning a range of length 1; in that case we want
836
+ // to return it as an address
837
+ const expression = (columns === 1 && rows === 1) ? {
838
+ ...arr[0][0].value.address,
839
+ } : {
840
+ type: 'range',
841
+ start: arr[0][0].value.address,
842
+ end: arr[columns - 1][rows - 1].value.address,
843
+ label: '', position: 0,
844
+ id: 0,
845
+ };
846
+ return {
847
+ type: ValueType.object,
848
+ value: expression,
849
+ };
850
+ }
851
+ // single cell
852
+ if ((row || rows === 1) && (column || columns === 1)) {
853
+ return {
854
+ type: ValueType.object,
855
+ value: arr[column ? column - 1 : 0][row ? row - 1 : 0].value.address,
856
+ };
857
+ }
858
+ // sub array
859
+ if (row) {
860
+ // return column
861
+ const expression = {
862
+ type: 'range',
863
+ start: arr[0][row - 1].value.address,
864
+ end: arr[columns - 1][row - 1].value.address,
865
+ label: '', position: 0,
866
+ id: 0,
867
+ };
868
+ return {
869
+ type: ValueType.object,
870
+ value: expression,
871
+ };
872
+ }
873
+ else if (column) {
874
+ // return row
875
+ const expression = {
876
+ type: 'range',
877
+ start: arr[column - 1][0].value.address,
878
+ end: arr[column - 1][rows - 1].value.address,
879
+ label: '', position: 0,
880
+ id: 0,
881
+ };
882
+ return {
883
+ type: ValueType.object,
884
+ value: expression,
885
+ };
886
+ }
887
+ return ArgumentError();
888
+ },
889
+ },
890
+ /**
891
+ * this one does not have to be here, it's just here because
892
+ * the rest of the reference/lookup functions are here
893
+ */
894
+ Rows: {
895
+ arguments: [{
896
+ name: 'reference', description: 'Array or reference'
897
+ },
898
+ ],
899
+ volatile: false,
900
+ fn: (reference) => {
901
+ if (!reference) {
902
+ return ArgumentError();
903
+ }
904
+ if (Array.isArray(reference)) {
905
+ const column = reference[0];
906
+ if (Array.isArray(column)) {
907
+ return { type: ValueType.number, value: column.length };
908
+ }
909
+ return ValueError();
910
+ }
911
+ return { type: ValueType.number, value: 1 };
912
+ },
913
+ },
914
+ /**
915
+ * this one does not have to be here, it's just here because
916
+ * the rest of the reference/lookup functions are here
917
+ */
918
+ Columns: {
919
+ arguments: [{
920
+ name: 'reference', description: 'Array or reference'
921
+ },
922
+ ],
923
+ volatile: false,
924
+ fn: (reference) => {
925
+ if (!reference) {
926
+ return ArgumentError();
927
+ }
928
+ if (Array.isArray(reference)) {
929
+ return { type: ValueType.number, value: reference.length };
930
+ }
931
+ return { type: ValueType.number, value: 1 };
932
+ },
933
+ },
934
+ /**
935
+ * not sure when this one appeared, but it's what I was looking for
936
+ *
937
+ * ---
938
+ * what an odd comment. what does that mean?
939
+ */
940
+ FormulaText: {
941
+ description: 'Returns a formula as a string',
942
+ arguments: [
943
+ { name: 'reference', description: 'Cell reference', metadata: true, unroll: true },
944
+ ],
945
+ fn: (reference) => {
946
+ if (!UnionIsMetadata(reference)) {
947
+ return ReferenceError();
948
+ }
949
+ const sheet = this.model.sheets.Find(reference.value?.address?.sheet_id || 0);
950
+ if (sheet) {
951
+ const cell = sheet.cells.GetCell(reference.value.address, false);
952
+ return {
953
+ type: ValueType.string,
954
+ value: cell?.value?.toString() || '',
955
+ };
956
+ }
957
+ return ReferenceError();
958
+ },
959
+ },
960
+ /**
961
+ * this should be in the 'information' library but it needs reference
962
+ * to the underlying cell (unresolved)
963
+ */
964
+ IsFormula: {
965
+ description: 'Returns true if the reference is a formula',
966
+ arguments: [{
967
+ name: 'Reference',
968
+ unroll: true,
969
+ metadata: true, /* OK with array metadata */
970
+ }],
971
+ fn: (ref) => {
972
+ // this is wasteful because we know that the range will all
973
+ // be in the same sheet... we don't need to look up every time
974
+ const addr = ref?.value?.address;
975
+ const sheet = this.model.sheets.Find(addr?.sheet_id || 0);
976
+ if (addr && sheet) {
977
+ const cell = sheet.cells.GetCell(addr, false);
978
+ return {
979
+ type: ValueType.boolean,
980
+ value: cell?.type === ValueType.formula,
981
+ };
982
+ }
983
+ return {
984
+ type: ValueType.boolean, value: false,
985
+ };
986
+ },
987
+ },
988
+ });
989
+ }
990
+ /**
991
+ * new async init method. we need this for subclasses, but
992
+ * we should consider moving base methods in here as well.
993
+ *
994
+ * this function must be idempotent, so we can call it from
995
+ * multiple paths
996
+ */
997
+ async InitResources() {
998
+ if (this.async_resource_init) {
999
+ // console.info("init resources noop");
1000
+ return;
1001
+ }
1002
+ // console.info("init resources (async)");
1003
+ this.async_resource_init = true;
1004
+ }
1005
+ /**
1006
+ * support for co-editing. we need to export calculated values from
1007
+ * the leader instance, because things like RAND() and NOW() are
1008
+ * nondeterministic (within reason).
1009
+ *
1010
+ * so the leader does the calculation and then we broadcast calculated
1011
+ * values to followers.
1012
+ */
1013
+ ExportCalculatedValues() {
1014
+ const data = {};
1015
+ for (const sheet of this.model.sheets.list) {
1016
+ const calculated = sheet.cells.toJSON({ calculated_value: true }).data;
1017
+ data[sheet.id] = calculated.filter(test => test.calculated !== undefined);
1018
+ }
1019
+ return data;
1020
+ }
1021
+ /**
1022
+ * support for co-editing. if we get calculated values from the leader,
1023
+ * we need to apply them to cells.
1024
+ *
1025
+ * to _see_ the data, you still have to make a couple of calls to repaint
1026
+ * and update annotations. see EmbeddedSpreadsheetBase.Recalculate for hints.
1027
+ *
1028
+ * note that we're checking for list mismatch in one direction but not the
1029
+ * other direction. should probably check both.
1030
+ */
1031
+ ApplyCalculatedValues(data) {
1032
+ for (const sheet of this.model.sheets.list) {
1033
+ const cells = data[sheet.id];
1034
+ if (!cells) {
1035
+ console.info('mismatch', sheet.id);
1036
+ }
1037
+ else {
1038
+ for (const cell of cells) {
1039
+ sheet.cells.data[cell.row][cell.column].SetCalculatedValue(cell.calculated);
1040
+ // console.info(sheet.id, cell.row, cell.column, '->', cell.calculated);
1041
+ }
1042
+ }
1043
+ }
1044
+ }
1045
+ AttachSpillData(area, cells) {
1046
+ if (!cells) {
1047
+ // can we assume active sheet here? actually I guess not, we'll
1048
+ // need to set that...
1049
+ const sheet = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id) : undefined;
1050
+ cells = sheet?.cells;
1051
+ }
1052
+ if (!cells) {
1053
+ console.info({ area, cells });
1054
+ throw new Error('invalid sheet ID in attach spill data');
1055
+ }
1056
+ const vertex = new StateLeafVertex();
1057
+ let counter = 0;
1058
+ let error = false;
1059
+ for (const { cell, row, column } of cells.IterateRC(area, true)) {
1060
+ if (counter++ && (cell.type !== ValueType.undefined || cell.area || cell.merge_area || cell.table)) {
1061
+ error = true; // spill error.
1062
+ }
1063
+ this.AddLeafVertexEdge({ row, column, sheet_id: area.start.sheet_id }, vertex);
1064
+ }
1065
+ // console.info("storing spill data");
1066
+ this.spill_data.push({ area, vertex });
1067
+ return { vertex, area, error };
1068
+ }
1069
+ SpillCallback(vertex, result) {
1070
+ const { reference, address } = vertex;
1071
+ const { value } = result;
1072
+ const recalculate_list = [];
1073
+ if (!reference) {
1074
+ // should throw but this is new and I don't want to break stuff rn
1075
+ console.error("invalid reference in spill callback");
1076
+ return recalculate_list;
1077
+ }
1078
+ if (!address || !address.sheet_id) {
1079
+ // should throw but this is new and I don't want to break stuff rn
1080
+ console.error("invalid address in spill callback");
1081
+ return recalculate_list;
1082
+ }
1083
+ // I guess we could do the one-cell version here
1084
+ if (value.length === 1 && value[0].length === 1) {
1085
+ reference.SetCalculatedValue(value[0][0].value);
1086
+ return recalculate_list;
1087
+ }
1088
+ if (!this.options.spill) {
1089
+ reference.SetCalculatedValue(value[0][0].value);
1090
+ return recalculate_list;
1091
+ }
1092
+ // console.info("SPILLING");
1093
+ const sheet = this.model.sheets.Find(address.sheet_id);
1094
+ const cells = sheet?.cells;
1095
+ if (cells) {
1096
+ // first thing we do is check for empty. if !empty, that's a
1097
+ // spill error and we can stop. also check for area, spill and
1098
+ // merge (and table).
1099
+ const columns = result.value.length;
1100
+ const rows = result.value[0].length;
1101
+ const area = new Area(address).Reshape(rows, columns);
1102
+ /*
1103
+ let counter = 0;
1104
+ let error = false;
1105
+ const leaf = new StateLeafVertex();
1106
+
1107
+ for (const {cell, row, column} of cells.IterateRC(area, true)) {
1108
+ if (counter++ && (cell.type !== ValueType.undefined || cell.area || cell.merge_area || cell.table)) {
1109
+ error = true; // spill error.
1110
+ }
1111
+ this.AddLeafVertexEdge({row, column, sheet_id: area.start.sheet_id}, leaf);
1112
+ }
1113
+
1114
+ console.info("storing spill data");
1115
+
1116
+ // this.spills.push(new Area(area.start, area.end));
1117
+ this.spill_data.push({area, vertex: leaf});
1118
+ */
1119
+ const { error } = this.AttachSpillData(area, cells);
1120
+ if (error) {
1121
+ // console.info("returning error");
1122
+ reference.SetCalculationError('SPILL');
1123
+ return recalculate_list;
1124
+ }
1125
+ // expand the sheet, if necessary (+1)
1126
+ if (sheet.rows < area.end.row + 1) {
1127
+ sheet.cells.EnsureRow(area.end.row + 1);
1128
+ this.grid_expanded = true;
1129
+ }
1130
+ if (sheet.columns < area.end.column + 1) {
1131
+ sheet.cells.EnsureColumn(area.end.column + 1);
1132
+ this.grid_expanded = true;
1133
+ }
1134
+ // hmmm... we need the grid to update... how can we ensure that?
1135
+ // we could use a flag that the embedded sheet checks after
1136
+ // calculation... which is kind of sloppy but I don't have a better
1137
+ // idea
1138
+ const sheet_id = address.sheet_id;
1139
+ // let dirty = false;
1140
+ for (const { row, column } of cells.IterateRC(area)) {
1141
+ if (row === address.row && column === address.column) {
1142
+ continue;
1143
+ }
1144
+ const vertex = this.GetVertex({ sheet_id, row, column }, false);
1145
+ if (vertex) {
1146
+ // onsole.info("Have vertex @", row, column, "dirty?", vertex.dirty);
1147
+ if (!vertex.dirty) {
1148
+ recalculate_list.push(vertex);
1149
+ }
1150
+ }
1151
+ // do we need these edges? if so, what for? (...)
1152
+ // I guess to propagate dirty if there's a dependent?
1153
+ // apparently not, although I'm not sure why...
1154
+ // this.AddEdge(address, {sheet_id, row, column});
1155
+ }
1156
+ /*
1157
+ // ok, now we can go on: copying a little from dynamic dependencies,
1158
+ // we're going to add vertices and check for dirty:
1159
+
1160
+ const sheet_id = address.sheet_id;
1161
+ let dirty = false;
1162
+
1163
+ for (const {row, column} of cells.IterateRC(area)) {
1164
+
1165
+ if (row === address.row && column === address.column) { continue; }
1166
+
1167
+ const vertex = this.GetVertex({sheet_id, row, column}, true);
1168
+ if (vertex && vertex.dirty) {
1169
+
1170
+ console.info(`Adding edge from ${{row: address.row, column: address.column}} -> ${{row, column}}`)
1171
+
1172
+ // see comments in DynamicDependencies()
1173
+
1174
+ this.AddEdge(address, {row, column, sheet_id});
1175
+ dirty = true;
1176
+
1177
+ }
1178
+
1179
+ }
1180
+
1181
+ console.info("DIRTY?", dirty);
1182
+
1183
+ if (dirty) {
1184
+ const current_vertex = this.GetVertex(address, true) as SpreadsheetVertex;
1185
+ current_vertex.short_circuit = true;
1186
+ return;
1187
+ }
1188
+ */
1189
+ //
1190
+ // maybe we could use a vertex here?
1191
+ // actually we also need to do a loop check
1192
+ // so I think the approach is
1193
+ //
1194
+ // 1 - create a vertex (spill -- array vertex?)
1195
+ // 2 - check for loops
1196
+ // 3 - if no loop, check for empty
1197
+ // 4 - if empty, fill in values
1198
+ //
1199
+ // and then we need to flush spill vertices at
1200
+ // some point, either always on recalc, or on
1201
+ // recalc if something is dirty. flushing spill
1202
+ // vertices implies removing all spilled values
1203
+ // so they will be empty if something changes
1204
+ // PLAN: start by flushing all spill vertices on
1205
+ // every recalc, and then we can trim it back
1206
+ // spill ok, set values
1207
+ for (let { cell, row, column } of cells.IterateRC(area)) {
1208
+ cell.spill = area;
1209
+ row -= address.row;
1210
+ column -= address.column;
1211
+ const v = result.value[column][row];
1212
+ switch (v.type) {
1213
+ case ValueType.object:
1214
+ case ValueType.array:
1215
+ case ValueType.function:
1216
+ break;
1217
+ default:
1218
+ cell.SetCalculatedValue(v.value, v.type);
1219
+ break;
1220
+ }
1221
+ }
1222
+ return recalculate_list;
1223
+ }
1224
+ //
1225
+ console.error("invalid cell reference in spill callback");
1226
+ reference.SetCalculationError('SPILL');
1227
+ return [];
1228
+ }
1229
+ /**
1230
+ * this is a mess [not as bad as it used to be]
1231
+ */
1232
+ SpreadCallback(vertex, value) {
1233
+ if (!vertex.address || !vertex.address.sheet_id) {
1234
+ throw new Error('spread callback called without sheet id');
1235
+ }
1236
+ // const cells = this.cells_map[vertex.address.sheet_id];
1237
+ const cells = this.model.sheets.Find(vertex.address.sheet_id)?.cells;
1238
+ if (!cells) {
1239
+ throw new Error('spread callback called without cells');
1240
+ }
1241
+ if (!vertex || !vertex.reference)
1242
+ return;
1243
+ const area = vertex.reference.area;
1244
+ if (area) {
1245
+ const rows = area.rows;
1246
+ const columns = area.columns;
1247
+ // if (Array.isArray(value)) {
1248
+ if (value.type === ValueType.array) {
1249
+ // value = Utilities.Transpose2(value);
1250
+ const values = Utilities.Transpose2(value.value);
1251
+ // FIXME: recycle [?]
1252
+ for (let row = 0; row < rows; row++) {
1253
+ if (values[row]) {
1254
+ let column = 0;
1255
+ for (; column < columns && column < values[row].length; column++) {
1256
+ // if there's a nested array, take the first value. but
1257
+ // don't recurse; if there's another array in there set
1258
+ // as undefined (should be error?)
1259
+ let indexed_value = values[row][column];
1260
+ if (indexed_value.type === ValueType.array) {
1261
+ indexed_value = indexed_value.value[0][0];
1262
+ }
1263
+ switch (indexed_value.type) {
1264
+ case ValueType.array:
1265
+ case ValueType.object:
1266
+ case ValueType.function:
1267
+ cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(undefined); // error?
1268
+ break;
1269
+ default:
1270
+ cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(indexed_value.value, indexed_value.type);
1271
+ }
1272
+ /*
1273
+ if (indexed_value.type !== ValueType.object) {
1274
+ cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(
1275
+ indexed_value.value, indexed_value.type);
1276
+ }
1277
+
1278
+ cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(
1279
+ values[row][column].value,
1280
+ values[row][column].type);
1281
+ */
1282
+ }
1283
+ for (; column < columns; column++) {
1284
+ cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(undefined, ValueType.undefined);
1285
+ }
1286
+ }
1287
+ else {
1288
+ for (let column = 0; column < columns; column++) {
1289
+ cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(undefined, ValueType.undefined);
1290
+ }
1291
+ }
1292
+ }
1293
+ }
1294
+ else {
1295
+ // single, recycle
1296
+ let applied = { ...value };
1297
+ if (applied.type === ValueType.object || applied.type === ValueType.function) {
1298
+ applied = { type: ValueType.undefined, value: undefined };
1299
+ }
1300
+ for (let row = 0; row < rows; row++) {
1301
+ for (let column = 0; column < columns; column++) {
1302
+ cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(applied.value, applied.type);
1303
+ }
1304
+ }
1305
+ }
1306
+ }
1307
+ }
1308
+ /**
1309
+ * FIXME: for this version, this should be synchronous; the whole thing
1310
+ * should run in a worker. should be much faster than context switching
1311
+ * every time.
1312
+ */
1313
+ CalculationCallback(vertex) {
1314
+ // must have address [UPDATE: don't do this]
1315
+ if (!vertex.address)
1316
+ throw (new Error('vertex missing address'));
1317
+ if (vertex.expression_error) {
1318
+ return {
1319
+ value: UnknownError(),
1320
+ };
1321
+ }
1322
+ return this.expression_calculator.Calculate(vertex.expression, vertex.address, vertex.reference?.area); // <- this one
1323
+ }
1324
+ /**
1325
+ * generic function, broken out from the Indirect function. checks dynamic
1326
+ * dependency for missing edges, and adds those edges.
1327
+ *
1328
+ * returns error on bad reference or circular dependency. this method
1329
+ * does not set the "short circuit" flag, callers should set as appropriate.
1330
+ */
1331
+ DynamicDependencies(expression, context, offset = false, offset_rows = 0, offset_columns = 0, resize_rows = 1, resize_columns = 1) {
1332
+ // UPDATE: use current context (passed in as argument) to resolve
1333
+ // relative references. otherwise the reference will change depending
1334
+ // on current/active sheet
1335
+ let area = this.ResolveExpressionAddress(expression, context);
1336
+ if (!area) {
1337
+ return undefined;
1338
+ }
1339
+ // flag. we're going to check _all_ dependencies at once, just in
1340
+ // case (for this function this would only happen if the argument
1341
+ // is an array).
1342
+ let dirty = false;
1343
+ // if (area) {
1344
+ let sheet;
1345
+ if (expression.type === 'address' || expression.type === 'range') {
1346
+ const address_expression = (expression.type === 'range') ? expression.start : expression;
1347
+ if (address_expression.sheet_id) {
1348
+ sheet = this.model.sheets.Find(address_expression.sheet_id);
1349
+ /*
1350
+ for (const test of this.model.sheets) {
1351
+ if (test.id === address_expression.sheet_id) {
1352
+ sheet = test;
1353
+ break;
1354
+ }
1355
+ }
1356
+ */
1357
+ }
1358
+ else if (address_expression.sheet) {
1359
+ sheet = this.model.sheets.Find(address_expression.sheet);
1360
+ /*
1361
+ const lc = address_expression.sheet.toLowerCase();
1362
+ for (const test of this.model.sheets) {
1363
+ if (test.name.toLowerCase() === lc) {
1364
+ sheet = test;
1365
+ break;
1366
+ }
1367
+ }
1368
+ */
1369
+ }
1370
+ }
1371
+ if (!sheet && context?.sheet_id) {
1372
+ sheet = this.model.sheets.Find(context.sheet_id);
1373
+ /*
1374
+ for (const test of this.model.sheets) {
1375
+ if (test.id === context.sheet_id) {
1376
+ sheet = test;
1377
+ break;
1378
+ }
1379
+ }
1380
+ */
1381
+ }
1382
+ if (!sheet) {
1383
+ throw new Error('missing sheet in dynamic dependencies [b21]');
1384
+ }
1385
+ // check any dirty...
1386
+ // THIS IS ALMOST CERTAINLY WRONG. we should not be using active_sheet
1387
+ // here, we should use the area sheet. FIXME
1388
+ area = sheet.RealArea(area);
1389
+ const sheet_id = area.start.sheet_id;
1390
+ if (offset) {
1391
+ area = new Area({
1392
+ column: area.start.column + offset_columns,
1393
+ row: area.start.row + offset_rows,
1394
+ sheet_id: area.start.sheet_id,
1395
+ }, {
1396
+ column: area.start.column + offset_columns + resize_rows - 1,
1397
+ row: area.start.row + offset_rows + resize_columns - 1,
1398
+ sheet_id: area.end.sheet_id,
1399
+ });
1400
+ }
1401
+ for (let row = area.start.row; row <= area.end.row; row++) {
1402
+ for (let column = area.start.column; column <= area.end.column; column++) {
1403
+ const vertex = this.GetVertex({ row, column, sheet_id }, false);
1404
+ if (vertex && vertex.dirty) {
1405
+ // so we know, given the structure of calculation, that there
1406
+ // is not an edge between these two vertices. we know that
1407
+ // because calculate() is never called on a vertex that has
1408
+ // dirty dependencies.
1409
+ // so if we create an edge here, the calculate method can
1410
+ // short-circuit, and then this cell will be re-evaluated
1411
+ // when that cell is calculated.
1412
+ // so all we have to do is add the edge. the question is,
1413
+ // do we need to remove that edge after the calculation?
1414
+ // or can we just wait for it to clean up on a rebuild?
1415
+ // (...) don't know for sure atm, test.
1416
+ // actually we have to set some flag to tell the vertex to
1417
+ // short-circuit...
1418
+ // before you set the short-circuit flag, test result so we
1419
+ // can error on circular ref
1420
+ // const edge_result =
1421
+ this.AddEdge({ row, column, sheet_id }, this.expression_calculator.context.address);
1422
+ //if (edge_result) {
1423
+ // return ReferenceError;
1424
+ //}
1425
+ dirty = true;
1426
+ }
1427
+ }
1428
+ }
1429
+ // }
1430
+ return { dirty, area };
1431
+ }
1432
+ /**
1433
+ * if locale has changed in Localization, update local resources.
1434
+ * this is necessary because (in chrome) worker doesn't get the system
1435
+ * locale properly (also, we might change it via parameter). we used to
1436
+ * just drop and reconstruct calculator, but we want to stop doing that
1437
+ * as part of supporting dynamic extension.
1438
+ */
1439
+ UpdateLocale() {
1440
+ // don't assume default, always set
1441
+ if (Localization.decimal_separator === ',') {
1442
+ this.parser.SetLocaleSettings(DecimalMarkType.Comma);
1443
+ // this.parser.decimal_mark = DecimalMarkType.Comma;
1444
+ // this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
1445
+ }
1446
+ else {
1447
+ this.parser.SetLocaleSettings(DecimalMarkType.Period);
1448
+ // this.parser.decimal_mark = DecimalMarkType.Period;
1449
+ // this.parser.argument_separator = ArgumentSeparatorType.Comma;
1450
+ }
1451
+ // this.expression_calculator.UpdateLocale();
1452
+ }
1453
+ /* *
1454
+ * lookup in function library
1455
+ *
1456
+ * it seems like the only place this is called is within this class,
1457
+ * so we could probably inline and drop this function
1458
+ *
1459
+ * @deprecated
1460
+ * /
1461
+ public GetFunction(name: string): ExtendedFunctionDescriptor {
1462
+ return this.library.Get(name);
1463
+ }
1464
+ */
1465
+ /**
1466
+ * returns a list of available functions, for AC/tooltips
1467
+ * FIXME: categories?
1468
+ * FIXME: need to separate annotation functions and sheet functions
1469
+ */
1470
+ SupportedFunctions() {
1471
+ const list = this.library.List();
1472
+ const function_list = Object.keys(list).map((key) => {
1473
+ let name = list[key].canonical_name;
1474
+ if (!name)
1475
+ name = key.replace(/_/g, '.');
1476
+ return {
1477
+ name,
1478
+ description: list[key].description,
1479
+ arguments: (list[key].arguments || []).map((argument) => {
1480
+ return { name: argument.name || '' };
1481
+ }),
1482
+ type: 'function', // DescriptorType.Function,
1483
+ };
1484
+ });
1485
+ // FIXME: this doesn't need to be here, if it's owned by model.
1486
+ // we should have model responsible for retrieving these names
1487
+ // (along with named ranges/expressions). also, should macro
1488
+ // functions support scoping?
1489
+ for (const macro of this.model.macro_functions.values()) {
1490
+ function_list.push({
1491
+ name: macro.name,
1492
+ description: macro.description,
1493
+ arguments: (macro.argument_names || []).map(argument => {
1494
+ return { name: argument };
1495
+ }),
1496
+ type: 'function', // DescriptorType.Function,
1497
+ });
1498
+ }
1499
+ /*
1500
+ for (const key of Object.keys(this.model.macro_functions)) {
1501
+ const macro = this.model.macro_functions[key];
1502
+ function_list.push({
1503
+ name: macro.name,
1504
+ description: macro.description,
1505
+ arguments: (macro.argument_names || []).map(argument => {
1506
+ return { name: argument };
1507
+ }),
1508
+ });
1509
+ }
1510
+ */
1511
+ return function_list;
1512
+ }
1513
+ /**
1514
+ *
1515
+ * @param name
1516
+ * @param map
1517
+ */
1518
+ RegisterLibrary(name, map) {
1519
+ if (this.registered_libraries[name]) {
1520
+ return false;
1521
+ }
1522
+ this.RegisterFunction(map);
1523
+ this.registered_libraries[name] = true;
1524
+ return true;
1525
+ }
1526
+ /**
1527
+ * dynamic extension
1528
+ */
1529
+ RegisterFunction(map) {
1530
+ for (const name of Object.keys(map)) {
1531
+ const descriptor = map[name];
1532
+ // the way we call this now, this is unecessary
1533
+ // @see `CallExpression` in `expression-calculator.ts`.
1534
+ /*
1535
+ const original_function = descriptor.fn;
1536
+
1537
+ // we don't bind to the actual context because that would allow
1538
+ // functions to change it, and potentially break subsequent functions
1539
+ // that rely on it. which is a pretty far-fetched scenario, but we might
1540
+ // as well protect against it.
1541
+
1542
+ console.info('wrapping...');
1543
+
1544
+ descriptor.fn = (...args: unknown[]) => {
1545
+
1546
+ console.info("wrapped?");
1547
+
1548
+ return original_function.apply({
1549
+ address: { ...this.expression_calculator.context.address},
1550
+ }, args);
1551
+ };
1552
+ */
1553
+ this.library.Register({ [name]: descriptor });
1554
+ }
1555
+ }
1556
+ /**
1557
+ * wrapper method for calculation
1558
+ */
1559
+ Calculate(subset) {
1560
+ // this.AttachModel();
1561
+ // this gets checked later, now... it would be better if we could
1562
+ // check it here are skip the later check, but that field is optional
1563
+ // it's better to report the error here so we can trace
1564
+ if (subset && !subset.start.sheet_id) {
1565
+ throw new Error('CalculateInternal called with subset w/out sheet ID');
1566
+ }
1567
+ if (this.full_rebuild_required) {
1568
+ subset = undefined;
1569
+ this.UpdateAnnotations();
1570
+ this.UpdateConditionals();
1571
+ this.UpdateConnectedElements();
1572
+ // this.UpdateNotifiers();
1573
+ this.full_rebuild_required = false; // unset
1574
+ }
1575
+ // this.expression_calculator.SetModel(model);
1576
+ this.RebuildGraph(subset);
1577
+ this.grid_expanded = false; // unset
1578
+ try {
1579
+ this.Recalculate();
1580
+ }
1581
+ catch (err) {
1582
+ console.error(err);
1583
+ console.info('calculation error trapped');
1584
+ }
1585
+ /*
1586
+ const callbacks: NotifierType[] = [];
1587
+ for (const notifier of this.notifiers) {
1588
+ if (notifier.vertex.state_id !== notifier.state) {
1589
+ notifier.state = notifier.vertex.state_id;
1590
+ if (notifier.notifier.callback) {
1591
+ callbacks.push(notifier.notifier);
1592
+ }
1593
+ }
1594
+ }
1595
+
1596
+ if (callbacks.length) {
1597
+ Promise.resolve().then(() => {
1598
+ for (const notifier of callbacks) {
1599
+ if (notifier.callback) {
1600
+ notifier.callback.call(undefined, notifier);
1601
+ }
1602
+ }
1603
+ });
1604
+ }
1605
+ */
1606
+ }
1607
+ /**
1608
+ * resets graph and graph status. this is called when structure changes --
1609
+ * such as adding or removing sheets -- so we need to preserve notifiers
1610
+ * across resets. we need to either add a flag or add a separate method
1611
+ * to handle clearing notifiers.
1612
+ */
1613
+ Reset() {
1614
+ this.FlushTree();
1615
+ // this.AttachModel();
1616
+ this.full_rebuild_required = true;
1617
+ }
1618
+ /**
1619
+ * get a list of functions that require decorating with "_xlfn" on
1620
+ * export. the embed caller will pass this to the export worker.
1621
+ * since we manage functions, we can manage the list.
1622
+ *
1623
+ * UPDATE: to support our MC functions (which may need _xll decoration),
1624
+ * map to type and then overload as necessary
1625
+ *
1626
+ */
1627
+ DecoratedFunctionList() {
1628
+ // const list: string[] = [];
1629
+ const map = {};
1630
+ const lib = this.library.List();
1631
+ for (const key of Object.keys(lib)) {
1632
+ const def = lib[key];
1633
+ if (def.xlfn) {
1634
+ // list.push(key);
1635
+ map[key] = '_xlfn';
1636
+ }
1637
+ else if (def.extension) {
1638
+ map[key] = '_xll';
1639
+ }
1640
+ }
1641
+ return map;
1642
+ }
1643
+ /** moved from embedded sheet */
1644
+ Evaluate(expression, active_sheet, options = {}, raw_result = false) {
1645
+ let parse_expression = options?.preparsed;
1646
+ if (!parse_expression) {
1647
+ this.parser.Save();
1648
+ if (options.argument_separator) {
1649
+ if (options.argument_separator === ',') {
1650
+ this.parser.SetLocaleSettings(DecimalMarkType.Period);
1651
+ // this.parser.argument_separator = ArgumentSeparatorType.Comma;
1652
+ // this.parser.decimal_mark = DecimalMarkType.Period;
1653
+ }
1654
+ else {
1655
+ this.parser.SetLocaleSettings(DecimalMarkType.Comma);
1656
+ // this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
1657
+ // this.parser.decimal_mark = DecimalMarkType.Comma;
1658
+ }
1659
+ }
1660
+ if (options.r1c1) {
1661
+ this.parser.flags.r1c1 = options.r1c1;
1662
+ }
1663
+ const parse_result = this.parser.Parse(expression);
1664
+ // reset
1665
+ // this.parser.argument_separator = current;
1666
+ // this.parser.decimal_mark = (current === ArgumentSeparatorType.Comma) ? DecimalMarkType.Period : DecimalMarkType.Comma;
1667
+ // this.parser.flags.r1c1 = r1c1_state;
1668
+ this.parser.Restore();
1669
+ parse_expression = parse_result.expression;
1670
+ if (parse_result.error) {
1671
+ throw new Error(parse_result.error);
1672
+ }
1673
+ }
1674
+ // OK
1675
+ if (parse_expression) {
1676
+ this.parser.Walk(parse_expression, (unit) => {
1677
+ if (unit.type === 'address' || unit.type === 'range') {
1678
+ // don't allow offset references, even in R1C1
1679
+ if (unit.type === 'address') {
1680
+ if (unit.offset_column || unit.offset_row) {
1681
+ throw new Error(`Evaluate does not support offset references`);
1682
+ }
1683
+ }
1684
+ else {
1685
+ if (unit.start.offset_column || unit.start.offset_row || unit.end.offset_column || unit.end.offset_row) {
1686
+ throw new Error(`Evaluate does not support offset references`);
1687
+ }
1688
+ }
1689
+ this.model.ResolveSheetID(unit, undefined, active_sheet);
1690
+ }
1691
+ return true;
1692
+ });
1693
+ // console.info({expression: parse_result.expression})
1694
+ const result = this.CalculateExpression(parse_expression, options.address);
1695
+ if (raw_result) {
1696
+ return result;
1697
+ }
1698
+ if (result.type === ValueType.array) {
1699
+ return result.value.map(row => row.map(value => value.value));
1700
+ }
1701
+ else {
1702
+ return result.value;
1703
+ }
1704
+ }
1705
+ // or? (...)
1706
+ throw new Error('invalid expression');
1707
+ }
1708
+ /**
1709
+ * calculate an expression, optionally setting a fake cell address.
1710
+ * this may have weird side-effects.
1711
+ */
1712
+ CalculateExpression(expression, address = { row: -1, column: -1 }, preserve_flags = false) {
1713
+ return this.expression_calculator.Calculate(expression, address, undefined, preserve_flags).value; // dropping volatile flag
1714
+ }
1715
+ /**
1716
+ * rebuild the graph, and set cells as clean. the vertices need internal
1717
+ * references to the calculated value, so that's set via the vertex method.
1718
+ *
1719
+ * we also need to manage the list of volatile cells, which is normally
1720
+ * built as a side-effect of calculation.
1721
+ *
1722
+ * UPDATE: optionally recalculate if there are volatile cells. that's used
1723
+ * for loading documents.
1724
+ */
1725
+ RebuildClean(recalculate_if_volatile = false) {
1726
+ this.full_rebuild_required = false; // unset
1727
+ this.RebuildGraph();
1728
+ // add leaf vertices for annotations
1729
+ this.UpdateAnnotations(); // all
1730
+ this.UpdateConditionals();
1731
+ this.UpdateConnectedElements();
1732
+ // and notifiers
1733
+ // this.UpdateNotifiers();
1734
+ // there's a weird back-and-forth that happens here
1735
+ // (calculator -> graph -> calculator) to check for volatile
1736
+ // cells. it could probably be simplified.
1737
+ this.InitializeGraph();
1738
+ if (recalculate_if_volatile && this.volatile_list.length) {
1739
+ this.Recalculate();
1740
+ }
1741
+ }
1742
+ /**
1743
+ * remove duplicates from list, dropping absolute
1744
+ */
1745
+ FlattenCellList(list) {
1746
+ const map = {};
1747
+ const flattened = [];
1748
+ for (const entry of list) {
1749
+ const address = {
1750
+ column: entry.column,
1751
+ row: entry.row,
1752
+ sheet_id: entry.sheet_id,
1753
+ };
1754
+ const label = Area.CellAddressToLabel(address, true);
1755
+ if (map[label]) {
1756
+ continue;
1757
+ }
1758
+ map[label] = label;
1759
+ flattened.push(address);
1760
+ }
1761
+ return flattened;
1762
+ }
1763
+ /* * remove all notifiers * /
1764
+ public RemoveNotifiers(): void {
1765
+ for (const internal of this.notifiers) {
1766
+ if (internal.vertex) {
1767
+ internal.vertex.Reset();
1768
+ this.RemoveLeafVertex(internal.vertex);
1769
+ }
1770
+ }
1771
+ this.notifiers = [];
1772
+ }
1773
+ */
1774
+ /* *
1775
+ * remove specified notifier. you can pass the returned ID or the original
1776
+ * object used to create it.
1777
+ * /
1778
+ public RemoveNotifier(notifier: NotifierType|number): void {
1779
+
1780
+ let internal: InternalNotifierType|undefined;
1781
+
1782
+ this.notifiers = this.notifiers.filter(test => {
1783
+ if (test.id === notifier || test === notifier) {
1784
+ internal = test;
1785
+ return false;
1786
+ }
1787
+ return true;
1788
+ });
1789
+
1790
+ if (!internal) {
1791
+ // FIXME: error
1792
+ console.warn('invalid notifier');
1793
+ }
1794
+ else {
1795
+
1796
+ // remove vertex
1797
+ if (internal.vertex) {
1798
+ internal.vertex.Reset();
1799
+ this.RemoveLeafVertex(internal.vertex);
1800
+ }
1801
+
1802
+ }
1803
+
1804
+ }
1805
+ */
1806
+ /* *
1807
+ * update a notifier or notifiers, or the entire list (default).
1808
+ * /
1809
+ protected UpdateNotifiers(notifiers: InternalNotifierType|InternalNotifierType[] = this.notifiers): void {
1810
+
1811
+ if (!Array.isArray(notifiers)) {
1812
+ notifiers = [notifiers];
1813
+ }
1814
+
1815
+ for (const notifier of notifiers) {
1816
+
1817
+ if (notifier.vertex) {
1818
+ notifier.vertex.Reset();
1819
+ }
1820
+ else {
1821
+ notifier.vertex = new LeafVertex();
1822
+ }
1823
+
1824
+ // construct formula (inlining)
1825
+
1826
+ const string_reference = notifier.references.map(reference => {
1827
+
1828
+ // I don't want to go through strings here... OTOH if we build an
1829
+ // expression manually it's going to be fragile to changes in the
1830
+ // parser...
1831
+
1832
+ let sheet_name = '';
1833
+ let base: ICellAddress;
1834
+ let label = '';
1835
+
1836
+ if (reference.count === 1) {
1837
+ base = reference.start;
1838
+ label = Area.CellAddressToLabel(reference.start, false);
1839
+ }
1840
+ else {
1841
+ base = reference.start;
1842
+ label = Area.CellAddressToLabel(reference.start, false) + ':' +
1843
+ Area.CellAddressToLabel(reference.end, false);
1844
+ }
1845
+
1846
+ for (const sheet of this.model.sheets.list) {
1847
+ if (sheet.id === base.sheet_id) {
1848
+ sheet_name = sheet.name;
1849
+ break;
1850
+ }
1851
+ }
1852
+
1853
+ if (!sheet_name) {
1854
+ throw new Error('invalid sheet in reference');
1855
+ }
1856
+
1857
+ if (QuotedSheetNameRegex.test(sheet_name)) {
1858
+ return `'${sheet_name}'!${label}`;
1859
+ }
1860
+
1861
+ return `${sheet_name}!${label}`;
1862
+
1863
+ }).join(',');
1864
+
1865
+ // the function (here "Notify") is never called. we're using a leaf
1866
+ // node, which bypasses the standard calculation system and only updates
1867
+ // a state reference when dirty. so here it's just an arbitrary string.
1868
+
1869
+ // still, we should use something that's not going to be used elsewhere
1870
+ // in the future...
1871
+
1872
+ const formula = `=Internal.Notify(${string_reference})`;
1873
+ // console.info('f', formula);
1874
+
1875
+ // we (theoretically) guarantee that all refeerences are qualified,
1876
+ // so we don't need a context (active sheet) for relative references.
1877
+ // we can just use model[0]
1878
+
1879
+ this.AddLeafVertex(notifier.vertex);
1880
+ this.UpdateLeafVertex(notifier.vertex, formula, this.model.sheets.list[0]);
1881
+
1882
+ // update state (gets reset?)
1883
+
1884
+ notifier.state = notifier.vertex.state_id;
1885
+
1886
+ }
1887
+ }
1888
+ */
1889
+ /* *
1890
+ * new notification API (testing)
1891
+ * /
1892
+ public AddNotifier(references: RangeReference|RangeReference[], notifier: NotifierType, context: Sheet): number {
1893
+
1894
+ if (!Array.isArray(references)) {
1895
+ references = [references];
1896
+ }
1897
+
1898
+ // even if these are strings we want to properly resolve them so
1899
+ // we can store qualified references
1900
+
1901
+ const qualified: Area[] = references.map(reference => {
1902
+
1903
+ if (typeof reference === 'string') {
1904
+ return this.ResolveArea(reference, context).Clone();
1905
+ }
1906
+ if (IsCellAddress(reference)) {
1907
+ return new Area({
1908
+ ...reference,
1909
+ sheet_id: reference.sheet_id || context.id,
1910
+ });
1911
+ }
1912
+
1913
+ return new Area({
1914
+ ...reference.start,
1915
+ sheet_id: reference.start.sheet_id || context.id,
1916
+ }, {
1917
+ ...reference.end,
1918
+ });
1919
+
1920
+ });
1921
+
1922
+ const internal: InternalNotifierType = {
1923
+ id: this.notifier_id_source++,
1924
+ notifier,
1925
+ references: qualified,
1926
+ vertex: new LeafVertex(),
1927
+ state: 0,
1928
+ };
1929
+
1930
+ // update
1931
+ this.UpdateNotifiers(internal);
1932
+
1933
+ // push to notifications
1934
+ this.notifiers.push(internal);
1935
+
1936
+ return internal.id;
1937
+
1938
+ }
1939
+ */
1940
+ Unresolve(ref, context, qualified = true, named = true) {
1941
+ let range = '';
1942
+ const area = IsCellAddress(ref) ? new Area(ref) : new Area(ref.start, ref.end);
1943
+ if (named) {
1944
+ const named_range = this.model.named.MatchSelection(area);
1945
+ if (named_range) {
1946
+ return named_range;
1947
+ }
1948
+ }
1949
+ /*
1950
+ if (area.count > 1) {
1951
+ range = Area.CellAddressToLabel(area.start) + ':' + Area.CellAddressToLabel(area.end);
1952
+ }
1953
+ else {
1954
+ range = Area.CellAddressToLabel(area.start);
1955
+ }
1956
+ */
1957
+ range = area.spreadsheet_label;
1958
+ if (!qualified) {
1959
+ return range;
1960
+ }
1961
+ // is there a function to resolve sheet? actually, don't we know that
1962
+ // the active selection must be on the active sheet? (...)
1963
+ const sheet_id = area.start.sheet_id || context?.id;
1964
+ const sheet_name = this.ResolveSheetName(sheet_id, true);
1965
+ return sheet_name ? sheet_name + '!' + range : range;
1966
+ }
1967
+ /**
1968
+ * FIXME: just add a quote option to the model method and we can drop this function
1969
+ */
1970
+ ResolveSheetName(id, quote = false) {
1971
+ const sheet = this.model.sheets.Find(id);
1972
+ if (sheet) {
1973
+ if (quote && QuotedSheetNameRegex.test(sheet.name)) {
1974
+ return `'${sheet.name}'`;
1975
+ }
1976
+ return sheet.name;
1977
+ }
1978
+ return undefined;
1979
+ }
1980
+ RemoveConditional(conditional) {
1981
+ if (conditional.type === 'expression') {
1982
+ const vertex = conditional.internal?.vertex;
1983
+ if (vertex) {
1984
+ vertex.Reset();
1985
+ this.RemoveLeafVertex(vertex);
1986
+ }
1987
+ }
1988
+ }
1989
+ RemoveConnectedELement(element) {
1990
+ const internal = element.internal;
1991
+ if (internal?.vertex) {
1992
+ this.RemoveLeafVertex(internal.vertex);
1993
+ return true;
1994
+ }
1995
+ return false;
1996
+ }
1997
+ UpdateConnectedElements(context, element) {
1998
+ // we have a problem here in that these elements are not bound
1999
+ // to sheets, so we might have no context. for now we'll
2000
+ // just grab the first sheet, although that's not necessarily
2001
+ // what you want. we should enforce that these have hard sheet
2002
+ // references when created.
2003
+ if (!context) {
2004
+ context = this.model.sheets.list[0];
2005
+ }
2006
+ if (element) {
2007
+ const internal = element.internal;
2008
+ if (internal?.vertex) {
2009
+ this.RemoveLeafVertex(internal.vertex);
2010
+ }
2011
+ }
2012
+ const elements = element ? [element] : this.model.connected_elements.values();
2013
+ for (const element of elements) {
2014
+ let internal = element.internal;
2015
+ if (!internal) {
2016
+ internal = {
2017
+ vertex: new StateLeafVertex(),
2018
+ };
2019
+ element.internal = internal;
2020
+ }
2021
+ const vertex = internal.vertex;
2022
+ this.AddLeafVertex(vertex);
2023
+ this.UpdateLeafVertex(vertex, element.formula, context);
2024
+ }
2025
+ }
2026
+ UpdateConditionals(list, context) {
2027
+ // this method is (1) relying on the leaf vertex Set to avoid duplication,
2028
+ // and (2) leaving orphansed conditionals in place. we should look to
2029
+ // cleaning things up.
2030
+ // is it also (3) adding unecessary calculations (building the expression,
2031
+ // below)?
2032
+ // NOTE: moving all conditional formats into EN-US (i.e. dot-separated).
2033
+ // make sure to evaluate them in this format.
2034
+ if (!list) {
2035
+ // we could in theory remove all of the leaves (the ones we know to
2036
+ // be used for conditionals), because they will be added back below.
2037
+ // how wasteful is that?
2038
+ // or maybe we could change the mark, and then use invalid marks
2039
+ // to check?
2040
+ // the alternative is just to leave them as orphans until the graph
2041
+ // is rebuilt. which is lazy, but probably not that bad...
2042
+ for (const sheet of this.model.sheets.list) {
2043
+ if (sheet.conditional_formats?.length) {
2044
+ this.UpdateConditionals(sheet.conditional_formats, sheet);
2045
+ }
2046
+ }
2047
+ return;
2048
+ }
2049
+ if (!context) {
2050
+ throw new Error('invalid call to update conditionals without context');
2051
+ }
2052
+ if (list && !Array.isArray(list)) {
2053
+ list = [list];
2054
+ }
2055
+ for (const entry of list) {
2056
+ let expression = '';
2057
+ switch (entry.type) {
2058
+ case 'cell-match':
2059
+ if (entry.between) {
2060
+ const addr = this.Unresolve(entry.area, context, true, false);
2061
+ expression = `BETWEEN(${[addr, ...entry.between].join(', ')})`;
2062
+ }
2063
+ else {
2064
+ expression = this.Unresolve(entry.area, context, true, false) + ' ' + entry.expression;
2065
+ }
2066
+ break;
2067
+ case 'expression':
2068
+ expression = entry.expression;
2069
+ break;
2070
+ case 'duplicate-values':
2071
+ expression = `UniqueValues(${this.Unresolve(entry.area, context, true, false)})`;
2072
+ if (!entry.unique) {
2073
+ expression = `NOT(${expression})`;
2074
+ }
2075
+ break;
2076
+ case 'data-bar':
2077
+ case 'gradient':
2078
+ expression = `=Gradient(${[
2079
+ this.Unresolve(entry.area, context, true, false),
2080
+ entry.min ?? '',
2081
+ entry.max ?? '',
2082
+ ...(entry.type === 'data-bar' ? ['TRUE'] : []),
2083
+ ].join(',') // is this correct? are we standardizing i18n? FIXME: check
2084
+ })`;
2085
+ break;
2086
+ default:
2087
+ continue;
2088
+ }
2089
+ if (!expression) {
2090
+ continue; // FIXME: warn?
2091
+ }
2092
+ // console.info({type: entry.type, expression});
2093
+ if (!entry.internal) {
2094
+ entry.internal = {};
2095
+ }
2096
+ if (!entry.internal.vertex) {
2097
+ const vertex = new CalculationLeafVertex();
2098
+ vertex.use = 'conditional';
2099
+ entry.internal.vertex = vertex;
2100
+ let options = {
2101
+ argument_separator: ',',
2102
+ };
2103
+ if (entry.type !== 'gradient' && entry.type !== 'duplicate-values' && entry.type !== 'data-bar') {
2104
+ options = { ...entry.options, ...options };
2105
+ }
2106
+ // first pass, run the calculation
2107
+ const check = this.Evaluate(expression, context, options, true);
2108
+ entry.internal.vertex.result = check;
2109
+ entry.internal.vertex.updated = true;
2110
+ }
2111
+ const vertex = entry.internal.vertex;
2112
+ this.AddLeafVertex(vertex);
2113
+ this.UpdateLeafVertex(vertex, expression, context, DecimalMarkType.Period); // force en-us
2114
+ }
2115
+ }
2116
+ RemoveAnnotation(annotation) {
2117
+ const vertex_data = annotation.temp;
2118
+ if (vertex_data.vertex) {
2119
+ vertex_data.vertex.Reset();
2120
+ this.RemoveLeafVertex(vertex_data.vertex);
2121
+ }
2122
+ }
2123
+ UpdateAnnotations(list, context) {
2124
+ if (!list) {
2125
+ // update: since we don't have access to active_sheet,
2126
+ // just add all annotations. slightly less efficient
2127
+ // (perhaps) but better for handling multiple views.
2128
+ for (const sheet of this.model.sheets.list) {
2129
+ this.UpdateAnnotations(sheet.annotations, sheet);
2130
+ }
2131
+ return;
2132
+ }
2133
+ if (!context) {
2134
+ throw new Error('invalid call to UpdateAnnotations with list but no sheet');
2135
+ }
2136
+ if (typeof list !== 'undefined' && !Array.isArray(list)) {
2137
+ list = [list];
2138
+ }
2139
+ for (const entry of list) {
2140
+ if (entry.data.formula) {
2141
+ const vertex_data = entry.temp;
2142
+ if (!vertex_data.vertex) {
2143
+ vertex_data.vertex = new StateLeafVertex();
2144
+ }
2145
+ this.AddLeafVertex(vertex_data.vertex);
2146
+ this.UpdateLeafVertex(vertex_data.vertex, entry.data.formula, context);
2147
+ }
2148
+ }
2149
+ }
2150
+ // --- protected -------------------------------------------------------------
2151
+ /**
2152
+ * assuming the expression is an address, range, or named range, resolve
2153
+ * to an address/area. returns undefined if the expression can't be resolved.
2154
+ */
2155
+ ResolveExpressionAddress(expr, context) {
2156
+ switch (expr.type) {
2157
+ case 'address':
2158
+ if (this.model.ResolveSheetID(expr, context)) {
2159
+ return new Area(expr);
2160
+ }
2161
+ break;
2162
+ case 'range':
2163
+ if (this.model.ResolveSheetID(expr, context)) {
2164
+ return new Area(expr.start, expr.end);
2165
+ }
2166
+ break;
2167
+ case 'identifier':
2168
+ {
2169
+ const named_range = this.model.GetName(expr.name, context?.sheet_id || 0);
2170
+ if (named_range && named_range.type === 'range') {
2171
+ return new Area(named_range.area.start, named_range.area.end);
2172
+ }
2173
+ }
2174
+ break;
2175
+ }
2176
+ return undefined;
2177
+ }
2178
+ NamedRangeToAddressUnit(unit, context) {
2179
+ const normalized = unit.name.toUpperCase();
2180
+ const named_range = this.model.GetName(normalized, context.sheet_id || 0);
2181
+ if (named_range && named_range.type === 'range') {
2182
+ if (named_range.area.count === 1) {
2183
+ return this.ConstructAddressUnit(named_range.area.start, normalized, unit.id, unit.position);
2184
+ }
2185
+ else {
2186
+ return {
2187
+ type: 'range',
2188
+ start: this.ConstructAddressUnit(named_range.area.start, normalized, unit.id, unit.position),
2189
+ end: this.ConstructAddressUnit(named_range.area.end, normalized, unit.id, unit.position),
2190
+ label: normalized,
2191
+ id: unit.id,
2192
+ position: unit.position,
2193
+ };
2194
+ }
2195
+ }
2196
+ return undefined;
2197
+ }
2198
+ /** named range support */
2199
+ ConstructAddressUnit(address, label, id, position) {
2200
+ return {
2201
+ type: 'address',
2202
+ row: address.row,
2203
+ column: address.column,
2204
+ sheet_id: address.sheet_id,
2205
+ label,
2206
+ id,
2207
+ position,
2208
+ };
2209
+ }
2210
+ /**
2211
+ * rebuild dependencies for a single expression (might be a cell, or an
2212
+ * annotation/leaf node). can recurse on elements, so the return value
2213
+ * is passed through. the first (outer) call can just leave it blank and
2214
+ * use the return value.
2215
+ *
2216
+ * we're adding the sheet name so that (in mc expression calculator) we
2217
+ * can turn address parameters into qualified labels. the normal routine
2218
+ * will just use the ID as the name, that's fine, as long as it's unique
2219
+ * (which it is).
2220
+ *
2221
+ * this might cause issues if we ever try to actually resolve from the
2222
+ * sheet name, though, so (...)
2223
+ *
2224
+ *
2225
+ * Q: why does this not use the parser Walk/Walk2 routine?
2226
+ *
2227
+ */
2228
+ RebuildDependencies(unit, relative_sheet_id, relative_sheet_name, dependencies = { addresses: {}, ranges: {} }, context_address) {
2229
+ if (!relative_sheet_name) {
2230
+ const sheet = this.model.sheets.Find(relative_sheet_id);
2231
+ if (sheet) {
2232
+ relative_sheet_name = sheet.name;
2233
+ }
2234
+ }
2235
+ switch (unit.type) {
2236
+ case 'literal':
2237
+ case 'missing':
2238
+ case 'operator':
2239
+ break;
2240
+ case 'identifier':
2241
+ {
2242
+ // update to handle named expressions. just descend into
2243
+ // the expression as if it were inline.
2244
+ const fetched = this.model.GetName(unit.name, context_address.sheet_id || 0);
2245
+ if (fetched?.type === 'expression') {
2246
+ this.RebuildDependencies(fetched.expression, relative_sheet_id, relative_sheet_name, dependencies, context_address);
2247
+ }
2248
+ else {
2249
+ const resolved = this.NamedRangeToAddressUnit(unit, context_address);
2250
+ if (resolved) {
2251
+ if (resolved.type === 'address') {
2252
+ dependencies.addresses[resolved.label] = resolved;
2253
+ }
2254
+ else {
2255
+ dependencies.ranges[resolved.label] = resolved;
2256
+ }
2257
+ }
2258
+ }
2259
+ }
2260
+ break;
2261
+ case 'structured-reference':
2262
+ // when building the graph, resolve the reference to the table.
2263
+ // this is the same thing we do in expression-calculator, and
2264
+ // we rely on the same rules to ensure that the reference either
2265
+ // stays consitent, or gets rebuilt.
2266
+ {
2267
+ const resolved = this.model.ResolveStructuredReference(unit, context_address);
2268
+ if (resolved) {
2269
+ if (resolved.type === 'address') {
2270
+ dependencies.addresses[resolved.sheet_id + '!' + resolved.label] = resolved;
2271
+ }
2272
+ else {
2273
+ dependencies.ranges[resolved.label] = resolved;
2274
+ }
2275
+ }
2276
+ const table = this.model.tables.get(unit.table.toLowerCase());
2277
+ if (table) {
2278
+ // see ResolveStructuredReference in expression calculator
2279
+ const row = context_address.row; // "this row"
2280
+ if (row < table.area.start.row || row > table.area.end.row) {
2281
+ break;
2282
+ }
2283
+ const reference_column = unit.column.toLowerCase();
2284
+ let column = -1;
2285
+ if (table.columns) { // FIXME: make this required
2286
+ for (let i = 0; i < table.columns.length; i++) {
2287
+ if (reference_column === table.columns[i]) {
2288
+ column = table.area.start.column + i;
2289
+ break;
2290
+ }
2291
+ }
2292
+ }
2293
+ if (column >= 0) {
2294
+ // does using the original label here, instead of a sheet
2295
+ // address as label, mean we potentially have multiple
2296
+ // references to the same cell? probably...
2297
+ const address = {
2298
+ label: unit.label,
2299
+ type: 'address',
2300
+ row,
2301
+ column,
2302
+ sheet_id: table.area.start.sheet_id,
2303
+ id: unit.id,
2304
+ position: unit.position,
2305
+ };
2306
+ dependencies.addresses[address.sheet_id + '!' + address.label] = address;
2307
+ }
2308
+ }
2309
+ }
2310
+ break;
2311
+ case 'address':
2312
+ if (!unit.sheet_id) {
2313
+ if (unit.sheet) {
2314
+ const sheet = this.model.sheets.Find(unit.sheet);
2315
+ if (sheet) {
2316
+ unit.sheet_id = sheet.id;
2317
+ }
2318
+ }
2319
+ else {
2320
+ unit.sheet_id = relative_sheet_id;
2321
+ unit.sheet = relative_sheet_name;
2322
+ }
2323
+ /*
2324
+ unit.sheet_id = unit.sheet ?
2325
+ (sheet_name_map[unit.sheet.toLowerCase()] || 0) :
2326
+ relative_sheet_id;
2327
+ if (!unit.sheet) { unit.sheet = relative_sheet_name; }
2328
+ */
2329
+ }
2330
+ if (!unit.sheet_id) {
2331
+ // FIXME: we don't necessarily need to warn here, because we'll
2332
+ // get a warning when it tries to calculate. still this is helpful
2333
+ // for debugging.
2334
+ console.warn('invalid address in range [9d]');
2335
+ }
2336
+ else {
2337
+ dependencies.addresses[unit.sheet_id + '!' + unit.label] = unit;
2338
+ }
2339
+ break; // this.AddressLabel(unit, offset);
2340
+ case 'range':
2341
+ if (!unit.start.sheet_id) {
2342
+ if (unit.start.sheet) {
2343
+ const sheet = this.model.sheets.Find(unit.start.sheet);
2344
+ if (sheet) {
2345
+ unit.start.sheet_id = sheet.id;
2346
+ }
2347
+ }
2348
+ else {
2349
+ unit.start.sheet_id = relative_sheet_id;
2350
+ unit.start.sheet = relative_sheet_name;
2351
+ }
2352
+ /*
2353
+ unit.start.sheet_id = unit.start.sheet ?
2354
+ (sheet_name_map[unit.start.sheet.toLowerCase()] || 0) :
2355
+ relative_sheet_id;
2356
+ if (!unit.start.sheet) { unit.start.sheet = relative_sheet_name; }
2357
+ */
2358
+ }
2359
+ if (!unit.start.sheet_id) {
2360
+ // see above in the address handler
2361
+ console.warn('invalid sheet in range', unit);
2362
+ }
2363
+ else {
2364
+ dependencies.ranges[unit.start.sheet_id + '!' + unit.start.label + ':' + unit.end.label] = unit;
2365
+ }
2366
+ break;
2367
+ case 'unary':
2368
+ this.RebuildDependencies(unit.operand, relative_sheet_id, relative_sheet_name, dependencies, context_address); //, sheet_name_map);
2369
+ break;
2370
+ case 'binary':
2371
+ this.RebuildDependencies(unit.left, relative_sheet_id, relative_sheet_name, dependencies, context_address); //, sheet_name_map);
2372
+ this.RebuildDependencies(unit.right, relative_sheet_id, relative_sheet_name, dependencies, context_address); //, sheet_name_map);
2373
+ break;
2374
+ case 'group':
2375
+ unit.elements.forEach((element) => this.RebuildDependencies(element, relative_sheet_id, relative_sheet_name, dependencies, context_address)); //, sheet_name_map));
2376
+ break;
2377
+ case 'implicit-call':
2378
+ {
2379
+ for (const arg of unit.args) {
2380
+ this.RebuildDependencies(arg, relative_sheet_id, relative_sheet_name, dependencies, context_address);
2381
+ }
2382
+ this.RebuildDependencies(unit.call, relative_sheet_id, relative_sheet_name, dependencies, context_address);
2383
+ }
2384
+ break;
2385
+ case 'call':
2386
+ // this is where we diverge. if there's a known function that has
2387
+ // an "address" parameter, we don't treat it as a dependency. this is
2388
+ // to support our weird MV syntax (weird here, but useful in Excel).
2389
+ // UPDATE: this is broadly useful for some other functions, like OFFSET.
2390
+ {
2391
+ const args = unit.args.slice(0);
2392
+ const func = this.library.Get(unit.name);
2393
+ if (func && func.arguments) {
2394
+ func.arguments.forEach((descriptor, index) => {
2395
+ if (descriptor && descriptor.address) {
2396
+ // we still want to fix sheet addresses, though, even if we're
2397
+ // not tracking the dependency. to do that, we can recurse with
2398
+ // a new (empty) dependency list, and just drop the new list
2399
+ this.RebuildDependencies(args[index], relative_sheet_id, relative_sheet_name, undefined, context_address); //, sheet_name_map);
2400
+ args[index] = { type: 'missing', id: -1 };
2401
+ }
2402
+ });
2403
+ }
2404
+ args.forEach((arg) => this.RebuildDependencies(arg, relative_sheet_id, relative_sheet_name, dependencies, context_address)); //, sheet_name_map));
2405
+ }
2406
+ break;
2407
+ }
2408
+ return dependencies;
2409
+ }
2410
+ UpdateLeafVertex(vertex, formula, context, decimal_mark) {
2411
+ vertex.Reset();
2412
+ if (decimal_mark) {
2413
+ this.parser.Save();
2414
+ this.parser.SetLocaleSettings(decimal_mark);
2415
+ }
2416
+ const parse_result = this.parser.Parse(formula);
2417
+ if (parse_result.expression) {
2418
+ const dependencies = this.RebuildDependencies(parse_result.expression,
2419
+ // this.model.active_sheet.id,
2420
+ // this.model.active_sheet.name,
2421
+ context.id, context.name, undefined, { row: 0, column: 0 });
2422
+ for (const key of Object.keys(dependencies.ranges)) {
2423
+ const unit = dependencies.ranges[key];
2424
+ const range = new Area(unit.start, unit.end);
2425
+ if (range.entire_column || range.entire_row || range.count > 1) {
2426
+ // this.AddLeafVertexEdge(range.start, vertex);
2427
+ this.AddLeafVertexArrayEdge(range, vertex);
2428
+ }
2429
+ else {
2430
+ this.AddLeafVertexEdge(range.start, vertex);
2431
+ }
2432
+ /*
2433
+ for (const address of range) {
2434
+ this.AddLeafVertexEdge(address, vertex);
2435
+ }
2436
+ */
2437
+ }
2438
+ for (const key of Object.keys(dependencies.addresses)) {
2439
+ const address = dependencies.addresses[key];
2440
+ this.AddLeafVertexEdge(address, vertex);
2441
+ }
2442
+ }
2443
+ vertex.expression = parse_result.expression || { type: 'missing', id: -1 };
2444
+ vertex.expression_error = !parse_result.valid;
2445
+ // vertex.UpdateState();
2446
+ if (decimal_mark) {
2447
+ this.parser.Restore();
2448
+ }
2449
+ }
2450
+ /**
2451
+ *
2452
+ */
2453
+ RebuildGraphCell(cell, address) {
2454
+ // FIXME/TODO: if spill is not enabled, we'll need to clean up
2455
+ // rendered values from the spill here
2456
+ if (cell.spill) {
2457
+ if (this.options.spill) {
2458
+ if (cell.spill.start.row === address.row && cell.spill.start.column === address.column) {
2459
+ // this.spills.push(new Area(cell.spill.start, cell.spill.end));
2460
+ this.AttachSpillData(new Area(cell.spill.start, cell.spill.end));
2461
+ }
2462
+ else {
2463
+ // ...
2464
+ this.AddEdge(cell.spill.start, address);
2465
+ }
2466
+ }
2467
+ }
2468
+ // array head
2469
+ if (cell.area && cell.area.start.column === address.column && cell.area.start.row === address.row) {
2470
+ const { start, end } = cell.area;
2471
+ const sheet_id = start.sheet_id || address.sheet_id; // ... should always be ===
2472
+ if (!start.sheet_id) {
2473
+ start.sheet_id = sheet_id;
2474
+ }
2475
+ for (let column = start.column; column <= end.column; column++) {
2476
+ for (let row = start.row; row <= end.row; row++) {
2477
+ this.ResetInbound({ column, row, sheet_id }, true, false); // set dirty, don't create
2478
+ }
2479
+ }
2480
+ this.SetDirty(address); // implicitly creates vertex for array head (if it doesn't already exist)
2481
+ // implicit vertices from array head -> array members. this is required
2482
+ // to correctly propagate dirtiness if a referenced cell changes state
2483
+ // from array -> !array and vice-versa
2484
+ for (let column = start.column; column <= end.column; column++) {
2485
+ for (let row = start.row; row <= end.row; row++) {
2486
+ if (row === start.row && column === start.column) {
2487
+ continue;
2488
+ }
2489
+ this.AddEdge(start, { ...start, row, column });
2490
+ }
2491
+ }
2492
+ }
2493
+ // formula?
2494
+ if (cell.type === ValueType.formula) {
2495
+ this.ResetInbound(address, true); // NOTE: sets dirty AND creates vertex if it doesn't exist
2496
+ const parse_result = this.parser.Parse(cell.value);
2497
+ // we have a couple of "magic" functions that can have loops
2498
+ // but shouldn't trigger circular references. we need to check
2499
+ // for those here...
2500
+ if (parse_result.expression) {
2501
+ // some functions have UI - render and/or click. we want to attach
2502
+ // those to the cell. this is complicated in the case of lambdas.
2503
+ // case 1: library function. check directly.
2504
+ // case 2: inline lambda. check the last parameter.
2505
+ // case 3: indirect lambda (implicit-call), also
2506
+ // case 4: named lambda: resolve, then handle like a lambda.
2507
+ // AND NOTE, these could be nested... ?
2508
+ // case 5: this is _not_ a pass through situation: an explicit call
2509
+ // to lambda, which just defines a function.
2510
+ let descriptor;
2511
+ let base = parse_result.expression;
2512
+ if (base?.type === 'implicit-call') {
2513
+ // if this is a function, it's easy
2514
+ if (base.call.type === 'call') {
2515
+ base = base.call;
2516
+ }
2517
+ else { // else if (base.call.type === 'address') {
2518
+ // if this is an address, we'll have to resolve it...
2519
+ // I don't _think_ we need to handle any other types here
2520
+ // TODO/FIXME
2521
+ }
2522
+ }
2523
+ else if (base?.type === 'call') {
2524
+ // handle case 5. top-level call, but indirect. this is the
2525
+ // case for lambdas defined in cell functions but not _called_
2526
+ descriptor = this.library.Get(base.name);
2527
+ if (descriptor?.pass_through_ui === 'indirect') {
2528
+ base = undefined;
2529
+ }
2530
+ }
2531
+ while (base?.type === 'call') {
2532
+ descriptor = this.library.Get(base.name);
2533
+ if (descriptor) {
2534
+ if (descriptor.pass_through_ui && base.args.length) {
2535
+ base = base.args[base.args.length - 1];
2536
+ }
2537
+ else {
2538
+ break;
2539
+ }
2540
+ }
2541
+ else {
2542
+ const named = this.model.GetName(base.name, address.sheet_id);
2543
+ if (named?.type === 'expression') {
2544
+ base = named.expression;
2545
+ }
2546
+ else {
2547
+ break;
2548
+ }
2549
+ }
2550
+ }
2551
+ if (descriptor) {
2552
+ cell.render_function = descriptor.render;
2553
+ cell.click_function = descriptor.click;
2554
+ }
2555
+ /*
2556
+ if (parse_result.expression.type === 'call') {
2557
+
2558
+ descriptor = this.library.Get(parse_result.expression.name);
2559
+ if (!descriptor) {
2560
+ const named = this.model.GetName(parse_result.expression.name, address.sheet_id);
2561
+ if (named?.type === 'expression' && named.expression.type === 'call') {
2562
+ descriptor = this.library.Get(named.expression.name);
2563
+ console.info("D", descriptor);
2564
+ }
2565
+ }
2566
+
2567
+ if (descriptor) {
2568
+ cell.render_function = descriptor.render;
2569
+ cell.click_function = descriptor.click;
2570
+ }
2571
+
2572
+ }
2573
+ */
2574
+ /*
2575
+ if (parse_result.expression.type === 'call') {
2576
+ const func = this.library.Get(parse_result.expression.name);
2577
+ if (func) {
2578
+
2579
+ cell.render_function = func.render;
2580
+ cell.click_function = func.click;
2581
+
2582
+ if (func.pass_through_ui) {
2583
+ // ...
2584
+ }
2585
+
2586
+ }
2587
+ else {
2588
+ const named = this.model.GetName(parse_result.expression.name, address.sheet_id);
2589
+ if (named?.type === 'expression') {
2590
+ if (named.expression.type === 'call') {
2591
+ const func = this.library.Get(named.expression.name);
2592
+ if (func?.pass_through_ui && named.expression.args.length) {
2593
+
2594
+ // check the _last_ argument
2595
+ const last = named.expression.args[named.expression.args.length - 1];
2596
+ console.info({last});
2597
+
2598
+ }
2599
+ }
2600
+ }
2601
+ }
2602
+ }
2603
+ */
2604
+ const dependencies = this.RebuildDependencies(parse_result.expression, address.sheet_id, '', undefined, address); // cell.sheet_id);
2605
+ for (const key of Object.keys(dependencies.ranges)) {
2606
+ const unit = dependencies.ranges[key];
2607
+ const range = new Area(unit.start, unit.end);
2608
+ // testing out array vertices (vertices that represent ranges).
2609
+ // this is an effort to reduce the number of vertices in the graph,
2610
+ // especially since these are generally unecessary (except for
2611
+ // formula cells).
2612
+ // if you want to drop this, go back to the non-array code below
2613
+ // and it should go back to the old way (but there will still be
2614
+ // some cruft in graph.ts, tests that will need to be removed).
2615
+ // actually it's probably something that could be balanced based
2616
+ // on the number of constants vs the number of formulae in the
2617
+ // range. more (or all) constants, use a range. more/all formula,
2618
+ // iterate.
2619
+ // --- array version -----------------------------------------------
2620
+ /*
2621
+ const status = this.AddArrayVertexEdge(range, cell);
2622
+
2623
+ if (status !== GraphStatus.OK) {
2624
+ global_status = status;
2625
+ if (!initial_reference) initial_reference = { ...cell };
2626
+ }
2627
+ */
2628
+ // --- non-array version -------------------------------------------
2629
+ /*
2630
+ range.Iterate((target: ICellAddress) => {
2631
+ this.AddEdge(target, address);
2632
+ });
2633
+ */
2634
+ // --- trying again... ---------------------------------------------
2635
+ if (range.entire_row || range.entire_column) {
2636
+ this.AddArrayEdge(range, address);
2637
+ }
2638
+ else {
2639
+ for (const target of range) {
2640
+ this.AddEdge(target, address);
2641
+ }
2642
+ // range.Iterate((target: ICellAddress) => this.AddEdge(target, address));
2643
+ }
2644
+ // --- end ---------------------------------------------------------
2645
+ }
2646
+ for (const key of Object.keys(dependencies.addresses)) {
2647
+ const dependency = dependencies.addresses[key];
2648
+ this.AddEdge(dependency, address);
2649
+ }
2650
+ }
2651
+ const vertex = this.GetVertex(address, true);
2652
+ if (vertex) {
2653
+ vertex.expression = parse_result.expression || { type: 'missing', id: -1 };
2654
+ vertex.expression_error = !parse_result.valid;
2655
+ }
2656
+ }
2657
+ else if (cell.value !== cell.calculated) {
2658
+ // sets dirty and removes inbound edges (in case the cell
2659
+ // previously contained a formula and now it contains a constant).
2660
+ this.ResetInbound(address, true, false); // NOTE: sets dirty
2661
+ }
2662
+ else if (cell.type === ValueType.undefined) {
2663
+ // in the new framework, we get here on any cleared cell, but
2664
+ // the behavior is OK
2665
+ // if we get here, it means that this cell was cleared but is not
2666
+ // 'empty'; in practice, that means it has a merge cell. reset inbound
2667
+ // and set dirty.
2668
+ // is this unecessarily flagging a number of cells? (...)
2669
+ this.ResetInbound(address, true, false, true);
2670
+ // we should be able to remove this vertex altogether; watch
2671
+ // out for arrays here
2672
+ // this.RemoveVertex(address); // implicit
2673
+ }
2674
+ else {
2675
+ // the reason you never get here is that the standard case is
2676
+ // value !== calculated. if you enter a constant, we flush
2677
+ // calculated first; so while the value doesn't change, it no
2678
+ // longer === calculated.
2679
+ // actually we do get here in the case of an array head with
2680
+ // a constant value. so we should stop shouting about it.
2681
+ // this is just a constant?
2682
+ // console.warn('UNHANDLED CASE', cell);
2683
+ }
2684
+ }
2685
+ /**
2686
+ * rebuild the graph; parse expressions, build a dependency map,
2687
+ * initialize edges between nodes.
2688
+ *
2689
+ * FIXME: if we want to compose functions, we could do that here,
2690
+ * which might result in some savings [?]
2691
+ */
2692
+ RebuildGraph(subset) {
2693
+ if (subset) {
2694
+ if (!subset.start.sheet_id) {
2695
+ throw new Error('subset missing sheet id');
2696
+ }
2697
+ // const cells = this.cells_map[subset.start.sheet_id];
2698
+ const cells = this.model.sheets.Find(subset.start.sheet_id)?.cells;
2699
+ if (cells) {
2700
+ for (let row = subset.start.row; row <= subset.end.row; row++) {
2701
+ const row_array = cells.data[row];
2702
+ if (row_array) {
2703
+ for (let column = subset.start.column; column <= subset.end.column; column++) {
2704
+ const cell = row_array[column];
2705
+ if (cell) {
2706
+ this.RebuildGraphCell(cell, { row, column, sheet_id: subset.start.sheet_id });
2707
+ }
2708
+ }
2709
+ }
2710
+ }
2711
+ }
2712
+ }
2713
+ else {
2714
+ for (const sheet of this.model.sheets.list || []) {
2715
+ const rows = sheet.cells.data.length;
2716
+ for (let row = 0; row < rows; row++) {
2717
+ const row_array = sheet.cells.data[row];
2718
+ if (row_array) {
2719
+ const columns = row_array.length;
2720
+ for (let column = 0; column < columns; column++) {
2721
+ const cell = row_array[column];
2722
+ if (cell) {
2723
+ this.RebuildGraphCell(cell, { row, column, sheet_id: sheet.id });
2724
+ }
2725
+ }
2726
+ }
2727
+ }
2728
+ }
2729
+ }
2730
+ }
2731
+ /*
2732
+ protected IsNativeOrTypedArray(val: unknown): boolean {
2733
+ return Array.isArray(val) || (val instanceof Float64Array) || (val instanceof Float32Array);
2734
+ }
2735
+ */
2736
+ /**
2737
+ * check if a cell is volatile. normally this falls out of the calculation,
2738
+ * but if we build the graph and set values explicitly, we need to check.
2739
+ */
2740
+ CheckVolatile(vertex) {
2741
+ if (!vertex.expression || vertex.expression_error)
2742
+ return false;
2743
+ let volatile = false;
2744
+ this.parser.Walk(vertex.expression, (unit) => {
2745
+ if (unit.type === 'call') {
2746
+ const func = this.library.Get(unit.name);
2747
+ if (func && func.volatile)
2748
+ volatile = true;
2749
+ }
2750
+ return !volatile; // short circuit
2751
+ });
2752
+ return volatile;
2753
+ }
2754
+ }
2755
+ //# sourceMappingURL=calculator.js.map