@dou-ui/sheet 0.0.0-stage → 0.1.0-alpha.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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/cell-types.js +20 -0
  4. package/dist/cell-types.js.map +1 -0
  5. package/dist/code/code-formula-bar.js +6 -0
  6. package/dist/code/code-formula-bar.js.map +1 -0
  7. package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js +192 -0
  8. package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  9. package/dist/code/index.js +12 -0
  10. package/dist/code/index.js.map +1 -0
  11. package/dist/code/language.js +51 -0
  12. package/dist/code/language.js.map +1 -0
  13. package/dist/code/style.d.ts +1 -0
  14. package/dist/code/style.js +2 -0
  15. package/dist/collaboration/anchor.js +143 -0
  16. package/dist/collaboration/anchor.js.map +1 -0
  17. package/dist/collaboration/index.js +770 -0
  18. package/dist/collaboration/index.js.map +1 -0
  19. package/dist/collaboration/peers.js +87 -0
  20. package/dist/collaboration/peers.js.map +1 -0
  21. package/dist/collaboration/shared.js +450 -0
  22. package/dist/collaboration/shared.js.map +1 -0
  23. package/dist/components/cell-panel.js +6 -0
  24. package/dist/components/cell-panel.js.map +1 -0
  25. package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js +70 -0
  26. package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  27. package/dist/components/cell-renders.js +40 -0
  28. package/dist/components/cell-renders.js.map +1 -0
  29. package/dist/components/date-panel.js +6 -0
  30. package/dist/components/date-panel.js.map +1 -0
  31. package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js +48 -0
  32. package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  33. package/dist/components/filter-panel.js +6 -0
  34. package/dist/components/filter-panel.js.map +1 -0
  35. package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js +259 -0
  36. package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  37. package/dist/components/format-dialog.js +6 -0
  38. package/dist/components/format-dialog.js.map +1 -0
  39. package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js +432 -0
  40. package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/components/format-list.js +6 -0
  42. package/dist/components/format-list.js.map +1 -0
  43. package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js +187 -0
  44. package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/components/format-presets.js +97 -0
  46. package/dist/components/format-presets.js.map +1 -0
  47. package/dist/components/option-panel.js +6 -0
  48. package/dist/components/option-panel.js.map +1 -0
  49. package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js +117 -0
  50. package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  51. package/dist/components/sheet-comment.js +6 -0
  52. package/dist/components/sheet-comment.js.map +1 -0
  53. package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js +104 -0
  54. package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js.map +1 -0
  55. package/dist/components/sheet-formula-bar.js +6 -0
  56. package/dist/components/sheet-formula-bar.js.map +1 -0
  57. package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js +124 -0
  58. package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  59. package/dist/components/sheet-link.js +6 -0
  60. package/dist/components/sheet-link.js.map +1 -0
  61. package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js +249 -0
  62. package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/components/sheet-menu.js +6 -0
  64. package/dist/components/sheet-menu.js.map +1 -0
  65. package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js +115 -0
  66. package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  67. package/dist/components/sheet-name-box.js +6 -0
  68. package/dist/components/sheet-name-box.js.map +1 -0
  69. package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js +91 -0
  70. package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js.map +1 -0
  71. package/dist/components/sheet-note.js +6 -0
  72. package/dist/components/sheet-note.js.map +1 -0
  73. package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js +146 -0
  74. package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js.map +1 -0
  75. package/dist/components/sheet-outline.js +6 -0
  76. package/dist/components/sheet-outline.js.map +1 -0
  77. package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js +237 -0
  78. package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  79. package/dist/components/sheet-tabs.js +6 -0
  80. package/dist/components/sheet-tabs.js.map +1 -0
  81. package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js +487 -0
  82. package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js.map +1 -0
  83. package/dist/components/sheet-toolbar.js +6 -0
  84. package/dist/components/sheet-toolbar.js.map +1 -0
  85. package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js +1562 -0
  86. package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  87. package/dist/components/validation-dialog.js +6 -0
  88. package/dist/components/validation-dialog.js.map +1 -0
  89. package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js +370 -0
  90. package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  91. package/dist/context.js +12 -0
  92. package/dist/context.js.map +1 -0
  93. package/dist/core/address.js +256 -0
  94. package/dist/core/address.js.map +1 -0
  95. package/dist/core/axis.js +72 -0
  96. package/dist/core/axis.js.map +1 -0
  97. package/dist/core/cell-type.js +30 -0
  98. package/dist/core/cell-type.js.map +1 -0
  99. package/dist/core/clipboard.js +141 -0
  100. package/dist/core/clipboard.js.map +1 -0
  101. package/dist/core/commands.js +63 -0
  102. package/dist/core/commands.js.map +1 -0
  103. package/dist/core/comments.js +74 -0
  104. package/dist/core/comments.js.map +1 -0
  105. package/dist/core/conditional.js +362 -0
  106. package/dist/core/conditional.js.map +1 -0
  107. package/dist/core/csv.js +191 -0
  108. package/dist/core/csv.js.map +1 -0
  109. package/dist/core/data.js +118 -0
  110. package/dist/core/data.js.map +1 -0
  111. package/dist/core/document.js +187 -0
  112. package/dist/core/document.js.map +1 -0
  113. package/dist/core/extension.js +60 -0
  114. package/dist/core/extension.js.map +1 -0
  115. package/dist/core/file.js +336 -0
  116. package/dist/core/file.js.map +1 -0
  117. package/dist/core/fill.js +153 -0
  118. package/dist/core/fill.js.map +1 -0
  119. package/dist/core/find.js +42 -0
  120. package/dist/core/find.js.map +1 -0
  121. package/dist/core/format.js +404 -0
  122. package/dist/core/format.js.map +1 -0
  123. package/dist/core/group.js +134 -0
  124. package/dist/core/group.js.map +1 -0
  125. package/dist/core/history.js +68 -0
  126. package/dist/core/history.js.map +1 -0
  127. package/dist/core/index.js +17 -0
  128. package/dist/core/link.js +34 -0
  129. package/dist/core/link.js.map +1 -0
  130. package/dist/core/merge.js +105 -0
  131. package/dist/core/merge.js.map +1 -0
  132. package/dist/core/model.js +48 -0
  133. package/dist/core/model.js.map +1 -0
  134. package/dist/core/nav.js +44 -0
  135. package/dist/core/nav.js.map +1 -0
  136. package/dist/core/paint.js +106 -0
  137. package/dist/core/paint.js.map +1 -0
  138. package/dist/core/plain-cell-types.js +54 -0
  139. package/dist/core/plain-cell-types.js.map +1 -0
  140. package/dist/core/protect.js +246 -0
  141. package/dist/core/protect.js.map +1 -0
  142. package/dist/core/selection.js +37 -0
  143. package/dist/core/selection.js.map +1 -0
  144. package/dist/core/transaction.js +1582 -0
  145. package/dist/core/transaction.js.map +1 -0
  146. package/dist/core/validation.js +135 -0
  147. package/dist/core/validation.js.map +1 -0
  148. package/dist/core/versions.js +161 -0
  149. package/dist/core/versions.js.map +1 -0
  150. package/dist/export/color.js +36 -0
  151. package/dist/export/color.js.map +1 -0
  152. package/dist/export/draw.js +316 -0
  153. package/dist/export/draw.js.map +1 -0
  154. package/dist/export/index.js +390 -0
  155. package/dist/export/index.js.map +1 -0
  156. package/dist/formula/edit.js +210 -0
  157. package/dist/formula/edit.js.map +1 -0
  158. package/dist/formula/engine.js +526 -0
  159. package/dist/formula/engine.js.map +1 -0
  160. package/dist/formula/error.js +39 -0
  161. package/dist/formula/error.js.map +1 -0
  162. package/dist/formula/functions.js +1493 -0
  163. package/dist/formula/functions.js.map +1 -0
  164. package/dist/formula/help.js +408 -0
  165. package/dist/formula/help.js.map +1 -0
  166. package/dist/formula/index.js +7 -0
  167. package/dist/formula/parse.js +369 -0
  168. package/dist/formula/parse.js.map +1 -0
  169. package/dist/formula/values.js +123 -0
  170. package/dist/formula/values.js.map +1 -0
  171. package/dist/formulas.js +30 -0
  172. package/dist/formulas.js.map +1 -0
  173. package/dist/index.js +39 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/locale.js +1312 -0
  176. package/dist/locale.js.map +1 -0
  177. package/dist/sheet.js +6 -0
  178. package/dist/sheet.js.map +1 -0
  179. package/dist/sheet.vue_vue_type_script_setup_true_lang.js +7166 -0
  180. package/dist/sheet.vue_vue_type_script_setup_true_lang.js.map +1 -0
  181. package/dist/style.css +1 -0
  182. package/dist/style.d.ts +1 -0
  183. package/dist/style.js +17 -0
  184. package/dist/types/cell-types.d.ts +2 -0
  185. package/dist/types/code/code-formula-bar.vue.d.ts +3 -0
  186. package/dist/types/code/index.d.ts +12 -0
  187. package/dist/types/code/language.d.ts +13 -0
  188. package/dist/types/collaboration/anchor.d.ts +65 -0
  189. package/dist/types/collaboration/index.d.ts +59 -0
  190. package/dist/types/collaboration/peers.d.ts +21 -0
  191. package/dist/types/collaboration/shared.d.ts +75 -0
  192. package/dist/types/components/cell-panel.vue.d.ts +20 -0
  193. package/dist/types/components/cell-renders.d.ts +9 -0
  194. package/dist/types/components/date-panel.vue.d.ts +9 -0
  195. package/dist/types/components/filter-panel.vue.d.ts +21 -0
  196. package/dist/types/components/format-dialog.vue.d.ts +24 -0
  197. package/dist/types/components/format-list.vue.d.ts +25 -0
  198. package/dist/types/components/format-presets.d.ts +30 -0
  199. package/dist/types/components/option-panel.vue.d.ts +13 -0
  200. package/dist/types/components/sheet-comment.vue.d.ts +29 -0
  201. package/dist/types/components/sheet-formula-bar.vue.d.ts +3 -0
  202. package/dist/types/components/sheet-link.vue.d.ts +32 -0
  203. package/dist/types/components/sheet-menu.vue.d.ts +20 -0
  204. package/dist/types/components/sheet-name-box.vue.d.ts +3 -0
  205. package/dist/types/components/sheet-note.vue.d.ts +22 -0
  206. package/dist/types/components/sheet-outline.vue.d.ts +36 -0
  207. package/dist/types/components/sheet-tabs.vue.d.ts +9 -0
  208. package/dist/types/components/sheet-toolbar.vue.d.ts +11 -0
  209. package/dist/types/components/validation-dialog.vue.d.ts +25 -0
  210. package/dist/types/context.d.ts +189 -0
  211. package/dist/types/core/address.d.ts +109 -0
  212. package/dist/types/core/axis.d.ts +23 -0
  213. package/dist/types/core/cell-type.d.ts +157 -0
  214. package/dist/types/core/clipboard.d.ts +48 -0
  215. package/dist/types/core/commands.d.ts +76 -0
  216. package/dist/types/core/comments.d.ts +22 -0
  217. package/dist/types/core/conditional.d.ts +114 -0
  218. package/dist/types/core/csv.d.ts +93 -0
  219. package/dist/types/core/data.d.ts +26 -0
  220. package/dist/types/core/document.d.ts +49 -0
  221. package/dist/types/core/extension.d.ts +177 -0
  222. package/dist/types/core/file.d.ts +301 -0
  223. package/dist/types/core/fill.d.ts +23 -0
  224. package/dist/types/core/find.d.ts +39 -0
  225. package/dist/types/core/format.d.ts +47 -0
  226. package/dist/types/core/group.d.ts +76 -0
  227. package/dist/types/core/history.d.ts +27 -0
  228. package/dist/types/core/index.d.ts +33 -0
  229. package/dist/types/core/link.d.ts +14 -0
  230. package/dist/types/core/merge.d.ts +26 -0
  231. package/dist/types/core/model.d.ts +363 -0
  232. package/dist/types/core/nav.d.ts +15 -0
  233. package/dist/types/core/paint.d.ts +19 -0
  234. package/dist/types/core/plain-cell-types.d.ts +6 -0
  235. package/dist/types/core/protect.d.ts +34 -0
  236. package/dist/types/core/selection.d.ts +25 -0
  237. package/dist/types/core/transaction.d.ts +470 -0
  238. package/dist/types/core/validation.d.ts +55 -0
  239. package/dist/types/core/versions.d.ts +38 -0
  240. package/dist/types/export/color.d.ts +7 -0
  241. package/dist/types/export/draw.d.ts +98 -0
  242. package/dist/types/export/index.d.ts +47 -0
  243. package/dist/types/formula/edit.d.ts +62 -0
  244. package/dist/types/formula/engine.d.ts +99 -0
  245. package/dist/types/formula/error.d.ts +30 -0
  246. package/dist/types/formula/functions.d.ts +62 -0
  247. package/dist/types/formula/help.d.ts +12 -0
  248. package/dist/types/formula/index.d.ts +13 -0
  249. package/dist/types/formula/parse.d.ts +92 -0
  250. package/dist/types/formula/values.d.ts +65 -0
  251. package/dist/types/formulas.d.ts +32 -0
  252. package/dist/types/index.d.ts +225 -0
  253. package/dist/types/locale.d.ts +531 -0
  254. package/dist/types/sheet.vue.d.ts +60 -0
  255. package/dist/types/types.d.ts +498 -0
  256. package/dist/types/use-comments.d.ts +47 -0
  257. package/dist/types/use-messages.d.ts +7 -0
  258. package/dist/use-comments.js +130 -0
  259. package/dist/use-comments.js.map +1 -0
  260. package/dist/use-messages.js +29 -0
  261. package/dist/use-messages.js.map +1 -0
  262. package/package.json +93 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit.js","names":[],"sources":["../../src/formula/edit.ts"],"sourcesContent":["import type { SheetBounds } from '../core/address'\nimport { formulaTokens, type SheetFormulaToken } from './parse'\n\n/*\n * What editing a formula needs to know about the text being typed: where\n * its references are, whether a reference can go at the caret, which\n * function call the caret is in, and the name being typed.\n */\n\n/** How many colors references cycle through while a formula is edited. */\nexport const REF_COLORS = 7\n\n/** A reference in a formula being edited, with the color it is marked in. */\nexport interface SheetFormulaRefMark {\n start: number\n end: number\n text: string\n /** The sheet it names, as written; `undefined` for the formula's own sheet. */\n sheet?: string\n bounds: SheetBounds\n /** From 0; the same reference written twice has the same color. */\n color: number\n}\n\n/** The references of a formula being typed, each with a color. */\nexport function formulaReferences(formula: string): SheetFormulaRefMark[] {\n if (!formula.startsWith('=')) return []\n const colors = new Map<string, number>()\n const out: SheetFormulaRefMark[] = []\n for (const token of formulaTokens(formula)) {\n if (token.kind !== 'ref') continue\n const { sheet, top, left, bottom, right } = token.ref\n const key = `${sheet?.toLowerCase() ?? ''}!${top}:${left}:${bottom}:${right}`\n let color = colors.get(key)\n if (color == null) colors.set(key, (color = colors.size % REF_COLORS))\n out.push({\n start: token.start,\n end: token.end,\n text: token.text,\n sheet,\n bounds: { top, left, bottom, right },\n color,\n })\n }\n return out\n}\n\n/** Operators after which a value comes, so a reference can go there. */\nconst BEFORE_VALUE = new Set([\n '=',\n '(',\n ',',\n ';',\n '+',\n '-',\n '*',\n '/',\n '^',\n '&',\n '<',\n '>',\n '<=',\n '>=',\n '<>',\n])\n/** What may follow a value. */\nconst AFTER_VALUE = new Set([\n ')',\n ',',\n ';',\n '+',\n '-',\n '*',\n '/',\n '^',\n '&',\n '=',\n '<',\n '>',\n '<=',\n '>=',\n '<>',\n '%',\n])\n\n/**\n * Whether a reference can be put at the caret of a formula being typed:\n * right after `=`, an operator, `(` or `,`, and before nothing that is a\n * value itself. There, the arrow keys and clicking cells write references.\n */\nexport function canPointAt(formula: string, caret: number) {\n if (!formula.startsWith('=') || caret < 1) return false\n const tokens = formulaTokens(formula)\n let before: SheetFormulaToken | undefined\n let after: SheetFormulaToken | undefined\n for (const token of tokens) {\n if (token.end <= caret) before = token\n else if (token.start >= caret) {\n after = token\n break\n } else return false // the caret is inside a token\n }\n const fits = !before || (before.kind === 'op' && BEFORE_VALUE.has(before.text))\n const room = !after || (after.kind === 'op' && AFTER_VALUE.has(after.text))\n return fits && room\n}\n\n/** The function call the caret is in, innermost first, and which of its arguments, from 0. */\nexport function callAt(\n formula: string,\n caret: number,\n): { name: string; arg: number; count: number } | null {\n if (!formula.startsWith('=')) return null\n const tokens = formulaTokens(formula)\n const stack: { name: string | null; arg: number; open: number }[] = []\n let last: SheetFormulaToken | undefined\n for (const [i, token] of tokens.entries()) {\n if (token.start >= caret) break\n if (token.kind === 'op') {\n if (token.text === '(')\n stack.push({\n name: last?.kind === 'name' && last.end === token.start ? last.text.toUpperCase() : null,\n arg: 0,\n open: i,\n })\n else if (token.text === ')') stack.pop()\n else if ((token.text === ',' || token.text === ';') && stack.length)\n stack[stack.length - 1]!.arg++\n }\n last = token\n }\n for (let i = stack.length - 1; i >= 0; i--) {\n const call = stack[i]!\n if (!call.name) continue\n // all its arguments, also those after the caret, up to its `)`\n let count = 1\n let depth = 0\n for (const token of tokens.slice(call.open + 1)) {\n if (token.kind !== 'op') continue\n if (token.text === '(') depth++\n else if (token.text === ')') {\n if (!depth--) break\n } else if ((token.text === ',' || token.text === ';') && !depth) count++\n }\n return { name: call.name, arg: call.arg, count }\n }\n return null\n}\n\n/** The number an argument's name ends with, such as `2` of `[值2`. */\nconst numberOf = (name: string) => /(\\d+)\\]?$/.exec(name.replace(/^\\[/, ''))?.[1]\n\n/**\n * Which of a function's argument names the argument at `index` (of `count`)\n * is, or -1. After `…` the names before it repeat as a group, such as the\n * pairs of `SUMIFS` or `SWITCH`, numbered alike (`区域1, 条件1`); names after\n * `…`, such as `SWITCH`'s default, are the last arguments when the pairs come out even.\n */\nexport function argNameAt(args: string[], index: number, count: number): number {\n const more = args.indexOf('…')\n if (more === -1) return index < args.length ? index : -1\n if (index < more) return index\n const last = numberOf(args[more - 1] ?? '')\n let group = 1\n if (last != null) while (group < more && numberOf(args[more - 1 - group]!) === last) group++\n const tail = args.length - more - 1\n if (tail && count - tail >= more && (count - tail - more) % group === 0) {\n const fromEnd = count - index\n if (fromEnd <= tail) return args.length - fromEnd\n }\n const start = more - group\n return start + ((index - start) % group)\n}\n\n/**\n * The name being typed at the caret, to complete as a function: letters\n * ending at the caret, not yet followed by `(`.\n */\nexport function nameAt(\n formula: string,\n caret: number,\n): { start: number; end: number; text: string } | null {\n if (!formula.startsWith('=')) return null\n const tokens = formulaTokens(formula)\n for (const [i, token] of tokens.entries()) {\n if (token.end !== caret) continue\n // a cell name such as `A1` or `SUM1` reads as a reference while typing; complete its letters too\n const text =\n token.kind === 'ref' && /^[a-z]+\\d*$/i.test(token.text)\n ? token.text\n : token.kind === 'name'\n ? token.text\n : null\n if (!text || !/^[\\p{L}_]/u.test(text)) return null\n const next = tokens[i + 1]\n if (next && next.start === token.end && next.text === '(') return null\n return { start: token.start, end: token.end, text }\n }\n return null\n}\n\n/** A piece of a formula being typed, as it is colored in the edit box and the formula bar. */\nexport interface SheetFormulaPart {\n text: string\n /** The reference's color, as in `SheetFormulaRefMark`. */\n color?: number\n /** The syntax color (`dou-syntax-…`) of what isn't a reference. */\n syntax?:\n | 'function'\n | 'string'\n | 'number'\n | 'invalid'\n | 'keyword'\n | 'variable'\n | 'operator'\n | 'punctuation'\n}\n\n/**\n * A formula cut into its colored pieces: references in their colors, and\n * function names, \"text\", numbers, error values and operators in the\n * syntax colors of `@dou-ui/code`, so the cell and either formula bar color\n * it alike. Ends with a zero-width space, so a last line break still takes\n * a line.\n */\nexport function formulaParts(\n formula: string,\n marks: readonly SheetFormulaRefMark[] = formulaReferences(formula),\n): SheetFormulaPart[] {\n const parts: SheetFormulaPart[] = []\n if (!formula.startsWith('=')) return [{ text: `${formula}​` }]\n const colors = new Map(marks.map((mark) => [mark.start, mark.color]))\n const tokens = formulaTokens(formula)\n parts.push({ text: '=', syntax: 'operator' })\n let at = 1\n for (const [i, token] of tokens.entries()) {\n if (token.start > at) parts.push({ text: formula.slice(at, token.start) })\n const part: SheetFormulaPart = { text: token.text }\n if (token.kind === 'ref') {\n const color = colors.get(token.start)\n if (color == null) part.syntax = 'variable'\n else part.color = color\n } else if (token.kind === 'number') part.syntax = 'number'\n else if (token.kind === 'text') part.syntax = 'string'\n else if (token.kind === 'error') part.syntax = 'invalid'\n else if (token.kind === 'name')\n part.syntax =\n tokens[i + 1]?.text === '('\n ? 'function'\n : /^(?:TRUE|FALSE)$/i.test(token.text)\n ? 'keyword'\n : 'variable'\n else part.syntax = /^[(),;{}]$/.test(token.text) ? 'punctuation' : 'operator'\n parts.push(part)\n at = token.end\n }\n parts.push({ text: `${formula.slice(at)}​` })\n return parts\n}\n\n/** The class a piece of `formulaParts` is shown with. */\nexport function partClass(part: SheetFormulaPart): string | undefined {\n if (part.color != null) return `is-ref-${part.color}`\n return part.syntax && `dou-syntax-${part.syntax}`\n}\n"],"mappings":"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"}
@@ -0,0 +1,526 @@
1
+ import { parseCell } from "../core/address.js";
2
+ import { colCountOf, rowCountOf, valueOf } from "../core/model.js";
3
+ import { CYCLE, DIV0, NA, NAME, NUM, REF, SYNTAX, SheetError, VALUE, isSheetError } from "./error.js";
4
+ import { compareValues, dateText, isDateText, isRange, isTimeText, timeOrDays, toNumber, toText } from "./values.js";
5
+ import { sheetKey } from "../core/transaction.js";
6
+ import { builtinFunctions, scalarOf } from "./functions.js";
7
+ import { parseFormula, referencesOf } from "./parse.js";
8
+ // src/formula/engine.ts
9
+ /** Columns per row in a cell's number; rows are multiplied by it. */
10
+ var WIDTH = 16384;
11
+ /** Ranges wider than this are checked one by one rather than by column. */
12
+ var WIDE = 64;
13
+ var pointOf = (at, col) => typeof at === "number" ? {
14
+ row: at,
15
+ col: col ?? 0
16
+ } : typeof at === "string" ? parseCell(at) : at;
17
+ var SYNTAX_NODE = {
18
+ type: "error",
19
+ code: "#ERROR!"
20
+ };
21
+ /**
22
+ * The formulas of a workbook and their results. Load a workbook, ask for
23
+ * results with `result`, and tell it about each change with `update`.
24
+ */
25
+ var SheetFormulaEngine = class {
26
+ slots = /* @__PURE__ */ new Map();
27
+ byName = /* @__PURE__ */ new Map();
28
+ functions = /* @__PURE__ */ new Map();
29
+ parsed = /* @__PURE__ */ new Map();
30
+ volatiles = /* @__PURE__ */ new Set();
31
+ clock = 0;
32
+ epoch = 0;
33
+ context;
34
+ now;
35
+ /** Where the formula running links to, once it called `HYPERLINK`. */
36
+ linked;
37
+ /** The formulas the one running needed but had no results for. */
38
+ missing = [];
39
+ current = {
40
+ version: 1,
41
+ sheets: []
42
+ };
43
+ /** The workbook it has. */
44
+ get doc() {
45
+ return this.current;
46
+ }
47
+ constructor(doc, options = {}) {
48
+ for (const fn of builtinFunctions) this.functions.set(fn.name.toUpperCase(), fn);
49
+ for (const fn of options.functions ?? []) this.functions.set(fn.name.toUpperCase(), fn);
50
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
51
+ this.context = {
52
+ now: this.now(),
53
+ locale: options.locale,
54
+ link: (target) => void (this.linked = target)
55
+ };
56
+ if (doc) this.load(doc);
57
+ }
58
+ /** The functions formulas can call, built in and added, by name. */
59
+ functionList() {
60
+ return [...this.functions.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
61
+ }
62
+ /** Whether a function of this name can be called, built in or added. */
63
+ hasFunction(name) {
64
+ return this.functions.has(name.toUpperCase());
65
+ }
66
+ /** Starts over with a workbook: every result is worked out again when asked for. */
67
+ load(doc) {
68
+ this.current = doc;
69
+ this.slots.clear();
70
+ this.byName.clear();
71
+ this.volatiles.clear();
72
+ if (this.parsed.size > 5e4) this.parsed.clear();
73
+ this.epoch = ++this.clock;
74
+ this.context = {
75
+ ...this.context,
76
+ now: this.now()
77
+ };
78
+ for (const data of doc.sheets) {
79
+ const slot = {
80
+ key: sheetKey(data),
81
+ data,
82
+ formulas: /* @__PURE__ */ new Map(),
83
+ results: /* @__PURE__ */ new Map(),
84
+ links: /* @__PURE__ */ new Map(),
85
+ cellDeps: /* @__PURE__ */ new Map(),
86
+ colDeps: /* @__PURE__ */ new Map(),
87
+ wideDeps: /* @__PURE__ */ new Set(),
88
+ versions: /* @__PURE__ */ new Map()
89
+ };
90
+ this.slots.set(slot.key, slot);
91
+ this.byName.set(data.name.toLowerCase(), slot);
92
+ }
93
+ for (const slot of this.slots.values()) {
94
+ const cells = slot.data.cells;
95
+ for (let row = 0; row < cells.length; row++) {
96
+ const line = cells[row];
97
+ if (!line) continue;
98
+ for (let col = 0; col < line.length; col++) {
99
+ const value = valueOf(line[col]);
100
+ if (typeof value === "string" && value.startsWith("=")) this.add(slot, row, col, value);
101
+ }
102
+ }
103
+ }
104
+ }
105
+ /**
106
+ * Catches up with a change, such as a `SheetTransaction`: changes to
107
+ * cells forget only the results depending on them; rows, columns or
108
+ * sheets added, removed, moved or renamed start over, and so does a
109
+ * change made to a workbook other than the one it has.
110
+ */
111
+ update(change) {
112
+ const { doc, steps, before } = change;
113
+ if (before !== this.doc || steps.some(restructures)) return this.load(doc);
114
+ this.current = doc;
115
+ for (const data of doc.sheets) {
116
+ const slot = this.slots.get(sheetKey(data));
117
+ if (!slot) return this.load(doc);
118
+ slot.data = data;
119
+ }
120
+ this.context = {
121
+ ...this.context,
122
+ now: this.now()
123
+ };
124
+ const changed = [];
125
+ for (const step of steps) {
126
+ if (step.type !== "setCell") continue;
127
+ const slot = this.slots.get(step.sheet);
128
+ const pos = step.row * WIDTH + step.col;
129
+ const old = slot.formulas.get(pos);
130
+ const value = valueOf(step.cell);
131
+ const formula = typeof value === "string" && value.startsWith("=") ? value : null;
132
+ if (old) this.remove(old);
133
+ if (formula) this.add(slot, step.row, step.col, formula);
134
+ changed.push({
135
+ slot,
136
+ row: step.row,
137
+ col: step.col
138
+ });
139
+ }
140
+ for (const f of this.volatiles) changed.push(f);
141
+ this.forget(changed);
142
+ }
143
+ result(sheet, at, col) {
144
+ const slot = this.slotOf(sheet);
145
+ const point = pointOf(at, col);
146
+ if (!slot || !point) return REF;
147
+ return this.read(slot, point.row, point.col, true);
148
+ }
149
+ /**
150
+ * When the results in a row last changed, to redraw only rows whose
151
+ * results did; any change to the workbook that starts over changes all.
152
+ */
153
+ rowVersion(sheet, row) {
154
+ return this.slotOf(sheet)?.versions.get(row) ?? this.epoch;
155
+ }
156
+ link(sheet, at, col) {
157
+ const slot = this.slotOf(sheet);
158
+ const point = pointOf(at, col);
159
+ if (!slot || !point) return void 0;
160
+ const pos = point.row * WIDTH + point.col;
161
+ if (!slot.formulas.has(pos)) return void 0;
162
+ this.read(slot, point.row, point.col, true);
163
+ return slot.links.get(pos);
164
+ }
165
+ isFormula(sheet, at, col) {
166
+ const point = pointOf(at, col);
167
+ return !!point && !!this.slotOf(sheet)?.formulas.has(point.row * WIDTH + point.col);
168
+ }
169
+ /** Works out a formula as if it were in a cell, without keeping it, e.g. for a preview while typing. */
170
+ evaluate(formula, sheet = 0) {
171
+ const slot = this.slotOf(sheet);
172
+ if (!slot) return REF;
173
+ const f = {
174
+ slot,
175
+ row: -1,
176
+ col: -1,
177
+ pos: -1,
178
+ node: this.parse(formula) ?? SYNTAX_NODE,
179
+ volatile: false,
180
+ cells: [],
181
+ ranges: []
182
+ };
183
+ const missing = this.missing;
184
+ try {
185
+ for (;;) {
186
+ this.missing = [];
187
+ const value = this.run(f);
188
+ if (!this.missing.length) return value;
189
+ for (const m of this.missing) if (!m.slot.results.has(m.pos)) this.compute(m);
190
+ }
191
+ } finally {
192
+ this.missing = missing;
193
+ }
194
+ }
195
+ slotOf(sheet) {
196
+ if (typeof sheet === "number") {
197
+ const data = this.doc.sheets[sheet];
198
+ return data ? this.slots.get(sheetKey(data)) : void 0;
199
+ }
200
+ return this.slots.get(sheet);
201
+ }
202
+ parse(text) {
203
+ let node = this.parsed.get(text);
204
+ if (node === void 0) {
205
+ try {
206
+ node = parseFormula(text);
207
+ } catch {
208
+ node = null;
209
+ }
210
+ this.parsed.set(text, node);
211
+ }
212
+ return node;
213
+ }
214
+ add(slot, row, col, text) {
215
+ const node = this.parse(text);
216
+ const pos = row * WIDTH + col;
217
+ const volatile = !!node && this.isVolatile(node);
218
+ const f = {
219
+ slot,
220
+ row,
221
+ col,
222
+ pos,
223
+ node,
224
+ volatile,
225
+ cells: [],
226
+ ranges: []
227
+ };
228
+ slot.formulas.set(pos, f);
229
+ if (volatile) this.volatiles.add(f);
230
+ if (!node) return;
231
+ for (const ref of referencesOf(node)) {
232
+ const target = ref.sheet == null ? slot : this.byName.get(ref.sheet.toLowerCase());
233
+ if (!target) continue;
234
+ if (ref.cell) {
235
+ const at = ref.top * WIDTH + ref.left;
236
+ const had = target.cellDeps.get(at);
237
+ if (!had) target.cellDeps.set(at, f);
238
+ else if (had instanceof Set) had.add(f);
239
+ else if (had !== f) target.cellDeps.set(at, /* @__PURE__ */ new Set([had, f]));
240
+ f.cells.push([target, at]);
241
+ continue;
242
+ }
243
+ const dep = {
244
+ formula: f,
245
+ slot: target,
246
+ top: ref.top,
247
+ left: ref.left,
248
+ bottom: ref.bottom,
249
+ right: ref.right
250
+ };
251
+ f.ranges.push(dep);
252
+ if (ref.right - ref.left >= WIDE) target.wideDeps.add(dep);
253
+ else for (let c = ref.left; c <= ref.right; c++) {
254
+ let set = target.colDeps.get(c);
255
+ if (!set) target.colDeps.set(c, set = /* @__PURE__ */ new Set());
256
+ set.add(dep);
257
+ }
258
+ }
259
+ }
260
+ isVolatile(node) {
261
+ switch (node.type) {
262
+ case "call": return !!this.functions.get(node.name)?.volatile || node.args.some((arg) => this.isVolatile(arg));
263
+ case "unary": return this.isVolatile(node.arg);
264
+ case "binary": return this.isVolatile(node.left) || this.isVolatile(node.right);
265
+ default: return false;
266
+ }
267
+ }
268
+ remove(f) {
269
+ f.slot.formulas.delete(f.pos);
270
+ this.volatiles.delete(f);
271
+ for (const [target, at] of f.cells) {
272
+ const had = target.cellDeps.get(at);
273
+ if (had === f) target.cellDeps.delete(at);
274
+ else if (had instanceof Set) had.delete(f);
275
+ }
276
+ for (const dep of f.ranges) if (dep.right - dep.left >= WIDE) dep.slot.wideDeps.delete(dep);
277
+ else for (let c = dep.left; c <= dep.right; c++) dep.slot.colDeps.get(c)?.delete(dep);
278
+ }
279
+ /** Forgets the results of these cells and of every formula depending on them. */
280
+ forget(cells) {
281
+ const queue = cells;
282
+ for (const { slot, row, col } of queue) {
283
+ slot.results.delete(row * WIDTH + col);
284
+ slot.versions.set(row, ++this.clock);
285
+ }
286
+ const visit = (f) => {
287
+ if (!f.slot.results.delete(f.pos)) return;
288
+ f.slot.versions.set(f.row, ++this.clock);
289
+ queue.push(f);
290
+ };
291
+ for (let i = 0; i < queue.length; i++) {
292
+ const { slot, row, col } = queue[i];
293
+ const direct = slot.cellDeps.get(row * WIDTH + col);
294
+ if (direct instanceof Set) for (const f of direct) visit(f);
295
+ else if (direct) visit(direct);
296
+ const ranges = slot.colDeps.get(col);
297
+ if (ranges) {
298
+ for (const dep of ranges) if (row >= dep.top && row <= dep.bottom) visit(dep.formula);
299
+ }
300
+ for (const dep of slot.wideDeps) if (row >= dep.top && row <= dep.bottom && col >= dep.left && col <= dep.right) visit(dep.formula);
301
+ }
302
+ }
303
+ /** A cell's value or result; `top` works out a result not kept yet, otherwise it is noted as missing. */
304
+ read(slot, row, col, top = false) {
305
+ const pos = row * WIDTH + col;
306
+ const f = slot.formulas.get(pos);
307
+ if (f) {
308
+ const kept = slot.results.get(pos);
309
+ if (kept !== void 0) return kept;
310
+ if (top) return this.compute(f);
311
+ this.missing.push(f);
312
+ return null;
313
+ }
314
+ const value = valueOf(slot.data.cells[row]?.[col]);
315
+ if (typeof value === "string") {
316
+ if (value === "") return null;
317
+ if (value.startsWith("'=")) return value.slice(1);
318
+ }
319
+ return value;
320
+ }
321
+ /** Works out a formula's result, and first those it needs, one after another. */
322
+ compute(target) {
323
+ const stack = [target];
324
+ const waiting = /* @__PURE__ */ new Set();
325
+ while (stack.length) {
326
+ const f = stack[stack.length - 1];
327
+ if (f.slot.results.has(f.pos)) {
328
+ stack.pop();
329
+ continue;
330
+ }
331
+ const missing = this.missing;
332
+ this.missing = [];
333
+ this.linked = void 0;
334
+ let value = this.run(f);
335
+ const link = this.linked;
336
+ const needed = this.missing;
337
+ this.missing = missing;
338
+ if (needed.length) {
339
+ if (needed.some((m) => waiting.has(m) || m === f)) value = CYCLE;
340
+ else {
341
+ for (let i = needed.length - 1; i >= 0; i--) {
342
+ const m = needed[i];
343
+ if (!m.slot.results.has(m.pos)) stack.push(m);
344
+ }
345
+ waiting.add(f);
346
+ continue;
347
+ }
348
+ }
349
+ f.slot.results.set(f.pos, value);
350
+ if (link !== void 0) f.slot.links.set(f.pos, link);
351
+ else f.slot.links.delete(f.pos);
352
+ stack.pop();
353
+ waiting.delete(f);
354
+ }
355
+ return target.slot.results.get(target.pos);
356
+ }
357
+ run(f) {
358
+ if (!f.node) return SYNTAX;
359
+ return this.scalar(this.ev(f.node, f.slot)) ?? 0;
360
+ }
361
+ scalar(arg) {
362
+ return isRange(arg) ? scalarOf(arg) : arg;
363
+ }
364
+ range(slot, top, left, bottom, right) {
365
+ const lastRow = Math.min(bottom, rowCountOf(slot.data) - 1);
366
+ const lastCol = Math.min(right, colCountOf(slot.data) - 1);
367
+ if (top > lastRow || left > lastCol) return REF;
368
+ return {
369
+ kind: "range",
370
+ rows: lastRow - top + 1,
371
+ cols: lastCol - left + 1,
372
+ at: (r, c) => this.read(slot, top + r, left + c),
373
+ entries: () => this.entries(slot, top, left, lastRow, lastCol)
374
+ };
375
+ }
376
+ *entries(slot, top, left, bottom, right) {
377
+ const cells = slot.data.cells;
378
+ const end = Math.min(bottom, cells.length - 1);
379
+ for (let row = top; row <= end; row++) {
380
+ const line = cells[row];
381
+ if (!line) continue;
382
+ const stop = Math.min(right, line.length - 1);
383
+ for (let col = left; col <= stop; col++) {
384
+ if (line[col] == null) continue;
385
+ const value = this.read(slot, row, col);
386
+ if (value != null) yield [
387
+ row - top,
388
+ col - left,
389
+ value
390
+ ];
391
+ }
392
+ }
393
+ }
394
+ ev(node, slot) {
395
+ switch (node.type) {
396
+ case "number":
397
+ case "text":
398
+ case "boolean": return node.value;
399
+ case "error": return SheetError.of(node.code);
400
+ case "empty": return null;
401
+ case "ref": {
402
+ const target = node.sheet == null ? slot : this.byName.get(node.sheet.toLowerCase());
403
+ if (!target) return REF;
404
+ if (node.cell) return this.read(target, node.top, node.left);
405
+ return this.range(target, node.top, node.left, node.bottom, node.right);
406
+ }
407
+ case "unary": {
408
+ const a = this.ev(node.arg, slot);
409
+ if (isArray(a)) return cellByCell([a], (v) => unary(node.op, v));
410
+ return unary(node.op, this.scalar(a));
411
+ }
412
+ case "binary": {
413
+ const a = this.ev(node.left, slot);
414
+ const b = this.ev(node.right, slot);
415
+ if (isArray(a) || isArray(b)) return cellByCell([a, b], (x, y) => this.binary(node.op, x, y));
416
+ return this.binary(node.op, this.scalar(a), this.scalar(b));
417
+ }
418
+ case "call": {
419
+ const fn = this.functions.get(node.name);
420
+ if (!fn) return NAME;
421
+ const count = node.args.length;
422
+ if (fn.minArgs != null && count < fn.minArgs || fn.maxArgs != null && count > fn.maxArgs) return VALUE;
423
+ let out;
424
+ try {
425
+ out = fn.lazy ? fn.run(node.args.map((arg) => () => this.ev(arg, slot)), this.context) : fn.run(node.args.map((arg) => this.ev(arg, slot)), this.context);
426
+ } catch {
427
+ return VALUE;
428
+ }
429
+ return out === void 0 ? null : out;
430
+ }
431
+ }
432
+ }
433
+ binary(op, a, b) {
434
+ if (isSheetError(a)) return a;
435
+ if (isSheetError(b)) return b;
436
+ if (op === "&") return toText(a) + toText(b);
437
+ if (op.length === 2 || op === "=" || op === "<" || op === ">") {
438
+ const c = compareValues(a, b);
439
+ switch (op) {
440
+ case "=": return c === 0;
441
+ case "<>": return c !== 0;
442
+ case "<": return c < 0;
443
+ case ">": return c > 0;
444
+ case "<=": return c <= 0;
445
+ default: return c >= 0;
446
+ }
447
+ }
448
+ const x = toNumber(a);
449
+ if (isSheetError(x)) return x;
450
+ const y = toNumber(b);
451
+ if (isSheetError(y)) return y;
452
+ let out;
453
+ switch (op) {
454
+ case "+":
455
+ out = x + y;
456
+ if (isDateText(a) !== isDateText(b)) return dateText(out);
457
+ if (isTimeText(a) || isTimeText(b)) return clock(out);
458
+ break;
459
+ case "-":
460
+ out = x - y;
461
+ if (isDateText(a) && !isDateText(b)) return dateText(out);
462
+ if (isTimeText(a) && !isDateText(b)) return clock(out);
463
+ break;
464
+ case "*":
465
+ out = x * y;
466
+ break;
467
+ case "/":
468
+ if (y === 0) return DIV0;
469
+ out = x / y;
470
+ break;
471
+ default:
472
+ if (x === 0 && y < 0) return DIV0;
473
+ out = x ** y;
474
+ }
475
+ return Number.isFinite(out) ? out : NUM;
476
+ }
477
+ };
478
+ var clock = (days) => Number.isFinite(days) ? timeOrDays(days) : NUM;
479
+ /** Whether an argument is a range of more than one cell. */
480
+ var isArray = (arg) => isRange(arg) && (arg.rows > 1 || arg.cols > 1);
481
+ function unary(op, v) {
482
+ if (isSheetError(v)) return v;
483
+ if (op === "+") return v;
484
+ const n = toNumber(v);
485
+ if (isSheetError(n)) return n;
486
+ return op === "-" ? -n : n / 100;
487
+ }
488
+ /** The cell of an argument that goes with `row` and `col` of `cellByCell`'s result. */
489
+ function pick(arg, row, col) {
490
+ if (!isRange(arg)) return arg;
491
+ const r = arg.rows === 1 ? 0 : row;
492
+ const c = arg.cols === 1 ? 0 : col;
493
+ return r < arg.rows && c < arg.cols ? arg.at(r, c) : NA;
494
+ }
495
+ /**
496
+ * Works out an operator for each cell of its ranges, into a range as big as
497
+ * the biggest. A single value, or a range one row or column across, is used
498
+ * for every row or column; past the end of a smaller range the cell is `#N/A`.
499
+ */
500
+ function cellByCell(args, run) {
501
+ let rows = 1;
502
+ let cols = 1;
503
+ for (const arg of args) if (isRange(arg)) {
504
+ rows = Math.max(rows, arg.rows);
505
+ cols = Math.max(cols, arg.cols);
506
+ }
507
+ const values = [];
508
+ for (let row = 0; row < rows; row++) for (let col = 0; col < cols; col++) values.push(run(...args.map((arg) => pick(arg, row, col))));
509
+ return {
510
+ kind: "range",
511
+ rows,
512
+ cols,
513
+ at: (row, col) => values[row * cols + col] ?? null,
514
+ *entries() {
515
+ for (let i = 0; i < values.length; i++) if (values[i] != null) yield [
516
+ Math.floor(i / cols),
517
+ i % cols,
518
+ values[i]
519
+ ];
520
+ }
521
+ };
522
+ }
523
+ var restructures = (step) => step.type === "insertLines" || step.type === "removeLines" || step.type === "moveLines" || step.type === "insertSheet" || step.type === "removeSheet" || step.type === "updateSheet" && ("name" in step.patch || "id" in step.patch || "rowCount" in step.patch || "colCount" in step.patch);
524
+ export { SheetFormulaEngine };
525
+
526
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","names":[],"sources":["../../src/formula/engine.ts"],"sourcesContent":["import { rowCountOf, colCountOf, valueOf, type SheetData, type SheetWorkbook } from '../core/model'\nimport { parseCell, type SheetCellTarget, type SheetPoint } from '../core/address'\nimport { sheetKey, type SheetStep } from '../core/transaction'\nimport { CYCLE, isSheetError, NA, NAME, REF, SheetError, SYNTAX, VALUE, DIV0, NUM } from './error'\nimport {\n builtinFunctions,\n scalarOf,\n type SheetFunction,\n type SheetFunctionContext,\n} from './functions'\nimport { parseFormula, referencesOf, type SheetFormulaNode } from './parse'\nimport {\n compareValues,\n dateText,\n isDateText,\n isRange,\n isTimeText,\n timeOrDays,\n toNumber,\n toText,\n type SheetArg,\n type SheetRangeArg,\n type SheetResult,\n} from './values'\n\n/*\n * Works out formulas when their results are asked for, and only those:\n * a grid asks for the cells it shows, so a big workbook costs only what is\n * on screen. A result is kept until a cell it depends on changes; a change\n * then forgets the results that depend on it, following what refers to\n * what, and nothing else.\n *\n * Working out one result runs on a stack of its own rather than by calling\n * itself, so a chain of 100 000 formulas each using the one before works.\n * A formula that needs results not worked out yet asks for all of them at\n * once, they are worked out first, and it runs again. One that needs\n * itself, directly or not, is `#CYCLE!`.\n */\n\n/** Columns per row in a cell's number; rows are multiplied by it. */\nconst WIDTH = 16384\n/** Ranges wider than this are checked one by one rather than by column. */\nconst WIDE = 64\n\ninterface Formula {\n slot: Slot\n row: number\n col: number\n pos: number\n /** `null` when the text isn't a formula. */\n node: SheetFormulaNode | null\n volatile: boolean\n /** Where it is registered as depending on cells, to take it out again. */\n cells: [Slot, number][]\n ranges: RangeDep[]\n}\n\ninterface RangeDep {\n formula: Formula\n slot: Slot\n top: number\n left: number\n bottom: number\n right: number\n}\n\ninterface Cell {\n slot: Slot\n row: number\n col: number\n}\n\n/** A sheet as the engine keeps it. */\ninterface Slot {\n key: string\n data: SheetData\n formulas: Map<number, Formula>\n results: Map<number, SheetResult>\n /** Where formulas that called `HYPERLINK` link to, kept with their results. */\n links: Map<number, string>\n /** Formulas referring to single cells, by the cell; most cells have one, kept without a set. */\n cellDeps: Map<number, Formula | Set<Formula>>\n /** Formulas referring to ranges, by column. */\n colDeps: Map<number, Set<RangeDep>>\n wideDeps: Set<RangeDep>\n /** When each row's results last changed, for redrawing only those rows. */\n versions: Map<number, number>\n}\n\nexport interface SheetFormulaEngineOptions {\n /** More functions, or ones that replace built-in ones of the same name. */\n functions?: readonly SheetFunction[]\n /** The language `TEXT` writes dates in. */\n locale?: string\n /** The time `TODAY()` and `NOW()` use; the current time by default. */\n now?: () => Date\n}\n\n/** A change for `update`: a `SheetTransaction` is one. */\nexport interface SheetFormulaChange {\n /** The workbook after the change. */\n readonly doc: SheetWorkbook\n /** What changed. */\n readonly steps: readonly SheetStep[]\n /** The workbook before it: when it isn't the one the engine has, it starts over. */\n readonly before: SheetWorkbook\n}\n\nconst pointOf = (at: SheetCellTarget | number, col?: number): SheetPoint | null =>\n typeof at === 'number' ? { row: at, col: col ?? 0 } : typeof at === 'string' ? parseCell(at) : at\n\nconst SYNTAX_NODE: SheetFormulaNode = { type: 'error', code: '#ERROR!' }\n\n/**\n * The formulas of a workbook and their results. Load a workbook, ask for\n * results with `result`, and tell it about each change with `update`.\n */\nexport class SheetFormulaEngine {\n private slots = new Map<string, Slot>()\n private byName = new Map<string, Slot>()\n private functions = new Map<string, SheetFunction>()\n private parsed = new Map<string, SheetFormulaNode | null>()\n private volatiles = new Set<Formula>()\n private clock = 0\n private epoch = 0\n private context: SheetFunctionContext\n private now: () => Date\n /** Where the formula running links to, once it called `HYPERLINK`. */\n private linked: string | undefined\n /** The formulas the one running needed but had no results for. */\n private missing: Formula[] = []\n private current: SheetWorkbook = { version: 1, sheets: [] }\n\n /** The workbook it has. */\n get doc() {\n return this.current\n }\n\n constructor(doc?: SheetWorkbook, options: SheetFormulaEngineOptions = {}) {\n for (const fn of builtinFunctions) this.functions.set(fn.name.toUpperCase(), fn)\n for (const fn of options.functions ?? []) this.functions.set(fn.name.toUpperCase(), fn)\n this.now = options.now ?? (() => new Date())\n this.context = {\n now: this.now(),\n locale: options.locale,\n link: (target) => void (this.linked = target),\n }\n if (doc) this.load(doc)\n }\n\n /** The functions formulas can call, built in and added, by name. */\n functionList(): SheetFunction[] {\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a fresh array\n return [...this.functions.values()].sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : 0,\n )\n }\n\n /** Whether a function of this name can be called, built in or added. */\n hasFunction(name: string) {\n return this.functions.has(name.toUpperCase())\n }\n\n /** Starts over with a workbook: every result is worked out again when asked for. */\n load(doc: SheetWorkbook) {\n this.current = doc\n this.slots.clear()\n this.byName.clear()\n this.volatiles.clear()\n // a long session can type many formulas; keep only what may come back\n if (this.parsed.size > 50_000) this.parsed.clear()\n this.epoch = ++this.clock\n this.context = { ...this.context, now: this.now() }\n for (const data of doc.sheets) {\n const slot: Slot = {\n key: sheetKey(data),\n data,\n formulas: new Map(),\n results: new Map(),\n links: new Map(),\n cellDeps: new Map(),\n colDeps: new Map(),\n wideDeps: new Set(),\n versions: new Map(),\n }\n this.slots.set(slot.key, slot)\n this.byName.set(data.name.toLowerCase(), slot)\n }\n for (const slot of this.slots.values()) {\n const cells = slot.data.cells\n for (let row = 0; row < cells.length; row++) {\n const line = cells[row]\n if (!line) continue\n for (let col = 0; col < line.length; col++) {\n const value = valueOf(line[col])\n if (typeof value === 'string' && value.startsWith('=')) this.add(slot, row, col, value)\n }\n }\n }\n }\n\n /**\n * Catches up with a change, such as a `SheetTransaction`: changes to\n * cells forget only the results depending on them; rows, columns or\n * sheets added, removed, moved or renamed start over, and so does a\n * change made to a workbook other than the one it has.\n */\n update(change: SheetFormulaChange) {\n const { doc, steps, before } = change\n if (before !== this.doc || steps.some(restructures)) return this.load(doc)\n this.current = doc\n for (const data of doc.sheets) {\n const slot = this.slots.get(sheetKey(data))\n if (!slot) return this.load(doc)\n slot.data = data\n }\n this.context = { ...this.context, now: this.now() }\n const changed: Cell[] = []\n for (const step of steps) {\n if (step.type !== 'setCell') continue\n const slot = this.slots.get(step.sheet)!\n const pos = step.row * WIDTH + step.col\n const old = slot.formulas.get(pos)\n const value = valueOf(step.cell)\n const formula = typeof value === 'string' && value.startsWith('=') ? value : null\n if (old) this.remove(old)\n if (formula) this.add(slot, step.row, step.col, formula)\n changed.push({ slot, row: step.row, col: step.col })\n }\n for (const f of this.volatiles) changed.push(f)\n this.forget(changed)\n }\n\n /**\n * What a cell comes to: its formula's result, or its value (`'=…` text\n * without its `'`, empty text as nothing). `sheet` is a sheet's key\n * (its `id`, or its name) or index; the cell is a name such as `'D7'`, a\n * point, or a row and a column.\n */\n result(sheet: string | number, cell: SheetCellTarget): SheetResult\n result(sheet: string | number, row: number, col: number): SheetResult\n result(sheet: string | number, at: SheetCellTarget | number, col?: number): SheetResult {\n const slot = this.slotOf(sheet)\n const point = pointOf(at, col)\n if (!slot || !point) return REF\n return this.read(slot, point.row, point.col, true)\n }\n\n /**\n * When the results in a row last changed, to redraw only rows whose\n * results did; any change to the workbook that starts over changes all.\n */\n rowVersion(sheet: string | number, row: number) {\n return this.slotOf(sheet)?.versions.get(row) ?? this.epoch\n }\n\n /**\n * Where a formula's cell links to, when the formula called `HYPERLINK`;\n * the cell as in `result`.\n */\n link(sheet: string | number, cell: SheetCellTarget): string | undefined\n link(sheet: string | number, row: number, col: number): string | undefined\n link(sheet: string | number, at: SheetCellTarget | number, col?: number) {\n const slot = this.slotOf(sheet)\n const point = pointOf(at, col)\n if (!slot || !point) return undefined\n const pos = point.row * WIDTH + point.col\n if (!slot.formulas.has(pos)) return undefined\n this.read(slot, point.row, point.col, true)\n return slot.links.get(pos)\n }\n\n /** Whether a cell holds a formula; the cell as in `result`. */\n isFormula(sheet: string | number, cell: SheetCellTarget): boolean\n isFormula(sheet: string | number, row: number, col: number): boolean\n isFormula(sheet: string | number, at: SheetCellTarget | number, col?: number) {\n const point = pointOf(at, col)\n return !!point && !!this.slotOf(sheet)?.formulas.has(point.row * WIDTH + point.col)\n }\n\n /** Works out a formula as if it were in a cell, without keeping it, e.g. for a preview while typing. */\n evaluate(formula: string, sheet: string | number = 0): SheetResult {\n const slot = this.slotOf(sheet)\n if (!slot) return REF\n const node = this.parse(formula) ?? SYNTAX_NODE\n const f: Formula = {\n slot,\n row: -1,\n col: -1,\n pos: -1,\n node,\n volatile: false,\n cells: [],\n ranges: [],\n }\n const missing = this.missing\n try {\n for (;;) {\n this.missing = []\n const value = this.run(f)\n if (!this.missing.length) return value\n // work out what it needs, then run it again\n for (const m of this.missing) if (!m.slot.results.has(m.pos)) this.compute(m)\n }\n } finally {\n this.missing = missing\n }\n }\n\n private slotOf(sheet: string | number) {\n if (typeof sheet === 'number') {\n const data = this.doc.sheets[sheet]\n return data ? this.slots.get(sheetKey(data)) : undefined\n }\n return this.slots.get(sheet)\n }\n\n private parse(text: string) {\n let node = this.parsed.get(text)\n if (node === undefined) {\n try {\n node = parseFormula(text)\n } catch {\n node = null\n }\n this.parsed.set(text, node)\n }\n return node\n }\n\n private add(slot: Slot, row: number, col: number, text: string) {\n const node = this.parse(text)\n const pos = row * WIDTH + col\n const volatile = !!node && this.isVolatile(node)\n const f: Formula = { slot, row, col, pos, node, volatile, cells: [], ranges: [] }\n slot.formulas.set(pos, f)\n if (volatile) this.volatiles.add(f)\n if (!node) return\n for (const ref of referencesOf(node)) {\n const target = ref.sheet == null ? slot : this.byName.get(ref.sheet.toLowerCase())\n if (!target) continue\n if (ref.cell) {\n const at = ref.top * WIDTH + ref.left\n const had = target.cellDeps.get(at)\n if (!had) target.cellDeps.set(at, f)\n else if (had instanceof Set) had.add(f)\n else if (had !== f) target.cellDeps.set(at, new Set([had, f]))\n f.cells.push([target, at])\n continue\n }\n const dep: RangeDep = {\n formula: f,\n slot: target,\n top: ref.top,\n left: ref.left,\n bottom: ref.bottom,\n right: ref.right,\n }\n f.ranges.push(dep)\n if (ref.right - ref.left >= WIDE) target.wideDeps.add(dep)\n else\n for (let c = ref.left; c <= ref.right; c++) {\n let set = target.colDeps.get(c)\n if (!set) target.colDeps.set(c, (set = new Set()))\n set.add(dep)\n }\n }\n }\n\n private isVolatile(node: SheetFormulaNode): boolean {\n switch (node.type) {\n case 'call':\n return (\n !!this.functions.get(node.name)?.volatile || node.args.some((arg) => this.isVolatile(arg))\n )\n case 'unary':\n return this.isVolatile(node.arg)\n case 'binary':\n return this.isVolatile(node.left) || this.isVolatile(node.right)\n default:\n return false\n }\n }\n\n private remove(f: Formula) {\n f.slot.formulas.delete(f.pos)\n this.volatiles.delete(f)\n for (const [target, at] of f.cells) {\n const had = target.cellDeps.get(at)\n if (had === f) target.cellDeps.delete(at)\n else if (had instanceof Set) had.delete(f)\n }\n for (const dep of f.ranges) {\n if (dep.right - dep.left >= WIDE) dep.slot.wideDeps.delete(dep)\n else for (let c = dep.left; c <= dep.right; c++) dep.slot.colDeps.get(c)?.delete(dep)\n }\n }\n\n /** Forgets the results of these cells and of every formula depending on them. */\n private forget(cells: Cell[]) {\n const queue = cells\n for (const { slot, row, col } of queue) {\n slot.results.delete(row * WIDTH + col)\n slot.versions.set(row, ++this.clock)\n }\n const visit = (f: Formula) => {\n // a result that isn't kept has nothing kept depending on it either,\n // and one forgotten already has been followed\n if (!f.slot.results.delete(f.pos)) return\n f.slot.versions.set(f.row, ++this.clock)\n queue.push(f)\n }\n for (let i = 0; i < queue.length; i++) {\n const { slot, row, col } = queue[i]!\n const direct = slot.cellDeps.get(row * WIDTH + col)\n if (direct instanceof Set) for (const f of direct) visit(f)\n else if (direct) visit(direct)\n const ranges = slot.colDeps.get(col)\n if (ranges)\n for (const dep of ranges) if (row >= dep.top && row <= dep.bottom) visit(dep.formula)\n for (const dep of slot.wideDeps)\n if (row >= dep.top && row <= dep.bottom && col >= dep.left && col <= dep.right)\n visit(dep.formula)\n }\n }\n\n /** A cell's value or result; `top` works out a result not kept yet, otherwise it is noted as missing. */\n private read(slot: Slot, row: number, col: number, top = false): SheetResult {\n const pos = row * WIDTH + col\n const f = slot.formulas.get(pos)\n if (f) {\n const kept = slot.results.get(pos)\n if (kept !== undefined) return kept\n if (top) return this.compute(f)\n this.missing.push(f)\n return null\n }\n const value = valueOf(slot.data.cells[row]?.[col])\n if (typeof value === 'string') {\n if (value === '') return null\n if (value.startsWith(\"'=\")) return value.slice(1)\n }\n return value\n }\n\n /** Works out a formula's result, and first those it needs, one after another. */\n private compute(target: Formula): SheetResult {\n const stack = [target]\n /*\n * Formulas that ran and wait for what they need, which is above them on\n * the stack: each is worked out from those above it, so a formula\n * needing one that waits needs itself.\n */\n const waiting = new Set<Formula>()\n while (stack.length) {\n const f = stack[stack.length - 1]!\n // one asked for by two formulas is on the stack twice\n if (f.slot.results.has(f.pos)) {\n stack.pop()\n continue\n }\n const missing = this.missing\n this.missing = []\n this.linked = undefined\n let value = this.run(f)\n const link = this.linked\n const needed = this.missing\n this.missing = missing\n if (needed.length) {\n if (needed.some((m) => waiting.has(m) || m === f)) value = CYCLE\n else {\n // the first needed on top: in a column of running totals each then needs only the one done before\n for (let i = needed.length - 1; i >= 0; i--) {\n const m = needed[i]!\n if (!m.slot.results.has(m.pos)) stack.push(m)\n }\n waiting.add(f)\n continue\n }\n }\n f.slot.results.set(f.pos, value)\n if (link !== undefined) f.slot.links.set(f.pos, link)\n else f.slot.links.delete(f.pos)\n stack.pop()\n waiting.delete(f)\n }\n return target.slot.results.get(target.pos)!\n }\n\n private run(f: Formula): SheetResult {\n if (!f.node) return SYNTAX\n const value = this.scalar(this.ev(f.node, f.slot))\n return value ?? 0\n }\n\n private scalar(arg: SheetArg): SheetResult {\n return isRange(arg) ? scalarOf(arg) : arg\n }\n\n private range(slot: Slot, top: number, left: number, bottom: number, right: number): SheetArg {\n const lastRow = Math.min(bottom, rowCountOf(slot.data) - 1)\n const lastCol = Math.min(right, colCountOf(slot.data) - 1)\n if (top > lastRow || left > lastCol) return REF\n const range: SheetRangeArg = {\n kind: 'range',\n rows: lastRow - top + 1,\n cols: lastCol - left + 1,\n at: (r, c) => this.read(slot, top + r, left + c),\n entries: () => this.entries(slot, top, left, lastRow, lastCol),\n }\n return range\n }\n\n private *entries(\n slot: Slot,\n top: number,\n left: number,\n bottom: number,\n right: number,\n ): Generator<[number, number, SheetResult]> {\n const cells = slot.data.cells\n const end = Math.min(bottom, cells.length - 1)\n for (let row = top; row <= end; row++) {\n const line = cells[row]\n if (!line) continue\n const stop = Math.min(right, line.length - 1)\n for (let col = left; col <= stop; col++) {\n if (line[col] == null) continue\n const value = this.read(slot, row, col)\n if (value != null) yield [row - top, col - left, value]\n }\n }\n }\n\n private ev(node: SheetFormulaNode, slot: Slot): SheetArg {\n switch (node.type) {\n case 'number':\n case 'text':\n case 'boolean':\n return node.value\n case 'error':\n return SheetError.of(node.code)\n case 'empty':\n return null\n case 'ref': {\n const target = node.sheet == null ? slot : this.byName.get(node.sheet.toLowerCase())\n if (!target) return REF\n if (node.cell) return this.read(target, node.top, node.left)\n return this.range(target, node.top, node.left, node.bottom, node.right)\n }\n case 'unary': {\n const a = this.ev(node.arg, slot)\n if (isArray(a)) return cellByCell([a], (v) => unary(node.op, v))\n return unary(node.op, this.scalar(a))\n }\n case 'binary': {\n const a = this.ev(node.left, slot)\n const b = this.ev(node.right, slot)\n // a range given to an operator is worked out cell by cell, as in SUMPRODUCT((A1:A9=\"x\")*B1:B9)\n if (isArray(a) || isArray(b))\n return cellByCell([a, b], (x, y) => this.binary(node.op, x, y))\n return this.binary(node.op, this.scalar(a), this.scalar(b))\n }\n case 'call': {\n const fn = this.functions.get(node.name)\n if (!fn) return NAME\n const count = node.args.length\n if (\n (fn.minArgs != null && count < fn.minArgs) ||\n (fn.maxArgs != null && count > fn.maxArgs)\n )\n return VALUE\n let out: SheetArg | undefined\n try {\n out = fn.lazy\n ? fn.run(\n node.args.map((arg) => () => this.ev(arg, slot)),\n this.context,\n )\n : fn.run(\n node.args.map((arg) => this.ev(arg, slot)),\n this.context,\n )\n } catch {\n return VALUE\n }\n return out === undefined ? null : out\n }\n }\n }\n\n private binary(op: string, a: SheetResult, b: SheetResult): SheetResult {\n if (isSheetError(a)) return a\n if (isSheetError(b)) return b\n if (op === '&') return (toText(a) as string) + (toText(b) as string)\n if (op.length === 2 || op === '=' || op === '<' || op === '>') {\n const c = compareValues(a, b)\n switch (op) {\n case '=':\n return c === 0\n case '<>':\n return c !== 0\n case '<':\n return c < 0\n case '>':\n return c > 0\n case '<=':\n return c <= 0\n default:\n return c >= 0\n }\n }\n const x = toNumber(a)\n if (isSheetError(x)) return x\n const y = toNumber(b)\n if (isSheetError(y)) return y\n let out: number\n switch (op) {\n case '+':\n out = x + y\n // a date and a number of days (or a time) is a date; a time and a time, or a part of a day, a time\n if (isDateText(a) !== isDateText(b)) return dateText(out)\n if (isTimeText(a) || isTimeText(b)) return clock(out)\n break\n case '-':\n out = x - y\n if (isDateText(a) && !isDateText(b)) return dateText(out)\n // two times apart is how long between them, as a time too\n if (isTimeText(a) && !isDateText(b)) return clock(out)\n break\n case '*':\n out = x * y\n break\n case '/':\n if (y === 0) return DIV0\n out = x / y\n break\n default:\n if (x === 0 && y < 0) return DIV0\n out = x ** y\n }\n return Number.isFinite(out) ? out : NUM\n }\n}\n\nconst clock = (days: number): SheetResult => (Number.isFinite(days) ? timeOrDays(days) : NUM)\n\n/** Whether an argument is a range of more than one cell. */\nconst isArray = (arg: SheetArg): arg is SheetRangeArg =>\n isRange(arg) && (arg.rows > 1 || arg.cols > 1)\n\nfunction unary(op: string, v: SheetResult): SheetResult {\n if (isSheetError(v)) return v\n if (op === '+') return v\n const n = toNumber(v)\n if (isSheetError(n)) return n\n return op === '-' ? -n : n / 100\n}\n\n/** The cell of an argument that goes with `row` and `col` of `cellByCell`'s result. */\nfunction pick(arg: SheetArg, row: number, col: number): SheetResult {\n if (!isRange(arg)) return arg\n const r = arg.rows === 1 ? 0 : row\n const c = arg.cols === 1 ? 0 : col\n return r < arg.rows && c < arg.cols ? arg.at(r, c) : NA\n}\n\n/**\n * Works out an operator for each cell of its ranges, into a range as big as\n * the biggest. A single value, or a range one row or column across, is used\n * for every row or column; past the end of a smaller range the cell is `#N/A`.\n */\nfunction cellByCell(\n args: SheetArg[],\n run: (...values: SheetResult[]) => SheetResult,\n): SheetRangeArg {\n let rows = 1\n let cols = 1\n for (const arg of args)\n if (isRange(arg)) {\n rows = Math.max(rows, arg.rows)\n cols = Math.max(cols, arg.cols)\n }\n const values: SheetResult[] = []\n for (let row = 0; row < rows; row++)\n for (let col = 0; col < cols; col++) values.push(run(...args.map((arg) => pick(arg, row, col))))\n return {\n kind: 'range',\n rows,\n cols,\n at: (row, col) => values[row * cols + col] ?? null,\n *entries() {\n for (let i = 0; i < values.length; i++)\n if (values[i] != null) yield [Math.floor(i / cols), i % cols, values[i]!]\n },\n }\n}\n\nconst restructures = (step: SheetStep) =>\n step.type === 'insertLines' ||\n step.type === 'removeLines' ||\n step.type === 'moveLines' ||\n step.type === 'insertSheet' ||\n step.type === 'removeSheet' ||\n (step.type === 'updateSheet' &&\n ('name' in step.patch ||\n 'id' in step.patch ||\n 'rowCount' in step.patch ||\n 'colCount' in step.patch))\n"],"mappings":"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"}