solve-engine 2.23.0 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -32
- package/dist/{BytecodeBuilder-aqVa7Plx.d.cts → BytecodeBuilder-DSWKZi4f.d.cts} +11 -9
- package/dist/{BytecodeBuilder-aqVa7Plx.d.ts → BytecodeBuilder-DSWKZi4f.d.ts} +11 -9
- package/dist/{Configuration-BGQn-cJ8.d.cts → Configuration-DnzYPmoK.d.cts} +33 -0
- package/dist/{Configuration-BGQn-cJ8.d.ts → Configuration-DnzYPmoK.d.ts} +33 -0
- package/dist/{EngineError-DTk7I7hZ.d.cts → EngineError-fR1K1rvx.d.cts} +6 -0
- package/dist/{EngineError-DTk7I7hZ.d.ts → EngineError-fR1K1rvx.d.ts} +6 -0
- package/dist/{Lexer-CqagTewQ.d.ts → Lexer-Bmctxl0n.d.ts} +84 -35
- package/dist/{Lexer-CCHFDcgP.d.cts → Lexer-aB95pjoX.d.cts} +84 -35
- package/dist/{PackageCompatibility-BGXSFuL9.d.ts → PackageCompatibility-BludTs7v.d.ts} +1 -1
- package/dist/{PackageCompatibility-BZCqaTOO.d.cts → PackageCompatibility-CUVg8tmt.d.cts} +1 -1
- package/dist/{PackageRegistry-BRoVOYzg.d.ts → PackageRegistry-DT-x32OM.d.ts} +67 -14
- package/dist/{PackageRegistry-rMGnTh7W.d.cts → PackageRegistry-Dvn4i2QK.d.cts} +67 -14
- package/dist/{Parselet-Bkbp9CKD.d.cts → Parselet-BawfGuTF.d.cts} +8 -2
- package/dist/{Parselet-B-WyUtX4.d.ts → Parselet-DqTbbtWA.d.ts} +8 -2
- package/dist/{ScopeManager-gB9UengK.d.ts → ScopeManager-B636am7Y.d.ts} +39 -8
- package/dist/{ScopeManager-bCYewWVt.d.cts → ScopeManager-CdwX0VTw.d.cts} +39 -8
- package/dist/{TokenNormalizer-OTPS0Otq.d.ts → TokenNormalizer-CLBtiE8M.d.ts} +45 -26
- package/dist/{TokenNormalizer-MXaKLJ_m.d.cts → TokenNormalizer-CjTDmHgB.d.cts} +45 -26
- package/dist/{VMCheckpoints-DGar9Yg8.d.cts → VMCheckpoints-CYqeq8IV.d.cts} +3 -3
- package/dist/{VMCheckpoints-BDRY1Kx8.d.ts → VMCheckpoints-DDlxNssY.d.ts} +3 -3
- package/dist/{Value-DCTqTSeP.d.cts → Value-Ds3Gy07C.d.cts} +1 -1
- package/dist/{Value-DCTqTSeP.d.ts → Value-Ds3Gy07C.d.ts} +1 -1
- package/dist/{WorkerError-DGBNM3gA.d.cts → WorkerError-B1dF3VYT.d.cts} +1 -1
- package/dist/{WorkerError-DpZgKRks.d.ts → WorkerError-dneODN-u.d.ts} +1 -1
- package/dist/{chunk-YAONCEAW.cjs → chunk-4BAUQE6A.cjs} +3 -3
- package/dist/{chunk-YAONCEAW.cjs.map → chunk-4BAUQE6A.cjs.map} +1 -1
- package/dist/chunk-4BRTUFTH.js +2 -0
- package/dist/chunk-4BRTUFTH.js.map +1 -0
- package/dist/chunk-4NZXCHQL.cjs +2 -0
- package/dist/chunk-4NZXCHQL.cjs.map +1 -0
- package/dist/chunk-57PSJQQW.js +3 -0
- package/dist/chunk-57PSJQQW.js.map +1 -0
- package/dist/chunk-634ILVMP.cjs +2 -0
- package/dist/chunk-634ILVMP.cjs.map +1 -0
- package/dist/{chunk-HMC7FJO7.js → chunk-63ZOOUOL.js} +2 -2
- package/dist/{chunk-HMC7FJO7.js.map → chunk-63ZOOUOL.js.map} +1 -1
- package/dist/chunk-72PYPENZ.js +2 -0
- package/dist/chunk-72PYPENZ.js.map +1 -0
- package/dist/chunk-77KI7AKJ.cjs +2 -0
- package/dist/chunk-77KI7AKJ.cjs.map +1 -0
- package/dist/chunk-7DP57BA7.js +2 -0
- package/dist/chunk-7DP57BA7.js.map +1 -0
- package/dist/chunk-7N3IJSWF.js +5 -0
- package/dist/chunk-7N3IJSWF.js.map +1 -0
- package/dist/chunk-BMXYR5LI.cjs +2 -0
- package/dist/chunk-BMXYR5LI.cjs.map +1 -0
- package/dist/chunk-DUYTXLO2.js +2 -0
- package/dist/chunk-DUYTXLO2.js.map +1 -0
- package/dist/chunk-EL2XUOWX.js +2 -0
- package/dist/chunk-EL2XUOWX.js.map +1 -0
- package/dist/chunk-FYAMQFZZ.cjs +3 -0
- package/dist/chunk-FYAMQFZZ.cjs.map +1 -0
- package/dist/chunk-FYTB7SDP.cjs +2 -0
- package/dist/chunk-FYTB7SDP.cjs.map +1 -0
- package/dist/chunk-GBCN7FNV.cjs +2 -0
- package/dist/chunk-GBCN7FNV.cjs.map +1 -0
- package/dist/{chunk-7E7NXKQ5.js → chunk-IJHL3LMH.js} +2 -2
- package/dist/chunk-IJHL3LMH.js.map +1 -0
- package/dist/{chunk-4ETS224G.js → chunk-JFZ5SLF7.js} +2 -2
- package/dist/{chunk-4ETS224G.js.map → chunk-JFZ5SLF7.js.map} +1 -1
- package/dist/chunk-JSM3O7EM.cjs +2 -0
- package/dist/chunk-JSM3O7EM.cjs.map +1 -0
- package/dist/chunk-MZMA32NE.js +3 -0
- package/dist/chunk-MZMA32NE.js.map +1 -0
- package/dist/chunk-N4SSR7Q6.js +2 -0
- package/dist/chunk-N4SSR7Q6.js.map +1 -0
- package/dist/{chunk-TL545PXW.cjs → chunk-NEGZZB7F.cjs} +2 -2
- package/dist/{chunk-TL545PXW.cjs.map → chunk-NEGZZB7F.cjs.map} +1 -1
- package/dist/{chunk-3GR46UOX.js → chunk-NMU5E7W7.js} +2 -2
- package/dist/{chunk-3GR46UOX.js.map → chunk-NMU5E7W7.js.map} +1 -1
- package/dist/chunk-OB2VX4IL.js +2 -0
- package/dist/chunk-OB2VX4IL.js.map +1 -0
- package/dist/{chunk-MRRMIBHE.js → chunk-OONQ3V3I.js} +2 -2
- package/dist/{chunk-MRRMIBHE.js.map → chunk-OONQ3V3I.js.map} +1 -1
- package/dist/{chunk-N37PUZJR.cjs → chunk-RAPEZR5L.cjs} +2 -2
- package/dist/{chunk-N37PUZJR.cjs.map → chunk-RAPEZR5L.cjs.map} +1 -1
- package/dist/{chunk-KEA5HRL3.js → chunk-RDYXH7ML.js} +2 -2
- package/dist/{chunk-KEA5HRL3.js.map → chunk-RDYXH7ML.js.map} +1 -1
- package/dist/{chunk-3SSMOVWB.cjs → chunk-SAF3B3AX.cjs} +2 -2
- package/dist/{chunk-3SSMOVWB.cjs.map → chunk-SAF3B3AX.cjs.map} +1 -1
- package/dist/chunk-SPOVFRZO.cjs +5 -0
- package/dist/chunk-SPOVFRZO.cjs.map +1 -0
- package/dist/{chunk-75JLWK4L.cjs → chunk-SQHBOWES.cjs} +2 -2
- package/dist/{chunk-75JLWK4L.cjs.map → chunk-SQHBOWES.cjs.map} +1 -1
- package/dist/chunk-UF5XMZTT.js +2 -0
- package/dist/chunk-UF5XMZTT.js.map +1 -0
- package/dist/chunk-UV3EUIBT.cjs +3 -0
- package/dist/chunk-UV3EUIBT.cjs.map +1 -0
- package/dist/chunk-VMSCRVNX.cjs +2 -0
- package/dist/chunk-VMSCRVNX.cjs.map +1 -0
- package/dist/{chunk-J7ABVGZH.cjs → chunk-WGP4UUO5.cjs} +2 -2
- package/dist/{chunk-J7ABVGZH.cjs.map → chunk-WGP4UUO5.cjs.map} +1 -1
- package/dist/chunk-XBGK3TTU.cjs +3 -0
- package/dist/chunk-XBGK3TTU.cjs.map +1 -0
- package/dist/chunk-XHQYHXRE.js +3 -0
- package/dist/chunk-XHQYHXRE.js.map +1 -0
- package/dist/chunk-XX55RR4Q.js +3 -0
- package/dist/chunk-XX55RR4Q.js.map +1 -0
- package/dist/chunk-YBQPEWT7.cjs +3 -0
- package/dist/chunk-YBQPEWT7.cjs.map +1 -0
- package/dist/{chunk-CHSO3DCS.js → chunk-ZDCZ5IDD.js} +3 -3
- package/dist/{chunk-CHSO3DCS.js.map → chunk-ZDCZ5IDD.js.map} +1 -1
- package/dist/{chunk-LNZNJSRW.cjs → chunk-ZVPZQ66D.cjs} +2 -2
- package/dist/chunk-ZVPZQ66D.cjs.map +1 -0
- package/dist/constants.cjs +1 -1
- package/dist/constants.d.cts +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/engine.cjs +1 -1
- package/dist/engine.d.cts +12 -12
- package/dist/engine.d.ts +12 -12
- package/dist/engine.js +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.d.cts +3 -3
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +1 -1
- package/dist/format.cjs +1 -1
- package/dist/format.d.cts +1 -1
- package/dist/format.d.ts +1 -1
- package/dist/format.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -12
- package/dist/index.d.ts +14 -12
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/language.d.cts +11 -11
- package/dist/language.d.ts +11 -11
- package/dist/lexer.cjs +1 -1
- package/dist/lexer.cjs.map +1 -1
- package/dist/lexer.d.cts +8 -7
- package/dist/lexer.d.ts +8 -7
- package/dist/lexer.js +1 -1
- package/dist/lexer.js.map +1 -1
- package/dist/normalizer.cjs +1 -1
- package/dist/normalizer.d.cts +16 -10
- package/dist/normalizer.d.ts +16 -10
- package/dist/normalizer.js +1 -1
- package/dist/packages.cjs +1 -1
- package/dist/packages.d.cts +10 -10
- package/dist/packages.d.ts +10 -10
- package/dist/packages.js +1 -1
- package/dist/parser.cjs +1 -1
- package/dist/parser.d.cts +4 -4
- package/dist/parser.d.ts +4 -4
- package/dist/parser.js +1 -1
- package/dist/{pipeline-B4wf3M1h.d.cts → pipeline-69q2tsLE.d.cts} +1 -1
- package/dist/{pipeline-QIT4iD8f.d.ts → pipeline-DTqGLPsV.d.ts} +1 -1
- package/dist/resolvers.cjs +1 -1
- package/dist/resolvers.d.cts +19 -3
- package/dist/resolvers.d.ts +19 -3
- package/dist/resolvers.js +1 -1
- package/dist/testing.cjs +2 -2
- package/dist/testing.d.cts +11 -11
- package/dist/testing.d.ts +11 -11
- package/dist/testing.js +1 -1
- package/dist/uom.cjs +1 -1
- package/dist/uom.d.cts +2 -2
- package/dist/uom.d.ts +2 -2
- package/dist/uom.js +1 -1
- package/dist/vm.cjs +1 -1
- package/dist/vm.d.cts +8 -8
- package/dist/vm.d.ts +8 -8
- package/dist/vm.js +1 -1
- package/dist/worker.cjs +2 -2
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +34 -25
- package/dist/worker.d.ts +34 -25
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-42JXN3BH.js +0 -5
- package/dist/chunk-42JXN3BH.js.map +0 -1
- package/dist/chunk-4DJJIUQF.js +0 -2
- package/dist/chunk-4DJJIUQF.js.map +0 -1
- package/dist/chunk-6YM67DWH.cjs +0 -3
- package/dist/chunk-6YM67DWH.cjs.map +0 -1
- package/dist/chunk-7E7NXKQ5.js.map +0 -1
- package/dist/chunk-7SZBEE5R.cjs +0 -2
- package/dist/chunk-7SZBEE5R.cjs.map +0 -1
- package/dist/chunk-A5M4QZIK.js +0 -2
- package/dist/chunk-A5M4QZIK.js.map +0 -1
- package/dist/chunk-AF7HQN6I.js +0 -2
- package/dist/chunk-AF7HQN6I.js.map +0 -1
- package/dist/chunk-ALY4ZMYO.js +0 -2
- package/dist/chunk-ALY4ZMYO.js.map +0 -1
- package/dist/chunk-BRJ4RT5U.js +0 -2
- package/dist/chunk-BRJ4RT5U.js.map +0 -1
- package/dist/chunk-CO2BI6WL.cjs +0 -3
- package/dist/chunk-CO2BI6WL.cjs.map +0 -1
- package/dist/chunk-EN3CDYOS.cjs +0 -2
- package/dist/chunk-EN3CDYOS.cjs.map +0 -1
- package/dist/chunk-EQDT42JG.js +0 -3
- package/dist/chunk-EQDT42JG.js.map +0 -1
- package/dist/chunk-FMMT5C5H.cjs +0 -3
- package/dist/chunk-FMMT5C5H.cjs.map +0 -1
- package/dist/chunk-FP6Z6PYR.js +0 -2
- package/dist/chunk-FP6Z6PYR.js.map +0 -1
- package/dist/chunk-H3JXNH7X.cjs +0 -3
- package/dist/chunk-H3JXNH7X.cjs.map +0 -1
- package/dist/chunk-HZOLIL5T.js +0 -3
- package/dist/chunk-HZOLIL5T.js.map +0 -1
- package/dist/chunk-JVMINMAB.js +0 -2
- package/dist/chunk-JVMINMAB.js.map +0 -1
- package/dist/chunk-JYLNQOPU.cjs +0 -2
- package/dist/chunk-JYLNQOPU.cjs.map +0 -1
- package/dist/chunk-KRXO3MOW.cjs +0 -2
- package/dist/chunk-KRXO3MOW.cjs.map +0 -1
- package/dist/chunk-KVLJDOZ7.cjs +0 -5
- package/dist/chunk-KVLJDOZ7.cjs.map +0 -1
- package/dist/chunk-LNZNJSRW.cjs.map +0 -1
- package/dist/chunk-O3BDXOQJ.js +0 -2
- package/dist/chunk-O3BDXOQJ.js.map +0 -1
- package/dist/chunk-PIZQIQVM.js +0 -3
- package/dist/chunk-PIZQIQVM.js.map +0 -1
- package/dist/chunk-QTSDIDAS.js +0 -3
- package/dist/chunk-QTSDIDAS.js.map +0 -1
- package/dist/chunk-SGIDBZ4T.cjs +0 -2
- package/dist/chunk-SGIDBZ4T.cjs.map +0 -1
- package/dist/chunk-UNKSCVIL.cjs +0 -2
- package/dist/chunk-UNKSCVIL.cjs.map +0 -1
- package/dist/chunk-USQ4KL6S.cjs +0 -2
- package/dist/chunk-USQ4KL6S.cjs.map +0 -1
- package/dist/chunk-XDXIPVZB.cjs +0 -2
- package/dist/chunk-XDXIPVZB.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -52,8 +52,14 @@ const value = engine.evaluateExpression("2 + 2 * 10");
|
|
|
52
52
|
console.log(value.toNumber()); // 22
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
Two kinds of failure, deliberately different. A line the parser cannot read
|
|
56
|
+
(`10 +`), or one that names a variable no line defined, throws an `EngineError`
|
|
57
|
+
(see `solve-engine/errors`), so wrap calls on untrusted input in a `try`/`catch`.
|
|
58
|
+
A line the engine can run but cannot answer (an impossible conversion, a rate it
|
|
59
|
+
has no data for, a live value still loading) comes back as a `Value` whose
|
|
60
|
+
`isError()` or `isPending()` says so, never as a throw, which is the right shape
|
|
61
|
+
for input being typed one character at a time. Check `isFault()` before
|
|
62
|
+
`toNumber()`: a faulted value reads as `0` through it.
|
|
57
63
|
|
|
58
64
|
For line-oriented input (e.g. a document made of multiple expressions, some referencing
|
|
59
65
|
variables defined on earlier lines), use `evaluateLine`/`parseDocument` instead, see the
|
|
@@ -104,13 +110,15 @@ they are:
|
|
|
104
110
|
|
|
105
111
|
| Subpath | Purpose |
|
|
106
112
|
|---|---|
|
|
107
|
-
| `solve-engine` | Start here
|
|
113
|
+
| `solve-engine` | Start here: `createEngine`, `ExpressionEngine`, `defineFunction`, `IEnginePackage`, and the result surface `Value`, `ValueType`, `formatValue`. |
|
|
108
114
|
| `solve-engine/engine` | `ExpressionEngine` and its supporting types (`Explanation`, `EngineSnapshot`, etc.) directly, without the package-registration wrapper. |
|
|
109
|
-
| `solve-engine/vm` | The bytecode VM: `Value`/`ValueType`, opcode dispatch
|
|
110
|
-
| `solve-engine/format` | Turning a `Value` into a display string (numbers, dates, units, vectors, ...). |
|
|
115
|
+
| `solve-engine/vm` | The bytecode VM: `Value`/`ValueType`, the value constructors, opcode dispatch. |
|
|
116
|
+
| `solve-engine/format` | Turning a `Value` into a display string (numbers, dates, units, vectors, ...) and the formatting settings. |
|
|
111
117
|
| `solve-engine/language` | Editor-agnostic language service: token categories, completions, highlighting. |
|
|
112
118
|
| `solve-engine/packages` | The built-in packages (arithmetic, datetime, time, dice, uom, currency, vector, conditionals, converters, mathphrases, ...). |
|
|
113
|
-
| `solve-engine/constants` | Engine configuration types and defaults (`EngineConfig`, `VMConfig`, ...). |
|
|
119
|
+
| `solve-engine/constants` | Engine configuration types and defaults (`EngineConfig`, `VMConfig`, `NetworkConfig`, ...). |
|
|
120
|
+
| `solve-engine/worker` | Off-main-thread evaluation: `createWorkerEngine`, `startWorkerRuntime`, the transports and the result DTOs. |
|
|
121
|
+
| `solve-engine/testing` | A test kit for package authors: `createTestEngine`, `expectExpression`, `expectPackage`. |
|
|
114
122
|
|
|
115
123
|
The following subpaths are **advanced-public**, everything a third-party package author
|
|
116
124
|
needs to extend the engine, but with a looser stability contract than the tier above (these
|
|
@@ -132,32 +140,70 @@ Anything not listed above (`telemetry`, `cache`, `diagnostics`, `types`, `worker
|
|
|
132
140
|
internal and not part of the package's public contract, it may change or disappear between
|
|
133
141
|
minor versions without notice.
|
|
134
142
|
|
|
143
|
+
## Adding a function
|
|
144
|
+
|
|
145
|
+
The shortest way to teach the engine a new name is `defineFunction`: declare the
|
|
146
|
+
name, the argument types and the return type, and get back a package that plugs
|
|
147
|
+
in beside the built-ins. No parser, no bytecode.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { createEngine, defineFunction } from "solve-engine";
|
|
151
|
+
|
|
152
|
+
const vat = defineFunction({
|
|
153
|
+
name: "vat",
|
|
154
|
+
args: [{ name: "amount", type: "number" }],
|
|
155
|
+
returns: "number",
|
|
156
|
+
call: (amount) => amount * 1.2,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const engine = createEngine({ extraPackages: [vat] });
|
|
160
|
+
engine.evaluateExpression("vat(100)").toNumber(); // 120
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Arguments are a fixed list of `number`, `string` or `boolean`, and `call` is
|
|
164
|
+
synchronous; a mismatched call produces a structured error naming the argument.
|
|
165
|
+
Anything beyond that shape (a unit-bearing argument, a phrase rather than a
|
|
166
|
+
call, live data) is a package proper.
|
|
167
|
+
|
|
135
168
|
## Authoring a package
|
|
136
169
|
|
|
137
|
-
A **package** (`IEnginePackage`) is a plain data descriptor bundling everything
|
|
138
|
-
extend the engine with a new domain:
|
|
139
|
-
|
|
140
|
-
[
|
|
141
|
-
|
|
170
|
+
A **package** (`IEnginePackage`) is a plain data descriptor bundling everything
|
|
171
|
+
needed to extend the engine with a new domain: token vocabulary, normaliser
|
|
172
|
+
rules, parselets, plugin functions, `as` converters, completions, and optional
|
|
173
|
+
async resolvers. The [package author guides](https://liamriddell.github.io/solve-engine/packages/authoring-a-package/)
|
|
174
|
+
walk through each extension point; [`IEnginePackage`](https://github.com/LiamRiddell/solve-engine/blob/main/packages/engine/src/api/PackageRegistry.ts)
|
|
175
|
+
carries the full field list with inline documentation.
|
|
142
176
|
|
|
143
|
-
Minimal shape:
|
|
177
|
+
Minimal shape, a keyword that calls a function:
|
|
144
178
|
|
|
145
179
|
```typescript
|
|
146
|
-
import { allocatePluginFunctionIndex } from "solve-engine/vm";
|
|
147
180
|
import type { IEnginePackage } from "solve-engine";
|
|
148
|
-
|
|
149
|
-
|
|
181
|
+
import type { PrefixParselet } from "solve-engine/parser";
|
|
182
|
+
import { stringValue } from "solve-engine/vm";
|
|
183
|
+
|
|
184
|
+
const reverseParselet: PrefixParselet = {
|
|
185
|
+
category: "Text",
|
|
186
|
+
parse(parser, _token, builder) {
|
|
187
|
+
parser.consume("LPAREN");
|
|
188
|
+
parser.parseExpression(0, builder);
|
|
189
|
+
parser.consume("RPAREN");
|
|
190
|
+
// By name; the engine assigns the index when the package registers.
|
|
191
|
+
builder.emitPluginCall("reverse", 1);
|
|
192
|
+
},
|
|
193
|
+
};
|
|
150
194
|
|
|
151
195
|
export const MY_PACKAGE: IEnginePackage = {
|
|
152
|
-
name: "
|
|
153
|
-
// engineVersion: "^0.
|
|
154
|
-
|
|
155
|
-
|
|
196
|
+
name: "my-package",
|
|
197
|
+
// engineVersion: "^2.0.0", // optional, see below
|
|
198
|
+
lexerVocabulary: { keywords: { reverse: "REVERSE_FN" } },
|
|
199
|
+
prefixParselets: { REVERSE_FN: reverseParselet },
|
|
200
|
+
pluginFunctions: { reverse: ([text]) => stringValue([...String(text.value)].reverse().join("")) },
|
|
156
201
|
};
|
|
157
202
|
```
|
|
158
203
|
|
|
159
|
-
Register it
|
|
160
|
-
|
|
204
|
+
Register it as one of the packages passed to the `ExpressionEngine` constructor
|
|
205
|
+
(or `createEngine`'s `extraPackages`), or at runtime via
|
|
206
|
+
`ExpressionEngine.registerPackage()` / `unregisterPackage()`.
|
|
161
207
|
|
|
162
208
|
### Declaring engine-version compatibility
|
|
163
209
|
|
|
@@ -210,17 +256,21 @@ published package, see `files` in `package.json`, only `dist/` ships):
|
|
|
210
256
|
|
|
211
257
|
## Known limitations
|
|
212
258
|
|
|
213
|
-
**
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
259
|
+
**Exchange rates are shared across engines, by design.** Plugin functions, the
|
|
260
|
+
opcode registry and the lexer are owned per `ExpressionEngine`, so two engines
|
|
261
|
+
with different package sets do not interfere. The currency rate cache is the one
|
|
262
|
+
deliberate exception: rates are market data with a fifteen-minute freshness
|
|
263
|
+
window, and two engines in one process fetching the same pair separately could
|
|
264
|
+
disagree about it. A host that primes rates (`currencyExchangeService.primeRates`)
|
|
265
|
+
primes them for every engine in the process.
|
|
266
|
+
|
|
267
|
+
**A live value reaches the document only through the event stream.** When a
|
|
268
|
+
fetch lands, the engine does not push the new value at you; it emits a
|
|
269
|
+
`lines-updated` event on `engine.getEventStream()` naming the lines to
|
|
270
|
+
re-evaluate. A host that reads neither that stream nor sets
|
|
271
|
+
`AsyncResolutionBatcher.onLineResult` sees the line stay pending, and a warning
|
|
272
|
+
says so once. The [async guide](https://liamriddell.github.io/solve-engine/guide/async-and-live-data/)
|
|
273
|
+
shows the loop.
|
|
224
274
|
|
|
225
275
|
## Development
|
|
226
276
|
|
|
@@ -199,6 +199,8 @@ declare class BytecodeBuilder {
|
|
|
199
199
|
private numbers;
|
|
200
200
|
private strings;
|
|
201
201
|
private stringIndex;
|
|
202
|
+
/** Value (or {@link NEGATIVE_ZERO_KEY}) to its slot in `numbers`, so a repeated literal is emitted once. */
|
|
203
|
+
private numberIndex;
|
|
202
204
|
private _hasAsync;
|
|
203
205
|
private userFunctionBodies;
|
|
204
206
|
private anonymousBodies;
|
|
@@ -222,17 +224,17 @@ declare class BytecodeBuilder {
|
|
|
222
224
|
/** Emit an {@link OpCode} instruction. */
|
|
223
225
|
emitOpcode(op: OpCode): void;
|
|
224
226
|
/**
|
|
225
|
-
* Emit a numeric literal:
|
|
226
|
-
*
|
|
227
|
-
* `PUSH_NUMBER <idx>`).
|
|
227
|
+
* Emit a numeric literal: interns `n` into the program's constant pool
|
|
228
|
+
* (deduplicated, like {@link emitString}) and writes its index into the
|
|
229
|
+
* opcode stream (read back by the VM as e.g. `PUSH_NUMBER <idx>`).
|
|
228
230
|
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
231
|
+
* Deduplicated so that the 256-entry pool counts distinct values rather
|
|
232
|
+
* than occurrences: a long line of repeated literals used to exhaust it
|
|
233
|
+
* long before it held 256 different numbers. Negative zero keeps its own
|
|
234
|
+
* slot, because `1 / -0` is not `1 / 0`; NaN shares one, since every NaN
|
|
235
|
+
* reads the same.
|
|
234
236
|
*
|
|
235
|
-
* @throws If the
|
|
237
|
+
* @throws If the pool would exceed 256 distinct entries.
|
|
236
238
|
*/
|
|
237
239
|
emitNumber(n: number): void;
|
|
238
240
|
/**
|
|
@@ -199,6 +199,8 @@ declare class BytecodeBuilder {
|
|
|
199
199
|
private numbers;
|
|
200
200
|
private strings;
|
|
201
201
|
private stringIndex;
|
|
202
|
+
/** Value (or {@link NEGATIVE_ZERO_KEY}) to its slot in `numbers`, so a repeated literal is emitted once. */
|
|
203
|
+
private numberIndex;
|
|
202
204
|
private _hasAsync;
|
|
203
205
|
private userFunctionBodies;
|
|
204
206
|
private anonymousBodies;
|
|
@@ -222,17 +224,17 @@ declare class BytecodeBuilder {
|
|
|
222
224
|
/** Emit an {@link OpCode} instruction. */
|
|
223
225
|
emitOpcode(op: OpCode): void;
|
|
224
226
|
/**
|
|
225
|
-
* Emit a numeric literal:
|
|
226
|
-
*
|
|
227
|
-
* `PUSH_NUMBER <idx>`).
|
|
227
|
+
* Emit a numeric literal: interns `n` into the program's constant pool
|
|
228
|
+
* (deduplicated, like {@link emitString}) and writes its index into the
|
|
229
|
+
* opcode stream (read back by the VM as e.g. `PUSH_NUMBER <idx>`).
|
|
228
230
|
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
231
|
+
* Deduplicated so that the 256-entry pool counts distinct values rather
|
|
232
|
+
* than occurrences: a long line of repeated literals used to exhaust it
|
|
233
|
+
* long before it held 256 different numbers. Negative zero keeps its own
|
|
234
|
+
* slot, because `1 / -0` is not `1 / 0`; NaN shares one, since every NaN
|
|
235
|
+
* reads the same.
|
|
234
236
|
*
|
|
235
|
-
* @throws If the
|
|
237
|
+
* @throws If the pool would exceed 256 distinct entries.
|
|
236
238
|
*/
|
|
237
239
|
emitNumber(n: number): void;
|
|
238
240
|
/**
|
|
@@ -207,6 +207,37 @@ interface BackgroundRefreshConfig {
|
|
|
207
207
|
/** Master switch. When false, no timers run and every value refreshes only on re-evaluation. */
|
|
208
208
|
readonly enabled: boolean;
|
|
209
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Whether the engine may reach the network at all.
|
|
212
|
+
*
|
|
213
|
+
* On by default, which is the historic behaviour: a city name or a currency
|
|
214
|
+
* pair in a line fetches live data from a public endpoint as soon as the
|
|
215
|
+
* line evaluates. A host embedding the engine somewhere that must not make
|
|
216
|
+
* outbound requests (an offline document, a sandboxed evaluation, a tenant
|
|
217
|
+
* with egress rules) switches it off here and gets the same engine with no
|
|
218
|
+
* egress: every live-data form answers with a `NETWORK_DISABLED` error
|
|
219
|
+
* naming the setting, instead of a request.
|
|
220
|
+
*
|
|
221
|
+
* The gate closes before any request is made. It stops every async
|
|
222
|
+
* resolver a package registers (weather, currency, stocks, crypto,
|
|
223
|
+
* knowledge, and any host-supplied one) unless the resolver declares
|
|
224
|
+
* itself `local` (it reads engine state, never a network; the engine's own
|
|
225
|
+
* global-variable resolver is the one built-in case). It also refuses the
|
|
226
|
+
* result of a plugin function that returns a promise. That last case is a
|
|
227
|
+
* boundary rather than a guarantee: such a function has already run by the
|
|
228
|
+
* time the engine sees the promise, so a package that fetches inside a
|
|
229
|
+
* plugin function rather than through an async resolver may still have
|
|
230
|
+
* started its request. The documented shape for live data is the resolver
|
|
231
|
+
* (`createQueryResolver`), which this gate stops cold.
|
|
232
|
+
*
|
|
233
|
+
* Rates a host primes by hand (`currencyExchangeService.primeRates`) keep
|
|
234
|
+
* working with the network off, which is what an offline host with its own
|
|
235
|
+
* rate table wants.
|
|
236
|
+
*/
|
|
237
|
+
interface NetworkConfig {
|
|
238
|
+
/** Master switch. When false, no async resolver runs and no live value is fetched. */
|
|
239
|
+
readonly enabled: boolean;
|
|
240
|
+
}
|
|
210
241
|
/**
|
|
211
242
|
* Virtual Machine configuration.
|
|
212
243
|
* Controls the internal bytecode VM that executes compiled expressions.
|
|
@@ -321,6 +352,8 @@ interface EngineConfig {
|
|
|
321
352
|
readonly diagnostic: DiagnosticConfig;
|
|
322
353
|
/** Proactive background refresh of live async values */
|
|
323
354
|
readonly backgroundRefresh: BackgroundRefreshConfig;
|
|
355
|
+
/** Whether live data may be fetched at all. See {@link NetworkConfig}. */
|
|
356
|
+
readonly network: NetworkConfig;
|
|
324
357
|
}
|
|
325
358
|
/**
|
|
326
359
|
* A partial {@link EngineConfig} override, one section at a time.
|
|
@@ -207,6 +207,37 @@ interface BackgroundRefreshConfig {
|
|
|
207
207
|
/** Master switch. When false, no timers run and every value refreshes only on re-evaluation. */
|
|
208
208
|
readonly enabled: boolean;
|
|
209
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Whether the engine may reach the network at all.
|
|
212
|
+
*
|
|
213
|
+
* On by default, which is the historic behaviour: a city name or a currency
|
|
214
|
+
* pair in a line fetches live data from a public endpoint as soon as the
|
|
215
|
+
* line evaluates. A host embedding the engine somewhere that must not make
|
|
216
|
+
* outbound requests (an offline document, a sandboxed evaluation, a tenant
|
|
217
|
+
* with egress rules) switches it off here and gets the same engine with no
|
|
218
|
+
* egress: every live-data form answers with a `NETWORK_DISABLED` error
|
|
219
|
+
* naming the setting, instead of a request.
|
|
220
|
+
*
|
|
221
|
+
* The gate closes before any request is made. It stops every async
|
|
222
|
+
* resolver a package registers (weather, currency, stocks, crypto,
|
|
223
|
+
* knowledge, and any host-supplied one) unless the resolver declares
|
|
224
|
+
* itself `local` (it reads engine state, never a network; the engine's own
|
|
225
|
+
* global-variable resolver is the one built-in case). It also refuses the
|
|
226
|
+
* result of a plugin function that returns a promise. That last case is a
|
|
227
|
+
* boundary rather than a guarantee: such a function has already run by the
|
|
228
|
+
* time the engine sees the promise, so a package that fetches inside a
|
|
229
|
+
* plugin function rather than through an async resolver may still have
|
|
230
|
+
* started its request. The documented shape for live data is the resolver
|
|
231
|
+
* (`createQueryResolver`), which this gate stops cold.
|
|
232
|
+
*
|
|
233
|
+
* Rates a host primes by hand (`currencyExchangeService.primeRates`) keep
|
|
234
|
+
* working with the network off, which is what an offline host with its own
|
|
235
|
+
* rate table wants.
|
|
236
|
+
*/
|
|
237
|
+
interface NetworkConfig {
|
|
238
|
+
/** Master switch. When false, no async resolver runs and no live value is fetched. */
|
|
239
|
+
readonly enabled: boolean;
|
|
240
|
+
}
|
|
210
241
|
/**
|
|
211
242
|
* Virtual Machine configuration.
|
|
212
243
|
* Controls the internal bytecode VM that executes compiled expressions.
|
|
@@ -321,6 +352,8 @@ interface EngineConfig {
|
|
|
321
352
|
readonly diagnostic: DiagnosticConfig;
|
|
322
353
|
/** Proactive background refresh of live async values */
|
|
323
354
|
readonly backgroundRefresh: BackgroundRefreshConfig;
|
|
355
|
+
/** Whether live data may be fetched at all. See {@link NetworkConfig}. */
|
|
356
|
+
readonly network: NetworkConfig;
|
|
324
357
|
}
|
|
325
358
|
/**
|
|
326
359
|
* A partial {@link EngineConfig} override, one section at a time.
|
|
@@ -38,6 +38,8 @@ declare const CoreErrorCodes: {
|
|
|
38
38
|
readonly UNEXPECTED_TRAILING_TOKEN: "UNEXPECTED_TRAILING_TOKEN";
|
|
39
39
|
readonly PARSE_ERROR: "PARSE_ERROR";
|
|
40
40
|
readonly TOO_MANY_NUMERIC_CONSTANTS: "TOO_MANY_NUMERIC_CONSTANTS";
|
|
41
|
+
/** A raw bytecode operand outside 0 to 255, or a jump patch outside the emitted stream (`parser/BytecodeBuilder.ts`'s `emitIndex`, `emitByte`, `patchJump`). `build()` keeps one byte per operand, so 300 used to become 44 with no error and the program read the wrong constant or plugin function. A package-authoring fault, reported at compile time. */
|
|
42
|
+
readonly BYTECODE_OPERAND_OUT_OF_RANGE: "BYTECODE_OPERAND_OUT_OF_RANGE";
|
|
41
43
|
readonly TOO_MANY_STRING_CONSTANTS: "TOO_MANY_STRING_CONSTANTS";
|
|
42
44
|
readonly NO_MATCHING_PHRASE_ALTERNATIVE: "NO_MATCHING_PHRASE_ALTERNATIVE";
|
|
43
45
|
readonly INVALID_PHRASE_PATTERN: "INVALID_PHRASE_PATTERN";
|
|
@@ -103,6 +105,8 @@ declare const CoreErrorCodes: {
|
|
|
103
105
|
readonly INTERNAL_MISSING_FUNCTION_BODY: "INTERNAL_MISSING_FUNCTION_BODY";
|
|
104
106
|
/** `MAP_INVOKE`/`REDUCE_INVOKE`'s anonymous-body-index lookup failing. Same class as `INTERNAL_MISSING_FUNCTION_BODY` above, for the `anonymousBodies` side-table instead of `userFunctionBodies`. */
|
|
105
107
|
readonly INTERNAL_MISSING_ANONYMOUS_BODY: "INTERNAL_MISSING_ANONYMOUS_BODY";
|
|
108
|
+
/** `CALL_BUILTIN` naming an index no builtin is registered at. It used to pop its arguments and push nothing, so the opcode after it read a neighbour's operand as its own; now an Error value on the stack, the shape `map`/`reduce` already used for the same fault. Bytecode-level, so unreachable from source without a compiler or snapshot fault. */
|
|
109
|
+
readonly UNKNOWN_BUILTIN_FUNCTION: "UNKNOWN_BUILTIN_FUNCTION";
|
|
106
110
|
/** An operand byte read past the end of the stream: the program ends in the middle of an instruction. */
|
|
107
111
|
readonly MALFORMED_BYTECODE_TRUNCATED: "MALFORMED_BYTECODE_TRUNCATED";
|
|
108
112
|
/** A constant-pool operand indexing a `numbers`/`strings` entry that does not exist, or that is not of the pool's type. */
|
|
@@ -164,6 +168,8 @@ declare const CoreErrorCodes: {
|
|
|
164
168
|
readonly UNKNOWN_SAVINGS_PERIOD: "UNKNOWN_SAVINGS_PERIOD";
|
|
165
169
|
/** Colon-separated numbers that are not a time any clock can show ("24:00", "9:60", "100:5"). Raised by the labeled-line fallback, which used to answer them with whatever stood after the colon. */
|
|
166
170
|
readonly INVALID_TIME_LITERAL: "INVALID_TIME_LITERAL";
|
|
171
|
+
/** A live-data form evaluated on an engine whose host switched the network off (`network.enabled: false`, see `constants/Configuration.ts`'s `NetworkConfig`). A recoverable Error value, raised by the VM for a currency conversion with no primed rate and for a plugin function that returned a promise, and by `createQueryResolver`'s plugin function when its preflight was skipped. Names the setting, so the reader knows it is policy rather than an outage. */
|
|
172
|
+
readonly NETWORK_DISABLED: "NETWORK_DISABLED";
|
|
167
173
|
/** `fromJSON()` handed an object that is not a snapshot at all, or whose serialised-shape version does not match this engine's reader. The versioning gate that refuses an incompatible snapshot clearly rather than restoring it wrongly. See `engine/EngineSnapshot.ts`'s `assertRestorable()`. */
|
|
168
174
|
readonly SNAPSHOT_VERSION_MISMATCH: "SNAPSHOT_VERSION_MISMATCH";
|
|
169
175
|
/** A snapshot with the right envelope but internally inconsistent contents (an unrecognised number sentinel, an unknown value tag). Distinct from a version mismatch: the format is right, the payload is not. */
|
|
@@ -38,6 +38,8 @@ declare const CoreErrorCodes: {
|
|
|
38
38
|
readonly UNEXPECTED_TRAILING_TOKEN: "UNEXPECTED_TRAILING_TOKEN";
|
|
39
39
|
readonly PARSE_ERROR: "PARSE_ERROR";
|
|
40
40
|
readonly TOO_MANY_NUMERIC_CONSTANTS: "TOO_MANY_NUMERIC_CONSTANTS";
|
|
41
|
+
/** A raw bytecode operand outside 0 to 255, or a jump patch outside the emitted stream (`parser/BytecodeBuilder.ts`'s `emitIndex`, `emitByte`, `patchJump`). `build()` keeps one byte per operand, so 300 used to become 44 with no error and the program read the wrong constant or plugin function. A package-authoring fault, reported at compile time. */
|
|
42
|
+
readonly BYTECODE_OPERAND_OUT_OF_RANGE: "BYTECODE_OPERAND_OUT_OF_RANGE";
|
|
41
43
|
readonly TOO_MANY_STRING_CONSTANTS: "TOO_MANY_STRING_CONSTANTS";
|
|
42
44
|
readonly NO_MATCHING_PHRASE_ALTERNATIVE: "NO_MATCHING_PHRASE_ALTERNATIVE";
|
|
43
45
|
readonly INVALID_PHRASE_PATTERN: "INVALID_PHRASE_PATTERN";
|
|
@@ -103,6 +105,8 @@ declare const CoreErrorCodes: {
|
|
|
103
105
|
readonly INTERNAL_MISSING_FUNCTION_BODY: "INTERNAL_MISSING_FUNCTION_BODY";
|
|
104
106
|
/** `MAP_INVOKE`/`REDUCE_INVOKE`'s anonymous-body-index lookup failing. Same class as `INTERNAL_MISSING_FUNCTION_BODY` above, for the `anonymousBodies` side-table instead of `userFunctionBodies`. */
|
|
105
107
|
readonly INTERNAL_MISSING_ANONYMOUS_BODY: "INTERNAL_MISSING_ANONYMOUS_BODY";
|
|
108
|
+
/** `CALL_BUILTIN` naming an index no builtin is registered at. It used to pop its arguments and push nothing, so the opcode after it read a neighbour's operand as its own; now an Error value on the stack, the shape `map`/`reduce` already used for the same fault. Bytecode-level, so unreachable from source without a compiler or snapshot fault. */
|
|
109
|
+
readonly UNKNOWN_BUILTIN_FUNCTION: "UNKNOWN_BUILTIN_FUNCTION";
|
|
106
110
|
/** An operand byte read past the end of the stream: the program ends in the middle of an instruction. */
|
|
107
111
|
readonly MALFORMED_BYTECODE_TRUNCATED: "MALFORMED_BYTECODE_TRUNCATED";
|
|
108
112
|
/** A constant-pool operand indexing a `numbers`/`strings` entry that does not exist, or that is not of the pool's type. */
|
|
@@ -164,6 +168,8 @@ declare const CoreErrorCodes: {
|
|
|
164
168
|
readonly UNKNOWN_SAVINGS_PERIOD: "UNKNOWN_SAVINGS_PERIOD";
|
|
165
169
|
/** Colon-separated numbers that are not a time any clock can show ("24:00", "9:60", "100:5"). Raised by the labeled-line fallback, which used to answer them with whatever stood after the colon. */
|
|
166
170
|
readonly INVALID_TIME_LITERAL: "INVALID_TIME_LITERAL";
|
|
171
|
+
/** A live-data form evaluated on an engine whose host switched the network off (`network.enabled: false`, see `constants/Configuration.ts`'s `NetworkConfig`). A recoverable Error value, raised by the VM for a currency conversion with no primed rate and for a plugin function that returned a promise, and by `createQueryResolver`'s plugin function when its preflight was skipped. Names the setting, so the reader knows it is policy rather than an outage. */
|
|
172
|
+
readonly NETWORK_DISABLED: "NETWORK_DISABLED";
|
|
167
173
|
/** `fromJSON()` handed an object that is not a snapshot at all, or whose serialised-shape version does not match this engine's reader. The versioning gate that refuses an incompatible snapshot clearly rather than restoring it wrongly. See `engine/EngineSnapshot.ts`'s `assertRestorable()`. */
|
|
168
174
|
readonly SNAPSHOT_VERSION_MISMATCH: "SNAPSHOT_VERSION_MISMATCH";
|
|
169
175
|
/** A snapshot with the right envelope but internally inconsistent contents (an unrecognised number sentinel, an unknown value tag). Distinct from a version mismatch: the format is right, the payload is not. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { T as Token } from './Token-CbP_OutD.js';
|
|
2
|
+
import { E as EngineError } from './EngineError-fR1K1rvx.js';
|
|
2
3
|
|
|
3
4
|
/** Trie node for multi-word phrase matching. */
|
|
4
5
|
interface PhraseNode {
|
|
@@ -101,6 +102,14 @@ interface ScanLineResult {
|
|
|
101
102
|
tokens: Token[];
|
|
102
103
|
/** Inline solve spans found in this line (empty if none). */
|
|
103
104
|
inlineSolves: InlineSolveSpan[];
|
|
105
|
+
/**
|
|
106
|
+
* The structured error the tokeniser raised on this line, if it raised one
|
|
107
|
+
* (an unterminated string literal is the one it can raise today). The line
|
|
108
|
+
* carries no tokens then, and the rest of the document is unaffected: a
|
|
109
|
+
* half-typed quote used to escape {@link ExpressionLexer.scanDocument} as
|
|
110
|
+
* a throw and take every other line's result down with it.
|
|
111
|
+
*/
|
|
112
|
+
error?: EngineError;
|
|
104
113
|
}
|
|
105
114
|
/**
|
|
106
115
|
* A token, as the lexer produces it.
|
|
@@ -208,16 +217,6 @@ interface LexerVocabulary {
|
|
|
208
217
|
*/
|
|
209
218
|
declare class ExpressionLexer {
|
|
210
219
|
private static readonly CHAR_CLASS;
|
|
211
|
-
/**
|
|
212
|
-
* Configured TokenLookup from TokenClassRegistry. When set, replaces
|
|
213
|
-
* the internal keyword map and unit set with registry-built equivalents.
|
|
214
|
-
* Enables data-driven keyword/unit registration across locale keywords,
|
|
215
|
-
* provider keywords, and plugins.
|
|
216
|
-
*
|
|
217
|
-
* Set at construction time via the constructor parameter. Plugin-registered
|
|
218
|
-
* keywords/units (via registerVocabulary()) are checked alongside
|
|
219
|
-
* the configuredLookup, neither source is bypassed.
|
|
220
|
-
*/ private configuredLookup;
|
|
221
220
|
private input;
|
|
222
221
|
private pos;
|
|
223
222
|
private len;
|
|
@@ -227,7 +226,16 @@ declare class ExpressionLexer {
|
|
|
227
226
|
private keywordMap;
|
|
228
227
|
private mergedKeywords;
|
|
229
228
|
private mergedUnits;
|
|
230
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Who registered each plugin keyword, unit and two-character operator, in
|
|
231
|
+
* registration order. Two packages may claim the same word (the
|
|
232
|
+
* compatibility index warns, and the last registered wins, as before); the
|
|
233
|
+
* owner lists make unregistration exact, so removing one package hands the
|
|
234
|
+
* word back to the other rather than deleting it for both.
|
|
235
|
+
*/
|
|
236
|
+
private pluginKeywordOwners;
|
|
237
|
+
private pluginUnitOwners;
|
|
238
|
+
private pluginOperatorOwners;
|
|
231
239
|
private pluginOperators;
|
|
232
240
|
private pluginUnits;
|
|
233
241
|
private hasPluginOps;
|
|
@@ -235,7 +243,7 @@ declare class ExpressionLexer {
|
|
|
235
243
|
private locale;
|
|
236
244
|
/**
|
|
237
245
|
* Inline solve spans collected during the most recent tokenization pass.
|
|
238
|
-
* Populated by
|
|
246
|
+
* Populated by tokenizeInto() and consumed by scanDocument().
|
|
239
247
|
*/
|
|
240
248
|
_inlineSolveSpans: InlineSolveSpan[];
|
|
241
249
|
private pluginRawLinePatterns;
|
|
@@ -260,7 +268,14 @@ declare class ExpressionLexer {
|
|
|
260
268
|
private matchRawLine;
|
|
261
269
|
/** Rebuild merged keyword and unit collections after plugin registration. */
|
|
262
270
|
private rebuildMergedUnits;
|
|
263
|
-
|
|
271
|
+
/**
|
|
272
|
+
* @param localeCode - Locale whose keyword table seeds the lexer.
|
|
273
|
+
* @param _lookup - Ignored. The lexer built its keyword, unit and phrase
|
|
274
|
+
* tables from the locale and the registered packages and never read the
|
|
275
|
+
* lookup it was handed; the parameter stays so existing callers compile.
|
|
276
|
+
* @deprecated Removed in 3.0.
|
|
277
|
+
*/
|
|
278
|
+
constructor(localeCode?: string, _lookup?: TokenLookup);
|
|
264
279
|
/**
|
|
265
280
|
* Register a plugin to extend the lexer with custom tokens.
|
|
266
281
|
*
|
|
@@ -276,6 +291,10 @@ declare class ExpressionLexer {
|
|
|
276
291
|
* that conflicts with a built-in one.
|
|
277
292
|
*/
|
|
278
293
|
registerVocabulary(plugin: LexerVocabulary): void;
|
|
294
|
+
/** Record `owner`'s claim on `key`. The newest claim is the one in force. */
|
|
295
|
+
private claim;
|
|
296
|
+
/** Drop `owner`'s claim on `key`, and return the claim now in force, if any is left. */
|
|
297
|
+
private release;
|
|
279
298
|
/**
|
|
280
299
|
* Unregister a plugin, removing its custom tokens from the lexer.
|
|
281
300
|
*
|
|
@@ -307,9 +326,9 @@ declare class ExpressionLexer {
|
|
|
307
326
|
* lines that classifyLine() marks as having inline solves.
|
|
308
327
|
*
|
|
309
328
|
* Tokenization is scoped to each line by temporarily restricting
|
|
310
|
-
* `this.len` to the line end position, so
|
|
311
|
-
*
|
|
312
|
-
*
|
|
329
|
+
* `this.len` to the line end position, so tokenizeInto() naturally
|
|
330
|
+
* stops at the line boundary. After tokenization, `this.len` is
|
|
331
|
+
* restored and `this.pos` advances past the newline.
|
|
313
332
|
*
|
|
314
333
|
* @param text The full document text (with newlines).
|
|
315
334
|
* @returns Array of ScanLineResult, one per line, in document order.
|
|
@@ -318,9 +337,7 @@ declare class ExpressionLexer {
|
|
|
318
337
|
/**
|
|
319
338
|
* Tokenize an expression string into an array of Tokens.
|
|
320
339
|
*
|
|
321
|
-
*
|
|
322
|
-
* yielded tokens via Array.from(). For memory-sensitive use cases, prefer
|
|
323
|
-
* iterating the lexer directly with for...of to avoid array allocation.
|
|
340
|
+
* Runs {@link tokenizeInto} into a fresh array.
|
|
324
341
|
*
|
|
325
342
|
* Optimizations:
|
|
326
343
|
* - CHAR_CLASS jump table (Uint8Array) → switch on small integers
|
|
@@ -332,20 +349,31 @@ declare class ExpressionLexer {
|
|
|
332
349
|
*/
|
|
333
350
|
tokenizeAll(): Token[];
|
|
334
351
|
/**
|
|
335
|
-
*
|
|
336
|
-
*
|
|
352
|
+
* Scan the current input and append every token to `out`.
|
|
353
|
+
*
|
|
354
|
+
* This is the scanner itself: {@link tokenizeAll} and the iterator are
|
|
355
|
+
* thin wrappers over it. It pushes into an array the caller owns rather
|
|
356
|
+
* than yielding, because a generator paid a resume per token and
|
|
357
|
+
* `Array.from` a second pass on top, and because a caller that catches a
|
|
358
|
+
* tokeniser fault (highlighting a line with an unterminated quote) still
|
|
359
|
+
* holds the tokens read before it.
|
|
337
360
|
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
361
|
+
* IMPORTANT: `this.len` is read ONCE on entry (const len = this.len).
|
|
362
|
+
* `scanDocument()` relies on this to scope a pass to a single line by
|
|
363
|
+
* restricting `this.len` to the line end before calling. Do not re-read
|
|
364
|
+
* `this.len` mid-loop without also updating `scanDocument()`.
|
|
342
365
|
*
|
|
343
|
-
*
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
*
|
|
348
|
-
*
|
|
366
|
+
* @param out - The array to append to. Left as it was if the input is empty.
|
|
367
|
+
*/
|
|
368
|
+
tokenizeInto(out: Token[]): void;
|
|
369
|
+
/**
|
|
370
|
+
* Iterate the tokens of the current input.
|
|
371
|
+
*
|
|
372
|
+
* Runs {@link tokenizeInto} first, so the tokens are the same as before,
|
|
373
|
+
* but a fault part way through the line is thrown from the first
|
|
374
|
+
* `next()` rather than at the token it occurred on. A caller that wants
|
|
375
|
+
* the tokens read before a fault calls {@link tokenizeInto} with its own
|
|
376
|
+
* array.
|
|
349
377
|
*/
|
|
350
378
|
[Symbol.iterator](): Generator<Token, void, undefined>;
|
|
351
379
|
/**
|
|
@@ -456,6 +484,27 @@ declare class ExpressionLexer {
|
|
|
456
484
|
* `#12345` (length 5), `#ff0000zz` (trailing word) do not.
|
|
457
485
|
*/
|
|
458
486
|
private matchHexColourEnd;
|
|
487
|
+
/**
|
|
488
|
+
* Whether an inline-solve opener (`s` + backtick) occurs in `[from, end)`.
|
|
489
|
+
*
|
|
490
|
+
* Bounded by hand rather than written as `input.indexOf("s\`", from)`. This
|
|
491
|
+
* used to be the `indexOf` form with an `idx < end` check afterwards, which
|
|
492
|
+
* is correct but not bounded: when {@link scanDocument} is classifying,
|
|
493
|
+
* `this.input` is the whole document, so a line with no marker scanned to
|
|
494
|
+
* the END OF THE DOCUMENT before the check could reject the hit. Every line
|
|
495
|
+
* paid for every line after it, and a whole-document parse was quadratic in
|
|
496
|
+
* line count (measured at 28.9 us per line at 10,000 lines against 10.1 us
|
|
497
|
+
* at 1,000, and two thirds of the parse's self time in a profile). A prose
|
|
498
|
+
* document with many candidate `s` characters was worse still. The
|
|
499
|
+
* wikilink close in {@link indexOfWithin} had the same shape.
|
|
500
|
+
*/
|
|
501
|
+
private hasInlineSolveWithin;
|
|
502
|
+
/**
|
|
503
|
+
* `input.indexOf(needle, from)` restricted to `[from, end)`, returning -1
|
|
504
|
+
* when the needle does not occur wholly before `end`. See
|
|
505
|
+
* {@link hasInlineSolveWithin} for why the plain `indexOf` was not enough.
|
|
506
|
+
*/
|
|
507
|
+
private indexOfWithin;
|
|
459
508
|
private classifyFromPositions;
|
|
460
509
|
/**
|
|
461
510
|
* Classify a single line of markdown text.
|
|
@@ -530,11 +579,11 @@ declare class Lexer {
|
|
|
530
579
|
private tokenIdx;
|
|
531
580
|
/**
|
|
532
581
|
* @param localeCode - Locale code (e.g., "en", "de"). Defaults to "en".
|
|
533
|
-
* @param
|
|
534
|
-
*
|
|
535
|
-
*
|
|
582
|
+
* @param _tokenLookup - Ignored. The lexer never read the lookup it was
|
|
583
|
+
* handed; the parameter stays so existing callers compile.
|
|
584
|
+
* @deprecated Removed in 3.0.
|
|
536
585
|
*/
|
|
537
|
-
constructor(localeCode?: string,
|
|
586
|
+
constructor(localeCode?: string, _tokenLookup?: TokenLookup);
|
|
538
587
|
reset(input: string, state?: LexerState): void;
|
|
539
588
|
/**
|
|
540
589
|
* Classify a single line of markdown text (Phase B).
|