@veltra/sheet 2.0.1 → 2.1.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 (211) hide show
  1. package/dist/index.d.ts +4 -26
  2. package/dist/index.js +3 -23
  3. package/dist/tools/builtin.js +279 -8
  4. package/dist/tools/context.d.ts +54 -11
  5. package/dist/tools/context.js +28 -5
  6. package/dist/tools/download.js +68 -0
  7. package/dist/tools/export.worker.d.ts +16 -0
  8. package/dist/tools/export.worker.js +29 -0
  9. package/dist/tools/registry.d.ts +7 -4
  10. package/dist/tools/registry.js +1 -3
  11. package/dist/types/index.d.ts +1 -0
  12. package/dist/types/sheet.d.ts +6 -5
  13. package/dist/vue/formula-bar.js +393 -0
  14. package/dist/vue/formula-suggest-list.js +66 -0
  15. package/dist/vue/import-file.js +140 -0
  16. package/dist/vue/insert-count-menu-item.js +68 -0
  17. package/dist/vue/insert-image.js +79 -0
  18. package/dist/vue/popup-helpers.js +73 -0
  19. package/dist/vue/popups/border-popup.js +75 -0
  20. package/dist/vue/popups/border-preset-glyph.js +106 -0
  21. package/dist/vue/popups/export-popup.js +51 -0
  22. package/dist/vue/popups/fill-color-popup.js +37 -0
  23. package/dist/vue/popups/find-popup.js +113 -0
  24. package/dist/vue/popups/font-color-popup.js +37 -0
  25. package/dist/vue/popups/font-size-popup.js +63 -0
  26. package/dist/vue/popups/import.worker.d.ts +21 -0
  27. package/dist/vue/popups/import.worker.js +41 -0
  28. package/dist/vue/popups/insert-image-popup.js +38 -0
  29. package/dist/vue/sheet-context-menu.js +200 -0
  30. package/dist/vue/sheet-tabs.js +162 -0
  31. package/dist/vue/sheet-toolbar.js +106 -0
  32. package/dist/vue/sheet.js +144 -164
  33. package/dist/vue/style.d.ts +11 -1
  34. package/dist/vue/style.js +10 -0
  35. package/dist/vue/style2.css +564 -3
  36. package/dist/vue/use-find-replace.js +161 -0
  37. package/dist/vue/use-formula-ref-select.js +37 -0
  38. package/dist/vue/use-formula-suggest.js +126 -0
  39. package/dist/vue/use-sheet-grid.js +180 -0
  40. package/dist/vue/use-sheet-state.js +122 -0
  41. package/dist/vue/use-sheet-tabs-bar.js +90 -0
  42. package/dist/vue/use-tool-groups.js +42 -0
  43. package/dist/vue/use-tool-popup.js +123 -0
  44. package/dist/vue/use-toolbar-scroll.js +72 -0
  45. package/package.json +12 -7
  46. package/src/env.d.ts +11 -0
  47. package/src/index.ts +203 -14
  48. package/src/tools/__test__/builtin.test.ts +208 -66
  49. package/src/tools/__test__/context.test.ts +62 -2
  50. package/src/tools/builtin.ts +350 -6
  51. package/src/tools/context.ts +89 -11
  52. package/src/tools/download.ts +82 -0
  53. package/src/tools/export.worker.ts +61 -0
  54. package/src/tools/registry.ts +16 -1
  55. package/src/types/index.ts +6 -0
  56. package/src/types/sheet.ts +5 -3
  57. package/src/vue/__test__/formula-bar.test.ts +455 -0
  58. package/src/vue/__test__/import-file.test.ts +183 -0
  59. package/src/vue/__test__/insert-count-menu-item.test.ts +123 -0
  60. package/src/vue/__test__/insert-image-popup.test.ts +89 -0
  61. package/src/vue/__test__/insert-image.test.ts +100 -0
  62. package/src/vue/__test__/sheet-component.test.ts +716 -27
  63. package/src/vue/__test__/sheet-context-menu.test.ts +206 -0
  64. package/src/vue/__test__/use-formula-ref-select.test.ts +35 -0
  65. package/src/vue/__test__/use-formula-suggest.test.ts +83 -0
  66. package/src/vue/formula-bar.vue +473 -0
  67. package/src/vue/formula-suggest-list.vue +64 -0
  68. package/src/vue/import-file.ts +173 -0
  69. package/src/vue/insert-count-menu-item.vue +69 -0
  70. package/src/vue/insert-image.ts +90 -0
  71. package/src/vue/parsing.ts +11 -0
  72. package/src/vue/popup-helpers.ts +50 -0
  73. package/src/vue/popups/border-popup.vue +93 -0
  74. package/src/vue/popups/border-preset-glyph.vue +111 -0
  75. package/src/vue/popups/export-popup.vue +52 -0
  76. package/src/vue/popups/fill-color-popup.vue +39 -0
  77. package/src/vue/popups/find-popup.vue +111 -0
  78. package/src/vue/popups/font-color-popup.vue +39 -0
  79. package/src/vue/popups/font-size-popup.vue +52 -0
  80. package/src/vue/popups/import.worker.ts +66 -0
  81. package/src/vue/popups/insert-image-popup.vue +39 -0
  82. package/src/vue/sheet-context-menu.ts +229 -0
  83. package/src/vue/sheet-tabs.vue +162 -0
  84. package/src/vue/sheet-toolbar.vue +91 -0
  85. package/src/vue/sheet.vue +161 -186
  86. package/src/vue/style.scss +646 -3
  87. package/src/vue/style.ts +11 -0
  88. package/src/vue/use-find-replace.ts +200 -0
  89. package/src/vue/use-formula-ref-select.ts +34 -0
  90. package/src/vue/use-formula-suggest.ts +150 -0
  91. package/src/vue/use-sheet-grid.ts +243 -0
  92. package/src/vue/use-sheet-state.ts +161 -0
  93. package/src/vue/use-sheet-tabs-bar.ts +115 -0
  94. package/src/vue/use-tool-groups.ts +61 -0
  95. package/src/vue/use-tool-popup.ts +150 -0
  96. package/src/vue/use-toolbar-scroll.ts +95 -0
  97. package/dist/core/address.d.ts +0 -60
  98. package/dist/core/address.js +0 -142
  99. package/dist/core/address.js.map +0 -1
  100. package/dist/core/cell-store.d.ts +0 -61
  101. package/dist/core/cell-store.js +0 -135
  102. package/dist/core/cell-store.js.map +0 -1
  103. package/dist/core/command/default-registry.d.ts +0 -10
  104. package/dist/core/command/default-registry.js +0 -18
  105. package/dist/core/command/default-registry.js.map +0 -1
  106. package/dist/core/command/history.d.ts +0 -64
  107. package/dist/core/command/history.js +0 -127
  108. package/dist/core/command/history.js.map +0 -1
  109. package/dist/core/command/merge-cells.d.ts +0 -21
  110. package/dist/core/command/merge-cells.js +0 -74
  111. package/dist/core/command/merge-cells.js.map +0 -1
  112. package/dist/core/command/registry.d.ts +0 -17
  113. package/dist/core/command/registry.js +0 -26
  114. package/dist/core/command/registry.js.map +0 -1
  115. package/dist/core/command/set-cell-formula.d.ts +0 -17
  116. package/dist/core/command/set-cell-formula.js +0 -29
  117. package/dist/core/command/set-cell-formula.js.map +0 -1
  118. package/dist/core/command/set-cell-value.d.ts +0 -22
  119. package/dist/core/command/set-cell-value.js +0 -35
  120. package/dist/core/command/set-cell-value.js.map +0 -1
  121. package/dist/core/command/types.d.ts +0 -68
  122. package/dist/core/events.js +0 -29
  123. package/dist/core/events.js.map +0 -1
  124. package/dist/core/fill.d.ts +0 -34
  125. package/dist/core/fill.js +0 -320
  126. package/dist/core/fill.js.map +0 -1
  127. package/dist/core/formula/ast.d.ts +0 -58
  128. package/dist/core/formula/ast.js +0 -38
  129. package/dist/core/formula/ast.js.map +0 -1
  130. package/dist/core/formula/dependency-graph.d.ts +0 -70
  131. package/dist/core/formula/dependency-graph.js +0 -269
  132. package/dist/core/formula/dependency-graph.js.map +0 -1
  133. package/dist/core/formula/errors.d.ts +0 -25
  134. package/dist/core/formula/errors.js +0 -36
  135. package/dist/core/formula/errors.js.map +0 -1
  136. package/dist/core/formula/evaluator.d.ts +0 -41
  137. package/dist/core/formula/evaluator.js +0 -154
  138. package/dist/core/formula/evaluator.js.map +0 -1
  139. package/dist/core/formula/functions.d.ts +0 -30
  140. package/dist/core/formula/functions.js +0 -209
  141. package/dist/core/formula/functions.js.map +0 -1
  142. package/dist/core/formula/parser.d.ts +0 -7
  143. package/dist/core/formula/parser.js +0 -208
  144. package/dist/core/formula/parser.js.map +0 -1
  145. package/dist/core/formula/tokenizer.d.ts +0 -37
  146. package/dist/core/formula/tokenizer.js +0 -146
  147. package/dist/core/formula/tokenizer.js.map +0 -1
  148. package/dist/core/merge-manager.d.ts +0 -64
  149. package/dist/core/merge-manager.js +0 -111
  150. package/dist/core/merge-manager.js.map +0 -1
  151. package/dist/core/selection.d.ts +0 -31
  152. package/dist/core/selection.js +0 -62
  153. package/dist/core/selection.js.map +0 -1
  154. package/dist/core/sheet.d.ts +0 -116
  155. package/dist/core/sheet.js +0 -224
  156. package/dist/core/sheet.js.map +0 -1
  157. package/dist/core/workbook.d.ts +0 -43
  158. package/dist/core/workbook.js +0 -77
  159. package/dist/core/workbook.js.map +0 -1
  160. package/dist/grid/sheet-grid.d.ts +0 -81
  161. package/dist/grid/sheet-grid.js +0 -331
  162. package/dist/grid/sheet-grid.js.map +0 -1
  163. package/dist/grid/vtable-theme.js +0 -80
  164. package/dist/grid/vtable-theme.js.map +0 -1
  165. package/dist/tools/builtin.js.map +0 -1
  166. package/dist/tools/context.js.map +0 -1
  167. package/dist/tools/registry.js.map +0 -1
  168. package/dist/vue/index.d.ts +0 -2
  169. package/dist/vue/sheet.js.map +0 -1
  170. package/dist/vue/sheet.vue.d.ts +0 -24
  171. package/src/core/__test__/address.test.ts +0 -113
  172. package/src/core/__test__/cell-store.test.ts +0 -97
  173. package/src/core/__test__/command.test.ts +0 -377
  174. package/src/core/__test__/fill.test.ts +0 -140
  175. package/src/core/__test__/formula-adversarial.test.ts +0 -265
  176. package/src/core/__test__/formula-engine.test.ts +0 -470
  177. package/src/core/__test__/formula-functions.test.ts +0 -178
  178. package/src/core/__test__/formula-parser.test.ts +0 -160
  179. package/src/core/__test__/history.test.ts +0 -225
  180. package/src/core/__test__/merge-manager.test.ts +0 -106
  181. package/src/core/__test__/sheet.test.ts +0 -142
  182. package/src/core/address.ts +0 -166
  183. package/src/core/cell-store.ts +0 -173
  184. package/src/core/command/default-registry.ts +0 -15
  185. package/src/core/command/history.ts +0 -167
  186. package/src/core/command/index.ts +0 -7
  187. package/src/core/command/merge-cells.ts +0 -74
  188. package/src/core/command/registry.ts +0 -30
  189. package/src/core/command/set-cell-formula.ts +0 -29
  190. package/src/core/command/set-cell-value.ts +0 -38
  191. package/src/core/command/types.ts +0 -73
  192. package/src/core/events.ts +0 -37
  193. package/src/core/fill.ts +0 -314
  194. package/src/core/formula/ast.ts +0 -66
  195. package/src/core/formula/dependency-graph.ts +0 -329
  196. package/src/core/formula/errors.ts +0 -41
  197. package/src/core/formula/evaluator.ts +0 -219
  198. package/src/core/formula/functions.ts +0 -266
  199. package/src/core/formula/index.ts +0 -7
  200. package/src/core/formula/parser.ts +0 -198
  201. package/src/core/formula/tokenizer.ts +0 -160
  202. package/src/core/merge-manager.ts +0 -158
  203. package/src/core/selection.ts +0 -66
  204. package/src/core/sheet.ts +0 -282
  205. package/src/core/workbook.ts +0 -99
  206. package/src/grid/__test__/canvas-mock.ts +0 -49
  207. package/src/grid/__test__/setup.ts +0 -3
  208. package/src/grid/__test__/sheet-grid-formula.test.ts +0 -140
  209. package/src/grid/__test__/sheet-grid.test.ts +0 -275
  210. package/src/grid/sheet-grid.ts +0 -407
  211. package/src/grid/vtable-theme.ts +0 -61
@@ -1,269 +0,0 @@
1
- import { cellKey, rangeContainsAddress } from "../address.js";
2
- import { collectReferences } from "./ast.js";
3
- import { formulaError, isFormulaError, isFormulaErrorCode } from "./errors.js";
4
- import { evaluateAst } from "./evaluator.js";
5
- import { invokeFormulaFunction } from "./functions.js";
6
- import { FormulaParseError } from "./tokenizer.js";
7
- import { parseFormula } from "./parser.js";
8
- //#region src/core/formula/dependency-graph.ts
9
- var DependencyGraph = class {
10
- sheets = /* @__PURE__ */ new Map();
11
- /** 正向索引宿主:sheetName → cellKey → 公式节点 */
12
- nodes = /* @__PURE__ */ new Map();
13
- /** 反向索引(单格引用):sheetName → cellKey → 依赖者集合 */
14
- exact = /* @__PURE__ */ new Map();
15
- /** 反向索引(区域引用):sheetName → { 区域, 依赖者 } 集合(线性扫描) */
16
- ranged = /* @__PURE__ */ new Map();
17
- registerSheet(sheet) {
18
- this.sheets.set(sheet.name, sheet);
19
- }
20
- /** 注销 sheet 并移除其全部公式节点(引用它的公式在下次重算时得 #REF!) */
21
- unregisterSheet(sheet) {
22
- if (this.sheets.get(sheet.name) !== sheet) return;
23
- this.sheets.delete(sheet.name);
24
- const sheetNodes = this.nodes.get(sheet.name);
25
- if (sheetNodes) for (const node of sheetNodes.values()) this.removeNode(node);
26
- }
27
- getSheet(name) {
28
- return this.sheets.get(name);
29
- }
30
- getNode(sheetName, addr) {
31
- return this.nodes.get(sheetName)?.get(cellKey(addr));
32
- }
33
- /** 公式节点总数(测试/调试用) */
34
- get nodeCount() {
35
- let count = 0;
36
- for (const sheetNodes of this.nodes.values()) count += sheetNodes.size;
37
- return count;
38
- }
39
- /**
40
- * 单元格补丁后的图同步:f 增/删/改 → 节点增/删/重建。
41
- * 由 Sheet.applyPatch(唯一变更通道)调用,命令与 undo/redo 回放均覆盖。
42
- */
43
- syncCell(sheet, addr, before, after) {
44
- const beforeF = before?.f;
45
- const afterF = after?.f;
46
- if (beforeF === afterF) return;
47
- const existing = this.getNode(sheet.name, addr);
48
- if (existing) this.removeNode(existing);
49
- if (afterF != null && afterF !== "") this.addNode(sheet, addr, afterF);
50
- }
51
- /**
52
- * 变更集 → 标脏 + 拓扑序重算 → 派生补丁(未应用)。
53
- * 调用方(Sheet.executeCommand)负责应用补丁并并入同一 undo 单元。
54
- */
55
- recalc(changed) {
56
- const dirty = /* @__PURE__ */ new Set();
57
- const queue = [];
58
- const mark = (node) => {
59
- if (node && !dirty.has(node)) {
60
- dirty.add(node);
61
- queue.push(node);
62
- }
63
- };
64
- for (const { sheet, addr } of changed) {
65
- mark(this.getNode(sheet.name, addr));
66
- for (const dep of this.dependentsOf(sheet.name, addr)) mark(dep);
67
- }
68
- for (let i = 0; i < queue.length; i++) {
69
- const node = queue[i];
70
- for (const dep of this.dependentsOf(node.sheetName, node.addr)) mark(dep);
71
- }
72
- if (dirty.size === 0) return [];
73
- const passValues = /* @__PURE__ */ new Map();
74
- const inProgress = /* @__PURE__ */ new Set();
75
- const stack = [];
76
- const cycleMembers = /* @__PURE__ */ new Set();
77
- const cycleError = formulaError("#CYCLE!");
78
- const evaluateNode = (node) => {
79
- const cached = passValues.get(node);
80
- if (cached !== void 0) return cached;
81
- if (inProgress.has(node)) {
82
- const start = stack.indexOf(node);
83
- for (let i = start; i < stack.length; i++) cycleMembers.add(stack[i]);
84
- return cycleError;
85
- }
86
- if (!node.ast) {
87
- const error = formulaError("#ERROR!");
88
- passValues.set(node, error);
89
- return error;
90
- }
91
- inProgress.add(node);
92
- stack.push(node);
93
- let value;
94
- try {
95
- value = evaluateAst(node.ast, {
96
- currentSheet: node.sheetName,
97
- readCell,
98
- readRange,
99
- callFunction: invokeFormulaFunction
100
- });
101
- } catch {
102
- value = formulaError("#ERROR!");
103
- }
104
- inProgress.delete(node);
105
- stack.pop();
106
- if (Array.isArray(value)) value = formulaError("#VALUE!");
107
- if (cycleMembers.has(node)) value = cycleError;
108
- passValues.set(node, value);
109
- return value;
110
- };
111
- const readCell = (sheetName, addr) => {
112
- const sheet = this.sheets.get(sheetName);
113
- if (!sheet) return formulaError("#REF!");
114
- const dep = this.getNode(sheetName, addr);
115
- if (dep && dirty.has(dep)) return evaluateNode(dep);
116
- return cellDataToScalar(sheet.store.getCell(addr));
117
- };
118
- const readRange = (sheetName, range) => {
119
- const sheet = this.sheets.get(sheetName);
120
- if (!sheet) return formulaError("#REF!");
121
- const values = [];
122
- for (const [addr, data] of sheet.store.entriesInRange(range)) {
123
- const dep = this.getNode(sheetName, addr);
124
- values.push(dep && dirty.has(dep) ? evaluateNode(dep) : cellDataToScalar(data));
125
- }
126
- return values;
127
- };
128
- for (const node of dirty) evaluateNode(node);
129
- const patches = [];
130
- for (const node of dirty) {
131
- const { v, t } = serializeResult(passValues.get(node));
132
- const sheet = this.sheets.get(node.sheetName);
133
- if (!sheet) continue;
134
- const before = sheet.store.getCell(node.addr);
135
- if (before && before.f === node.formula && before.v === v && before.t === t) continue;
136
- patches.push({
137
- kind: "cell",
138
- sheet,
139
- addr: node.addr,
140
- before,
141
- after: {
142
- f: node.formula,
143
- v,
144
- t
145
- }
146
- });
147
- }
148
- return patches;
149
- }
150
- addNode(sheet, addr, formula) {
151
- let ast = null;
152
- try {
153
- ast = parseFormula(formula);
154
- } catch (error) {
155
- if (!(error instanceof FormulaParseError)) throw error;
156
- }
157
- const deps = [];
158
- if (ast) {
159
- const seen = /* @__PURE__ */ new Set();
160
- for (const ref of collectReferences(ast)) {
161
- const sheetName = ref.sheet ?? sheet.name;
162
- const { start, end } = ref.range;
163
- const key = `${sheetName}|${start.row},${start.col},${end.row},${end.col}`;
164
- if (seen.has(key)) continue;
165
- seen.add(key);
166
- deps.push({
167
- sheetName,
168
- range: ref.range
169
- });
170
- }
171
- }
172
- const node = {
173
- sheetName: sheet.name,
174
- addr: { ...addr },
175
- formula,
176
- ast,
177
- deps,
178
- cleanups: []
179
- };
180
- let sheetNodes = this.nodes.get(sheet.name);
181
- if (!sheetNodes) {
182
- sheetNodes = /* @__PURE__ */ new Map();
183
- this.nodes.set(sheet.name, sheetNodes);
184
- }
185
- sheetNodes.set(cellKey(addr), node);
186
- for (const dep of deps) if (dep.range.start.row === dep.range.end.row && dep.range.start.col === dep.range.end.col) {
187
- let sheetExact = this.exact.get(dep.sheetName);
188
- if (!sheetExact) {
189
- sheetExact = /* @__PURE__ */ new Map();
190
- this.exact.set(dep.sheetName, sheetExact);
191
- }
192
- const key = cellKey(dep.range.start);
193
- let set = sheetExact.get(key);
194
- if (!set) {
195
- set = /* @__PURE__ */ new Set();
196
- sheetExact.set(key, set);
197
- }
198
- set.add(node);
199
- node.cleanups.push(() => {
200
- set.delete(node);
201
- if (set.size === 0) sheetExact.delete(key);
202
- });
203
- } else {
204
- let set = this.ranged.get(dep.sheetName);
205
- if (!set) {
206
- set = /* @__PURE__ */ new Set();
207
- this.ranged.set(dep.sheetName, set);
208
- }
209
- const entry = {
210
- range: dep.range,
211
- node
212
- };
213
- set.add(entry);
214
- node.cleanups.push(() => set.delete(entry));
215
- }
216
- }
217
- removeNode(node) {
218
- for (const cleanup of node.cleanups) cleanup();
219
- const sheetNodes = this.nodes.get(node.sheetName);
220
- if (!sheetNodes) return;
221
- sheetNodes.delete(cellKey(node.addr));
222
- if (sheetNodes.size === 0) this.nodes.delete(node.sheetName);
223
- }
224
- dependentsOf(sheetName, addr) {
225
- const result = /* @__PURE__ */ new Set();
226
- const exactSet = this.exact.get(sheetName)?.get(cellKey(addr));
227
- if (exactSet) for (const node of exactSet) result.add(node);
228
- const rangedSet = this.ranged.get(sheetName);
229
- if (rangedSet) {
230
- for (const entry of rangedSet) if (rangeContainsAddress(entry.range, addr)) result.add(entry.node);
231
- }
232
- return result;
233
- }
234
- };
235
- /** 单元格存储 → 求值标量(t='e' 还原为错误标记;空 → null) */
236
- function cellDataToScalar(data) {
237
- if (!data || data.v == null) return null;
238
- if (data.t === "e") return formulaError(isFormulaErrorCode(data.v) ? data.v : "#ERROR!");
239
- return data.v;
240
- }
241
- /** 求值结果 → 存储形态(str = 公式结果字符串,e = 错误;与决策 2 一致) */
242
- function serializeResult(value) {
243
- if (isFormulaError(value)) return {
244
- v: value.code,
245
- t: "e"
246
- };
247
- if (value === null) return {
248
- v: 0,
249
- t: "n"
250
- };
251
- switch (typeof value) {
252
- case "number": return {
253
- v: value,
254
- t: "n"
255
- };
256
- case "boolean": return {
257
- v: value,
258
- t: "b"
259
- };
260
- default: return {
261
- v: value,
262
- t: "str"
263
- };
264
- }
265
- }
266
- //#endregion
267
- export { DependencyGraph };
268
-
269
- //# sourceMappingURL=dependency-graph.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dependency-graph.js","names":[],"sources":["../../../src/core/formula/dependency-graph.ts"],"sourcesContent":["import { cellKey, rangeContainsAddress, type CellAddress, type CellRange } from '../address'\nimport type { CellData, CellType, CellValue } from '../cell-store'\nimport type { CellPatch } from '../command/types'\nimport type { Sheet } from '../sheet'\nimport { collectReferences, type AstNode } from './ast'\nimport { formulaError, isFormulaError, isFormulaErrorCode, type FormulaError } from './errors'\nimport { evaluateAst, type ScalarValue } from './evaluator'\nimport { invokeFormulaFunction } from './functions'\nimport { parseFormula } from './parser'\nimport { FormulaParseError } from './tokenizer'\n\n/**\n * 依赖图(工作簿级计算中枢):\n * - sheet 注册表:跨表引用按名解析(不存在 → #REF!)\n * - 正向索引:公式格 → 引用格集合(节点 deps);反向索引:引用格 → 依赖者\n * (单格引用走 cellKey 精确索引 O(1),区域引用走线性扫描)\n * - 变更时标脏(向上 BFS 收集全部依赖者)+ 拓扑序增量重算(递归向下,memo 去重)\n * - 循环引用检测:求值在途栈遇回边 → 环上所有格 #CYCLE!;依赖环外格只传播错误,\n * 打破循环(编辑环上格)后经标脏重算自动恢复\n *\n * 图状态与单元格存储严格同步:所有 f 变更(命令/undo/redo/rollback 回放)都经\n * Sheet.applyPatch → syncCell 维护节点;重算只发生在命令执行后(见 Sheet.executeCommand),\n * undo/redo 靠派生补丁精确回放缓存值,不重算。\n */\n\n/** 公式节点的一个依赖(sheet 名已补齐为具体值) */\nexport interface FormulaDependency {\n sheetName: string\n range: CellRange\n}\n\n/** 公式节点(依赖图管理的公式格状态;依赖图外部只读使用) */\nexport interface FormulaNode {\n readonly sheetName: string\n readonly addr: CellAddress\n readonly formula: string\n /** null = 解析失败(求值 #ERROR!,无依赖) */\n readonly ast: AstNode | null\n readonly deps: FormulaDependency[]\n /** 反向索引清理器(removeNode 时执行) */\n readonly cleanups: (() => void)[]\n}\n\nexport class DependencyGraph {\n private readonly sheets = new Map<string, Sheet>()\n /** 正向索引宿主:sheetName → cellKey → 公式节点 */\n private readonly nodes = new Map<string, Map<number, FormulaNode>>()\n /** 反向索引(单格引用):sheetName → cellKey → 依赖者集合 */\n private readonly exact = new Map<string, Map<number, Set<FormulaNode>>>()\n /** 反向索引(区域引用):sheetName → { 区域, 依赖者 } 集合(线性扫描) */\n private readonly ranged = new Map<string, Set<{ range: CellRange; node: FormulaNode }>>()\n\n // ─── sheet 注册表 ─────────────────────────────────────────\n\n registerSheet(sheet: Sheet): void {\n this.sheets.set(sheet.name, sheet)\n }\n\n /** 注销 sheet 并移除其全部公式节点(引用它的公式在下次重算时得 #REF!) */\n unregisterSheet(sheet: Sheet): void {\n if (this.sheets.get(sheet.name) !== sheet) return\n this.sheets.delete(sheet.name)\n const sheetNodes = this.nodes.get(sheet.name)\n if (sheetNodes) {\n for (const node of sheetNodes.values()) this.removeNode(node)\n }\n }\n\n getSheet(name: string): Sheet | undefined {\n return this.sheets.get(name)\n }\n\n // ─── 节点查询 ─────────────────────────────────────────────\n\n getNode(sheetName: string, addr: CellAddress): FormulaNode | undefined {\n return this.nodes.get(sheetName)?.get(cellKey(addr))\n }\n\n /** 公式节点总数(测试/调试用) */\n get nodeCount(): number {\n let count = 0\n for (const sheetNodes of this.nodes.values()) count += sheetNodes.size\n return count\n }\n\n // ─── 图同步 ───────────────────────────────────────────────\n\n /**\n * 单元格补丁后的图同步:f 增/删/改 → 节点增/删/重建。\n * 由 Sheet.applyPatch(唯一变更通道)调用,命令与 undo/redo 回放均覆盖。\n */\n syncCell(sheet: Sheet, addr: CellAddress, before?: CellData, after?: CellData): void {\n const beforeF = before?.f\n const afterF = after?.f\n if (beforeF === afterF) return\n const existing = this.getNode(sheet.name, addr)\n if (existing) this.removeNode(existing)\n if (afterF != null && afterF !== '') this.addNode(sheet, addr, afterF)\n }\n\n // ─── 增量重算 ─────────────────────────────────────────────\n\n /**\n * 变更集 → 标脏 + 拓扑序重算 → 派生补丁(未应用)。\n * 调用方(Sheet.executeCommand)负责应用补丁并并入同一 undo 单元。\n */\n recalc(changed: readonly { sheet: Sheet; addr: CellAddress }[]): CellPatch[] {\n // 1. 标脏:变更格自身(若是公式)+ 向上 BFS 全部传递依赖者\n const dirty = new Set<FormulaNode>()\n const queue: FormulaNode[] = []\n const mark = (node: FormulaNode | undefined): void => {\n if (node && !dirty.has(node)) {\n dirty.add(node)\n queue.push(node)\n }\n }\n for (const { sheet, addr } of changed) {\n mark(this.getNode(sheet.name, addr))\n for (const dep of this.dependentsOf(sheet.name, addr)) mark(dep)\n }\n for (let i = 0; i < queue.length; i++) {\n const node = queue[i]!\n for (const dep of this.dependentsOf(node.sheetName, node.addr)) mark(dep)\n }\n if (dirty.size === 0) return []\n\n // 2. 拓扑序求值:递归向下(依赖先于使用求值),memo 保证每格最多算一次;\n // 在途栈遇回边 → 栈中自回边点起全部入环(#CYCLE!)\n const passValues = new Map<FormulaNode, ScalarValue | FormulaError>()\n const inProgress = new Set<FormulaNode>()\n const stack: FormulaNode[] = []\n const cycleMembers = new Set<FormulaNode>()\n const cycleError = formulaError('#CYCLE!')\n\n const evaluateNode = (node: FormulaNode): ScalarValue | FormulaError => {\n const cached = passValues.get(node)\n if (cached !== undefined) return cached\n if (inProgress.has(node)) {\n const start = stack.indexOf(node)\n for (let i = start; i < stack.length; i++) cycleMembers.add(stack[i]!)\n return cycleError\n }\n if (!node.ast) {\n const error = formulaError('#ERROR!')\n passValues.set(node, error)\n return error\n }\n inProgress.add(node)\n stack.push(node)\n let value: ScalarValue | FormulaError | (ScalarValue | FormulaError)[]\n try {\n value = evaluateAst(node.ast, {\n currentSheet: node.sheetName,\n readCell,\n readRange,\n callFunction: invokeFormulaFunction\n })\n } catch {\n // 求值期异常(如自定义函数抛错)不应击穿重算 → 记为 #ERROR!\n value = formulaError('#ERROR!')\n }\n inProgress.delete(node)\n stack.pop()\n // 区域直接作为公式结果('=A1:B2')→ #VALUE!\n if (Array.isArray(value)) value = formulaError('#VALUE!')\n if (cycleMembers.has(node)) value = cycleError\n passValues.set(node, value)\n return value\n }\n\n const readCell = (sheetName: string, addr: CellAddress): ScalarValue | FormulaError => {\n const sheet = this.sheets.get(sheetName)\n if (!sheet) return formulaError('#REF!')\n const dep = this.getNode(sheetName, addr)\n if (dep && dirty.has(dep)) return evaluateNode(dep)\n return cellDataToScalar(sheet.store.getCell(addr))\n }\n\n const readRange = (\n sheetName: string,\n range: CellRange\n ): (ScalarValue | FormulaError)[] | FormulaError => {\n const sheet = this.sheets.get(sheetName)\n if (!sheet) return formulaError('#REF!')\n const values: (ScalarValue | FormulaError)[] = []\n // 只迭代稀疏存在的格\n for (const [addr, data] of sheet.store.entriesInRange(range)) {\n const dep = this.getNode(sheetName, addr)\n values.push(dep && dirty.has(dep) ? evaluateNode(dep) : cellDataToScalar(data))\n }\n return values\n }\n\n for (const node of dirty) evaluateNode(node)\n\n // 3. 派生补丁:缓存值与重算结果一致的跳过(收敛时不产生冗余变更)\n const patches: CellPatch[] = []\n for (const node of dirty) {\n const value = passValues.get(node)!\n const { v, t } = serializeResult(value)\n const sheet = this.sheets.get(node.sheetName)\n if (!sheet) continue\n const before = sheet.store.getCell(node.addr)\n if (before && before.f === node.formula && before.v === v && before.t === t) continue\n patches.push({\n kind: 'cell',\n sheet,\n addr: node.addr,\n before,\n after: { f: node.formula, v, t }\n })\n }\n return patches\n }\n\n // ─── 内部 ─────────────────────────────────────────────────\n\n private addNode(sheet: Sheet, addr: CellAddress, formula: string): void {\n let ast: AstNode | null = null\n try {\n ast = parseFormula(formula)\n } catch (error) {\n if (!(error instanceof FormulaParseError)) throw error\n // 解析失败:ast 保持 null(求值 #ERROR!,无依赖)\n }\n const deps: FormulaDependency[] = []\n if (ast) {\n const seen = new Set<string>()\n for (const ref of collectReferences(ast)) {\n const sheetName = ref.sheet ?? sheet.name\n const { start, end } = ref.range\n const key = `${sheetName}|${start.row},${start.col},${end.row},${end.col}`\n if (seen.has(key)) continue\n seen.add(key)\n deps.push({ sheetName, range: ref.range })\n }\n }\n const node: FormulaNode = {\n sheetName: sheet.name,\n addr: { ...addr },\n formula,\n ast,\n deps,\n cleanups: []\n }\n let sheetNodes = this.nodes.get(sheet.name)\n if (!sheetNodes) {\n sheetNodes = new Map()\n this.nodes.set(sheet.name, sheetNodes)\n }\n sheetNodes.set(cellKey(addr), node)\n\n for (const dep of deps) {\n const isSingleCell =\n dep.range.start.row === dep.range.end.row && dep.range.start.col === dep.range.end.col\n if (isSingleCell) {\n let sheetExact = this.exact.get(dep.sheetName)\n if (!sheetExact) {\n sheetExact = new Map()\n this.exact.set(dep.sheetName, sheetExact)\n }\n const key = cellKey(dep.range.start)\n let set = sheetExact.get(key)\n if (!set) {\n set = new Set()\n sheetExact.set(key, set)\n }\n set.add(node)\n node.cleanups.push(() => {\n set.delete(node)\n if (set.size === 0) sheetExact.delete(key)\n })\n } else {\n let set = this.ranged.get(dep.sheetName)\n if (!set) {\n set = new Set()\n this.ranged.set(dep.sheetName, set)\n }\n const entry = { range: dep.range, node }\n set.add(entry)\n node.cleanups.push(() => set.delete(entry))\n }\n }\n }\n\n private removeNode(node: FormulaNode): void {\n for (const cleanup of node.cleanups) cleanup()\n const sheetNodes = this.nodes.get(node.sheetName)\n if (!sheetNodes) return\n sheetNodes.delete(cellKey(node.addr))\n if (sheetNodes.size === 0) this.nodes.delete(node.sheetName)\n }\n\n private dependentsOf(sheetName: string, addr: CellAddress): Set<FormulaNode> {\n const result = new Set<FormulaNode>()\n const exactSet = this.exact.get(sheetName)?.get(cellKey(addr))\n if (exactSet) for (const node of exactSet) result.add(node)\n const rangedSet = this.ranged.get(sheetName)\n if (rangedSet) {\n for (const entry of rangedSet) {\n if (rangeContainsAddress(entry.range, addr)) result.add(entry.node)\n }\n }\n return result\n }\n}\n\n/** 单元格存储 → 求值标量(t='e' 还原为错误标记;空 → null) */\nfunction cellDataToScalar(data: CellData | undefined): ScalarValue | FormulaError {\n if (!data || data.v == null) return null\n if (data.t === 'e') {\n return formulaError(isFormulaErrorCode(data.v) ? data.v : '#ERROR!')\n }\n return data.v\n}\n\n/** 求值结果 → 存储形态(str = 公式结果字符串,e = 错误;与决策 2 一致) */\nfunction serializeResult(value: ScalarValue | FormulaError): { v: CellValue; t: CellType } {\n if (isFormulaError(value)) return { v: value.code, t: 'e' }\n if (value === null) return { v: 0, t: 'n' } // 防御:公式结果不会是 null\n switch (typeof value) {\n case 'number':\n return { v: value, t: 'n' }\n case 'boolean':\n return { v: value, t: 'b' }\n default:\n return { v: value, t: 'str' }\n }\n}\n"],"mappings":"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"}
@@ -1,25 +0,0 @@
1
- //#region src/core/formula/errors.d.ts
2
- /**
3
- * 公式错误值体系。
4
- *
5
- * 求值器内部以 FormulaError 标记对象传递错误(随运算传播);
6
- * 写入单元格时序列化为 `v = 错误码, t = 'e'`(决策 2 的 CellData 格式)。
7
- */
8
- /** 公式错误码(Excel 子集 + 解析失败 #ERROR!、循环引用 #CYCLE!) */
9
- declare const FORMULA_ERROR_CODES: readonly ["#DIV/0!", "#VALUE!", "#NAME?", "#REF!", "#ERROR!", "#CYCLE!"];
10
- type FormulaErrorCode = (typeof FORMULA_ERROR_CODES)[number];
11
- declare const ERROR_BRAND: unique symbol;
12
- /** 求值过程中的错误标记(不参与普通值运算,遇运算即传播) */
13
- interface FormulaError {
14
- readonly [ERROR_BRAND]: true;
15
- readonly code: FormulaErrorCode;
16
- }
17
- /** 构造错误标记 */
18
- declare function formulaError(code: FormulaErrorCode): FormulaError;
19
- /** 判定错误标记 */
20
- declare function isFormulaError(value: unknown): value is FormulaError;
21
- /** 判定合法错误码(读取 t='e' 的单元格时校验) */
22
- declare function isFormulaErrorCode(value: unknown): value is FormulaErrorCode;
23
- //#endregion
24
- export { FORMULA_ERROR_CODES, FormulaError, FormulaErrorCode, formulaError, isFormulaError, isFormulaErrorCode };
25
- //# sourceMappingURL=errors.d.ts.map
@@ -1,36 +0,0 @@
1
- //#region src/core/formula/errors.ts
2
- /**
3
- * 公式错误值体系。
4
- *
5
- * 求值器内部以 FormulaError 标记对象传递错误(随运算传播);
6
- * 写入单元格时序列化为 `v = 错误码, t = 'e'`(决策 2 的 CellData 格式)。
7
- */
8
- /** 公式错误码(Excel 子集 + 解析失败 #ERROR!、循环引用 #CYCLE!) */
9
- const FORMULA_ERROR_CODES = [
10
- "#DIV/0!",
11
- "#VALUE!",
12
- "#NAME?",
13
- "#REF!",
14
- "#ERROR!",
15
- "#CYCLE!"
16
- ];
17
- const ERROR_BRAND = Symbol("veltra-sheet.formula-error");
18
- /** 构造错误标记 */
19
- function formulaError(code) {
20
- return {
21
- [ERROR_BRAND]: true,
22
- code
23
- };
24
- }
25
- /** 判定错误标记 */
26
- function isFormulaError(value) {
27
- return typeof value === "object" && value !== null && ERROR_BRAND in value;
28
- }
29
- /** 判定合法错误码(读取 t='e' 的单元格时校验) */
30
- function isFormulaErrorCode(value) {
31
- return typeof value === "string" && FORMULA_ERROR_CODES.includes(value);
32
- }
33
- //#endregion
34
- export { FORMULA_ERROR_CODES, formulaError, isFormulaError, isFormulaErrorCode };
35
-
36
- //# sourceMappingURL=errors.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../../../src/core/formula/errors.ts"],"sourcesContent":["/**\n * 公式错误值体系。\n *\n * 求值器内部以 FormulaError 标记对象传递错误(随运算传播);\n * 写入单元格时序列化为 `v = 错误码, t = 'e'`(决策 2 的 CellData 格式)。\n */\n\n/** 公式错误码(Excel 子集 + 解析失败 #ERROR!、循环引用 #CYCLE!) */\nexport const FORMULA_ERROR_CODES = [\n '#DIV/0!',\n '#VALUE!',\n '#NAME?',\n '#REF!',\n '#ERROR!',\n '#CYCLE!'\n] as const\n\nexport type FormulaErrorCode = (typeof FORMULA_ERROR_CODES)[number]\n\nconst ERROR_BRAND: unique symbol = Symbol('veltra-sheet.formula-error')\n\n/** 求值过程中的错误标记(不参与普通值运算,遇运算即传播) */\nexport interface FormulaError {\n readonly [ERROR_BRAND]: true\n readonly code: FormulaErrorCode\n}\n\n/** 构造错误标记 */\nexport function formulaError(code: FormulaErrorCode): FormulaError {\n return { [ERROR_BRAND]: true, code }\n}\n\n/** 判定错误标记 */\nexport function isFormulaError(value: unknown): value is FormulaError {\n return typeof value === 'object' && value !== null && ERROR_BRAND in value\n}\n\n/** 判定合法错误码(读取 t='e' 的单元格时校验) */\nexport function isFormulaErrorCode(value: unknown): value is FormulaErrorCode {\n return typeof value === 'string' && (FORMULA_ERROR_CODES as readonly string[]).includes(value)\n}\n"],"mappings":";;;;;;;;AAQA,MAAa,sBAAsB;CACjC;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,MAAM,cAA6B,OAAO,4BAA4B;;AAStE,SAAgB,aAAa,MAAsC;CACjE,OAAO;GAAG,cAAc;EAAM;CAAK;AACrC;;AAGA,SAAgB,eAAe,OAAuC;CACpE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,eAAe;AACvE;;AAGA,SAAgB,mBAAmB,OAA2C;CAC5E,OAAO,OAAO,UAAU,YAAa,oBAA0C,SAAS,KAAK;AAC/F"}
@@ -1,41 +0,0 @@
1
- import { CellAddress, CellRange } from "../address.js";
2
- import { AstNode } from "./ast.js";
3
- import { FormulaError } from "./errors.js";
4
- //#region src/core/formula/evaluator.d.ts
5
- /**
6
- * AST 求值器(纯函数,不持有状态;单元格读取经 FormulaEvalContext 注入)。
7
- *
8
- * 空格(null)参与运算的规则同 Excel:
9
- * - 数字上下文按 0(空单元格 +1 = 1);空字符串字面量参与算术 → #VALUE!
10
- * - 文本上下文按 '';布尔上下文按 FALSE
11
- * - 比较时空格归一为对方类型的零值
12
- * 错误值遇运算即传播(左操作数优先)。
13
- */
14
- /** 标量值(单元格/字面量可取的全集;null = 空单元格) */
15
- type ScalarValue = number | string | boolean | null;
16
- /** 求值结果:标量 / 错误 / 区域展开数组(数组仅作为函数参数形态出现) */
17
- type EvalValue = ScalarValue | FormulaError | (ScalarValue | FormulaError)[];
18
- /** 求值上下文(由依赖图按公式节点注入) */
19
- interface FormulaEvalContext {
20
- /** 当前公式所在 sheet(裸引用缺省表) */
21
- readonly currentSheet: string;
22
- /** 读取单格(原始存储语义;表不存在 → #REF!) */
23
- readCell(sheet: string, addr: CellAddress): ScalarValue | FormulaError;
24
- /** 读取区域(只含稀疏存在的格;表不存在 → #REF!) */
25
- readRange(sheet: string, range: CellRange): (ScalarValue | FormulaError)[] | FormulaError;
26
- /** 调用函数(名称未知 → #NAME?;参数个数非法 → #VALUE!) */
27
- callFunction(name: string, nodes: AstNode[], evalNode: (node: AstNode) => EvalValue): EvalValue;
28
- }
29
- /** 强转数字:null→0,布尔→1/0,数字文本→数字,其余文本→#VALUE!,错误传播 */
30
- declare function coerceToNumber(value: EvalValue): number | FormulaError;
31
- /** 强转文本:null→'',布尔→TRUE/FALSE,错误传播 */
32
- declare function coerceToText(value: EvalValue): string | FormulaError;
33
- /** 强转布尔:null→FALSE,数字≠0,TRUE/FALSE 文本,其余文本→#VALUE!,错误传播 */
34
- declare function coerceToBoolean(value: EvalValue): boolean | FormulaError;
35
- /** 比较:同类型按类型规则(文本大小写不敏感);混合类型 数字 < 文本 < 布尔;null 归一为对方零值 */
36
- declare function compareScalars(left: ScalarValue, right: ScalarValue): number;
37
- /** AST 求值 */
38
- declare function evaluateAst(node: AstNode, ctx: FormulaEvalContext): EvalValue;
39
- //#endregion
40
- export { EvalValue, FormulaEvalContext, ScalarValue, coerceToBoolean, coerceToNumber, coerceToText, compareScalars, evaluateAst };
41
- //# sourceMappingURL=evaluator.d.ts.map
@@ -1,154 +0,0 @@
1
- import { formulaError, isFormulaError } from "./errors.js";
2
- //#region src/core/formula/evaluator.ts
3
- const NUMERIC_TEXT_RE = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
4
- /** 强转数字:null→0,布尔→1/0,数字文本→数字,其余文本→#VALUE!,错误传播 */
5
- function coerceToNumber(value) {
6
- if (isFormulaError(value)) return value;
7
- if (Array.isArray(value)) return formulaError("#VALUE!");
8
- if (value === null) return 0;
9
- switch (typeof value) {
10
- case "number": return value;
11
- case "boolean": return value ? 1 : 0;
12
- case "string": {
13
- const text = value.trim();
14
- if (text === "") return formulaError("#VALUE!");
15
- if (text.toUpperCase() === "TRUE") return 1;
16
- if (text.toUpperCase() === "FALSE") return 0;
17
- if (NUMERIC_TEXT_RE.test(text)) return Number.parseFloat(text);
18
- return formulaError("#VALUE!");
19
- }
20
- }
21
- }
22
- /** 强转文本:null→'',布尔→TRUE/FALSE,错误传播 */
23
- function coerceToText(value) {
24
- if (isFormulaError(value)) return value;
25
- if (Array.isArray(value)) return formulaError("#VALUE!");
26
- if (value === null) return "";
27
- switch (typeof value) {
28
- case "string": return value;
29
- case "number": return String(value);
30
- case "boolean": return value ? "TRUE" : "FALSE";
31
- }
32
- }
33
- /** 强转布尔:null→FALSE,数字≠0,TRUE/FALSE 文本,其余文本→#VALUE!,错误传播 */
34
- function coerceToBoolean(value) {
35
- if (isFormulaError(value)) return value;
36
- if (Array.isArray(value)) return formulaError("#VALUE!");
37
- if (value === null) return false;
38
- switch (typeof value) {
39
- case "boolean": return value;
40
- case "number": return value !== 0;
41
- case "string": {
42
- const text = value.trim().toUpperCase();
43
- if (text === "TRUE") return true;
44
- if (text === "FALSE") return false;
45
- return formulaError("#VALUE!");
46
- }
47
- }
48
- }
49
- /** 比较:同类型按类型规则(文本大小写不敏感);混合类型 数字 < 文本 < 布尔;null 归一为对方零值 */
50
- function compareScalars(left, right) {
51
- if (left === null || right === null) {
52
- if (left === null && right === null) return 0;
53
- if (left === null) return compareScalars(zeroLike(right), right);
54
- return compareScalars(left, zeroLike(left));
55
- }
56
- const leftType = typeof left;
57
- const rightType = typeof right;
58
- if (leftType === "number" && rightType === "number") return left < right ? -1 : left > right ? 1 : 0;
59
- if (leftType === "string" && rightType === "string") {
60
- const a = left.toUpperCase();
61
- const b = right.toUpperCase();
62
- return a < b ? -1 : a > b ? 1 : 0;
63
- }
64
- if (leftType === "boolean" && rightType === "boolean") return left === right ? 0 : left ? 1 : -1;
65
- return typeRank(leftType) - typeRank(rightType);
66
- }
67
- function zeroLike(value) {
68
- if (typeof value === "number") return 0;
69
- if (typeof value === "string") return "";
70
- return false;
71
- }
72
- function typeRank(type) {
73
- if (type === "number") return 0;
74
- if (type === "string") return 1;
75
- return 2;
76
- }
77
- const COMPARISON_OPS = /* @__PURE__ */ new Set([
78
- "=",
79
- "<>",
80
- "<",
81
- "<=",
82
- ">",
83
- ">="
84
- ]);
85
- /** AST 求值 */
86
- function evaluateAst(node, ctx) {
87
- switch (node.kind) {
88
- case "number":
89
- case "string":
90
- case "boolean": return node.value;
91
- case "name": return formulaError("#NAME?");
92
- case "cell": return ctx.readCell(node.sheet ?? ctx.currentSheet, node.addr);
93
- case "range": return ctx.readRange(node.sheet ?? ctx.currentSheet, node.range);
94
- case "unary": {
95
- const n = coerceToNumber(evaluateAst(node.operand, ctx));
96
- if (isFormulaError(n)) return n;
97
- return node.op === "-" ? -n : n;
98
- }
99
- case "percent": {
100
- const n = coerceToNumber(evaluateAst(node.operand, ctx));
101
- if (isFormulaError(n)) return n;
102
- return n / 100;
103
- }
104
- case "binary": return evaluateBinary(node.op, node.left, node.right, ctx);
105
- case "call": return ctx.callFunction(node.name, node.args, (arg) => evaluateAst(arg, ctx));
106
- }
107
- }
108
- function evaluateBinary(op, leftNode, rightNode, ctx) {
109
- const left = evaluateAst(leftNode, ctx);
110
- if (isFormulaError(left)) return left;
111
- const right = evaluateAst(rightNode, ctx);
112
- if (isFormulaError(right)) return right;
113
- if (op === "&") {
114
- const l = coerceToText(left);
115
- if (isFormulaError(l)) return l;
116
- const r = coerceToText(right);
117
- if (isFormulaError(r)) return r;
118
- return l + r;
119
- }
120
- if (COMPARISON_OPS.has(op)) {
121
- if (Array.isArray(left) || Array.isArray(right)) return formulaError("#VALUE!");
122
- const cmp = compareScalars(left, right);
123
- switch (op) {
124
- case "=": return cmp === 0;
125
- case "<>": return cmp !== 0;
126
- case "<": return cmp < 0;
127
- case "<=": return cmp <= 0;
128
- case ">": return cmp > 0;
129
- default: return cmp >= 0;
130
- }
131
- }
132
- const l = coerceToNumber(left);
133
- if (isFormulaError(l)) return l;
134
- const r = coerceToNumber(right);
135
- if (isFormulaError(r)) return r;
136
- switch (op) {
137
- case "+": return l + r;
138
- case "-": return l - r;
139
- case "*": return l * r;
140
- case "/":
141
- if (r === 0) return formulaError("#DIV/0!");
142
- return l / r;
143
- case "^": {
144
- if (l === 0 && r < 0) return formulaError("#DIV/0!");
145
- const value = Math.pow(l, r);
146
- return Number.isFinite(value) ? value : formulaError("#VALUE!");
147
- }
148
- default: return formulaError("#ERROR!");
149
- }
150
- }
151
- //#endregion
152
- export { coerceToBoolean, coerceToNumber, coerceToText, compareScalars, evaluateAst };
153
-
154
- //# sourceMappingURL=evaluator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"evaluator.js","names":[],"sources":["../../../src/core/formula/evaluator.ts"],"sourcesContent":["import type { CellAddress, CellRange } from '../address'\nimport type { AstNode, BinaryOperator } from './ast'\nimport { formulaError, isFormulaError, type FormulaError } from './errors'\n\n/**\n * AST 求值器(纯函数,不持有状态;单元格读取经 FormulaEvalContext 注入)。\n *\n * 空格(null)参与运算的规则同 Excel:\n * - 数字上下文按 0(空单元格 +1 = 1);空字符串字面量参与算术 → #VALUE!\n * - 文本上下文按 '';布尔上下文按 FALSE\n * - 比较时空格归一为对方类型的零值\n * 错误值遇运算即传播(左操作数优先)。\n */\n\n/** 标量值(单元格/字面量可取的全集;null = 空单元格) */\nexport type ScalarValue = number | string | boolean | null\n\n/** 求值结果:标量 / 错误 / 区域展开数组(数组仅作为函数参数形态出现) */\nexport type EvalValue = ScalarValue | FormulaError | (ScalarValue | FormulaError)[]\n\n/** 求值上下文(由依赖图按公式节点注入) */\nexport interface FormulaEvalContext {\n /** 当前公式所在 sheet(裸引用缺省表) */\n readonly currentSheet: string\n /** 读取单格(原始存储语义;表不存在 → #REF!) */\n readCell(sheet: string, addr: CellAddress): ScalarValue | FormulaError\n /** 读取区域(只含稀疏存在的格;表不存在 → #REF!) */\n readRange(sheet: string, range: CellRange): (ScalarValue | FormulaError)[] | FormulaError\n /** 调用函数(名称未知 → #NAME?;参数个数非法 → #VALUE!) */\n callFunction(name: string, nodes: AstNode[], evalNode: (node: AstNode) => EvalValue): EvalValue\n}\n\nconst NUMERIC_TEXT_RE = /^[+-]?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?$/\n\n/** 强转数字:null→0,布尔→1/0,数字文本→数字,其余文本→#VALUE!,错误传播 */\nexport function coerceToNumber(value: EvalValue): number | FormulaError {\n if (isFormulaError(value)) return value\n if (Array.isArray(value)) return formulaError('#VALUE!')\n if (value === null) return 0\n switch (typeof value) {\n case 'number':\n return value\n case 'boolean':\n return value ? 1 : 0\n case 'string': {\n const text = value.trim()\n if (text === '') return formulaError('#VALUE!')\n if (text.toUpperCase() === 'TRUE') return 1\n if (text.toUpperCase() === 'FALSE') return 0\n if (NUMERIC_TEXT_RE.test(text)) return Number.parseFloat(text)\n return formulaError('#VALUE!')\n }\n }\n}\n\n/** 强转文本:null→'',布尔→TRUE/FALSE,错误传播 */\nexport function coerceToText(value: EvalValue): string | FormulaError {\n if (isFormulaError(value)) return value\n if (Array.isArray(value)) return formulaError('#VALUE!')\n if (value === null) return ''\n switch (typeof value) {\n case 'string':\n return value\n case 'number':\n return String(value)\n case 'boolean':\n return value ? 'TRUE' : 'FALSE'\n }\n}\n\n/** 强转布尔:null→FALSE,数字≠0,TRUE/FALSE 文本,其余文本→#VALUE!,错误传播 */\nexport function coerceToBoolean(value: EvalValue): boolean | FormulaError {\n if (isFormulaError(value)) return value\n if (Array.isArray(value)) return formulaError('#VALUE!')\n if (value === null) return false\n switch (typeof value) {\n case 'boolean':\n return value\n case 'number':\n return value !== 0\n case 'string': {\n const text = value.trim().toUpperCase()\n if (text === 'TRUE') return true\n if (text === 'FALSE') return false\n return formulaError('#VALUE!')\n }\n }\n}\n\n/** 比较:同类型按类型规则(文本大小写不敏感);混合类型 数字 < 文本 < 布尔;null 归一为对方零值 */\nexport function compareScalars(left: ScalarValue, right: ScalarValue): number {\n if (left === null || right === null) {\n if (left === null && right === null) return 0\n if (left === null) return compareScalars(zeroLike(right), right)\n return compareScalars(left, zeroLike(left))\n }\n const leftType = typeof left\n const rightType = typeof right\n if (leftType === 'number' && rightType === 'number') {\n return left < right ? -1 : left > right ? 1 : 0\n }\n if (leftType === 'string' && rightType === 'string') {\n const a = (left as string).toUpperCase()\n const b = (right as string).toUpperCase()\n return a < b ? -1 : a > b ? 1 : 0\n }\n if (leftType === 'boolean' && rightType === 'boolean') {\n return left === right ? 0 : left ? 1 : -1\n }\n return typeRank(leftType) - typeRank(rightType)\n}\n\nfunction zeroLike(value: ScalarValue): ScalarValue {\n if (typeof value === 'number') return 0\n if (typeof value === 'string') return ''\n return false\n}\n\nfunction typeRank(type: string): number {\n if (type === 'number') return 0\n if (type === 'string') return 1\n return 2\n}\n\nconst COMPARISON_OPS = new Set(['=', '<>', '<', '<=', '>', '>='])\n\n/** AST 求值 */\nexport function evaluateAst(node: AstNode, ctx: FormulaEvalContext): EvalValue {\n switch (node.kind) {\n case 'number':\n case 'string':\n case 'boolean':\n return node.value\n case 'name':\n return formulaError('#NAME?')\n case 'cell':\n return ctx.readCell(node.sheet ?? ctx.currentSheet, node.addr)\n case 'range':\n return ctx.readRange(node.sheet ?? ctx.currentSheet, node.range)\n case 'unary': {\n const n = coerceToNumber(evaluateAst(node.operand, ctx))\n if (isFormulaError(n)) return n\n return node.op === '-' ? -n : n\n }\n case 'percent': {\n const n = coerceToNumber(evaluateAst(node.operand, ctx))\n if (isFormulaError(n)) return n\n return n / 100\n }\n case 'binary':\n return evaluateBinary(node.op, node.left, node.right, ctx)\n case 'call':\n return ctx.callFunction(node.name, node.args, (arg) => evaluateAst(arg, ctx))\n }\n}\n\nfunction evaluateBinary(\n op: BinaryOperator,\n leftNode: AstNode,\n rightNode: AstNode,\n ctx: FormulaEvalContext\n): EvalValue {\n const left = evaluateAst(leftNode, ctx)\n if (isFormulaError(left)) return left\n const right = evaluateAst(rightNode, ctx)\n if (isFormulaError(right)) return right\n\n if (op === '&') {\n const l = coerceToText(left)\n if (isFormulaError(l)) return l\n const r = coerceToText(right)\n if (isFormulaError(r)) return r\n return l + r\n }\n\n if (COMPARISON_OPS.has(op)) {\n if (Array.isArray(left) || Array.isArray(right)) return formulaError('#VALUE!')\n const cmp = compareScalars(left, right)\n switch (op) {\n case '=':\n return cmp === 0\n case '<>':\n return cmp !== 0\n case '<':\n return cmp < 0\n case '<=':\n return cmp <= 0\n case '>':\n return cmp > 0\n default:\n return cmp >= 0\n }\n }\n\n const l = coerceToNumber(left)\n if (isFormulaError(l)) return l\n const r = coerceToNumber(right)\n if (isFormulaError(r)) return r\n\n switch (op) {\n case '+':\n return l + r\n case '-':\n return l - r\n case '*':\n return l * r\n case '/':\n if (r === 0) return formulaError('#DIV/0!')\n return l / r\n case '^': {\n if (l === 0 && r < 0) return formulaError('#DIV/0!')\n const value = Math.pow(l, r)\n return Number.isFinite(value) ? value : formulaError('#VALUE!')\n }\n // & 与比较运算已在上方提前返回\n default:\n return formulaError('#ERROR!')\n }\n}\n"],"mappings":"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"}
@@ -1,30 +0,0 @@
1
- import { AstNode } from "./ast.js";
2
- import { EvalValue } from "./evaluator.js";
3
- //#region src/core/formula/functions.d.ts
4
- /**
5
- * 函数注册表(可扩展)+ 基础函数集。
6
- *
7
- * 参数形态约定:区域引用求值为数组(只含稀疏存在的格),直接参数为标量。
8
- * 聚合函数据此区分 Excel 语义:区域内的文本/布尔被忽略,直接参数则强转(非法 → #VALUE!)。
9
- * lazy 函数自行求值参数(IF 的短路分支,未选分支的副作用/错误不产生)。
10
- */
11
- type FormulaFunction = {
12
- kind?: 'normal';
13
- minArgs?: number;
14
- maxArgs?: number;
15
- impl: (args: EvalValue[]) => EvalValue;
16
- } | {
17
- kind: 'lazy';
18
- minArgs?: number;
19
- maxArgs?: number;
20
- impl: (nodes: AstNode[], evalNode: (node: AstNode) => EvalValue) => EvalValue;
21
- };
22
- /** 注册函数(名称大小写不敏感;同名覆盖,供扩展/自定义函数) */
23
- declare function registerFormulaFunction(name: string, def: FormulaFunction): void;
24
- /** 查询函数(大小写不敏感) */
25
- declare function getFormulaFunction(name: string): FormulaFunction | undefined;
26
- /** 求值器回调:名称解析 + 参数个数校验 + 按 kind 分发 */
27
- declare function invokeFormulaFunction(name: string, nodes: AstNode[], evalNode: (node: AstNode) => EvalValue): EvalValue;
28
- //#endregion
29
- export { FormulaFunction, getFormulaFunction, invokeFormulaFunction, registerFormulaFunction };
30
- //# sourceMappingURL=functions.d.ts.map