solve-engine 1.0.0-beta.0 → 1.0.0-beta.2

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 (253) hide show
  1. package/dist/BytecodeBuilder-3egMyuDi.d.cts +274 -0
  2. package/dist/BytecodeBuilder-3egMyuDi.d.ts +274 -0
  3. package/dist/Configuration-fVpd-HC-.d.cts +228 -0
  4. package/dist/Configuration-fVpd-HC-.d.ts +228 -0
  5. package/dist/EngineError-DvPSnhZi.d.cts +304 -0
  6. package/dist/EngineError-DvPSnhZi.d.ts +304 -0
  7. package/dist/Lexer-BJdhlnej.d.cts +531 -0
  8. package/dist/Lexer-D9l4Yrk2.d.ts +531 -0
  9. package/dist/NormalizerRule-BrVoVjmP.d.cts +163 -0
  10. package/dist/NormalizerRule-CEjf1FyD.d.ts +163 -0
  11. package/dist/PackageRegistry-D-Tv_7ca.d.cts +2572 -0
  12. package/dist/PackageRegistry-DaH4aIKP.d.ts +2572 -0
  13. package/dist/Parselet-CK8cQu2a.d.cts +299 -0
  14. package/dist/Parselet-ConOIdRO.d.ts +299 -0
  15. package/dist/ScopeManager-B5k0nk05.d.ts +559 -0
  16. package/dist/ScopeManager-CKfQtl7l.d.cts +559 -0
  17. package/dist/Token-0jpvUdPY.d.cts +245 -0
  18. package/dist/Token-0jpvUdPY.d.ts +245 -0
  19. package/dist/VMCheckpoints-BmDwIXgK.d.ts +171 -0
  20. package/dist/VMCheckpoints-CqFuLwrT.d.cts +171 -0
  21. package/dist/Value-KtAQNHur.d.cts +295 -0
  22. package/dist/Value-KtAQNHur.d.ts +295 -0
  23. package/dist/chunk-2RWAXT6O.js +3 -0
  24. package/dist/chunk-2RWAXT6O.js.map +1 -0
  25. package/dist/chunk-34RRD7PC.js +1492 -0
  26. package/dist/chunk-34RRD7PC.js.map +1 -0
  27. package/dist/chunk-3PPFLFH4.js +5097 -0
  28. package/dist/chunk-3PPFLFH4.js.map +1 -0
  29. package/dist/chunk-3VGDNEE3.js +84 -0
  30. package/dist/chunk-3VGDNEE3.js.map +1 -0
  31. package/dist/chunk-3YNVWKR2.cjs +370 -0
  32. package/dist/chunk-3YNVWKR2.cjs.map +1 -0
  33. package/dist/chunk-47LRVGOT.cjs +316 -0
  34. package/dist/chunk-47LRVGOT.cjs.map +1 -0
  35. package/dist/chunk-4MG4XKO2.js +1644 -0
  36. package/dist/chunk-4MG4XKO2.js.map +1 -0
  37. package/dist/chunk-4QADQTWS.js +2392 -0
  38. package/dist/chunk-4QADQTWS.js.map +1 -0
  39. package/dist/chunk-53B6KDDJ.cjs +1651 -0
  40. package/dist/chunk-53B6KDDJ.cjs.map +1 -0
  41. package/dist/chunk-5X2PTP6F.cjs +104 -0
  42. package/dist/chunk-5X2PTP6F.cjs.map +1 -0
  43. package/dist/chunk-5YEMOYSE.js +264 -0
  44. package/dist/chunk-5YEMOYSE.js.map +1 -0
  45. package/dist/chunk-64W6GLLZ.js +481 -0
  46. package/dist/chunk-64W6GLLZ.js.map +1 -0
  47. package/dist/chunk-6BKTCEUP.cjs +233 -0
  48. package/dist/chunk-6BKTCEUP.cjs.map +1 -0
  49. package/dist/chunk-6NTVRDQV.cjs +1642 -0
  50. package/dist/chunk-6NTVRDQV.cjs.map +1 -0
  51. package/dist/chunk-6RDSQURE.js +290 -0
  52. package/dist/chunk-6RDSQURE.js.map +1 -0
  53. package/dist/chunk-75JP4BZX.js +93 -0
  54. package/dist/chunk-75JP4BZX.js.map +1 -0
  55. package/dist/chunk-B7LXJ6LC.cjs +36 -0
  56. package/dist/chunk-B7LXJ6LC.cjs.map +1 -0
  57. package/dist/chunk-BVAR5J2F.js +257 -0
  58. package/dist/chunk-BVAR5J2F.js.map +1 -0
  59. package/dist/chunk-C4XZV6E7.cjs +269 -0
  60. package/dist/chunk-C4XZV6E7.cjs.map +1 -0
  61. package/dist/chunk-CCJPSHGI.cjs +40 -0
  62. package/dist/chunk-CCJPSHGI.cjs.map +1 -0
  63. package/dist/chunk-CLL7RUQV.cjs +316 -0
  64. package/dist/chunk-CLL7RUQV.cjs.map +1 -0
  65. package/dist/chunk-DM3LMRBC.js +329 -0
  66. package/dist/chunk-DM3LMRBC.js.map +1 -0
  67. package/dist/chunk-DUEZFTK2.js +38 -0
  68. package/dist/chunk-DUEZFTK2.js.map +1 -0
  69. package/dist/chunk-EBSPLUW4.cjs +489 -0
  70. package/dist/chunk-EBSPLUW4.cjs.map +1 -0
  71. package/dist/chunk-EHAHVROS.cjs +261 -0
  72. package/dist/chunk-EHAHVROS.cjs.map +1 -0
  73. package/dist/chunk-EIGTWK5N.js +167 -0
  74. package/dist/chunk-EIGTWK5N.js.map +1 -0
  75. package/dist/chunk-EPOXXJBK.js +96 -0
  76. package/dist/chunk-EPOXXJBK.js.map +1 -0
  77. package/dist/chunk-FEAIOR7N.cjs +91 -0
  78. package/dist/chunk-FEAIOR7N.cjs.map +1 -0
  79. package/dist/chunk-G535KJEG.js +312 -0
  80. package/dist/chunk-G535KJEG.js.map +1 -0
  81. package/dist/chunk-GCM5JHMX.js +88 -0
  82. package/dist/chunk-GCM5JHMX.js.map +1 -0
  83. package/dist/chunk-GOLDJNMZ.js +368 -0
  84. package/dist/chunk-GOLDJNMZ.js.map +1 -0
  85. package/dist/chunk-GW32KPCU.cjs +360 -0
  86. package/dist/chunk-GW32KPCU.cjs.map +1 -0
  87. package/dist/chunk-HTXVVJRA.cjs +1549 -0
  88. package/dist/chunk-HTXVVJRA.cjs.map +1 -0
  89. package/dist/chunk-HWSZJQCI.js +311 -0
  90. package/dist/chunk-HWSZJQCI.js.map +1 -0
  91. package/dist/chunk-IF532O7C.js +101 -0
  92. package/dist/chunk-IF532O7C.js.map +1 -0
  93. package/dist/chunk-J73SJHR3.cjs +2415 -0
  94. package/dist/chunk-J73SJHR3.cjs.map +1 -0
  95. package/dist/chunk-JBSYC7BB.cjs +7473 -0
  96. package/dist/chunk-JBSYC7BB.cjs.map +1 -0
  97. package/dist/chunk-KVILKGMS.js +228 -0
  98. package/dist/chunk-KVILKGMS.js.map +1 -0
  99. package/dist/chunk-LIPPNDBE.js +258 -0
  100. package/dist/chunk-LIPPNDBE.js.map +1 -0
  101. package/dist/chunk-LR7YASZF.cjs +172 -0
  102. package/dist/chunk-LR7YASZF.cjs.map +1 -0
  103. package/dist/chunk-M4N5GA5Z.cjs +309 -0
  104. package/dist/chunk-M4N5GA5Z.cjs.map +1 -0
  105. package/dist/chunk-M5LX5AOO.js +7459 -0
  106. package/dist/chunk-M5LX5AOO.js.map +1 -0
  107. package/dist/chunk-MZOVP3WI.cjs +96 -0
  108. package/dist/chunk-MZOVP3WI.cjs.map +1 -0
  109. package/dist/chunk-N5YFR4UP.cjs +10 -0
  110. package/dist/chunk-N5YFR4UP.cjs.map +1 -0
  111. package/dist/chunk-NGBBNL4K.cjs +4 -0
  112. package/dist/chunk-NGBBNL4K.cjs.map +1 -0
  113. package/dist/chunk-NH2O2AUR.js +146 -0
  114. package/dist/chunk-NH2O2AUR.js.map +1 -0
  115. package/dist/chunk-NLNDMWZC.js +31 -0
  116. package/dist/chunk-NLNDMWZC.js.map +1 -0
  117. package/dist/chunk-NMCRQP3Z.cjs +778 -0
  118. package/dist/chunk-NMCRQP3Z.cjs.map +1 -0
  119. package/dist/chunk-NMD5VRN4.cjs +1327 -0
  120. package/dist/chunk-NMD5VRN4.cjs.map +1 -0
  121. package/dist/chunk-OC6LTXYS.cjs +286 -0
  122. package/dist/chunk-OC6LTXYS.cjs.map +1 -0
  123. package/dist/chunk-OT6OJY7C.cjs +114 -0
  124. package/dist/chunk-OT6OJY7C.cjs.map +1 -0
  125. package/dist/chunk-PFSDGGYL.js +8 -0
  126. package/dist/chunk-PFSDGGYL.js.map +1 -0
  127. package/dist/chunk-QNJ4ACRT.cjs +155 -0
  128. package/dist/chunk-QNJ4ACRT.cjs.map +1 -0
  129. package/dist/chunk-QWY3VEZN.js +1637 -0
  130. package/dist/chunk-QWY3VEZN.js.map +1 -0
  131. package/dist/chunk-RFYD5TJE.js +111 -0
  132. package/dist/chunk-RFYD5TJE.js.map +1 -0
  133. package/dist/chunk-RIN643A3.js +354 -0
  134. package/dist/chunk-RIN643A3.js.map +1 -0
  135. package/dist/chunk-RZCWSXTA.cjs +5122 -0
  136. package/dist/chunk-RZCWSXTA.cjs.map +1 -0
  137. package/dist/chunk-SDGRK7EP.js +774 -0
  138. package/dist/chunk-SDGRK7EP.js.map +1 -0
  139. package/dist/chunk-VEKIWQ3F.cjs +39 -0
  140. package/dist/chunk-VEKIWQ3F.cjs.map +1 -0
  141. package/dist/chunk-WQTTOGXC.cjs +99 -0
  142. package/dist/chunk-WQTTOGXC.cjs.map +1 -0
  143. package/dist/chunk-X6K3HUOY.cjs +86 -0
  144. package/dist/chunk-X6K3HUOY.cjs.map +1 -0
  145. package/dist/chunk-XJCMXB2E.cjs +333 -0
  146. package/dist/chunk-XJCMXB2E.cjs.map +1 -0
  147. package/dist/chunk-XVWCOTR6.js +1324 -0
  148. package/dist/chunk-XVWCOTR6.js.map +1 -0
  149. package/dist/chunk-YLJ4XMA6.js +33 -0
  150. package/dist/chunk-YLJ4XMA6.js.map +1 -0
  151. package/dist/chunk-Z2XP767T.cjs +35 -0
  152. package/dist/chunk-Z2XP767T.cjs.map +1 -0
  153. package/dist/chunk-Z4X2RWOF.js +37 -0
  154. package/dist/chunk-Z4X2RWOF.js.map +1 -0
  155. package/dist/constants.cjs +22 -0
  156. package/dist/constants.cjs.map +1 -0
  157. package/dist/constants.d.cts +12 -0
  158. package/dist/constants.d.ts +12 -0
  159. package/dist/constants.js +5 -0
  160. package/dist/constants.js.map +1 -0
  161. package/dist/engine.cjs +79 -0
  162. package/dist/engine.cjs.map +1 -0
  163. package/dist/engine.d.cts +587 -0
  164. package/dist/engine.d.ts +587 -0
  165. package/dist/engine.js +34 -0
  166. package/dist/engine.js.map +1 -0
  167. package/dist/errors.cjs +81 -0
  168. package/dist/errors.cjs.map +1 -0
  169. package/dist/errors.d.cts +95 -0
  170. package/dist/errors.d.ts +95 -0
  171. package/dist/errors.js +4 -0
  172. package/dist/errors.js.map +1 -0
  173. package/dist/format.cjs +171 -0
  174. package/dist/format.cjs.map +1 -0
  175. package/dist/format.d.cts +51 -0
  176. package/dist/format.d.ts +51 -0
  177. package/dist/format.js +168 -0
  178. package/dist/format.js.map +1 -0
  179. package/dist/index.cjs +97 -0
  180. package/dist/index.cjs.map +1 -0
  181. package/dist/index.d.cts +152 -0
  182. package/dist/index.d.ts +152 -0
  183. package/dist/index.js +75 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/language.cjs +308 -0
  186. package/dist/language.cjs.map +1 -0
  187. package/dist/language.d.cts +110 -0
  188. package/dist/language.d.ts +110 -0
  189. package/dist/language.js +287 -0
  190. package/dist/language.js.map +1 -0
  191. package/dist/lexer.cjs +64 -0
  192. package/dist/lexer.cjs.map +1 -0
  193. package/dist/lexer.d.cts +56 -0
  194. package/dist/lexer.d.ts +56 -0
  195. package/dist/lexer.js +11 -0
  196. package/dist/lexer.js.map +1 -0
  197. package/dist/normalizer.cjs +40 -0
  198. package/dist/normalizer.cjs.map +1 -0
  199. package/dist/normalizer.d.cts +364 -0
  200. package/dist/normalizer.d.ts +364 -0
  201. package/dist/normalizer.js +11 -0
  202. package/dist/normalizer.js.map +1 -0
  203. package/dist/packages.cjs +121 -0
  204. package/dist/packages.cjs.map +1 -0
  205. package/dist/packages.d.cts +691 -0
  206. package/dist/packages.d.ts +691 -0
  207. package/dist/packages.js +24 -0
  208. package/dist/packages.js.map +1 -0
  209. package/dist/parser.cjs +55 -0
  210. package/dist/parser.cjs.map +1 -0
  211. package/dist/parser.d.cts +46 -0
  212. package/dist/parser.d.ts +46 -0
  213. package/dist/parser.js +10 -0
  214. package/dist/parser.js.map +1 -0
  215. package/dist/pipeline-CrsRPmsv.d.ts +285 -0
  216. package/dist/pipeline-DPUQt7yL.d.cts +285 -0
  217. package/dist/resolvers.cjs +13 -0
  218. package/dist/resolvers.cjs.map +1 -0
  219. package/dist/resolvers.d.cts +111 -0
  220. package/dist/resolvers.d.ts +111 -0
  221. package/dist/resolvers.js +4 -0
  222. package/dist/resolvers.js.map +1 -0
  223. package/dist/services.cjs +21 -0
  224. package/dist/services.cjs.map +1 -0
  225. package/dist/services.d.cts +30 -0
  226. package/dist/services.d.ts +30 -0
  227. package/dist/services.js +4 -0
  228. package/dist/services.js.map +1 -0
  229. package/dist/uom.cjs +56 -0
  230. package/dist/uom.cjs.map +1 -0
  231. package/dist/uom.d.cts +224 -0
  232. package/dist/uom.d.ts +224 -0
  233. package/dist/uom.js +11 -0
  234. package/dist/uom.js.map +1 -0
  235. package/dist/utilities.cjs +32 -0
  236. package/dist/utilities.cjs.map +1 -0
  237. package/dist/utilities.d.cts +113 -0
  238. package/dist/utilities.d.ts +113 -0
  239. package/dist/utilities.js +7 -0
  240. package/dist/utilities.js.map +1 -0
  241. package/dist/variables.cjs +17 -0
  242. package/dist/variables.cjs.map +1 -0
  243. package/dist/variables.d.cts +87 -0
  244. package/dist/variables.d.ts +87 -0
  245. package/dist/variables.js +4 -0
  246. package/dist/variables.js.map +1 -0
  247. package/dist/vm.cjs +284 -0
  248. package/dist/vm.cjs.map +1 -0
  249. package/dist/vm.d.cts +68 -0
  250. package/dist/vm.d.ts +68 -0
  251. package/dist/vm.js +194 -0
  252. package/dist/vm.js.map +1 -0
  253. package/package.json +4 -4
@@ -0,0 +1,559 @@
1
+ import { V as Value } from './Value-KtAQNHur.cjs';
2
+ import { U as UserFunctionDef, A as AnonymousBodyDef, a as BytecodeProgram } from './BytecodeBuilder-3egMyuDi.cjs';
3
+ import { E as EngineError } from './EngineError-DvPSnhZi.cjs';
4
+ import { D as DiagnosticPipeline } from './pipeline-DPUQt7yL.cjs';
5
+ import { VariableResolver } from './variables.cjs';
6
+
7
+ /**
8
+ * Create a new VM instance with the given opcode registry and configurable limits.
9
+ *
10
+ * The VM is a stack machine that executes compiled bytecode. It manages:
11
+ * - A value stack (bounded by `maxStackDepth`)
12
+ * - A variable store (Map of name → Value)
13
+ * - An instruction counter (bounded by `maxInstructions`)
14
+ * - An AbortSignal for async cancellation
15
+ *
16
+ * @param registry - Opcode handler registry for plugin-extensible opcodes
17
+ * @param maxStackDepth - Maximum stack slots (default 200)
18
+ * @param maxInstructions - Maximum opcodes per expression (default 50000)
19
+ * @param maxFunctionRecursionDepth - Maximum nested user-defined-function
20
+ * calls (default 50). See the VM interface's `pushCallFrame` doc for why
21
+ * this exists as its own dedicated guard, separate from `maxInstructions`.
22
+ */
23
+ declare function createVM(registry: OpRegistry, maxStackDepth?: number, maxInstructions?: number, maxFunctionRecursionDepth?: number, context?: EngineContext): VM;
24
+ /**
25
+ * Compiled bytecode ready for VM execution.
26
+ *
27
+ * Uses packed TypedArrays for cache-friendly memory layout:
28
+ * - `opcodes`: Uint8Array of OpCode values
29
+ * - `numbers`: Float64Array of numeric literals (indexed by opcode operands)
30
+ * - `strings`: String table for identifiers, UoM units, and BigInt literals
31
+ */
32
+ interface Bytecode {
33
+ opcodes: Uint8Array;
34
+ numbers: Float64Array;
35
+ strings: string[];
36
+ /** User-defined-function bodies compiled alongside this program. See `parser/BytecodeBuilder.ts`'s `BytecodeProgram.userFunctionBodies`. */
37
+ userFunctionBodies?: UserFunctionDef[];
38
+ /** map/reduce anonymous transform bodies. See `parser/BytecodeBuilder.ts`'s `BytecodeProgram.anonymousBodies`. */
39
+ anonymousBodies?: AnonymousBodyDef[];
40
+ }
41
+ /**
42
+ * Per-line execution context, threaded optionally through
43
+ * {@link executeBytecode} down to `CALL_PLUGIN`'s plugin-function handlers
44
+ * (see `vm/VMBuiltins.ts`'s `pluginFunctionRegistry`).
45
+ *
46
+ * Exists so a package can implement cross-line features (`prev`, `line<N>`,
47
+ * range/above aggregation. See `packages/lines/`) without every other
48
+ * plugin function having to care: it's optional, and every existing
49
+ * handler ignores it unchanged. Before this, a plugin function's only
50
+ * input was its own call-site arguments, no line number, no access to
51
+ * any other line's cached result. `getLineResult`/`isLineBoundary` are
52
+ * both `undefined` when there's no real document (e.g.
53
+ * `ExpressionEngine.evaluateExpression()`'s single-expression path, which
54
+ * uses `lineIndex = -1` as its existing "no document" sentinel), a
55
+ * plugin function needing document access must check for that itself
56
+ * and return a clear error, never silently treat it as line 0.
57
+ */
58
+ interface LineExecutionContext {
59
+ /** 1-based current line number, or -1 when there is no real document (see class doc above). */
60
+ lineIndex: number;
61
+ /** Look up another line's cached result by 1-based line number. `undefined` = not evaluated yet (or out of range), distinct from a line that evaluated to an actual `undefined`-like Value, which can't happen (every Value type has a concrete representation). */
62
+ getLineResult?: (lineNumber: number) => Value | undefined;
63
+ /** Whether line `lineNumber` is a blank line or a `#` heading, the stopping condition for "total above"/"sum above"/"average above" aggregation. */
64
+ isLineBoundary?: (lineNumber: number) => boolean;
65
+ }
66
+ /**
67
+ * Discriminated union returned by {@link executeBytecode}.
68
+ *
69
+ * Two variants:
70
+ * - `value`: execution completed synchronously with a concrete Value
71
+ * - `pending`: an async plugin call was encountered, the orchestrator
72
+ * must await the resolver Promise, then re-execute
73
+ *
74
+ * Replaces the old throw-AsyncSuspenseError pattern, eliminating the need
75
+ * for try/catch in the engine.
76
+ */
77
+ type EvalResult = {
78
+ type: 'value';
79
+ value: Value;
80
+ } | {
81
+ type: 'pending';
82
+ queryKey: string;
83
+ resolver: Promise<Value>;
84
+ packageId: string;
85
+ signal: AbortSignal;
86
+ }
87
+ /**
88
+ * NEW third arm, an internal invariant violation (stack underflow via
89
+ * `safePop()`, an unresolved global variable bypassing preflight, a
90
+ * safety limit exceeded) surfaced as a controlled, structured
91
+ * `EngineError` instead of letting a raw exception escape
92
+ * `executeBytecode()` uncaught. See `executeBytecode()`'s own doc
93
+ * comment on the try/catch this arm makes possible.
94
+ */
95
+ | {
96
+ type: 'error';
97
+ error: EngineError;
98
+ };
99
+ /**
100
+ * Execute bytecode with optional diagnostic pipeline integration.
101
+ *
102
+ * Performance notes:
103
+ * - Uses a `switch(op)` statement for dispatch. V8 compiles dense integer
104
+ * switches (OpCode values 0–200) into a jump table with O(1) dispatch.
105
+ * All handler code is inlined directly in the switch cases, allowing
106
+ * TurboFan to optimize across opcode boundaries.
107
+ * - ADD/SUB/MUL have an inlined numeric fast path that skips the `binaryOp()`
108
+ * function call + closure allocation when both operands are plain numbers
109
+ * (>90% of arithmetic ops).
110
+ * - Tracing uses a boolean guard (`shouldTrace`) that the JIT eliminates
111
+ * entirely when diagnostics are disabled. No function call overhead.
112
+ * - `Value.toNumber()` caches its result, computed once, read thereafter.
113
+ */
114
+ declare function executeBytecode(bytecode: Bytecode, vm: VM, pipeline?: DiagnosticPipeline | undefined, expression?: string, context?: LineExecutionContext, symbolicTolerant?: boolean): EvalResult;
115
+
116
+ /**
117
+ * Per-engine state that used to live in module-level singletons.
118
+ *
119
+ * Two {@link ExpressionEngine} instances in one process could not be isolated
120
+ * from each other, because the registries they depend on were module globals
121
+ * shared by every instance. Registering a package mutated state that another
122
+ * engine was already reading. Tests worked only because they cleared those
123
+ * globals by hand, and per-document engines were safe only because
124
+ * registration happened to be idempotent.
125
+ *
126
+ * An `EngineContext` is created by the `ExpressionEngine` constructor and owned
127
+ * by that engine. Anything that needs one of these registries receives the
128
+ * context rather than importing a singleton.
129
+ *
130
+ * Runtime imports here are restricted to leaf modules of `vm/`. This file is
131
+ * imported by `vm/`, which `engine/` imports in turn, so pulling in anything
132
+ * that reaches back into `engine/` would close a cycle. `OpRegistry` and
133
+ * `VariableResolver` are safe because they import only types plus the error
134
+ * factory. Everything else arrives
135
+ * through `import type`, which is erased before the code runs.
136
+ */
137
+
138
+ /**
139
+ * A function a package contributes to the VM, reachable from bytecode through
140
+ * `CALL_PLUGIN`.
141
+ *
142
+ * Returning a promise puts the line into the pending state rather than
143
+ * blocking: the engine resolves it and re-executes.
144
+ */
145
+ type PluginFunctionHandler = (args: Value[], context?: LineExecutionContext) => Value | Promise<Value>;
146
+ /**
147
+ * The registries one engine instance owns.
148
+ *
149
+ * Members are added here as each former singleton is migrated. The migration
150
+ * order is deliberate and recorded in `docs-internal/plans`: plugin functions,
151
+ * then the opcode registry, the variable resolver, the lexer, and finally
152
+ * currency exchange. The lexer comes near the end because package registration
153
+ * writes vocabularies into it, so moving it early would churn every package.
154
+ *
155
+ * Currency exchange is deliberately NOT migrated, which is a departure from
156
+ * that plan. `CurrencyExchangeService` holds a cache of live market rates with
157
+ * a fifteen minute freshness window and no per-engine configuration. Sharing is
158
+ * the correct behaviour there: giving each engine its own would make two
159
+ * engines in one process fetch the same public endpoint independently, and let
160
+ * them disagree about the rate for one currency pair at one moment. The
161
+ * singleton rationale, that one engine's registration should not be visible to
162
+ * another, does not apply to a cache of state that is global in the real world.
163
+ *
164
+ * If a host ever needs per-engine rates, for a what-if scenario against
165
+ * historical figures, the answer is an optional override on the context that
166
+ * falls back to the shared cache, not a private copy per engine.
167
+ */
168
+ interface EngineContext {
169
+ /**
170
+ * Package-contributed VM functions, keyed by the index the bytecode
171
+ * carries.
172
+ *
173
+ * Indices come from `allocatePluginFunctionIndex()`. Two packages picking
174
+ * the same number by hand would silently overwrite one another, which is
175
+ * why the allocator exists and why nothing should hardcode an index.
176
+ */
177
+ readonly pluginFunctions: Record<number, PluginFunctionHandler>;
178
+ /**
179
+ * Custom opcode handlers a package registered.
180
+ *
181
+ * Largely vestigial: packages reach the VM through `CALL_PLUGIN` and
182
+ * {@link EngineContext.pluginFunctions} now, and nothing in the built-in
183
+ * set registers an opcode. It moves here anyway because the VM interface
184
+ * still requires a registry, and one shared instance would put any future
185
+ * registration back into every engine at once.
186
+ */
187
+ readonly opRegistry: OpRegistry;
188
+ /**
189
+ * Named-variable sources a package contributed.
190
+ *
191
+ * A package registering a source used to make its variables visible to
192
+ * every engine in the process, including ones that never registered it.
193
+ */
194
+ readonly variableResolver: VariableResolver;
195
+ /**
196
+ * Which package registered each plugin function index.
197
+ *
198
+ * The VM emits a pending result when a plugin function returns a promise,
199
+ * and that result carries the owning package so diagnostics and the error
200
+ * surface can name it. Without this the VM had nothing to report and sent an
201
+ * empty string, so every async failure looked as though it came from
202
+ * nowhere.
203
+ */
204
+ readonly pluginFunctionOwners: Record<number, string>;
205
+ }
206
+
207
+ /**
208
+ * A stored bare (colon-less) equation of the shape `factor1*factor2*...*
209
+ * variable = rhs` (e.g. `a*x = [60;70]`, or `s*t*v = [vx;vy;1]`)
210
+ * registered when that line is EXECUTED (mirroring `UserFunctionDef`'s own
211
+ * execution-time registration, not parse-time), and consulted when
212
+ * `<variable> =>` is later evaluated. Solving is `variable =
213
+ * inv(factor1*factor2*...) * rhs` (`vm/VM.ts`'s THEREFORE-handling code,
214
+ * `vm/MatrixOps.ts`'s `matrixMultiply()`/`inverse()`, both symbolic-aware
215
+ * so a factor whose OWN cells are still-unassigned free variables, e.g.
216
+ * `s = [sx,0,0;...]`, solves correctly too).
217
+ *
218
+ * `factorNames` are looked up via `vm.getVar()` at solve time (ordinary,
219
+ * ALREADY-evaluated Matrix values, ordinary ("bare") assignment always
220
+ * evaluates its RHS eagerly, even when that RHS itself contains
221
+ * unassigned names, via symbolic-tolerant evaluation, so by solve time
222
+ * each factor is already a genuine, possibly-partially-symbolic Matrix
223
+ * Value sitting in the variable store), NOT re-compiled bytecode, since
224
+ * this pattern only ever allows bare identifiers as factors (see
225
+ * `ExpressionEngine.ts`'s own equation-detection doc comment for why this
226
+ * stays a narrow, disclosed pattern rather than a general expression).
227
+ * `rhsProgram` IS a real compiled program (the right-hand side can be an
228
+ * arbitrary expression, e.g. `[vx;vy;1]`), evaluated in symbolic-tolerant
229
+ * mode at solve time.
230
+ */
231
+ interface EquationDef {
232
+ variable: string;
233
+ factorNames: string[];
234
+ rhsProgram: BytecodeProgram;
235
+ }
236
+ /**
237
+ * A stored scalar equation, the general `x^2 - 4 = 0` shape, kept separate from
238
+ * {@link EquationDef} rather than merged into it.
239
+ *
240
+ * The two are genuinely different problems solved by different machinery.
241
+ * {@link EquationDef} is a product chain of matrix factors solved by inverting
242
+ * their product; this is an arbitrary polynomial in one unknown solved by
243
+ * `symbolic/Solve.ts`. Merging them into one optional-field type would mean
244
+ * every consumer branching on which half is populated, and would put the older,
245
+ * already-shipped matrix path at risk of being changed by accident.
246
+ *
247
+ * Both sides are compiled programs rather than pre-evaluated values, because
248
+ * neither side can be evaluated until solve time: they contain the very unknown
249
+ * being solved for.
250
+ */
251
+ interface ScalarEquationDef {
252
+ /** The unknown this equation is keyed by and will be solved for. */
253
+ variable: string;
254
+ /** Compiled left-hand side, evaluated symbolic-tolerantly at solve time. */
255
+ lhsProgram: BytecodeProgram;
256
+ /** Compiled right-hand side, likewise. */
257
+ rhsProgram: BytecodeProgram;
258
+ }
259
+ /**
260
+ * Handler function for plugin-registered opcodes via CALL_PLUGIN (opcode 50).
261
+ * No longer dispatched directly from the VM switch, plugins register
262
+ * functions in pluginFunctionRegistry instead.
263
+ *
264
+ * @deprecated Use CALL_PLUGIN + pluginFunctionRegistry for plugin functionality.
265
+ * OpRegistry remains for the VM interface contract only.
266
+ */
267
+ type OpcodeHandler = (vm: VM, opcodes: Uint8Array, ip: number, numbers: Float64Array, strings: string[]) => number;
268
+ /** Registration payload for an opcode handler. Binds an OpCode to its handler function with plugin attribution. */
269
+ interface IOpcodeHandlerRegistration {
270
+ opcode: number;
271
+ handler: OpcodeHandler;
272
+ pluginName: string;
273
+ }
274
+ /**
275
+ * Legacy opcode registry, retained for the VM interface contract.
276
+ *
277
+ * Previously dispatched custom opcodes (>= 200) from the VM switch-default
278
+ * branch. Now plugins should use CALL_PLUGIN (opcode 50) via
279
+ * pluginFunctionRegistry instead.
280
+ */
281
+ declare class OpRegistry {
282
+ private handlers;
283
+ private nextOpcode;
284
+ register(registration: IOpcodeHandlerRegistration): void;
285
+ /**
286
+ * Remove a previously registered opcode handler.
287
+ * Used by package unregistration to reverse shared-registry contributions.
288
+ */
289
+ unregister(opcode: number): void;
290
+ get(opcode: number): OpcodeHandler | undefined;
291
+ has(opcode: number): boolean;
292
+ /**
293
+ * Allocate a unique opcode for a plugin's custom bytecode handler.
294
+ *
295
+ * Returns the next available opcode (starting at 201). Each call
296
+ * returns a distinct value. Plugins should call this once during
297
+ * registration and store the result.
298
+ *
299
+ * @throws If the dynamic opcode pool is exhausted.
300
+ * @returns A unique opcode number for the calling plugin.
301
+ */
302
+ allocateOpcode(): number;
303
+ }
304
+ /**
305
+ * VM interface consumed by opcode handlers and the bytecode executor.
306
+ *
307
+ * Provides stack operations, variable access, instruction counting,
308
+ * and abort signal management for async cancellation.
309
+ */
310
+ interface VM {
311
+ push(value: Value): void;
312
+ pop(): Value;
313
+ popNumber(): number;
314
+ popString(): string;
315
+ peek(): Value;
316
+ getStack(): Value[];
317
+ registry: OpRegistry;
318
+ /**
319
+ * Read a variable by name, checks the INNERMOST active user-defined-
320
+ * function call frame first (see `pushCallFrame`), then falls back to
321
+ * the flat, session-scoped variable store. This is why a function
322
+ * parameter needs no dedicated load opcode: an ordinary `LOAD_VAR
323
+ * "x"` inside a function body resolves to the current call's bound
324
+ * argument automatically, without the parser needing to know it's
325
+ * compiling a function body at all.
326
+ */
327
+ getVar(key: string): Value | undefined;
328
+ setVar(key: string, value: Value): void;
329
+ /**
330
+ * User-defined-function call frame, a name-keyed `Map` of this call's
331
+ * bound arguments, PUSHED before `CALL_USER_FUNCTION` executes the
332
+ * callee's body and POPPED immediately after (even if the body throws),
333
+ * so nested/recursive calls (`double(double(5))`) each get their own
334
+ * frame instead of clobbering a shared flat map. See `vm/VM.ts`'s
335
+ * `CALL_USER_FUNCTION` case. Deliberately NOT the same store as
336
+ * `setVar`'s flat `:name` variables, a call frame is call-scoped and
337
+ * stacked (only the INNERMOST frame is ever consulted by `getVar`, see
338
+ * its own doc comment, no lexical capture of an outer call's
339
+ * parameters), a variable is session-scoped and flat.
340
+ *
341
+ * @throws `FUNCTION_RECURSION_LIMIT_EXCEEDED` if pushing would exceed
342
+ * the VM's configured `maxFunctionRecursionDepth`, the backstop for
343
+ * `f(x) = f(x)`, which would otherwise recurse via nested
344
+ * `executeBytecode()` calls until the native V8 stack overflows
345
+ * uncatchably (each reentrant call gets its OWN fresh
346
+ * `localInstructionCount`, so `maxInstructions` cannot catch this).
347
+ */
348
+ pushCallFrame(frame: Map<string, Value>): void;
349
+ popCallFrame(): void;
350
+ /** Register (or redefine, overwrites any previous definition, matching `:name = value`'s own reassignment semantics) a user-defined function. */
351
+ defineUserFunction(name: string, params: string[], program: BytecodeProgram): void;
352
+ getUserFunction(name: string): UserFunctionDef | undefined;
353
+ hasUserFunction(name: string): boolean;
354
+ /** Register (or redefine) a bare equation (`a*x = rhs`), keyed by its free variable. See {@link EquationDef}. */
355
+ defineEquation(variable: string, factorNames: string[], rhsProgram: BytecodeProgram): void;
356
+ getEquation(variable: string): EquationDef | undefined;
357
+ hasEquation(variable: string): boolean;
358
+ /** Register (or redefine) a stored scalar equation (`x^2-4 = 0`), keyed by its unknown. See {@link ScalarEquationDef}. */
359
+ defineScalarEquation(variable: string, lhsProgram: BytecodeProgram, rhsProgram: BytecodeProgram): void;
360
+ getScalarEquation(variable: string): ScalarEquationDef | undefined;
361
+ hasScalarEquation(variable: string): boolean;
362
+ reset(): void;
363
+ getMaxInstructions(): number;
364
+ getMaxStackDepth(): number;
365
+ getInstructionCount(): number;
366
+ incrementInstructions(n: number): void;
367
+ /** Active AbortSignal for the current expression evaluation. Checked before cache writes. */
368
+ activeSignal?: AbortSignal;
369
+ /** Abort the current evaluation (called when expression changes before resolution). */
370
+ abortCurrent?: () => void;
371
+ /**
372
+ * Registries belonging to the engine that created this VM.
373
+ *
374
+ * `CALL_PLUGIN` resolves handlers through here rather than through a
375
+ * module-level registry, so two engines in one process no longer share the
376
+ * plugin functions their packages registered. This follows what
377
+ * `userFunctions` and `equations` already do by being VM-instance scoped.
378
+ */
379
+ context: EngineContext;
380
+ }
381
+ /** Shared singleton OpRegistry, used when no custom opcodes are needed. */
382
+ declare const sharedOpRegistry: OpRegistry;
383
+
384
+ /** Serialized snapshot of the dependency graph for diagnostic rendering. */
385
+ interface DagSnapshot {
386
+ consumers: Record<string, number[]>;
387
+ writes: Record<number, string[]>;
388
+ reads: Record<number, string[]>;
389
+ dataSourceDeps: Record<number, string[]>;
390
+ dataSourceConsumers: Record<string, number[]>;
391
+ }
392
+ /**
393
+ * Dependency graph for variable and data-source tracking across document lines.
394
+ *
395
+ * Tracks which lines read/write which variables, and propagates changes through
396
+ * the graph when a variable is modified. Supports:
397
+ * - Variable dependency tracking (registerLine, getAffectedLines)
398
+ * - Data-source dependency tracking (registerLineDataSourceDependency)
399
+ * - Topological ordering of affected lines (getAffectedLinesInOrder)
400
+ * - Efficient removal of deleted lines (removeLine)
401
+ */
402
+ declare class DependencyGraph {
403
+ private consumers;
404
+ private dependencies;
405
+ private writes;
406
+ private lineReads;
407
+ private dataSourceDependencies;
408
+ private dataSourceConsumers;
409
+ /**
410
+ * Register a line's variable reads and writes in the dependency graph.
411
+ *
412
+ * If re-registering the same line (e.g., after editing), old consumer
413
+ * references are cleaned up first. Write-variables are removed from
414
+ * the consumer set so that redefinition breaks the old dependency chain.
415
+ *
416
+ * @param lineNumber - 1-based line number in the document
417
+ * @param reads - Variable names this line reads
418
+ * @param writes - Variable names this line writes (assigns to)
419
+ */
420
+ registerLine(lineNumber: number, reads: string[], writes: string[]): void;
421
+ /**
422
+ * Register a line's dependency on an external data source (e.g., currency rate, OSRS GE price).
423
+ *
424
+ * When the data source updates, {@link getAffectedLinesByDataSource} returns all lines
425
+ * that depend on this data, enabling targeted re-evaluation.
426
+ *
427
+ * @param lineNumber - 1-based line number in the document
428
+ * @param dataSourceId - Unique identifier for the data source (e.g., "currency", "osrs-ge")
429
+ * @param queryKey - Query key array identifying the specific data (e.g., ["USD", "EUR"])
430
+ */
431
+ registerLineDataSourceDependency(lineNumber: number, dataSourceId: string, queryKey: string[]): void;
432
+ /**
433
+ * Find all lines affected by a changed variable via BFS through the consumer graph.
434
+ *
435
+ * When a variable is modified (e.g., `:x = 5` changes to `:x = 10`), this returns
436
+ * all lines that transitively depend on it, lines that read `x`, lines that read
437
+ * variables written by those lines, and so on.
438
+ *
439
+ * @param changedVariable - The variable name that changed
440
+ * @returns Set of line numbers that need re-evaluation
441
+ */
442
+ getAffectedLines(changedVariable: string): Set<number>;
443
+ /**
444
+ * Phase 1.4 DAG-walk optimization: return affected lines in dependency-safe
445
+ * topological order. Uses Kahn's algorithm (BFS-based) to ensure every line
446
+ * is evaluated AFTER all lines it depends on have been processed.
447
+ *
448
+ * This is more correct than ascending line-number sort, which fails when
449
+ * variable definitions and their consumers are not in document order.
450
+ *
451
+ * @returns Line numbers in topological order (producers before consumers).
452
+ */
453
+ getAffectedLinesInOrder(startVariable: string): number[];
454
+ /**
455
+ * Find all lines affected by a data source update.
456
+ *
457
+ * When an async data source resolves (e.g., currency rate fetch completes),
458
+ * this returns all lines that depend on that specific data query.
459
+ *
460
+ * @param dataSourceId - The data source identifier
461
+ * @param queryKey - The query key that was updated
462
+ * @returns Set of line numbers that need re-evaluation
463
+ */
464
+ getAffectedLinesByDataSource(dataSourceId: string, queryKey: string[]): Set<number>;
465
+ /**
466
+ * Remove a line from the dependency graph (e.g., when a line is deleted from the document).
467
+ *
468
+ * Cleans up all consumer references, write registrations, and data source dependencies
469
+ * for the removed line. O(k) where k is the number of variables the line reads.
470
+ *
471
+ * @param lineNumber - The line number being removed
472
+ */
473
+ removeLine(lineNumber: number): void;
474
+ /**
475
+ * Get all line numbers that consume (read) a given variable.
476
+ *
477
+ * @param variable - The variable name
478
+ * @returns Set of line numbers that read this variable, or empty set if none
479
+ */
480
+ getConsumers(variable: string): Set<number>;
481
+ /**
482
+ * Get all variables that a line depends on (reads).
483
+ *
484
+ * @param lineNumber - The line number to query
485
+ * @returns Set of variable names this line reads, or empty set if none
486
+ */
487
+ getDependencies(lineNumber: number): Set<string>;
488
+ /**
489
+ * Get all variables that a line writes (assigns to).
490
+ *
491
+ * @param lineNumber - The line number to query
492
+ * @returns Set of variable names this line writes, or empty set if none
493
+ */
494
+ getWrites(lineNumber: number): Set<string>;
495
+ /**
496
+ * Get a serializable snapshot of the entire dependency graph for diagnostics.
497
+ *
498
+ * Returns plain objects (not Maps/Sets) so consumers don't need to reach
499
+ * into private fields. Used by playground diagnostic tabs for DAG visualization.
500
+ */
501
+ getSnapshot(): DagSnapshot;
502
+ /** Clear all dependency graph state. Called on document switch or engine reset. */
503
+ clear(): void;
504
+ }
505
+
506
+ /**
507
+ * Record of a compiled expression in the scope.
508
+ * Tracks the expression text, compiled bytecode, last evaluation result,
509
+ * and the variable reads/writes for dependency tracking.
510
+ */
511
+ interface ExpressionRecord {
512
+ lineNumber: number;
513
+ expression: string;
514
+ bytecode: {
515
+ opcodes: Uint8Array;
516
+ numbers: Float64Array;
517
+ strings: string[];
518
+ };
519
+ lastResult: Value;
520
+ readVariables: string[];
521
+ writeVariable: string | null;
522
+ }
523
+ /**
524
+ * Scoped variable manager for expression evaluation.
525
+ *
526
+ * Tracks variable definitions across line numbers with write-on-read semantics.
527
+ * When a value is read, the most recent definition at or before the reading line
528
+ * is returned. Invalidation trims downstream definitions when a variable is redefined.
529
+ */
530
+ declare class ScopeManager {
531
+ private definitions;
532
+ /**
533
+ * Record a variable definition at `lineNumber`. A variable may have
534
+ * multiple definitions across different lines (e.g. redefined further
535
+ * down a document), each call adds one, keeping the internal stack
536
+ * sorted by line so {@link read} can binary-scan for "most recent
537
+ * definition at or before" a given line.
538
+ */
539
+ write(variable: string, lineNumber: number, expr: ExpressionRecord): void;
540
+ /**
541
+ * Look up a variable's value as seen from `readLine`: returns the result
542
+ * of the closest definition at or before `readLine`, not simply the most
543
+ * recently-written one, so a read on line 5 of a variable redefined on
544
+ * lines 2 and 10 sees line 2's value, not line 10's. Returns `undefined`
545
+ * if the variable has no definition at or before `readLine`.
546
+ */
547
+ read(variable: string, readLine: number): Value | undefined;
548
+ /**
549
+ * Drop every definition of `variable` that comes strictly after
550
+ * `definitionLine`. Call this when a line is re-evaluated with a new
551
+ * definition, so stale later-line definitions from a previous edit don't
552
+ * linger and get returned by {@link read}.
553
+ */
554
+ invalidateDownstream(variable: string, definitionLine: number): void;
555
+ /** Remove every tracked variable definition, used when an engine/document is reset. */
556
+ clear(): void;
557
+ }
558
+
559
+ export { type Bytecode as B, DependencyGraph as D, type ExpressionRecord as E, type LineExecutionContext as L, OpRegistry as O, type PluginFunctionHandler as P, ScopeManager as S, type VM as V, type DagSnapshot as a, type EngineContext as b, createVM as c, executeBytecode as e, sharedOpRegistry as s };