@thomasfosterau/effect-parse 0.4.0 → 0.5.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.
Files changed (148) hide show
  1. package/README.md +40 -14
  2. package/dist/Ascii.d.ts +11 -4
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +7 -1
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts.map +1 -1
  7. package/dist/Binary.js +64 -35
  8. package/dist/Binary.js.map +1 -1
  9. package/dist/Bits.d.ts.map +1 -1
  10. package/dist/Bits.js +7 -4
  11. package/dist/Bits.js.map +1 -1
  12. package/dist/Compiler.d.ts +74 -2
  13. package/dist/Compiler.d.ts.map +1 -1
  14. package/dist/Compiler.js +78 -1
  15. package/dist/Compiler.js.map +1 -1
  16. package/dist/Expression.d.ts.map +1 -1
  17. package/dist/Expression.js +86 -74
  18. package/dist/Expression.js.map +1 -1
  19. package/dist/Input.d.ts +37 -4
  20. package/dist/Input.d.ts.map +1 -1
  21. package/dist/Input.js +42 -0
  22. package/dist/Input.js.map +1 -1
  23. package/dist/Nom.d.ts +5 -15
  24. package/dist/Nom.d.ts.map +1 -1
  25. package/dist/Nom.js.map +1 -1
  26. package/dist/ParseError.d.ts +2 -2
  27. package/dist/ParseError.d.ts.map +1 -1
  28. package/dist/ParseError.js +1 -1
  29. package/dist/ParseError.js.map +1 -1
  30. package/dist/Parser.d.ts +370 -28
  31. package/dist/Parser.d.ts.map +1 -1
  32. package/dist/Parser.js +390 -19
  33. package/dist/Parser.js.map +1 -1
  34. package/dist/ParserSchema.js +2 -2
  35. package/dist/ParserSchema.js.map +1 -1
  36. package/dist/ParserStream.d.ts +3 -2
  37. package/dist/ParserStream.d.ts.map +1 -1
  38. package/dist/ParserStream.js +15 -10
  39. package/dist/ParserStream.js.map +1 -1
  40. package/dist/Syntax.d.ts +23 -5
  41. package/dist/Syntax.d.ts.map +1 -1
  42. package/dist/Syntax.js.map +1 -1
  43. package/dist/Text.d.ts.map +1 -1
  44. package/dist/Text.js +9 -9
  45. package/dist/Text.js.map +1 -1
  46. package/dist/Token.d.ts +55 -10
  47. package/dist/Token.d.ts.map +1 -1
  48. package/dist/Token.js +59 -21
  49. package/dist/Token.js.map +1 -1
  50. package/dist/internal/compiler/closure.d.ts.map +1 -1
  51. package/dist/internal/compiler/closure.js +257 -97
  52. package/dist/internal/compiler/closure.js.map +1 -1
  53. package/dist/internal/compiler/entries.d.ts +3 -1
  54. package/dist/internal/compiler/entries.d.ts.map +1 -1
  55. package/dist/internal/compiler/entries.js +3 -2
  56. package/dist/internal/compiler/entries.js.map +1 -1
  57. package/dist/internal/compiler/first.d.ts.map +1 -1
  58. package/dist/internal/compiler/first.js +7 -1
  59. package/dist/internal/compiler/first.js.map +1 -1
  60. package/dist/internal/compiler/graph.d.ts +17 -0
  61. package/dist/internal/compiler/graph.d.ts.map +1 -0
  62. package/dist/internal/compiler/graph.js +56 -0
  63. package/dist/internal/compiler/graph.js.map +1 -0
  64. package/dist/internal/compiler/jit.d.ts +5 -0
  65. package/dist/internal/compiler/jit.d.ts.map +1 -1
  66. package/dist/internal/compiler/jit.js +185 -96
  67. package/dist/internal/compiler/jit.js.map +1 -1
  68. package/dist/internal/compiler/registry.d.ts +10 -2
  69. package/dist/internal/compiler/registry.d.ts.map +1 -1
  70. package/dist/internal/compiler/registry.js.map +1 -1
  71. package/dist/internal/compiler/search.d.ts +32 -0
  72. package/dist/internal/compiler/search.d.ts.map +1 -0
  73. package/dist/internal/compiler/search.js +68 -0
  74. package/dist/internal/compiler/search.js.map +1 -0
  75. package/dist/internal/compiler/sets.d.ts.map +1 -1
  76. package/dist/internal/compiler/sets.js +1 -1
  77. package/dist/internal/compiler/sets.js.map +1 -1
  78. package/dist/internal/compiler/wasm.d.ts +8 -0
  79. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  80. package/dist/internal/compiler/wasm.js +2769 -0
  81. package/dist/internal/compiler/wasm.js.map +1 -0
  82. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  83. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  84. package/dist/internal/compiler/wasmAsm.js +427 -0
  85. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  86. package/dist/internal/entries.d.ts +3 -1
  87. package/dist/internal/entries.d.ts.map +1 -1
  88. package/dist/internal/entries.js +1 -2
  89. package/dist/internal/entries.js.map +1 -1
  90. package/dist/internal/node.d.ts +58 -2
  91. package/dist/internal/node.d.ts.map +1 -1
  92. package/dist/internal/node.js +13 -3
  93. package/dist/internal/node.js.map +1 -1
  94. package/dist/internal/numbers.d.ts.map +1 -1
  95. package/dist/internal/numbers.js.map +1 -1
  96. package/dist/internal/run.d.ts +12 -0
  97. package/dist/internal/run.d.ts.map +1 -1
  98. package/dist/internal/run.js +51 -11
  99. package/dist/internal/run.js.map +1 -1
  100. package/dist/internal/stack.d.ts +6 -0
  101. package/dist/internal/stack.d.ts.map +1 -1
  102. package/dist/internal/stack.js +8 -2
  103. package/dist/internal/stack.js.map +1 -1
  104. package/dist/internal/syntax.d.ts +4 -4
  105. package/dist/internal/syntax.d.ts.map +1 -1
  106. package/dist/internal/syntax.js +18 -10
  107. package/dist/internal/syntax.js.map +1 -1
  108. package/dist/internal/text.d.ts +32 -0
  109. package/dist/internal/text.d.ts.map +1 -1
  110. package/dist/internal/text.js +68 -14
  111. package/dist/internal/text.js.map +1 -1
  112. package/dist/internal/tokens.d.ts +13 -0
  113. package/dist/internal/tokens.d.ts.map +1 -1
  114. package/dist/internal/tokens.js +59 -39
  115. package/dist/internal/tokens.js.map +1 -1
  116. package/package.json +5 -4
  117. package/src/Ascii.ts +42 -9
  118. package/src/Binary.ts +151 -71
  119. package/src/Bits.ts +12 -6
  120. package/src/Compiler.ts +101 -10
  121. package/src/Expression.ts +94 -81
  122. package/src/Input.ts +65 -6
  123. package/src/Nom.ts +15 -5
  124. package/src/ParseError.ts +9 -4
  125. package/src/Parser.ts +682 -129
  126. package/src/ParserSchema.ts +1 -1
  127. package/src/ParserStream.ts +40 -19
  128. package/src/Syntax.ts +60 -36
  129. package/src/Text.ts +16 -15
  130. package/src/Token.ts +138 -29
  131. package/src/internal/compiler/closure.ts +362 -135
  132. package/src/internal/compiler/entries.ts +4 -2
  133. package/src/internal/compiler/first.ts +10 -0
  134. package/src/internal/compiler/graph.ts +68 -0
  135. package/src/internal/compiler/jit.ts +284 -154
  136. package/src/internal/compiler/registry.ts +10 -2
  137. package/src/internal/compiler/search.ts +68 -0
  138. package/src/internal/compiler/sets.ts +7 -3
  139. package/src/internal/compiler/wasm.ts +3203 -0
  140. package/src/internal/compiler/wasmAsm.ts +484 -0
  141. package/src/internal/entries.ts +10 -4
  142. package/src/internal/node.ts +54 -4
  143. package/src/internal/numbers.ts +3 -1
  144. package/src/internal/run.ts +71 -12
  145. package/src/internal/stack.ts +8 -1
  146. package/src/internal/syntax.ts +39 -18
  147. package/src/internal/text.ts +97 -9
  148. package/src/internal/tokens.ts +96 -14
@@ -0,0 +1,3203 @@
1
+ /**
2
+ * The WebAssembly compiler: scans the input in WebAssembly, then builds the
3
+ * value in JavaScript (see `docs/DESIGN.md`, "The WebAssembly compiler").
4
+ *
5
+ * A parse runs in two passes over one description of the grammar:
6
+ *
7
+ * 1. **Scan.** Generated WebAssembly matches the input, which is copied into
8
+ * linear memory, with the same control flow as the JIT (labelled blocks
9
+ * for failure, a function per rule, `state.mode` as a global). Where a
10
+ * value is used, it appends what the value needs to a _trace_ in linear
11
+ * memory: slice bounds, which branch or case matched, repetition counts,
12
+ * floats. When it backtracks it rewinds the trace with the cursor, so the
13
+ * trace only describes the successful parse.
14
+ * 2. **Build.** A JavaScript function generated for the grammar reads the
15
+ * trace in order and builds the value, with the same callbacks and the
16
+ * same object shapes as the JIT, without scanning anything.
17
+ *
18
+ * Parsers with no native form are _islands_: the scan calls back into
19
+ * JavaScript, which runs their JIT-compiled step on the shared state, and the
20
+ * trace records the index of their value. Streaming (resumable) builds,
21
+ * array input, and environments without `WebAssembly` or `new Function` use
22
+ * the JIT.
23
+ */
24
+ import * as Arr from "effect/Array"
25
+ import * as Predicate from "effect/Predicate"
26
+ import * as Option from "effect/Option"
27
+ import * as Result from "effect/Result"
28
+ import type { Encoding } from "../../Encoding.js"
29
+ import { Fail, State } from "../../Input.js"
30
+ import type { Node, NodeOf, ParserLike } from "../node.js"
31
+ import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js"
32
+ import { nodeOf } from "../node.js"
33
+ import { floatTexts, toFloat } from "../numbers.js"
34
+ import { charOf, fastLimit, latin1, string, utf16be, utf16le, utf8 } from "../text.js"
35
+ import type { Fallback, Step } from "./closure.js"
36
+ import { backend as jitBackend, jitSupported } from "./jit.js"
37
+ import { buildsEntries, isDiagnostic, pairOf } from "./entries.js"
38
+ import { countRefs, isLeaf, isLetter, isSmall, lowerAscii, selectorOf } from "./graph.js"
39
+ import type { Backend, Compiled } from "./registry.js"
40
+ import type { CharSet } from "./sets.js"
41
+ import { kindOfEncoding, TableSize, textSet, tokenSet } from "./sets.js"
42
+ import type { Label } from "./wasmAsm.js"
43
+ import { Body, encodeModule, F64, I32, Op, Simd } from "./wasmAsm.js"
44
+
45
+ // -----------------------------------------------------------------------------
46
+ // Layout
47
+ // -----------------------------------------------------------------------------
48
+
49
+ /** The encoding of a token node: `undefined` for `Token` / `Ascii`, as in `internal/node.ts`. */
50
+ type Enc = NodeOf<"Any">["encoding"]
51
+
52
+ /** Globals. `F` holds the float of the latest scan, the others are `i32`. */
53
+ const G = { mode: 0, end: 1, len: 2, tp: 3, tlim: 4, nv: 5, w: 6, f: 7 } as const
54
+
55
+ /** Imported functions. */
56
+ const Imp = { island: 0, grow: 1, test: 2, float: 3, check: 4 } as const
57
+ const importCount = 5
58
+
59
+ /**
60
+ * Function types: rules and decoders `(i32) -> i32`, imports and helpers of
61
+ * two arguments `(i32, i32) -> i32`, `grow` `() -> i32`, `float` `(i32, i32) -> f64`,
62
+ * `start` `(i32 × 5) -> i32`.
63
+ */
64
+ const Ty = { rule: 0, pair: 1, grow: 2, toF64: 3, start: 4 } as const
65
+
66
+ /** Linear memory: membership tables from 0, powers of ten, then the input, then the trace. */
67
+ const TablesEnd = 61440
68
+ const Pow10 = 61440
69
+ /** The string cache (see `intern`): `StringSlots` slots of a version, a length and `MaxInterned` units. */
70
+ const Strings = 65536
71
+ const StringSlots = 1024
72
+ const MaxInterned = 16
73
+ const SlotBytes = 8 + 2 * MaxInterned
74
+ const Input = 131072
75
+ const PageSize = 65536
76
+ /** Headroom kept free above the trace limit: more than the writes between two checks. */
77
+ const Slack = 65536
78
+ /** Trace words a function may write past the last check that dominates them (see `put`). */
79
+ const MaxWrites = 4096
80
+
81
+ /** Bytes left after the cursor beyond which a driver's parse of the same buffer runs the JIT (see `run`). */
82
+ const LargeRest = 4096
83
+
84
+ /** The most literals a compiled `takeUntilAny` checks at each candidate; more are searched in JavaScript. */
85
+ const MaxUntil = 64
86
+
87
+ /** The most leading bytes of each literal a SIMD search tests at once. */
88
+ const MaxPositions = 3
89
+
90
+ /** Teddy's buckets: one bit each in a nibble table's bytes. */
91
+ const Buckets = 8
92
+
93
+ /** For each low nibble, the high nibbles `from` to `from + 7` of the bytes `set` accepts, as bits from 0. */
94
+ /** Whether `set` accepts any byte. */
95
+ const accepts = (set: Membership): boolean =>
96
+ Array.from({ length: 256 }, (_, c) => (set.cs.table[c] === 1) !== set.negate).some((ok) => ok)
97
+
98
+ const nibbleTable = (set: Membership, from: number): Array<number> => {
99
+ const table = new Array<number>(16).fill(0)
100
+ for (let c = from << 4; c < (from + 8) << 4; c++) {
101
+ if ((set.cs.table[c] === 1) !== set.negate) table[c & 15] = (table[c & 15] ?? 0) | (1 << ((c >> 4) - from))
102
+ }
103
+ return table
104
+ }
105
+
106
+ /** The most rejected bytes a vectorised `takeWhile` compares with one by one. */
107
+ const MaxStops = 3
108
+
109
+ /** For a high nibble `h` from 8 up, bit `h - 8`; none below. */
110
+ const UpperNibbleBits: ReadonlyArray<number> = [0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 8, 16, 32, 64, 128]
111
+
112
+ /** For a high nibble `h`, bit `h` below 8 (ASCII), none above. */
113
+ const HighNibbleBits: ReadonlyArray<number> = [1, 2, 4, 8, 16, 32, 64, 128, 0, 0, 0, 0, 0, 0, 0, 0]
114
+
115
+ const Zeros: ReadonlyArray<number> = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
116
+ const LowNibbles: ReadonlyArray<number> = [15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15]
117
+
118
+ /**
119
+ * Teddy's tables for the first `m` bytes of `literals`: for each position,
120
+ * the low- and high-nibble tables, literal `i` in bucket `i % 8`.
121
+ */
122
+ const teddyTables = (literals: ReadonlyArray<Uint8Array>, m: number): Array<[Array<number>, Array<number>]> =>
123
+ Array.from({ length: m }, (_, j) => {
124
+ const lo = new Array<number>(16).fill(0)
125
+ const hi = new Array<number>(16).fill(0)
126
+ literals.forEach((lit, i) => {
127
+ const byte = lit[j] ?? 0
128
+ const bit = 1 << (i % Buckets)
129
+ lo[byte & 15] = (lo[byte & 15] ?? 0) | bit
130
+ hi[byte >> 4] = (hi[byte >> 4] ?? 0) | bit
131
+ })
132
+ return [lo, hi]
133
+ })
134
+
135
+ /**
136
+ * The bytes of literal units (`width` bytes each, little-endian), and the
137
+ * 0x20 to OR into each byte of a caseless letter, which `bytes` holds in
138
+ * lower case.
139
+ */
140
+ const unitBytes = (codes: ReadonlyArray<number>, caseless: boolean, width: number) => {
141
+ const bytes: Array<number> = []
142
+ const fold: Array<number> = []
143
+ for (const code of codes) {
144
+ const letter = caseless && isLetter(code)
145
+ const unit = letter ? lowerAscii(code) : code
146
+ for (let k = 0; k < width; k++) {
147
+ bytes.push((unit >> (8 * k)) & 0xff)
148
+ fold.push(letter && k === 0 ? 0x20 : 0)
149
+ }
150
+ }
151
+ return { bytes, fold }
152
+ }
153
+
154
+ /** The widest load, of 16 (with SIMD), 8, 4, 2 or 1 bytes, that `left` bytes fill. */
155
+ const chunkSize = (left: number): number => {
156
+ if (left >= 16 && simd()) return 16
157
+ return [8, 4, 2].find((n) => left >= n) ?? 1
158
+ }
159
+
160
+ /**
161
+ * With an address on the stack, pushes whether the 1, 2, 4, 8 or 16 bytes
162
+ * at it (plus `offset`), ORed with `fold`, differ from `bytes`.
163
+ */
164
+ const compareChunk = (b: Body, bytes: ReadonlyArray<number>, fold: ReadonlyArray<number>, offset: number): void => {
165
+ const n = bytes.length
166
+ const folds = fold.some((f) => f !== 0)
167
+ if (n === 16) {
168
+ b.simd(Simd.v128Load, 0, offset)
169
+ if (folds) b.v128(fold).simd(Simd.v128Or)
170
+ b.v128(bytes).simd(Simd.i8x16Eq).simd(Simd.i8x16AllTrue).op(Op.i32Eqz)
171
+ } else if (n === 8) {
172
+ b.mem(Op.i64Load, 0, offset)
173
+ if (folds) b.i64(littleEndian(fold, 0, 8)).op(Op.i64Or)
174
+ b.i64(littleEndian(bytes, 0, 8)).op(Op.i64Ne)
175
+ } else {
176
+ b.mem(n === 4 ? Op.i32Load : n === 2 ? Op.i32Load16U : Op.i32Load8U, 0, offset)
177
+ if (folds) b.i32(Number(littleEndian(fold, 0, n))).op(Op.i32Or)
178
+ b.i32(Number(BigInt.asIntN(32, littleEndian(bytes, 0, n)))).op(Op.i32Ne)
179
+ }
180
+ }
181
+
182
+ /** `n` bytes from `bytes[i]` as a little-endian integer (as a load reads them). */
183
+ const littleEndian = (bytes: ReadonlyArray<number>, i: number, n: number): bigint => {
184
+ let v = 0n
185
+ for (let k = n - 1; k >= 0; k--) v = (v << 8n) | BigInt(bytes[i + k] ?? 0)
186
+ return BigInt.asIntN(64, v)
187
+ }
188
+
189
+ /** Values of `G.mode`. */
190
+ const Backtrack = 0
191
+ const Cut = 1
192
+
193
+ /** Whether the scan decodes an encoding itself. */
194
+ const isBuiltin = (enc: Encoding<any>): boolean =>
195
+ enc === string || enc === utf8 || enc === utf16le || enc === utf16be || enc === latin1
196
+
197
+ // -----------------------------------------------------------------------------
198
+ // Code units
199
+ // -----------------------------------------------------------------------------
200
+
201
+ /**
202
+ * How the scan reads one code point: `fast` units are whole code points
203
+ * read inline, `width` input units long; the others are decoded by the
204
+ * helper function `decode`, which leaves the width in `G.w`.
205
+ */
206
+ interface Reader {
207
+ readonly width: number
208
+ readonly fast: number
209
+ /** Pushes a test that a whole unit starts at local `pos`. */
210
+ readonly guard: (b: Body, pos: number) => void
211
+ /** Pushes the unit at local `pos`. */
212
+ readonly unit: (b: Body, pos: number) => void
213
+ readonly decode: number
214
+ }
215
+
216
+ /** A token set being tested: the set, whether it is negated, and the local holding the code point. */
217
+ interface Membership {
218
+ readonly cs: CharSet
219
+ readonly negate: boolean
220
+ readonly c: number
221
+ }
222
+
223
+ /** The locals of an `escaped` (see `WasmCompiler.escaped`). */
224
+ interface EscapedLocals {
225
+ readonly r: Option.Option<Reader>
226
+ /** Whether its value is built from pieces: segments and escapes. */
227
+ readonly pieces: boolean
228
+ /** Whether a string of one segment is interned. */
229
+ readonly internsWhole: boolean
230
+ readonly start: number
231
+ readonly seg: number
232
+ readonly ok: number
233
+ readonly ts: number
234
+ readonly count: number
235
+ readonly pieceStart: number
236
+ readonly slot: number
237
+ /** The builder variable of a piece. */
238
+ readonly x: string
239
+ }
240
+
241
+ /**
242
+ * Where code is generated: the parser, the builder variable of its value
243
+ * (`""` when the value is unused), the function body, the label of its
244
+ * failure, the builder function's variables and the depth of inlining.
245
+ */
246
+ interface Site {
247
+ readonly p: ParserLike
248
+ readonly out: string
249
+ readonly b: Body
250
+ readonly fail: Label
251
+ readonly bf: Vars
252
+ readonly d: number
253
+ }
254
+
255
+ /** Pushes the code point of the surrogate pair in locals `hi` and `lo`. */
256
+ const pair = (b: Body, hi: number, lo: number): void => {
257
+ b.get(hi).i32(0xd800).op(Op.i32Sub).i32(10).op(Op.i32Shl)
258
+ b.get(lo).i32(0xdc00).op(Op.i32Sub).op(Op.i32Add).i32(0x10000).op(Op.i32Add)
259
+ }
260
+
261
+ /** Whether every key of a record of cases is one unit below `size` (a byte, as a numeric key, for `bytes`). */
262
+ const closedKeys = (keys: ReadonlyArray<PropertyKey>, size: number, bytes: boolean): boolean =>
263
+ keys.every(
264
+ (k) =>
265
+ Predicate.isString(k) && (bytes ? /^\d+$/.test(k) && Number(k) < size : k.length === 1 && k.charCodeAt(0) < size)
266
+ )
267
+
268
+ /** Per builder function: its variables. */
269
+ class Vars {
270
+ readonly names: Array<string> = []
271
+ }
272
+
273
+ /** A repetition's element: generates it, and returns the builder code that adds it to the result. */
274
+ type Element = (b: Body, fail: Label, bf: Vars) => string
275
+
276
+ const modeOf = (mode: string): number => (mode === "Cut" ? Cut : mode === "Backtrack" ? Backtrack : 2)
277
+
278
+ /** The inclusive runs of accepted code points of a set, when its ranges are known. */
279
+ const acceptedRuns = (cs: CharSet, negate: boolean, max: number): Option.Option<Array<[number, number]>> => {
280
+ if (cs.ranges === undefined) return Option.none()
281
+ const runs: Array<[number, number]> = []
282
+ for (const [a, b] of cs.ranges) if (a <= max) runs.push([a, Math.min(b, max)])
283
+ if (!negate) return Option.some(runs)
284
+ const out: Array<[number, number]> = []
285
+ let next = 0
286
+ for (const [a, b] of runs) {
287
+ if (a > next) out.push([next, a - 1])
288
+ next = b + 1
289
+ }
290
+ if (next <= max) out.push([next, max])
291
+ return Option.some(out)
292
+ }
293
+
294
+ let simdChecked = false
295
+ let simdSupported = false
296
+
297
+ /**
298
+ * Whether WebAssembly SIMD is available, for the widener of strings (checked
299
+ * once): a module of one `v128` load validates.
300
+ */
301
+ const simd = (): boolean => {
302
+ if (!simdChecked) {
303
+ simdChecked = true
304
+ simdSupported = validates((probe) => probe.get(0).simd(Simd.v128Load, 0, 0).simd(Simd.i8x16Bitmask))
305
+ }
306
+ return simdSupported
307
+ }
308
+
309
+ let relaxedChecked = false
310
+ let relaxedSupported = false
311
+
312
+ /**
313
+ * Whether relaxed SIMD is available (checked once): its `relaxed_swizzle`
314
+ * is `swizzle` without the fix-up for indices from 16 up, which the nibble
315
+ * lookups never pass.
316
+ */
317
+ const relaxed = (): boolean => {
318
+ if (!relaxedChecked) {
319
+ relaxedChecked = true
320
+ relaxedSupported =
321
+ simd() &&
322
+ validates((probe) =>
323
+ probe
324
+ .get(0)
325
+ .simd(Simd.v128Load, 0, 0)
326
+ .get(0)
327
+ .simd(Simd.v128Load, 0, 0)
328
+ .simd(Simd.i8x16RelaxedSwizzle)
329
+ .simd(Simd.i8x16Bitmask)
330
+ )
331
+ }
332
+ return relaxedSupported
333
+ }
334
+
335
+ /** Pushes a swizzle of a table by nibbles (indices below 16): relaxed where it validates. */
336
+ const nibbleSwizzle = (b: Body): void => {
337
+ b.simd(relaxed() ? Simd.i8x16RelaxedSwizzle : Simd.i8x16Swizzle)
338
+ }
339
+
340
+ /** Whether a module of one function `(i32) -> i32` with the body `write` writes validates. */
341
+ const validates = (write: (probe: Body) => void): boolean => {
342
+ const probe = new Body(1)
343
+ write(probe)
344
+ const bytes = encodeModule({
345
+ types: [{ params: [I32], results: [I32] }],
346
+ imports: [],
347
+ memoryPages: 1,
348
+ functions: [[0, probe]],
349
+ globals: [],
350
+ exports: [],
351
+ data: []
352
+ })
353
+ return WebAssembly.validate(bytes)
354
+ }
355
+
356
+ /** Whether a builder is available: it is generated with `new Function`. */
357
+ const supported = (): boolean => Predicate.isObject(globalThis.WebAssembly) && jitSupported()
358
+
359
+ // -----------------------------------------------------------------------------
360
+ // The compiler
361
+ // -----------------------------------------------------------------------------
362
+
363
+ class WasmCompiler {
364
+ readonly functions: Array<[number, Body]> = []
365
+ readonly builders: Array<string> = []
366
+ readonly constants: Array<unknown> = []
367
+ readonly islands: Array<{ readonly parser: ParserLike; readonly used: boolean }> = []
368
+ readonly fallbacks: Array<Fallback> = []
369
+ readonly tables: Array<Uint8Array> = []
370
+ readonly tests: Array<(c: number) => number> = []
371
+ // Keyed by identity: parsers and encodings are compared as objects, never structurally
372
+ private readonly rules = [new WeakMap<ParserLike, number>(), new WeakMap<ParserLike, number>()]
373
+ private readonly pending: Array<readonly [ParserLike, boolean, number]> = []
374
+ private refs = new WeakMap<ParserLike, number>()
375
+ private readonly decoders = new WeakMap<Encoding<any>, number>()
376
+ private readonly floatScanners = new WeakMap<Encoding<any>, number>()
377
+ /** The float scanner of `Token` / `Ascii` text, which has no encoding. */
378
+ private tokenFloatScanner = -1
379
+ private next = 0
380
+
381
+ constructor(readonly kind: 0 | 1) {}
382
+
383
+ /** Counts the distinct parents of each parser. */
384
+ countRefs(root: ParserLike): void {
385
+ this.refs = countRefs(root)
386
+ }
387
+
388
+ /** A constant of the builder, `C[i]`; the same value (by `Object.is`) is stored once. */
389
+ const<T>(value: T): string {
390
+ let i = this.constants.findIndex((c) => Object.is(c, value))
391
+ if (i < 0) i = this.constants.push(value) - 1
392
+ return `C[${i}]`
393
+ }
394
+
395
+ /** A JavaScript literal for simple values, otherwise a constant. */
396
+ lit<T>(value: T): string {
397
+ if (value === undefined) return "void 0"
398
+ if (value === null || Predicate.isBoolean(value)) return String(value)
399
+ if (Predicate.isNumber(value) && Number.isFinite(value) && !Object.is(value, -0)) return String(value)
400
+ return this.const(value)
401
+ }
402
+
403
+ v(bf: Vars): string {
404
+ const name = `v${this.next++}`
405
+ bf.names.push(name)
406
+ return name
407
+ }
408
+
409
+ /** Reserves a function index; its body is filled in later. */
410
+ private reserve(type: number, params = 1): [number, Body] {
411
+ const body = new Body(params)
412
+ this.functions.push([type, body])
413
+ return [importCount + this.functions.length - 1, body]
414
+ }
415
+
416
+ /** The function of a rule, generating it later if new. With `used`, it has a builder `b<index>`. */
417
+ rule(p: ParserLike, used: boolean): number {
418
+ const rules = this.rules[used ? 1 : 0]
419
+ const known = rules.get(p)
420
+ if (known !== undefined) return known
421
+ const [index] = this.reserve(Ty.rule)
422
+ rules.set(p, index)
423
+ this.pending.push([p, used, index])
424
+ return index
425
+ }
426
+
427
+ /** Generates the pending rules (and the rules they use). */
428
+ flush(): void {
429
+ for (let r = this.pending.shift(); r !== undefined; r = this.pending.shift()) {
430
+ const [p, used, index] = r
431
+ const body = this.functions[index - importCount][1]
432
+ const bf = new Vars()
433
+ const out = used ? this.v(bf) : ""
434
+ // The trace pointer lives in a local, written back before calls and on success
435
+ body.tp = body.local()
436
+ body.gget(G.tp).set(body.tp)
437
+ // So is the end of the input, which never changes during a parse
438
+ body.endLocal = body.local()
439
+ body.gget(G.end).set(body.endLocal)
440
+ this.check(body)
441
+ body.writes = 0
442
+ let built = ""
443
+ body.block((fail) => {
444
+ built = this.inline(p, out, body, fail, bf, 0)
445
+ body.get(body.tp).gset(G.tp)
446
+ body.get(0).op(Op.return)
447
+ })
448
+ body.i32(-1)
449
+ if (used) {
450
+ this.builders.push(
451
+ `function b${index}(i){${Arr.isArrayNonEmpty(bf.names) ? `let ${bf.names.join(",")};` : ""}${built}N=i;return ${out}}`
452
+ )
453
+ }
454
+ }
455
+ }
456
+
457
+ // ---------------------------------------------------------------------------
458
+ // Helpers for generated code
459
+ // ---------------------------------------------------------------------------
460
+
461
+ /** Fails with a mode. */
462
+ private failWith(b: Body, fail: Label, mode: number): void {
463
+ b.i32(mode).gset(G.mode).br(fail)
464
+ }
465
+
466
+ /** Fails with `Backtrack` when the `i32` at the top of the stack is true. */
467
+ private failIf(b: Body, fail: Label): void {
468
+ b.if(() => this.failWith(b, fail, Backtrack))
469
+ }
470
+
471
+ /** Propagates a failure unless its mode is `Backtrack`. */
472
+ private unlessBacktrack(b: Body, fail: Label): void {
473
+ b.gget(G.mode).brIf(fail)
474
+ }
475
+
476
+ /** Pushes the input unit at local `pos` plus `delta` units. */
477
+ load(b: Body, pos: number, delta = 0): void {
478
+ if (this.kind === 0)
479
+ b.get(pos)
480
+ .i32(1)
481
+ .op(Op.i32Shl)
482
+ .mem(Op.i32Load16U, 1, Input + 2 * delta)
483
+ else b.get(pos).mem(Op.i32Load8U, 0, Input + delta)
484
+ }
485
+
486
+ /** Pushes the end of the input: from a local in rule functions, from the global in helpers. */
487
+ private end(b: Body): Body {
488
+ return b.endLocal >= 0 ? b.get(b.endLocal) : b.gget(G.end)
489
+ }
490
+
491
+ /** Pushes `p >= E`. */
492
+ private atEnd(b: Body, pos: number): void {
493
+ this.end(b.get(pos)).op(Op.i32GeU)
494
+ }
495
+
496
+ /** `p += n`. */
497
+ private advance(b: Body, n: number): void {
498
+ b.get(0).i32(n).op(Op.i32Add).set(0)
499
+ }
500
+
501
+ /** Saves the cursor and the trace pointer. */
502
+ private save(b: Body): [number, number] {
503
+ const p = b.local()
504
+ const t = b.local()
505
+ b.get(0).set(p).get(b.tp).set(t)
506
+ return [p, t]
507
+ }
508
+
509
+ /** Restores a saved cursor and trace pointer. */
510
+ private restore(b: Body, [p, t]: [number, number]): void {
511
+ b.get(p).set(0).get(t).set(b.tp)
512
+ }
513
+
514
+ /** Grows the trace if it is past its limit. */
515
+ private check(b: Body): void {
516
+ b.get(b.tp).gget(G.tlim).op(Op.i32GtU)
517
+ b.if(() => b.call(Imp.grow).gset(G.tlim))
518
+ }
519
+
520
+ /**
521
+ * Appends words to the trace. Every function and loop iteration starts
522
+ * with a check, and the limit keeps `Slack` bytes free, so a check is
523
+ * only written here once a function wrote `MaxWrites` words since one.
524
+ */
525
+ put(b: Body, words: ReadonlyArray<() => void>): void {
526
+ b.writes += words.length
527
+ if (b.writes > MaxWrites) this.check(b)
528
+ words.forEach((push, k) => {
529
+ b.get(b.tp)
530
+ push()
531
+ b.mem(Op.i32Store, 2, 4 * k)
532
+ })
533
+ b.get(b.tp)
534
+ .i32(4 * words.length)
535
+ .op(Op.i32Add)
536
+ .set(b.tp)
537
+ }
538
+
539
+ /** Reserves a trace word, returning a local holding its address. */
540
+ private slot(b: Body): number {
541
+ const s = b.local()
542
+ b.get(b.tp).set(s)
543
+ this.put(b, [() => b.i32(0)])
544
+ return s
545
+ }
546
+
547
+ /** Appends the `f64` in `G.f` to the trace, aligned to 8 bytes. */
548
+ private putF64(b: Body): void {
549
+ b.writes += 4
550
+ if (b.writes > MaxWrites) this.check(b)
551
+ b.get(b.tp).i32(7).op(Op.i32Add).i32(-8).op(Op.i32And).set(b.tp)
552
+ b.get(b.tp).gget(G.f).mem(Op.f64Store, 3, 0)
553
+ b.get(b.tp).i32(8).op(Op.i32Add).set(b.tp)
554
+ }
555
+
556
+ /** A loop with a trace check at the start of each iteration. */
557
+ private loop(b: Body, body: (again: Label) => void): void {
558
+ const before = b.writes
559
+ b.loop((again) => {
560
+ this.check(b)
561
+ b.writes = 0
562
+ body(again)
563
+ })
564
+ b.writes += before
565
+ }
566
+
567
+ /** Builder code reading the next trace word. */
568
+ private static word = "T[i++]"
569
+
570
+ // ---------------------------------------------------------------------------
571
+ // Islands
572
+ // ---------------------------------------------------------------------------
573
+
574
+ /**
575
+ * Runs `p`'s JIT-compiled step on the shared state. With `out`, its value
576
+ * is kept by JavaScript and the trace records its index.
577
+ */
578
+ island(p: ParserLike, out: string, b: Body, fail: Label, reason: string): string {
579
+ const id = this.islands.length
580
+ this.islands.push({ parser: p, used: out !== "" })
581
+ this.fallbacks.push({ name: p.name, tag: nodeOf(p)._tag, reason })
582
+ const r = b.local()
583
+ b.i32(id).get(0).call(Imp.island).tee(r).i32(0).op(Op.i32LtS)
584
+ // -1: Backtrack, -2: Cut
585
+ b.if(() => {
586
+ b.i32(-1).get(r).op(Op.i32Sub).gset(G.mode).br(fail)
587
+ })
588
+ b.get(r).set(0)
589
+ if (out === "") return ""
590
+ this.put(b, [() => b.gget(G.nv)])
591
+ b.gget(G.nv).i32(1).op(Op.i32Add).gset(G.nv)
592
+ return `${out}=V[${WasmCompiler.word}];`
593
+ }
594
+
595
+ /** Builder functions of the value islands, `w<id>`, and what they apply to the value. */
596
+ readonly valueFns: Array<string> = []
597
+ readonly checks: Array<(value: Value) => Value> = []
598
+
599
+ /**
600
+ * A callback that needs `self`'s value while scanning (`filter`,
601
+ * `tryMap`, `filterMap`, `tap`): `self` is scanned here, then JavaScript
602
+ * builds its value from its trace (with a builder function of its own) and
603
+ * applies `apply`, which returns the result or `Fail` (a failure with
604
+ * `Backtrack`). The trace of `self` is then replaced by the index of the
605
+ * result.
606
+ */
607
+ private valueIsland(
608
+ self: ParserLike,
609
+ out: string,
610
+ b: Body,
611
+ fail: Label,
612
+ d: number,
613
+ apply: (value: Value) => Value
614
+ ): string {
615
+ const id = this.valueFns.length
616
+ this.valueFns.push("")
617
+ this.checks.push(apply)
618
+ const ts = b.local()
619
+ b.get(b.tp).set(ts)
620
+ const vars = new Vars()
621
+ const x = this.v(vars)
622
+ const code = this.gen(self, x, b, fail, vars, d)
623
+ this.valueFns[id] = `function w${id}(i){let ${vars.names.join(",")};${code}return ${x}}`
624
+ b.i32(id).get(ts).call(Imp.check).op(Op.i32Eqz)
625
+ this.failIf(b, fail)
626
+ b.get(ts).set(b.tp)
627
+ // JavaScript kept the result, used or not: count it
628
+ if (out !== "") this.put(b, [() => b.gget(G.nv)])
629
+ b.gget(G.nv).i32(1).op(Op.i32Add).gset(G.nv)
630
+ return out === "" ? "" : `${out}=V[T[i++]];`
631
+ }
632
+
633
+ // ---------------------------------------------------------------------------
634
+ // Parsers
635
+ // ---------------------------------------------------------------------------
636
+
637
+ call(index: number, out: string, b: Body, fail: Label): string {
638
+ b.get(b.tp).gset(G.tp)
639
+ b.get(0).call(index).tee(0).i32(0).op(Op.i32LtS).brIf(fail)
640
+ b.gget(G.tp).set(b.tp)
641
+ return out === "" ? "" : `${out}=b${index}(i);i=N;`
642
+ }
643
+
644
+ /**
645
+ * Scan code for `p`, falling through with `p` advanced, or branching to
646
+ * `fail` with `G.mode` set. With `out`, returns the builder code that
647
+ * reads its trace and sets `out` to its value.
648
+ */
649
+ gen(p: ParserLike, out: string, b: Body, fail: Label, bf: Vars, depth: number): string {
650
+ const node = nodeOf(p)
651
+ if (Predicate.isTagged(node, "Suspend")) return this.call(this.rule(node.resolve(), out !== ""), out, b, fail)
652
+ if (!isLeaf(node) && (((this.refs.get(p) ?? 0) > 1 && !isSmall(p)) || depth > 32)) {
653
+ return this.call(this.rule(p, out !== ""), out, b, fail)
654
+ }
655
+ return this.inline(p, out, b, fail, bf, depth)
656
+ }
657
+
658
+ /** Whether the scan reads tokens of this encoding itself. */
659
+ private native(enc: Enc): boolean {
660
+ if (enc === undefined) return true
661
+ return isBuiltin(enc) && kindOfEncoding(enc) === this.kind
662
+ }
663
+
664
+ inline(p: ParserLike, out: string, b: Body, fail: Label, bf: Vars, depth: number): string {
665
+ const node = nodeOf(p)
666
+ const s: Site = { p, out, b, fail, bf, d: depth + 1 }
667
+ const families: ReadonlyArray<(node: Node, s: Site) => Option.Option<string>> = [
668
+ (n, x) => this.constant(n, x),
669
+ (n, x) => this.slices(n, x),
670
+ (n, x) => this.values(n, x),
671
+ (n, x) => this.control(n, x),
672
+ (n, x) => this.structure(n, x)
673
+ ]
674
+ for (const family of families) {
675
+ const code = family(node, s)
676
+ if (Option.isSome(code)) return code.value
677
+ }
678
+ return this.island(p, out, b, fail, "no WebAssembly form")
679
+ }
680
+
681
+ /** `out = value;`, or nothing when the value is unused. */
682
+ private set(s: Site, value: string): string {
683
+ return s.out === "" ? "" : `${s.out}=${value};`
684
+ }
685
+
686
+ /** Constants, the end of the input, positions and rules. */
687
+ private constant(node: Node, s: Site): Option.Option<string> {
688
+ const { b, fail } = s
689
+ if (Predicate.isTagged(node, "Succeed")) return Option.some(this.set(s, this.lit(node.value)))
690
+ if (Predicate.isTagged(node, "Sync")) return Option.some(this.set(s, `${this.const(node.evaluate)}()`))
691
+ if (Predicate.isTagged(node, "Fail")) {
692
+ this.failWith(b, fail, modeOf(node.mode))
693
+ return Option.some(this.set(s, "void 0"))
694
+ }
695
+ if (Predicate.isTagged(node, "Eof")) {
696
+ this.atEnd(b, 0)
697
+ b.op(Op.i32Eqz)
698
+ this.failIf(b, fail)
699
+ return Option.some(this.set(s, "void 0"))
700
+ }
701
+ if (Predicate.isTagged(node, "Location") || Predicate.isTagged(node, "RestLen")) {
702
+ if (s.out !== "") this.put(b, [() => b.get(0)])
703
+ return Option.some(this.set(s, Predicate.isTagged(node, "Location") ? "T[i++]" : "E-T[i++]"))
704
+ }
705
+ if (Predicate.isTagged(node, "Suspend")) {
706
+ return Option.some(this.call(this.rule(node.resolve(), s.out !== ""), s.out, b, fail))
707
+ }
708
+ if (Predicate.isTagged(node, "Fixed") && this.kind === 1) {
709
+ // `Binary`'s numbers on bytes: the scan checks there are `size` bytes, the builder reads the number
710
+ this.end(b.get(0).i32(node.size).op(Op.i32Add)).op(Op.i32GtU)
711
+ this.failIf(b, fail)
712
+ if (s.out !== "") this.put(b, [() => b.get(0)])
713
+ this.advance(b, node.size)
714
+ return Option.some(this.set(s, `${this.const(node.read)}(I,T[i++],S)`))
715
+ }
716
+ return Option.none()
717
+ }
718
+
719
+ /** Tokens, `escaped`, and parsers whose value is the input they consumed. */
720
+ private slices(node: Node, s: Site): Option.Option<string> {
721
+ if (
722
+ Predicate.isTagged(node, "Any") ||
723
+ Predicate.isTagged(node, "OneOf") ||
724
+ Predicate.isTagged(node, "Literal") ||
725
+ Predicate.isTagged(node, "TakeWhile")
726
+ ) {
727
+ return Option.some(this.token(s, node))
728
+ }
729
+ if (Predicate.isTagged(node, "Escaped")) return Option.some(this.escaped(s, node))
730
+ if (Predicate.isTagged(node, "TakeUntil") || Predicate.isTagged(node, "TakeUntilAny")) {
731
+ return Option.some(this.takeUntil(s, node))
732
+ }
733
+ if (Predicate.isTagged(node, "Taken")) {
734
+ if (node.encoding !== undefined) {
735
+ return Option.some(this.island(s.p, s.out, s.b, s.fail, "text slices are validated in JavaScript"))
736
+ }
737
+ return Option.some(
738
+ this.sliceOf(s, node.self, this.kind === 0 ? "I.slice(T[i],T[i+1])" : "I.subarray(T[i],T[i+1])")
739
+ )
740
+ }
741
+ if (Predicate.isTagged(node, "Span")) return Option.some(this.sliceOf(s, node.self, "{start:T[i],end:T[i+1]}"))
742
+ if (Predicate.isTagged(node, "WithTaken") || Predicate.isTagged(node, "WithSpan")) {
743
+ return Option.some(this.withSlice(s, node))
744
+ }
745
+ return Option.none()
746
+ }
747
+
748
+ /** `self`, whose value is `value`, an expression of its start and end `T[i]`, `T[i+1]`. */
749
+ private sliceOf(s: Site, self: ParserLike, value: string): string {
750
+ const { b } = s
751
+ const st = b.local()
752
+ b.get(0).set(st)
753
+ this.gen(self, "", b, s.fail, s.bf, s.d)
754
+ if (s.out === "") return ""
755
+ this.put(b, [() => b.get(st), () => b.get(0)])
756
+ return `${s.out}=${value};i+=2;`
757
+ }
758
+
759
+ /** `withTaken` and `withSpan`: the value of `self` and its slice or span. */
760
+ private withSlice(s: Site, node: NodeOf<"WithTaken"> | NodeOf<"WithSpan">): string {
761
+ const { b, out } = s
762
+ if (out === "") return this.gen(node.self, "", b, s.fail, s.bf, s.d)
763
+ const st = b.local()
764
+ b.get(0).set(st)
765
+ this.put(b, [() => b.get(st)])
766
+ const x = this.v(s.bf)
767
+ const start = this.v(s.bf)
768
+ const code = this.gen(node.self, x, b, s.fail, s.bf, s.d)
769
+ this.put(b, [() => b.get(0)])
770
+ const extra = Predicate.isTagged(node, "WithSpan") ? `{start:${start},end:T[i++]}` : `S.slice(${start},T[i++])`
771
+ return `${start}=T[i++];${code}${out}=[${x},${extra}];`
772
+ }
773
+
774
+ /** Mapping, and callbacks on values. */
775
+ private values(node: Node, s: Site): Option.Option<string> {
776
+ const { b, fail, d } = s
777
+ if (Predicate.isTagged(node, "Map")) return Option.some(this.map(node, s.out, b, fail, s.bf, d))
778
+ if (Predicate.isTagged(node, "As")) {
779
+ this.gen(node.self, "", b, fail, s.bf, d)
780
+ return Option.some(this.set(s, this.lit(node.value)))
781
+ }
782
+ if (Predicate.isTagged(node, "Filter")) return Option.some(this.filter(s, node))
783
+ if (Predicate.isTagged(node, "TryMap")) {
784
+ const f = node.f
785
+ return Option.some(this.valueIsland(node.self, s.out, b, fail, d, (v) => Result.getOrElse(f(v), () => Fail)))
786
+ }
787
+ if (Predicate.isTagged(node, "FilterMap")) {
788
+ const f = node.f
789
+ return Option.some(this.valueIsland(node.self, s.out, b, fail, d, (v) => Option.getOrElse(f(v), () => Fail)))
790
+ }
791
+ if (Predicate.isTagged(node, "Tap")) {
792
+ const f = node.f
793
+ return Option.some(
794
+ this.valueIsland(node.self, s.out, b, fail, d, (v) => {
795
+ f(v)
796
+ return v
797
+ })
798
+ )
799
+ }
800
+ return Option.none()
801
+ }
802
+
803
+ /** `filter`: on a float, a test of the double; otherwise a value island. */
804
+ private filter(s: Site, node: NodeOf<"Filter">): string {
805
+ const predicate = node.predicate
806
+ if (predicate === Number.isFinite) {
807
+ const code = this.finiteFloat(node.self, s.out, s.b, s.fail, s.bf, s.d)
808
+ if (Option.isSome(code)) return code.value
809
+ }
810
+ return this.valueIsland(node.self, s.out, s.b, s.fail, s.d, (v) => (predicate(v) ? v : Fail))
811
+ }
812
+
813
+ /** Modes, diagnostics and lookahead. */
814
+ private control(node: Node, s: Site): Option.Option<string> {
815
+ const { b, fail, out, d } = s
816
+ if (Predicate.isTagged(node, "Mode")) {
817
+ return Option.some(this.withMode(b, node, fail, (bad) => this.gen(node.self, out, b, bad, s.bf, d)))
818
+ }
819
+ if (isDiagnostic(node)) return Option.some(this.gen(node.self, out, b, fail, s.bf, d))
820
+ if (Predicate.isTagged(node, "ResumeAfter")) {
821
+ if (out === "") return Option.some(this.gen(node.self, "", b, fail, s.bf, d))
822
+ const x = this.v(s.bf)
823
+ return Option.some(this.gen(node.self, x, b, fail, s.bf, d) + `${out}=R.some(${x});`)
824
+ }
825
+ if (Predicate.isTagged(node, "Opt") || Predicate.isTagged(node, "OrElseSucceed"))
826
+ return Option.some(this.opt(s, node))
827
+ if (Predicate.isTagged(node, "Peek")) {
828
+ const st = b.local()
829
+ b.get(0).set(st)
830
+ const code = this.gen(node.self, out, b, fail, s.bf, d)
831
+ b.get(st).set(0)
832
+ return Option.some(code)
833
+ }
834
+ if (Predicate.isTagged(node, "Not")) return Option.some(this.not(s, node))
835
+ return Option.none()
836
+ }
837
+
838
+ /**
839
+ * Runs `body`, which branches to the label it is given on failure; then
840
+ * `failed` runs, and may fall through to the code after both.
841
+ */
842
+ private attempt(b: Body, body: (bad: Label) => void, failed: () => void): void {
843
+ b.block((ok) => {
844
+ b.block((bad) => {
845
+ body(bad)
846
+ b.br(ok)
847
+ })
848
+ failed()
849
+ })
850
+ }
851
+
852
+ /** `cut` and `complete`: a failure of `body` with the mode `from` fails with `to`. */
853
+ private withMode(b: Body, node: NodeOf<"Mode">, fail: Label, body: (bad: Label) => string): string {
854
+ let code = ""
855
+ this.attempt(
856
+ b,
857
+ (bad) => {
858
+ code = body(bad)
859
+ },
860
+ () => {
861
+ b.gget(G.mode).i32(modeOf(node.from)).op(Op.i32Eq)
862
+ b.if(() => b.i32(modeOf(node.to)).gset(G.mode))
863
+ b.br(fail)
864
+ }
865
+ )
866
+ return code
867
+ }
868
+
869
+ /** `opt` and `orElseSucceed`: the trace holds 1 when `self` matched. */
870
+ private opt(s: Site, node: NodeOf<"Opt"> | NodeOf<"OrElseSucceed">): string {
871
+ const { b, out } = s
872
+ const flag = out === "" ? -1 : this.slot(b)
873
+ const saved = this.save(b)
874
+ const x = out === "" ? "" : this.v(s.bf)
875
+ let code = ""
876
+ this.attempt(
877
+ b,
878
+ (bad) => {
879
+ code = this.gen(node.self, x, b, bad, s.bf, s.d)
880
+ if (flag >= 0) this.putAt(b, flag, 1)
881
+ },
882
+ () => {
883
+ this.unlessBacktrack(b, s.fail)
884
+ this.restore(b, saved)
885
+ }
886
+ )
887
+ if (out === "") return ""
888
+ const some = Predicate.isTagged(node, "Opt") ? `R.some(${x})` : `${x}`
889
+ const none = Predicate.isTagged(node, "Opt") ? "R.none()" : `${this.const(node.value)}()`
890
+ return `if(T[i++]===1){${code}${out}=${some}}else ${out}=${none};`
891
+ }
892
+
893
+ /** `not`: succeeds, consuming nothing, where `self` fails with `Backtrack`. */
894
+ private not(s: Site, node: NodeOf<"Not">): string {
895
+ const { b, fail } = s
896
+ const saved = this.save(b)
897
+ this.attempt(
898
+ b,
899
+ (bad) => {
900
+ this.gen(node.self, "", b, bad, s.bf, s.d)
901
+ this.restore(b, saved)
902
+ this.failWith(b, fail, Backtrack)
903
+ },
904
+ () => {
905
+ this.unlessBacktrack(b, fail)
906
+ this.restore(b, saved)
907
+ }
908
+ )
909
+ return this.set(s, "void 0")
910
+ }
911
+
912
+ /** Sequences, choice and repetition. */
913
+ private structure(node: Node, s: Site): Option.Option<string> {
914
+ const { p, out, b, fail, bf, d } = s
915
+ if (Predicate.isTagged(node, "Seq")) return Option.some(this.seq(node, out, b, fail, bf, d))
916
+ if (Predicate.isTagged(node, "Struct")) return Option.some(this.struct(s, node))
917
+ if (Predicate.isTagged(node, "ZipWith")) return Option.some(this.zipWith(s, node))
918
+ if (Predicate.isTagged(node, "Alt")) return Option.some(this.alt(node, out, b, fail, bf, d))
919
+ if (Predicate.isTagged(node, "Dispatch")) return Option.some(this.dispatch(p, node, out, b, fail, bf, d))
920
+ if (Predicate.isTagged(node, "Repeat")) {
921
+ return Option.some(this.repeat(node, out, b, fail, bf, this.collect(node.self, out, d)))
922
+ }
923
+ if (Predicate.isTagged(node, "Fold")) {
924
+ return Option.some(
925
+ node.while === undefined
926
+ ? this.fold(node, out, b, fail, bf, d)
927
+ : this.island(p, out, b, fail, "the condition needs the accumulator")
928
+ )
929
+ }
930
+ if (Predicate.isTagged(node, "Separated")) {
931
+ return Option.some(this.separated(node, out, b, fail, bf, d, this.collect(node.self, out, d)))
932
+ }
933
+ return Option.none()
934
+ }
935
+
936
+ private struct(s: Site, node: NodeOf<"Struct">): string {
937
+ const { b, out } = s
938
+ if (out === "") {
939
+ for (const q of node.parsers) this.gen(q, "", b, s.fail, s.bf, s.d)
940
+ return ""
941
+ }
942
+ const vars = node.parsers.map(() => this.v(s.bf))
943
+ const code = node.parsers.map((q, i) => this.gen(q, vars[i], b, s.fail, s.bf, s.d)).join("")
944
+ if (node.keys.includes("__proto__")) {
945
+ return code + `${out}={};` + node.keys.map((k, i) => `${out}[${this.const(k)}]=${vars[i]};`).join("")
946
+ }
947
+ return code + `${out}={${node.keys.map((k, i) => `[${this.const(k)}]:${vars[i]}`).join(",")}};`
948
+ }
949
+
950
+ private zipWith(s: Site, node: NodeOf<"ZipWith">): string {
951
+ const { b, out } = s
952
+ if (out === "") {
953
+ this.gen(node.self, "", b, s.fail, s.bf, s.d)
954
+ this.gen(node.that, "", b, s.fail, s.bf, s.d)
955
+ return ""
956
+ }
957
+ const x = this.v(s.bf)
958
+ const y = this.v(s.bf)
959
+ return (
960
+ this.gen(node.self, x, b, s.fail, s.bf, s.d) +
961
+ this.gen(node.that, y, b, s.fail, s.bf, s.d) +
962
+ `${out}=${this.const(node.f)}(${x},${y});`
963
+ )
964
+ }
965
+
966
+ /** Writes `value` to the trace word at the address in local `slot`. */
967
+ private putAt(b: Body, slot: number, value: number): void {
968
+ b.get(slot).i32(value).mem(Op.i32Store, 2, 0)
969
+ }
970
+
971
+ // ---------------------------------------------------------------------------
972
+ // Mapping
973
+ // ---------------------------------------------------------------------------
974
+
975
+ private map(node: NodeOf<"Map">, out: string, b: Body, fail: Label, bf: Vars, d: number) {
976
+ if (out === "") return this.gen(node.self, "", b, fail, bf, d)
977
+ if (node.f === toFloat) {
978
+ const code = this.float(node, out, b, fail, bf, d)
979
+ if (Option.isSome(code)) return code.value
980
+ }
981
+ if (node.f === fromEntries && buildsEntries(node.self)) return this.entries(node.self, out, b, fail, bf, d)
982
+ const x = this.v(bf)
983
+ return this.gen(node.self, x, b, fail, bf, d) + `${out}=${this.const(node.f)}(${x});`
984
+ }
985
+
986
+ /** Whether `Ascii.float` / `Text.float` (`map(text, toFloat)`) can be scanned and converted in one pass. */
987
+ private floatScans(node: NodeOf<"Map">): boolean {
988
+ return node.f === toFloat && floatTexts.has(node.self) && Option.isSome(this.floatEncoding(node.self))
989
+ }
990
+
991
+ /**
992
+ * The encoding of the text parser of `Ascii.float` / `Text.float` (`none`
993
+ * for `Ascii`), when the scan reads it: decimal notation is ASCII, which
994
+ * every built-in encoding read here encodes as one unit per character.
995
+ */
996
+ private floatEncoding(p: ParserLike): Option.Option<Enc> {
997
+ let taken = nodeOf(p)
998
+ while (Predicate.isTagged(taken, "Expected")) taken = nodeOf(taken.self)
999
+ if (!Predicate.isTagged(taken, "Taken")) return Option.none()
1000
+ const enc = taken.encoding
1001
+ return this.native(enc) ? Option.some(enc) : Option.none()
1002
+ }
1003
+
1004
+ /**
1005
+ * `Ascii.float` and `Text.float`: decimal numbers are matched and
1006
+ * converted in one pass (see `scanFloat`); the other forms (`nan`, `inf`)
1007
+ * are matched by the float's text parser and converted by `toFloat`. The
1008
+ * trace holds the double.
1009
+ */
1010
+ private float(node: NodeOf<"Map">, out: string, b: Body, fail: Label, bf: Vars, d: number): Option.Option<string> {
1011
+ if (!this.floatScans(node)) return Option.none()
1012
+ return Option.some(this.scanFloatThen(node, out, b, fail, false, bf, d))
1013
+ }
1014
+
1015
+ /** `filter(float, Number.isFinite)`: the float scan, then a test of the double. */
1016
+ private finiteFloat(self: ParserLike, out: string, b: Body, fail: Label, bf: Vars, d: number): Option.Option<string> {
1017
+ const node = nodeOf(self)
1018
+ if (!Predicate.isTagged(node, "Map") || !this.floatScans(node)) return Option.none()
1019
+ return Option.some(this.scanFloatThen(node, out, b, fail, true, bf, d))
1020
+ }
1021
+
1022
+ /**
1023
+ * The float at `p`: the scan of a decimal number, or else the float's text
1024
+ * parser, whose text `toFloat` converts in JavaScript. A failure of the
1025
+ * scan fails with `Backtrack`, as the text parser would. With `finite`, a
1026
+ * value that isn't finite fails.
1027
+ */
1028
+ private scanFloatThen(
1029
+ map: NodeOf<"Map">,
1030
+ out: string,
1031
+ b: Body,
1032
+ fail: Label,
1033
+ finite: boolean,
1034
+ bf: Vars,
1035
+ d: number
1036
+ ): string {
1037
+ const enc = Option.getOrUndefined(this.floatEncoding(map.self))
1038
+ let taken = nodeOf(map.self)
1039
+ while (Predicate.isTagged(taken, "Expected")) taken = nodeOf(taken.self)
1040
+ const r = b.local()
1041
+ const st = b.local()
1042
+ b.get(0).set(st)
1043
+ b.get(0).call(this.scanFloat(enc)).tee(r).i32(-1).op(Op.i32Eq)
1044
+ b.if(
1045
+ () => {
1046
+ // Not decimal: the text parser (its text is ASCII, so `taken` has nothing to validate)
1047
+ if (Predicate.isTagged(taken, "Taken")) this.gen(taken.self, "", b, fail, bf, d)
1048
+ b.get(st).get(0).call(Imp.float).gset(G.f)
1049
+ },
1050
+ () => {
1051
+ b.get(r).i32(-2).op(Op.i32Eq)
1052
+ this.failIf(b, fail)
1053
+ b.get(r).set(0)
1054
+ }
1055
+ )
1056
+ if (finite) {
1057
+ // x - x is 0 for finite doubles, NaN otherwise
1058
+ b.gget(G.f).gget(G.f).op(Op.f64Sub).f64(0).op(Op.f64Ne)
1059
+ this.failIf(b, fail)
1060
+ }
1061
+ if (out === "") return ""
1062
+ this.putF64(b)
1063
+ return `i=(i+1)&-2;${out}=F[i>>1];i+=2;`
1064
+ }
1065
+
1066
+ // ---------------------------------------------------------------------------
1067
+ // Sequences and choice
1068
+ // ---------------------------------------------------------------------------
1069
+
1070
+ private seq(node: NodeOf<"Seq">, out: string, b: Body, fail: Label, bf: Vars, d: number): string {
1071
+ const output = node.output
1072
+ if (out === "") {
1073
+ for (const q of node.parsers) this.gen(q, "", b, fail, bf, d)
1074
+ return ""
1075
+ }
1076
+ if (Predicate.isNumber(output)) {
1077
+ return node.parsers.map((q, i) => this.gen(q, i === output ? out : "", b, fail, bf, d)).join("")
1078
+ }
1079
+ const keep = (i: number) => output === "tuple" || output.includes(i)
1080
+ const vars = node.parsers.map((_, i) => (keep(i) ? this.v(bf) : ""))
1081
+ const code = node.parsers.map((q, i) => this.gen(q, vars[i], b, fail, bf, d)).join("")
1082
+ // A pair keeps its indices' order, which may differ from the parsers'
1083
+ const kept = output === "tuple" ? vars.filter((v) => v !== "") : output.map((i) => vars[i])
1084
+ return code + `${out}=[${kept.join(",")}];`
1085
+ }
1086
+
1087
+ private alt(node: NodeOf<"Alt">, out: string, b: Body, fail: Label, bf: Vars, d: number): string {
1088
+ const parsers = node.parsers
1089
+ if (Arr.isReadonlyArrayEmpty(parsers)) {
1090
+ this.failWith(b, fail, Backtrack)
1091
+ return ""
1092
+ }
1093
+ if (parsers.length === 1) return this.gen(parsers[0], out, b, fail, bf, d)
1094
+ const saved = this.save(b)
1095
+ const cases: Array<string> = []
1096
+ b.block((done) => {
1097
+ parsers.forEach((q, i) => {
1098
+ b.block((bad) => {
1099
+ if (out !== "") this.put(b, [() => b.i32(i)])
1100
+ cases.push(this.gen(q, out, b, bad, bf, d))
1101
+ b.br(done)
1102
+ })
1103
+ this.unlessBacktrack(b, fail)
1104
+ this.restore(b, saved)
1105
+ })
1106
+ this.failWith(b, fail, Backtrack)
1107
+ })
1108
+ if (out === "") return ""
1109
+ return `switch(T[i++]){${cases.map((c, i) => `case ${i}:{${c}break}`).join("")}}`
1110
+ }
1111
+
1112
+ private dispatch(
1113
+ p: ParserLike,
1114
+ node: NodeOf<"Dispatch">,
1115
+ out: string,
1116
+ b: Body,
1117
+ fail: Label,
1118
+ bf: Vars,
1119
+ d: number
1120
+ ): string {
1121
+ const { node: sel, peek } = selectorOf(node.selector)
1122
+ const cases = node.cases
1123
+ if (cases === undefined || !Predicate.isTagged(sel, "Any") || !this.native(sel.encoding)) {
1124
+ return this.island(p, out, b, fail, "the selector runs in JavaScript")
1125
+ }
1126
+ const enc = sel.encoding
1127
+ const r = Option.map(Option.fromUndefinedOr(enc), (e) => this.reader(e))
1128
+ const size = this.dispatchSize(r)
1129
+ if (size === 0) return this.island(p, out, b, fail, "the selector runs in JavaScript")
1130
+ const bytes = enc === undefined && this.kind === 1
1131
+ const choose = (unit: number) => node.choose(bytes ? unit : charOf(unit))
1132
+ // The distinct cases, by the units below `size` that select them
1133
+ const targets: Array<ParserLike> = []
1134
+ for (let unit = 0; unit < size; unit++) {
1135
+ const q = choose(unit)
1136
+ if (q !== undefined && !targets.includes(q)) targets.push(q)
1137
+ }
1138
+ const closed = node.otherwise === undefined && closedKeys(Reflect.ownKeys(cases), size, bytes)
1139
+ const u = b.local()
1140
+ const st = b.local()
1141
+ b.get(0).set(st)
1142
+ this.selectorUnit(b, r, size)
1143
+ b.set(u)
1144
+ b.get(u).i32(-1).op(Op.i32Eq)
1145
+ this.failIf(b, fail)
1146
+ const code: Array<string> = []
1147
+ let general = ""
1148
+ b.block((done) =>
1149
+ // A block per case, then one for a unit that selects no case and one for the units from `size` up
1150
+ this.nested(
1151
+ b,
1152
+ targets.length + 2,
1153
+ (labels) => {
1154
+ const table = Arr.makeBy(size, (unit) => {
1155
+ const q = choose(unit)
1156
+ return q === undefined ? labels[targets.length] : labels[targets.indexOf(q)]
1157
+ })
1158
+ b.get(u).brTable(table, labels[targets.length + 1])
1159
+ },
1160
+ (j) => {
1161
+ if (j < targets.length) {
1162
+ if (!peek) this.advance(b, Option.match(r, { onNone: () => 1, onSome: (x) => x.width }))
1163
+ if (out !== "") this.put(b, [() => b.i32(j)])
1164
+ code[j] = this.gen(targets[j], out, b, fail, bf, d)
1165
+ b.br(done)
1166
+ } else if (j === targets.length || closed) {
1167
+ this.failWith(b, fail, Backtrack)
1168
+ } else {
1169
+ // The dispatch reads a code point beyond the fast units: it runs in JavaScript
1170
+ b.get(st).set(0)
1171
+ if (out !== "") this.put(b, [() => b.i32(targets.length)])
1172
+ general = this.island(p, out, b, fail, "the selector reads a code point beyond the fast units")
1173
+ b.br(done)
1174
+ }
1175
+ }
1176
+ )
1177
+ )
1178
+ if (out === "") return ""
1179
+ return `switch(T[i++]){${code.map((c, j) => `case ${j}:{${c}break}`).join("")}default:{${general}}}`
1180
+ }
1181
+
1182
+ /** How many units a `dispatch` switches on: the units that are whole code points, at most `TableSize`. */
1183
+ private dispatchSize(r: Option.Option<Reader>): number {
1184
+ const limit = Option.match(r, {
1185
+ onNone: () => (this.kind === 0 ? 0xd800 : TableSize),
1186
+ onSome: (x) => Math.min(x.fast, 0xd800)
1187
+ })
1188
+ return Math.min(limit, TableSize)
1189
+ }
1190
+
1191
+ /** Pushes the unit a `dispatch` switches on: -1 at the end, `size` for a unit cut short. */
1192
+ private selectorUnit(b: Body, r: Option.Option<Reader>, size: number): void {
1193
+ if (Option.isNone(r)) {
1194
+ this.atEnd(b, 0)
1195
+ b.ifI32(
1196
+ () => b.i32(-1),
1197
+ () => this.load(b, 0)
1198
+ )
1199
+ return
1200
+ }
1201
+ const reader = r.value
1202
+ reader.guard(b, 0)
1203
+ b.ifI32(
1204
+ () => reader.unit(b, 0),
1205
+ () => {
1206
+ this.atEnd(b, 0)
1207
+ b.ifI32(
1208
+ () => b.i32(-1),
1209
+ () => b.i32(size)
1210
+ )
1211
+ }
1212
+ )
1213
+ }
1214
+
1215
+ /**
1216
+ * `n` nested blocks around `inner`, which gets their labels, outermost
1217
+ * first; after the end of block `j` (where a branch to its label goes),
1218
+ * `after(j)` runs.
1219
+ */
1220
+ private nested(b: Body, n: number, inner: (labels: ReadonlyArray<Label>) => void, after: (j: number) => void): void {
1221
+ const open = (j: number, labels: ReadonlyArray<Label>): void => {
1222
+ if (j === n) return inner(labels)
1223
+ b.block((l) => open(j + 1, [...labels, l]))
1224
+ after(j)
1225
+ }
1226
+ open(0, [])
1227
+ }
1228
+
1229
+ // ---------------------------------------------------------------------------
1230
+ // Repetition
1231
+ // ---------------------------------------------------------------------------
1232
+
1233
+ /** An element that is pushed to an array (or not kept). */
1234
+ private collect(self: ParserLike, out: string, d: number): Element {
1235
+ return (b, fail, bf) => {
1236
+ if (out === "") {
1237
+ this.gen(self, "", b, fail, bf, d)
1238
+ return ""
1239
+ }
1240
+ const x = this.v(bf)
1241
+ return `${this.gen(self, x, b, fail, bf, d)}${out}.push(${x});`
1242
+ }
1243
+ }
1244
+
1245
+ /** The builder of a repetition: its count, then each element. */
1246
+ private many(out: string, init: string, element: string, finish: string, bf: Vars): string {
1247
+ if (out === "") return ""
1248
+ const n = this.v(bf)
1249
+ const k = this.v(bf)
1250
+ return `${init}${n}=T[i++];for(${k}=0;${k}<${n};${k}++){${element}}${finish}`
1251
+ }
1252
+
1253
+ /**
1254
+ * After a failed element or separator: fails unless it backtracked and
1255
+ * `n` reached `min`; otherwise restores the position and stops.
1256
+ */
1257
+ private stop(b: Body, fail: Label, n: number, min: number, restore: () => void, done: Label): void {
1258
+ this.unlessBacktrack(b, fail)
1259
+ if (min > 0) {
1260
+ b.get(n).i32(min).op(Op.i32LtU)
1261
+ b.if(() => b.br(fail))
1262
+ }
1263
+ restore()
1264
+ b.br(done)
1265
+ }
1266
+
1267
+ /** Counts an element in local `n` and goes on to the next. */
1268
+ private nextElement(b: Body, n: number, again: Label): void {
1269
+ b.get(n).i32(1).op(Op.i32Add).set(n)
1270
+ b.br(again)
1271
+ }
1272
+
1273
+ /** Writes the count in local `n` to the trace word reserved at `slot`, if any. Returns no builder code. */
1274
+ private counted(b: Body, slot: number, n: number): string {
1275
+ if (slot >= 0) b.get(slot).get(n).mem(Op.i32Store, 2, 0)
1276
+ return ""
1277
+ }
1278
+
1279
+ /** An unbounded repetition that consumed nothing since `from` fails with `Cut`, as the interpreter does. */
1280
+ private progress(b: Body, fail: Label, from: number): void {
1281
+ b.get(0).get(from).op(Op.i32Eq)
1282
+ b.if(() => this.failWith(b, fail, Cut))
1283
+ }
1284
+
1285
+ repeat(
1286
+ node: NodeOf<"Repeat"> | NodeOf<"Fold">,
1287
+ out: string,
1288
+ b: Body,
1289
+ fail: Label,
1290
+ bf: Vars,
1291
+ element: Element,
1292
+ init = out === "" ? "" : `${out}=[];`,
1293
+ finish = ""
1294
+ ): string {
1295
+ const { max, min } = node
1296
+ const n = b.local()
1297
+ const slot = out === "" ? -1 : this.slot(b)
1298
+ let elementCode = ""
1299
+ b.i32(0).set(n)
1300
+ b.block((done) => {
1301
+ this.loop(b, (again) => {
1302
+ if (max !== Infinity) b.get(n).i32(max).op(Op.i32GeU).brIf(done)
1303
+ const saved = this.save(b)
1304
+ this.attempt(
1305
+ b,
1306
+ (bad) => {
1307
+ elementCode = element(b, bad, bf)
1308
+ if (max === Infinity) this.progress(b, fail, saved[0])
1309
+ this.nextElement(b, n, again)
1310
+ },
1311
+ () => this.stop(b, fail, n, min, () => this.restore(b, saved), done)
1312
+ )
1313
+ })
1314
+ })
1315
+ return this.counted(b, slot, n) + this.many(out, init, elementCode, finish, bf)
1316
+ }
1317
+
1318
+ private fold(node: NodeOf<"Fold">, out: string, b: Body, fail: Label, bf: Vars, d: number): string {
1319
+ const f = this.const(node.f)
1320
+ const element: Element = (body, bad, vars) => {
1321
+ if (out === "") {
1322
+ this.gen(node.self, "", body, bad, vars, d)
1323
+ return ""
1324
+ }
1325
+ const x = this.v(vars)
1326
+ return `${this.gen(node.self, x, body, bad, vars, d)}${out}=${f}(${out},${x});`
1327
+ }
1328
+ return this.repeat(node, out, b, fail, bf, element, out === "" ? "" : `${out}=${this.const(node.init)}();`)
1329
+ }
1330
+
1331
+ separated(
1332
+ node: NodeOf<"Separated">,
1333
+ out: string,
1334
+ b: Body,
1335
+ fail: Label,
1336
+ bf: Vars,
1337
+ d: number,
1338
+ element: Element,
1339
+ init = out === "" ? "" : `${out}=[];`,
1340
+ finish = ""
1341
+ ): string {
1342
+ const { max, min } = node
1343
+ if (max === 0) return out === "" ? "" : `${init}${finish}`
1344
+ const n = b.local()
1345
+ const st = b.local()
1346
+ const t = b.local()
1347
+ const slot = out === "" ? -1 : this.slot(b)
1348
+ let elementCode = ""
1349
+ const mark = () => b.get(0).set(st).get(b.tp).set(t)
1350
+ const back = () => b.get(st).set(0).get(t).set(b.tp)
1351
+ b.i32(0).set(n)
1352
+ b.block((done) => {
1353
+ mark()
1354
+ this.attempt(
1355
+ b,
1356
+ (bad) => {
1357
+ elementCode = element(b, bad, bf)
1358
+ b.i32(1).set(n)
1359
+ },
1360
+ () => {
1361
+ // The first element failed: fatal unless it backtracked and none are needed
1362
+ this.unlessBacktrack(b, fail)
1363
+ if (min > 0) b.br(fail)
1364
+ back()
1365
+ b.br(done)
1366
+ }
1367
+ )
1368
+ this.loop(b, (again) => {
1369
+ if (max !== Infinity) b.get(n).i32(max).op(Op.i32GeU).brIf(done)
1370
+ mark()
1371
+ this.attempt(
1372
+ b,
1373
+ (bad) => this.gen(node.separator, "", b, bad, bf, d),
1374
+ () => this.stop(b, fail, n, min, back, done)
1375
+ )
1376
+ if (max === Infinity) this.progress(b, fail, st)
1377
+ this.attempt(
1378
+ b,
1379
+ (bad) => {
1380
+ // The same element code again: its builder code is the same
1381
+ element(b, bad, new Vars())
1382
+ this.nextElement(b, n, again)
1383
+ },
1384
+ () => this.stop(b, fail, n, min, back, done)
1385
+ )
1386
+ })
1387
+ })
1388
+ return this.counted(b, slot, n) + this.many(out, init, elementCode, finish, bf)
1389
+ }
1390
+
1391
+ /**
1392
+ * `map(self, Object.fromEntries)` where `buildsEntries(self)`: the scan is
1393
+ * the usual one, and the builder assigns each entry as it is read, as the
1394
+ * JIT does (see `internal/entries.ts`).
1395
+ */
1396
+ private entries(p: ParserLike, out: string, b: Body, fail: Label, bf: Vars, d: number): string {
1397
+ const node = nodeOf(p)
1398
+ if (Predicate.isTagged(node, "Mode")) {
1399
+ return this.withMode(b, node, fail, (bad) => this.entries(node.self, out, b, bad, bf, d))
1400
+ }
1401
+ if (Predicate.isTagged(node, "Seq") && Predicate.isNumber(node.output)) {
1402
+ const kept = node.output
1403
+ return node.parsers
1404
+ .map((q, j) => (j === kept ? this.entries(q, out, b, fail, bf, d) : this.gen(q, "", b, fail, bf, d)))
1405
+ .join("")
1406
+ }
1407
+ if (isDiagnostic(node)) return this.entries(node.self, out, b, fail, bf, d)
1408
+ const pair =
1409
+ Predicate.isTagged(node, "Repeat") || Predicate.isTagged(node, "Separated") ? pairOf(node.self) : undefined
1410
+ if ((Predicate.isTagged(node, "Repeat") || Predicate.isTagged(node, "Separated")) && pair !== undefined) {
1411
+ const deferred = this.v(bf)
1412
+ // The element's variables are declared in the enclosing builder function
1413
+ const element: Element = (body, bad) => {
1414
+ const key = this.v(bf)
1415
+ const value = this.v(bf)
1416
+ const code = pair.parsers
1417
+ .map((q, j) => this.gen(q, j === pair.key ? key : j === pair.value ? value : "", body, bad, bf, d))
1418
+ .join("")
1419
+ return (
1420
+ code +
1421
+ `if(${deferred}===void 0&&R.isPlainKey(${key}))${out}[${key}]=${value};` +
1422
+ `else ${deferred}=R.addEntry(${out},${deferred},${key},${value});`
1423
+ )
1424
+ }
1425
+ const init = `${out}={};${deferred}=void 0;`
1426
+ const finish = `if(${deferred}!==void 0)R.flushEntries(${out},${deferred});`
1427
+ return Predicate.isTagged(node, "Repeat")
1428
+ ? this.repeat(node, out, b, fail, bf, element, init, finish)
1429
+ : this.separated(node, out, b, fail, bf, d, element, init, finish)
1430
+ }
1431
+ const x = this.v(bf)
1432
+ return this.gen(p, x, b, fail, bf, d) + `${out}=${this.const(fromEntries)}(${x});`
1433
+ }
1434
+
1435
+ // ---------------------------------------------------------------------------
1436
+ // Tokens
1437
+ // ---------------------------------------------------------------------------
1438
+
1439
+ /** How the scan reads an encoding's code points. */
1440
+ reader(enc: Encoding<any>): Reader {
1441
+ if (enc === utf16le || enc === utf16be) {
1442
+ const [lo, hi] = enc === utf16le ? [0, 1] : [1, 0]
1443
+ return {
1444
+ width: 2,
1445
+ fast: 0xd800,
1446
+ guard: (b, pos) => this.end(b.get(pos).i32(1).op(Op.i32Add)).op(Op.i32LtU),
1447
+ unit: (b, pos) => {
1448
+ this.load(b, pos, lo)
1449
+ this.load(b, pos, hi)
1450
+ b.i32(8).op(Op.i32Shl, Op.i32Or)
1451
+ },
1452
+ decode: this.decoder(enc)
1453
+ }
1454
+ }
1455
+ return {
1456
+ width: 1,
1457
+ fast: enc === latin1 ? TableSize : fastLimit(enc),
1458
+ guard: (b, pos) => this.end(b.get(pos)).op(Op.i32LtU),
1459
+ unit: (b, pos) => this.load(b, pos),
1460
+ decode: enc === latin1 ? -1 : this.decoder(enc)
1461
+ }
1462
+ }
1463
+
1464
+ /**
1465
+ * Pushes whether the code point in local `c`, known to be below `below`,
1466
+ * is in the set (taking `negate` into account). Known ranges compile to
1467
+ * comparisons, testing the accepted runs or the rejected ones, whichever
1468
+ * takes fewer; otherwise a table below `TableSize`, then the ranges or the
1469
+ * set's test above it.
1470
+ */
1471
+ private member(cs: CharSet, negate: boolean, c: number, b: Body, below = 0x110000): void {
1472
+ const max = Math.min(below, this.kind === 1 && this.tokenBytes ? 256 : 0x110000) - 1
1473
+ const runs = acceptedRuns(cs, negate, max)
1474
+ const cost = (rs: ReadonlyArray<readonly [number, number]>) =>
1475
+ rs.reduce((n, [lo, hi]) => n + (lo === hi || lo === 0 || hi >= max ? 1 : 2), 0)
1476
+ /** Pushes whether `c` is in one of the runs. */
1477
+ const compare = (rs: ReadonlyArray<readonly [number, number]>) => {
1478
+ if (Arr.isReadonlyArrayEmpty(rs)) {
1479
+ b.i32(0)
1480
+ return
1481
+ }
1482
+ rs.forEach(([lo, hi], k) => {
1483
+ if (lo === hi) b.get(c).i32(lo).op(Op.i32Eq)
1484
+ else if (lo === 0) b.get(c).i32(hi).op(Op.i32LeU)
1485
+ else if (hi >= max) b.get(c).i32(lo).op(Op.i32GeU)
1486
+ else
1487
+ b.get(c)
1488
+ .i32(lo)
1489
+ .op(Op.i32Sub)
1490
+ .i32(hi - lo)
1491
+ .op(Op.i32LeU)
1492
+ if (k > 0) b.op(Op.i32Or)
1493
+ })
1494
+ }
1495
+ if (Option.isSome(runs)) {
1496
+ const accept = runs.value
1497
+ const reject = Option.getOrElse(acceptedRuns({ ...cs, ranges: accept }, true, max), () => [])
1498
+ if (Math.min(cost(accept), cost(reject)) <= 6) {
1499
+ if (cost(accept) <= cost(reject)) compare(accept)
1500
+ else {
1501
+ compare(reject)
1502
+ b.op(Op.i32Eqz)
1503
+ }
1504
+ return
1505
+ }
1506
+ }
1507
+ const table = new Uint8Array(TableSize)
1508
+ for (let x = 0; x < TableSize; x++) table[x] = (cs.table[x] === 1) !== negate ? 1 : 0
1509
+ const callTest = () => {
1510
+ const test = cs.test
1511
+ this.tests.push((x) => (test(x) !== negate ? 1 : 0))
1512
+ b.i32(this.tests.length - 1)
1513
+ .get(c)
1514
+ .call(Imp.test)
1515
+ }
1516
+ const offset = this.table(table)
1517
+ if (offset < 0) {
1518
+ callTest()
1519
+ return
1520
+ }
1521
+ if (max < TableSize) {
1522
+ b.get(c).mem(Op.i32Load8U, 0, offset)
1523
+ return
1524
+ }
1525
+ const high = Option.map(runs, (rs) => rs.filter(([, hi]) => hi >= TableSize))
1526
+ b.get(c).i32(TableSize).op(Op.i32LtU)
1527
+ b.ifI32(
1528
+ () => b.get(c).mem(Op.i32Load8U, 0, offset),
1529
+ () => {
1530
+ if (Option.isSome(high) && cost(high.value) <= 6) compare(high.value)
1531
+ else callTest()
1532
+ }
1533
+ )
1534
+ }
1535
+
1536
+ /** Whether the token being compiled reads bytes as `Token` does (code points never exceed 255). */
1537
+ private tokenBytes = false
1538
+
1539
+ /** Places a membership table in memory, returning its address. */
1540
+ private table(t: Uint8Array): number {
1541
+ for (let i = 0; i < this.tables.length; i++) {
1542
+ if (this.tables[i].every((x, j) => x === t[j])) return i * TableSize
1543
+ }
1544
+ // Full: -1, and the set is tested by its function
1545
+ if ((this.tables.length + 1) * TableSize > TablesEnd) return -1
1546
+ this.tables.push(t)
1547
+ return (this.tables.length - 1) * TableSize
1548
+ }
1549
+
1550
+ /** Pushes whether a whole unit below `r.fast` is at `p`, leaving it in local `c`. `p < E` holds. */
1551
+ private fastUnit(r: Reader, b: Body, c: number): void {
1552
+ if (r.width === 1) {
1553
+ r.unit(b, 0)
1554
+ b.tee(c).i32(r.fast).op(Op.i32LtU)
1555
+ return
1556
+ }
1557
+ r.guard(b, 0)
1558
+ b.ifI32(
1559
+ () => {
1560
+ r.unit(b, 0)
1561
+ b.tee(c).i32(r.fast).op(Op.i32LtU)
1562
+ },
1563
+ () => b.i32(0)
1564
+ )
1565
+ }
1566
+
1567
+ /**
1568
+ * Reads the code point at `p` into local `c` and its width into `w`,
1569
+ * branching to `stop` when it is malformed or cut short. `p < E` holds.
1570
+ */
1571
+ private readCodePoint(enc: Enc, b: Body, c: number, w: number, stop: Label): void {
1572
+ if (enc === undefined) {
1573
+ this.load(b, 0)
1574
+ b.set(c).i32(1).set(w)
1575
+ // Token on strings: code points, with lenient surrogates
1576
+ if (this.kind === 0) this.lenientPair(b, c, w)
1577
+ return
1578
+ }
1579
+ const r = this.reader(enc)
1580
+ if (r.decode < 0) {
1581
+ r.unit(b, 0)
1582
+ b.set(c).i32(1).set(w)
1583
+ return
1584
+ }
1585
+ this.fastUnit(r, b, c)
1586
+ b.if(
1587
+ () => b.i32(r.width).set(w),
1588
+ () => {
1589
+ b.get(0).call(r.decode).tee(c).i32(0).op(Op.i32LtS).brIf(stop)
1590
+ b.gget(G.w).set(w)
1591
+ }
1592
+ )
1593
+ }
1594
+
1595
+ /** `Token` on strings: a high surrogate in `c` followed by a low one is read as their pair. */
1596
+ private lenientPair(b: Body, c: number, w: number): void {
1597
+ b.get(c).i32(0xfc00).op(Op.i32And).i32(0xd800).op(Op.i32Eq)
1598
+ b.if(() => {
1599
+ b.get(0).i32(1).op(Op.i32Add).gget(G.len).op(Op.i32LtU)
1600
+ b.if(() => {
1601
+ this.load(b, 0, 1)
1602
+ const lo = b.local()
1603
+ b.tee(lo).i32(0xfc00).op(Op.i32And).i32(0xdc00).op(Op.i32Eq)
1604
+ b.if(() => {
1605
+ pair(b, c, lo)
1606
+ b.set(c)
1607
+ b.i32(2).set(w)
1608
+ })
1609
+ })
1610
+ })
1611
+ }
1612
+
1613
+ private token(s: Site, node: NodeOf<"Any" | "Literal" | "OneOf" | "TakeWhile">): string {
1614
+ const enc = node.encoding
1615
+ if (!this.native(enc)) return this.island(s.p, s.out, s.b, s.fail, "the encoding is decoded in JavaScript")
1616
+ this.tokenBytes = enc === undefined && this.kind === 1
1617
+ if (Predicate.isTagged(node, "Literal")) return this.literal(s, node)
1618
+ if (Predicate.isTagged(node, "TakeWhile")) return this.takeWhile(s, node)
1619
+ return this.single(s, node)
1620
+ }
1621
+
1622
+ /** `any` and `oneOf`: one code point. */
1623
+ private single(s: Site, node: NodeOf<"Any" | "OneOf">): string {
1624
+ const { b } = s
1625
+ const enc = node.encoding
1626
+ const c = b.local()
1627
+ const w = b.local()
1628
+ const st = b.local()
1629
+ b.get(0).set(st)
1630
+ this.attempt(
1631
+ b,
1632
+ (bad) => {
1633
+ this.atEnd(b, 0)
1634
+ b.brIf(bad)
1635
+ this.readCodePoint(enc, b, c, w, bad)
1636
+ if (Predicate.isTagged(node, "OneOf")) {
1637
+ this.member(enc === undefined ? tokenSet(node.set, this.kind) : textSet(node.set), node.negate, c, b)
1638
+ b.op(Op.i32Eqz).brIf(bad)
1639
+ }
1640
+ b.get(0).get(w).op(Op.i32Add).set(0)
1641
+ },
1642
+ () => this.failWith(b, s.fail, Backtrack)
1643
+ )
1644
+ if (s.out === "") return ""
1645
+ if (enc !== undefined) {
1646
+ this.put(b, [() => b.get(c)])
1647
+ return this.set(s, "R.charOf(T[i++])")
1648
+ }
1649
+ this.put(b, [() => b.get(st), () => b.get(0)])
1650
+ return this.set(s, this.kind === 0 ? "I.slice(T[i],T[i+1]);i+=2" : "I[T[i]];i+=2")
1651
+ }
1652
+
1653
+ private takeWhile(s: Site, node: NodeOf<"TakeWhile">): string {
1654
+ const { b } = s
1655
+ const enc = node.encoding
1656
+ const { max, min } = node
1657
+ const set: Membership = {
1658
+ cs: enc === undefined ? tokenSet(node.set, this.kind) : textSet(node.set),
1659
+ negate: node.negate,
1660
+ c: b.local()
1661
+ }
1662
+ const w = b.local()
1663
+ const st = b.local()
1664
+ b.get(0).set(st)
1665
+ const counted = max !== Infinity || min > 1
1666
+ const n = b.local()
1667
+ if (counted) b.i32(0).set(n)
1668
+ // UTF-8: whether the slice has a byte from 0x80 up, traced in the sign bit of its start
1669
+ const wide = enc === utf8 && s.out !== "" ? b.local() : -1
1670
+ if (wide >= 0) b.i32(0).set(wide)
1671
+ b.block((stop) => {
1672
+ if (!counted && this.vectorRun(enc, set)) this.byteRun(b, set, stop)
1673
+ b.loop((again) => {
1674
+ this.atEnd(b, 0)
1675
+ b.brIf(stop)
1676
+ if (max !== Infinity) b.get(n).i32(max).op(Op.i32GeU).brIf(stop)
1677
+ this.whileStep(enc, b, set, w, wide, stop)
1678
+ if (counted) b.get(n).i32(1).op(Op.i32Add).set(n)
1679
+ b.br(again)
1680
+ })
1681
+ })
1682
+ if (min > 0) {
1683
+ if (counted) b.get(n).i32(min).op(Op.i32LtU)
1684
+ else b.get(0).get(st).op(Op.i32Eq)
1685
+ this.failIf(b, s.fail)
1686
+ }
1687
+ return this.sliceValue(s, enc, st, wide)
1688
+ }
1689
+
1690
+ /**
1691
+ * The vector steps of an unbounded `takeWhile` on bytes, where every
1692
+ * byte is a token: sixteen bytes, up to the first that is not accepted,
1693
+ * which ends the run (most runs, tokens, end in the first sixteen); after
1694
+ * sixteen accepted, thirty-two at a time while all are. The
1695
+ * scalar loop takes the last fifteen bytes. Applies to `Token` and
1696
+ * Latin-1 text on bytes, where SIMD validates. (UTF-8, whose bytes from
1697
+ * 0x80 up are parts of code points, and strings, whose units are
1698
+ * sixteen-bit, stay scalar: on the JSON benchmark, whose runs are short,
1699
+ * a vector step there costs more than it saves.)
1700
+ */
1701
+ private byteRun(b: Body, set: Membership, stop: Label): void {
1702
+ const x = b.localV128()
1703
+ const y = b.localV128()
1704
+ const accepted = this.acceptedBytes(b, set)
1705
+ b.block((done) => {
1706
+ b.loop((again) => {
1707
+ this.end(b).get(0).op(Op.i32Sub).i32(16).op(Op.i32LtS).brIf(done)
1708
+ b.get(0).simd(Simd.v128Load, 0, Input).set(x)
1709
+ accepted(x)
1710
+ b.tee(x).simd(Simd.i8x16AllTrue)
1711
+ b.if(() => {
1712
+ this.advance(b, 16)
1713
+ // A run this long may be longer: thirty-two bytes at a time
1714
+ b.block((short) => {
1715
+ b.loop((more) => {
1716
+ this.end(b).get(0).op(Op.i32Sub).i32(32).op(Op.i32LtS).brIf(short)
1717
+ b.get(0).simd(Simd.v128Load, 0, Input).set(x)
1718
+ b.get(0)
1719
+ .simd(Simd.v128Load, 0, Input + 16)
1720
+ .set(y)
1721
+ accepted(x)
1722
+ accepted(y)
1723
+ b.simd(Simd.v128And).simd(Simd.i8x16AllTrue).op(Op.i32Eqz).brIf(short)
1724
+ this.advance(b, 32)
1725
+ b.br(more)
1726
+ })
1727
+ })
1728
+ b.br(again)
1729
+ })
1730
+ // Up to the first byte that is not accepted: the end of the run
1731
+ b.get(0).get(x).simd(Simd.i8x16Bitmask).i32(-1).op(Op.i32Xor).op(Op.i32Ctz).op(Op.i32Add).set(0)
1732
+ b.br(stop)
1733
+ })
1734
+ })
1735
+ }
1736
+
1737
+ /** Whether a `takeWhile` of `set` in `enc` has vector steps (see {@link byteRun}). */
1738
+ private vectorRun(enc: Enc, set: Membership): boolean {
1739
+ return this.kind === 1 && (enc === undefined || enc === latin1) && simd() && accepts(set)
1740
+ }
1741
+
1742
+ /**
1743
+ * Code that pushes, for the sixteen bytes in a local, 0xFF where the byte
1744
+ * is accepted and 0 where not: comparisons with the rejected bytes when
1745
+ * there are at most three of them (`takeTill` of a few stops), else two
1746
+ * swizzles of nibble tables for each half of the byte values. Its
1747
+ * constants are set up here, in locals.
1748
+ */
1749
+ private acceptedBytes(b: Body, set: Membership): (x: number) => void {
1750
+ const rejected: Array<number> = []
1751
+ for (let c = 0; c < 256 && rejected.length <= MaxStops; c++) {
1752
+ if ((set.cs.table[c] === 1) === set.negate) rejected.push(c)
1753
+ }
1754
+ if (rejected.length <= MaxStops) {
1755
+ const stops = rejected.map((c) => {
1756
+ const l = b.localV128()
1757
+ b.i32(c).simd(Simd.i8x16Splat).set(l)
1758
+ return l
1759
+ })
1760
+ return (x) => {
1761
+ b.v128(Zeros)
1762
+ for (const l of stops) b.get(x).get(l).simd(Simd.i8x16Eq).simd(Simd.v128Or)
1763
+ b.simd(Simd.v128Not)
1764
+ }
1765
+ }
1766
+ const tables = [nibbleTable(set, 0), nibbleTable(set, 8)].map((table, k) => {
1767
+ const lo = b.localV128()
1768
+ const bits = b.localV128()
1769
+ b.v128(table)
1770
+ .set(lo)
1771
+ .v128(k === 0 ? HighNibbleBits : UpperNibbleBits)
1772
+ .set(bits)
1773
+ return [lo, bits] as const
1774
+ })
1775
+ return (x) => {
1776
+ tables.forEach(([lo, bits], k) => {
1777
+ b.get(lo).get(x).v128(LowNibbles).simd(Simd.v128And)
1778
+ nibbleSwizzle(b)
1779
+ b.get(bits).get(x).i32(4).simd(Simd.i8x16ShrU)
1780
+ nibbleSwizzle(b)
1781
+ b.simd(Simd.v128And)
1782
+ if (k > 0) b.simd(Simd.v128Or)
1783
+ })
1784
+ b.v128(Zeros).simd(Simd.i8x16Ne)
1785
+ }
1786
+ }
1787
+
1788
+ /** One code point of a `takeWhile`: advances past it, or branches to `stop`. */
1789
+ private whileStep(enc: Enc, b: Body, set: Membership, w: number, wide: number, stop: Label): void {
1790
+ const r = Option.map(Option.fromUndefinedOr(enc), (e) => this.reader(e))
1791
+ if (Option.isSome(r) && r.value.decode >= 0) {
1792
+ // Text: the encoding's single-unit code points inline, the rest decoded
1793
+ const reader = r.value
1794
+ this.fastUnit(reader, b, set.c)
1795
+ b.if(
1796
+ () => {
1797
+ this.member(set.cs, set.negate, set.c, b, reader.fast)
1798
+ b.op(Op.i32Eqz).brIf(stop)
1799
+ this.advance(b, reader.width)
1800
+ },
1801
+ () => {
1802
+ b.get(0).call(reader.decode).tee(set.c).i32(0).op(Op.i32LtS).brIf(stop)
1803
+ if (wide >= 0) b.i32(1).set(wide)
1804
+ this.member(set.cs, set.negate, set.c, b)
1805
+ b.op(Op.i32Eqz).brIf(stop)
1806
+ b.get(0).gget(G.w).op(Op.i32Add).set(0)
1807
+ }
1808
+ )
1809
+ return
1810
+ }
1811
+ if (enc === undefined && this.kind === 0) {
1812
+ // Token on strings: a unit that isn't a surrogate is a code point
1813
+ this.load(b, 0)
1814
+ b.tee(set.c).i32(0xf800).op(Op.i32And).i32(0xd800).op(Op.i32Ne)
1815
+ b.if(
1816
+ () => {
1817
+ this.member(set.cs, set.negate, set.c, b, 0x10000)
1818
+ b.op(Op.i32Eqz).brIf(stop)
1819
+ this.advance(b, 1)
1820
+ },
1821
+ () => this.codePointStep(enc, b, set, w, stop)
1822
+ )
1823
+ return
1824
+ }
1825
+ this.codePointStep(enc, b, set, w, stop)
1826
+ }
1827
+
1828
+ /** Reads a code point and tests it, advancing past it, or branches to `stop`. */
1829
+ private codePointStep(enc: Enc, b: Body, set: Membership, w: number, stop: Label): void {
1830
+ this.readCodePoint(enc, b, set.c, w, stop)
1831
+ this.member(set.cs, set.negate, set.c, b)
1832
+ b.op(Op.i32Eqz).brIf(stop)
1833
+ b.get(0).get(w).op(Op.i32Add).set(0)
1834
+ }
1835
+
1836
+ /**
1837
+ * `takeUntil` and `takeUntilAny` on bytes: Teddy (as in ripgrep). Sixteen
1838
+ * starts at a time, the first `m` bytes of every literal (up to three)
1839
+ * are tested at once, with nibble tables: a byte belongs to bucket k at
1840
+ * position j when bit k is set both in the low-nibble table at its low
1841
+ * nibble and in the high-nibble table at its high nibble, and a start is
1842
+ * a candidate when one bucket holds at every position. Each candidate is
1843
+ * checked against the literals in full; the last starts, fewer than
1844
+ * sixteen, are checked one at a time. Without SIMD, with a range, or on
1845
+ * other input, the search runs in JavaScript.
1846
+ */
1847
+ private takeUntil(s: Site, node: NodeOf<"TakeUntil" | "TakeUntilAny">): string {
1848
+ const literals = Predicate.isTagged(node, "TakeUntil") ? [node.literal] : node.literals
1849
+ const bytes = literals.filter(Predicate.isUint8Array)
1850
+ const shortest = Math.min(...bytes.map((lit) => lit.length))
1851
+ const searchable =
1852
+ this.kind === 1 &&
1853
+ node.encoding === undefined &&
1854
+ node.min === 0 &&
1855
+ node.max === Infinity &&
1856
+ bytes.length === literals.length &&
1857
+ shortest > 0 &&
1858
+ shortest !== Infinity &&
1859
+ bytes.length <= MaxUntil &&
1860
+ simd()
1861
+ if (!searchable) return this.island(s.p, s.out, s.b, s.fail, "the search runs in JavaScript")
1862
+ const { b, fail } = s
1863
+ const m = Math.min(MaxPositions, shortest)
1864
+ const st = b.local()
1865
+ const c = b.local()
1866
+ const mask = b.local()
1867
+ const acc = b.localV128()
1868
+ // The tables and the nibble mask, in locals for the loop
1869
+ const position = this.teddyPosition(b, bytes, m, acc)
1870
+ b.get(0).set(st)
1871
+ b.block((found) => {
1872
+ b.block((tail) => {
1873
+ b.loop((again) => {
1874
+ // Sixteen starts need their `m` bytes inside the input
1875
+ this.end(b)
1876
+ .get(0)
1877
+ .op(Op.i32Sub)
1878
+ .i32(15 + m)
1879
+ .op(Op.i32LtS)
1880
+ .brIf(tail)
1881
+ for (let j = 0; j < m; j++) {
1882
+ position(j)
1883
+ if (j > 0) b.simd(Simd.v128And)
1884
+ }
1885
+ b.tee(acc).simd(Simd.v128AnyTrue).op(Op.i32Eqz)
1886
+ b.if(() => {
1887
+ this.advance(b, 16)
1888
+ b.br(again)
1889
+ })
1890
+ // Each candidate of the sixteen, in order, checked in full
1891
+ b.get(acc).v128(Zeros).simd(Simd.i8x16Ne).simd(Simd.i8x16Bitmask).set(mask)
1892
+ b.loop((bits) => {
1893
+ b.get(0).get(mask).op(Op.i32Ctz).op(Op.i32Add).set(c)
1894
+ this.literalAt(b, bytes, c, found)
1895
+ b.get(mask).get(mask).i32(1).op(Op.i32Sub).op(Op.i32And).tee(mask).brIf(bits)
1896
+ })
1897
+ this.advance(b, 16)
1898
+ b.br(again)
1899
+ })
1900
+ })
1901
+ b.loop((again) => {
1902
+ this.end(b).get(0).op(Op.i32Sub).i32(shortest).op(Op.i32LtS)
1903
+ this.failIf(b, fail)
1904
+ b.get(0).set(c)
1905
+ this.literalAt(b, bytes, c, found)
1906
+ this.advance(b, 1)
1907
+ b.br(again)
1908
+ })
1909
+ })
1910
+ return this.sliceValue(s, undefined, st, -1)
1911
+ }
1912
+
1913
+ /**
1914
+ * Code that pushes, for the sixteen starts from the cursor, what position
1915
+ * `j` of the literals says: with at most three distinct bytes there,
1916
+ * 0xFF where the byte is one of them (`i8x16.eq`); otherwise Teddy's
1917
+ * bucket bits, from two nibble-table swizzles. Either way a start is a
1918
+ * candidate where the AND over the positions is not zero. The constants
1919
+ * are set up here, in locals; `v` is a scratch local.
1920
+ */
1921
+ private teddyPosition(b: Body, literals: ReadonlyArray<Uint8Array>, m: number, v: number): (j: number) => void {
1922
+ const distinct = Array.from({ length: m }, (_, j) => Arr.dedupe(literals.map((lit) => lit[j] ?? 0)))
1923
+ if (distinct.every((bytes) => bytes.length <= MaxStops)) {
1924
+ const splats = distinct.map((bytes) =>
1925
+ bytes.map((byte) => {
1926
+ const l = b.localV128()
1927
+ b.i32(byte).simd(Simd.i8x16Splat).set(l)
1928
+ return l
1929
+ })
1930
+ )
1931
+ return (j) => {
1932
+ b.get(0)
1933
+ .simd(Simd.v128Load, 0, Input + j)
1934
+ .set(v)
1935
+ b.v128(Zeros)
1936
+ for (const l of splats[j] ?? []) b.get(v).get(l).simd(Simd.i8x16Eq).simd(Simd.v128Or)
1937
+ }
1938
+ }
1939
+ const nibbles = b.localV128()
1940
+ b.v128(LowNibbles).set(nibbles)
1941
+ const tables = teddyTables(literals, m).map(([lo, hi]) => {
1942
+ const l = b.localV128()
1943
+ const h = b.localV128()
1944
+ b.v128(lo).set(l).v128(hi).set(h)
1945
+ return [l, h] as const
1946
+ })
1947
+ return (j) => {
1948
+ const [lo, hi] = tables[j] ?? [-1, -1]
1949
+ b.get(0)
1950
+ .simd(Simd.v128Load, 0, Input + j)
1951
+ .set(v)
1952
+ b.get(lo).get(v).get(nibbles).simd(Simd.v128And)
1953
+ nibbleSwizzle(b)
1954
+ b.get(hi).get(v).i32(4).simd(Simd.i8x16ShrU)
1955
+ nibbleSwizzle(b)
1956
+ b.simd(Simd.v128And)
1957
+ }
1958
+ }
1959
+
1960
+ /** Moves the cursor to `c` and branches to `found` when one of `literals` starts at `c`. */
1961
+ private literalAt(b: Body, literals: ReadonlyArray<Uint8Array>, c: number, found: Label): void {
1962
+ for (const lit of literals) {
1963
+ b.block((next) => {
1964
+ this.end(b).get(c).op(Op.i32Sub).i32(lit.length).op(Op.i32LtS).brIf(next)
1965
+ // Compared as many bytes at a time as the literal fills
1966
+ const bytes = Array.from(lit)
1967
+ for (let i = 0; i < bytes.length;) {
1968
+ const n = chunkSize(bytes.length - i)
1969
+ b.get(c)
1970
+ compareChunk(b, bytes.slice(i, i + n), [], Input + i)
1971
+ b.brIf(next)
1972
+ i += n
1973
+ }
1974
+ b.get(c).set(0).br(found)
1975
+ })
1976
+ }
1977
+ }
1978
+
1979
+ /** The trace and builder code of a `takeWhile`'s slice `[st, p)`. */
1980
+ private sliceValue(s: Site, enc: Enc, st: number, wide: number): string {
1981
+ const { b } = s
1982
+ if (s.out === "") return ""
1983
+ if (enc === undefined && this.kind === 1) {
1984
+ this.put(b, [() => b.get(st), () => b.get(0)])
1985
+ return this.set(s, "I.subarray(T[i],T[i+1]);i+=2")
1986
+ }
1987
+ const start = () => {
1988
+ b.get(st)
1989
+ if (wide >= 0) b.get(wide).i32(31).op(Op.i32Shl).op(Op.i32Or)
1990
+ }
1991
+ // A string: short ones are interned, unless they are pieces of an `escaped`, which interns its own
1992
+ if (this.plainStrings) {
1993
+ this.put(b, [start, () => b.get(0)])
1994
+ return this.set(s, `${this.decodeSlice(enc)};i+=2`)
1995
+ }
1996
+ this.put(b, [start, () => b.get(0), () => this.internCall(b, st), () => b.gget(G.w)])
1997
+ return this.interned(s.out, this.decodeSlice(enc))
1998
+ }
1999
+
2000
+ /** Pushes the cache slot of `[st, p)` (see `intern`), testing the length inline: most long slices aren't hashed. */
2001
+ private internCall(b: Body, st: number): void {
2002
+ b.get(0).get(st).op(Op.i32Sub).i32(MaxInterned).op(Op.i32GtU)
2003
+ b.ifI32(
2004
+ () => b.i32(-1),
2005
+ () => b.get(st).get(0).call(this.intern())
2006
+ )
2007
+ }
2008
+
2009
+ /**
2010
+ * Builder code for the string of the slice of a `takeWhile` at `T[i]`,
2011
+ * `T[i+1]`. A UTF-8 slice whose start is not negative is ASCII, which
2012
+ * `R.ascii` decodes without looking for longer sequences.
2013
+ */
2014
+ private decodeSlice(enc: Enc): string {
2015
+ if (enc === utf8 && this.kind === 1) {
2016
+ return `(T[i]<0?${this.const(enc.text)}(I,T[i]&2147483647,T[i+1]):R.ascii(I,T[i],T[i+1]))`
2017
+ }
2018
+ return enc !== undefined && this.kind === 1 ? `${this.const(enc.text)}(I,T[i],T[i+1])` : "I.slice(T[i],T[i+1])"
2019
+ }
2020
+
2021
+ /** While generating the pieces of an `escaped` whose single-piece strings are interned. */
2022
+ private plainStrings = false
2023
+
2024
+ private literal(s: Site, node: NodeOf<"Literal">): string {
2025
+ const { b, fail } = s
2026
+ const units = this.literalUnits(node)
2027
+ if (Result.isFailure(units)) return this.island(s.p, s.out, b, fail, units.failure)
2028
+ const { codes, constant } = units.success
2029
+ const len = codes.length
2030
+ const st = b.local()
2031
+ b.get(0).set(st)
2032
+ if (len > 0) {
2033
+ this.end(b).get(0).op(Op.i32Sub).i32(len).op(Op.i32LtS)
2034
+ this.failIf(b, fail)
2035
+ this.compareUnits(b, codes, node.caseless, fail)
2036
+ this.advance(b, len)
2037
+ }
2038
+ if (s.out === "") return ""
2039
+ if (constant) return this.set(s, this.lit(node.literal))
2040
+ this.put(b, [() => b.get(st)])
2041
+ return this.set(s, this.kind === 0 ? `I.slice(T[i],T[i]+${len});i++` : `I.subarray(T[i],T[i]+${len});i++`)
2042
+ }
2043
+
2044
+ /**
2045
+ * The units a literal matches, and whether its value is the literal itself
2046
+ * (rather than the input it matched, as a caseless one returns), or why it
2047
+ * runs in JavaScript.
2048
+ */
2049
+ /**
2050
+ * Fails unless the input at the cursor holds `codes` (folding ASCII
2051
+ * letters with `caseless`): the units' bytes compared sixteen, eight,
2052
+ * four, two and one at a time, with `v128`, `i64` and `i32` loads, a
2053
+ * caseless letter's byte ORed with 0x20 first.
2054
+ */
2055
+ private compareUnits(b: Body, codes: ReadonlyArray<number>, caseless: boolean, fail: Label): void {
2056
+ const { bytes, fold } = unitBytes(codes, caseless, this.kind === 0 ? 2 : 1)
2057
+ // The address of the cursor's byte, once
2058
+ const at = b.local()
2059
+ if (this.kind === 0) b.get(0).i32(1).op(Op.i32Shl).set(at)
2060
+ else b.get(0).set(at)
2061
+ for (let i = 0; i < bytes.length;) {
2062
+ const n = chunkSize(bytes.length - i)
2063
+ b.get(at)
2064
+ compareChunk(b, bytes.slice(i, i + n), fold.slice(i, i + n), Input + i)
2065
+ this.failIf(b, fail)
2066
+ i += n
2067
+ }
2068
+ }
2069
+
2070
+ private literalUnits(
2071
+ node: NodeOf<"Literal">
2072
+ ): Result.Result<{ readonly codes: ReadonlyArray<number>; readonly constant: boolean }, string> {
2073
+ const lit = node.literal
2074
+ const enc = node.encoding
2075
+ if (enc !== undefined) {
2076
+ if (node.caseless || !Predicate.isString(lit)) return Result.fail("caseless text literal")
2077
+ const encoded = enc.encode(lit)
2078
+ return Result.succeed({
2079
+ codes: Predicate.isString(encoded) ? unitsOf(encoded) : Array.from(encoded),
2080
+ constant: true
2081
+ })
2082
+ }
2083
+ const constant = !node.caseless
2084
+ if (Predicate.isString(lit)) {
2085
+ return Result.succeed(
2086
+ this.kind === 0
2087
+ ? { codes: unitsOf(lit), constant }
2088
+ : { codes: Array.from(utf8Encoder.encode(lit)), constant: false }
2089
+ )
2090
+ }
2091
+ if (lit instanceof Uint8Array && this.kind === 1) return Result.succeed({ codes: Array.from(lit), constant })
2092
+ return Result.fail("the literal is not of the input's kind")
2093
+ }
2094
+
2095
+ /** `escaped`: segments of `normal` and escapes after the control character. */
2096
+ private escaped(s: Site, node: NodeOf<"Escaped">): string {
2097
+ const { b } = s
2098
+ const enc = node.encoding
2099
+ if (!this.native(enc)) return this.island(s.p, s.out, b, s.fail, "the encoding is decoded in JavaScript")
2100
+ const r = Option.map(Option.fromUndefinedOr(enc), (e) => this.reader(e))
2101
+ if (Option.isSome(r) && !(node.control < r.value.fast)) {
2102
+ return this.island(s.p, s.out, b, s.fail, "the control character is decoded")
2103
+ }
2104
+ const pieces = !node.raw && s.out !== ""
2105
+ const e: EscapedLocals = {
2106
+ r,
2107
+ pieces,
2108
+ // A string of one piece of `takeWhile` is interned as a whole
2109
+ internsWhole:
2110
+ pieces && Predicate.isTagged(nodeOf(node.normal), "TakeWhile") && (enc !== undefined || this.kind === 0),
2111
+ start: b.local(),
2112
+ seg: b.local(),
2113
+ ok: b.local(),
2114
+ ts: b.local(),
2115
+ count: b.local(),
2116
+ pieceStart: b.local(),
2117
+ slot: pieces ? this.slot(b) : -1,
2118
+ x: pieces ? this.v(s.bf) : ""
2119
+ }
2120
+ let normal = ""
2121
+ let escape = ""
2122
+ b.get(0).set(e.start)
2123
+ b.i32(0).set(e.count)
2124
+ b.block((exit) => {
2125
+ this.loop(b, (again) => {
2126
+ this.atEnd(b, 0)
2127
+ b.brIf(exit)
2128
+ normal = this.escapedNormal(s, node, e, again)
2129
+ // The control character?
2130
+ if (Option.isNone(r)) {
2131
+ this.atEnd(b, 0)
2132
+ b.brIf(exit)
2133
+ this.load(b, 0)
2134
+ } else {
2135
+ r.value.guard(b, 0)
2136
+ b.op(Op.i32Eqz).brIf(exit)
2137
+ r.value.unit(b, 0)
2138
+ }
2139
+ b.i32(node.control).op(Op.i32Ne).brIf(exit)
2140
+ this.advance(b, Option.match(r, { onNone: () => 1, onSome: (x) => x.width }))
2141
+ if (pieces) this.put(b, [() => b.i32(1)])
2142
+ escape = this.gen(node.escape, e.x, b, s.fail, s.bf, s.d)
2143
+ b.get(e.count).i32(1).op(Op.i32Add).set(e.count)
2144
+ b.br(again)
2145
+ })
2146
+ })
2147
+ return this.escapedValue(s, node, e, normal, escape)
2148
+ }
2149
+
2150
+ /**
2151
+ * A segment of `normal`: one that consumed input continues the loop; one
2152
+ * that matched nothing, or failed with `Backtrack`, is dropped (with its
2153
+ * trace) for the control character to be tested.
2154
+ */
2155
+ private escapedNormal(s: Site, node: NodeOf<"Escaped">, e: EscapedLocals, again: Label): string {
2156
+ const { b } = s
2157
+ let code = ""
2158
+ b.get(0).set(e.seg).get(b.tp).set(e.ts).i32(0).set(e.ok)
2159
+ b.block((bad) => {
2160
+ if (e.pieces) this.put(b, [() => b.i32(0)])
2161
+ const plain = this.plainStrings
2162
+ this.plainStrings = e.internsWhole
2163
+ code = this.gen(node.normal, e.x, b, bad, s.bf, s.d)
2164
+ this.plainStrings = plain
2165
+ b.i32(1).set(e.ok)
2166
+ })
2167
+ b.get(e.ok)
2168
+ b.if(
2169
+ () => {
2170
+ b.get(0)
2171
+ .get(e.seg)
2172
+ .op(node.raw ? Op.i32Eq : Op.i32Ne)
2173
+ if (node.raw) {
2174
+ // `takeEscaped`: a segment that matched nothing is an error
2175
+ b.if(() => this.failWith(b, s.fail, Cut))
2176
+ b.br(again)
2177
+ return
2178
+ }
2179
+ b.if(() => {
2180
+ b.get(e.count).i32(1).op(Op.i32Add).set(e.count)
2181
+ b.get(e.seg).set(e.pieceStart)
2182
+ b.br(again)
2183
+ })
2184
+ b.get(e.ts).set(b.tp)
2185
+ },
2186
+ () => {
2187
+ this.unlessBacktrack(b, s.fail)
2188
+ b.get(e.seg).set(0).get(e.ts).set(b.tp)
2189
+ }
2190
+ )
2191
+ return code
2192
+ }
2193
+
2194
+ /** The trace and builder code of an `escaped`'s value, from its pieces' code. */
2195
+ private escapedValue(s: Site, node: NodeOf<"Escaped">, e: EscapedLocals, normal: string, escape: string): string {
2196
+ const { b, out } = s
2197
+ const enc = node.encoding
2198
+ if (out === "") return ""
2199
+ if (node.raw) {
2200
+ this.put(b, [() => b.get(e.start), () => b.get(0)])
2201
+ if (enc !== undefined && this.kind === 1) return `${out}=${this.const(enc.text)}(I,T[i],T[i+1]);i+=2;`
2202
+ return `${out}=${this.kind === 0 ? "I.slice(T[i],T[i+1])" : "I.subarray(T[i],T[i+1])"};i+=2;`
2203
+ }
2204
+ b.get(e.slot).get(e.count).mem(Op.i32Store, 2, 0)
2205
+ const n = this.v(s.bf)
2206
+ const k = this.v(s.bf)
2207
+ const loop = `${out}="";for(${k}=0;${k}<${n};${k}++){` + `if(T[i++]===0){${normal}}else{${escape}}${out}+=${e.x};}`
2208
+ if (!e.internsWhole) {
2209
+ // Most strings are one segment without escapes
2210
+ return `${n}=T[i++];if(${n}===1&&T[i]===0){i++;${normal}${out}=${e.x}}else{${loop}}`
2211
+ }
2212
+ // One piece, not an escape: its slice is followed by its cache slot and version
2213
+ b.get(e.count).i32(1).op(Op.i32Eq)
2214
+ b.if(() => {
2215
+ b.get(e.slot).mem(Op.i32Load, 2, 4).op(Op.i32Eqz)
2216
+ b.if(() => this.put(b, [() => this.internCall(b, e.pieceStart), () => b.gget(G.w)]))
2217
+ })
2218
+ return `${n}=T[i++];if(${n}===1&&T[i]===0){i++;${this.interned(out, this.decodeSlice(enc))}}else{${loop}}`
2219
+ }
2220
+
2221
+ // ---------------------------------------------------------------------------
2222
+ // Interned strings
2223
+ // ---------------------------------------------------------------------------
2224
+
2225
+ /** The strings of the cache slots, and the version of the slot each was decoded from. */
2226
+ // Only read for a slot whose version the builder decoded, so the placeholders are never returned
2227
+ // Allocated when a grammar first interns a string
2228
+ private interning = false
2229
+ private internedStrings: Array<string> = []
2230
+ private internedVersions = /*#__PURE__*/ new Int32Array(0)
2231
+ private internIndex = -1
2232
+
2233
+ /**
2234
+ * Builder code for a string slice traced as start, end, slot and version
2235
+ * (see `intern`): the string decoded from that slot's version if there is
2236
+ * one, else `decode`, which is then kept for the slot.
2237
+ */
2238
+ private interned(out: string, decode: string): string {
2239
+ if (!this.interning) {
2240
+ this.interning = true
2241
+ this.internedStrings = new Array<string>(StringSlots).fill("")
2242
+ this.internedVersions = new Int32Array(StringSlots).fill(-1)
2243
+ }
2244
+ const ks = this.const(this.internedStrings)
2245
+ const kv = this.const(this.internedVersions)
2246
+ return (
2247
+ `${out}=T[i+2];if(${out}>=0&&${kv}[${out}]===T[i+3])${out}=${ks}[${out}];` +
2248
+ `else if(${out}>=0){${kv}[${out}]=T[i+3];${out}=${ks}[${out}]=${decode}}else ${out}=${decode};i+=4;`
2249
+ )
2250
+ }
2251
+
2252
+ /**
2253
+ * `(start, end) -> slot`: the cache slot of the units of `[start, end)`,
2254
+ * or -1 if there are more than `MaxInterned`, with the slot's version in
2255
+ * `G.w`. A slot holds the units of the latest slice hashed to it, and its
2256
+ * version changes whenever they do, so every slice traced with a slot and
2257
+ * a version has the same units, and the same string: the builder decodes
2258
+ * it once per version. The cache outlives a parse, and backtracking can't
2259
+ * invalidate it.
2260
+ */
2261
+ private intern(): number {
2262
+ if (this.internIndex >= 0) return this.internIndex
2263
+ const [index, b] = this.reserve(Ty.pair, 2)
2264
+ this.internIndex = index
2265
+ const start = 0
2266
+ const end = 1
2267
+ const len = b.local()
2268
+ const h = b.local()
2269
+ const k = b.local()
2270
+ const addr = b.local()
2271
+ const ver = b.local()
2272
+ const width = this.kind === 0 ? 2 : 1
2273
+ const unitAt = (pos: () => void) =>
2274
+ this.kind === 0
2275
+ ? (pos(), b.i32(1).op(Op.i32Shl).mem(Op.i32Load16U, 1, Input))
2276
+ : (pos(), b.mem(Op.i32Load8U, 0, Input))
2277
+ const cached = (j: () => void) => {
2278
+ b.get(addr)
2279
+ j()
2280
+ if (width === 2) b.i32(1).op(Op.i32Shl)
2281
+ b.op(Op.i32Add)
2282
+ b.mem(width === 2 ? Op.i32Load16U : Op.i32Load8U, width === 2 ? 1 : 0, 8)
2283
+ }
2284
+ b.get(end).get(start).op(Op.i32Sub).tee(len).i32(MaxInterned).op(Op.i32GtU)
2285
+ b.if(() => b.i32(-1).op(Op.return))
2286
+ b.get(len).set(h)
2287
+ b.get(start).set(k)
2288
+ b.block((done) =>
2289
+ b.loop((again) => {
2290
+ b.get(k).get(end).op(Op.i32GeU).brIf(done)
2291
+ b.get(h).i32(31).op(Op.i32Mul)
2292
+ unitAt(() => b.get(k))
2293
+ b.op(Op.i32Add).set(h)
2294
+ b.get(k).i32(1).op(Op.i32Add).set(k)
2295
+ b.br(again)
2296
+ })
2297
+ )
2298
+ b.get(h)
2299
+ .get(h)
2300
+ .i32(10)
2301
+ .op(Op.i32ShrU)
2302
+ .op(Op.i32Xor)
2303
+ .i32(StringSlots - 1)
2304
+ .op(Op.i32And)
2305
+ .set(h)
2306
+ b.get(h).i32(SlotBytes).op(Op.i32Mul).i32(Strings).op(Op.i32Add).set(addr)
2307
+ // The same units?
2308
+ b.block((differ) => {
2309
+ b.get(addr).mem(Op.i32Load, 2, 4).get(len).op(Op.i32Ne).brIf(differ)
2310
+ b.i32(0).set(k)
2311
+ b.block((same) =>
2312
+ b.loop((again) => {
2313
+ b.get(k).get(len).op(Op.i32GeU).brIf(same)
2314
+ cached(() => b.get(k))
2315
+ unitAt(() => b.get(start).get(k).op(Op.i32Add))
2316
+ b.op(Op.i32Ne).brIf(differ)
2317
+ b.get(k).i32(1).op(Op.i32Add).set(k)
2318
+ b.br(again)
2319
+ })
2320
+ )
2321
+ b.get(addr).mem(Op.i32Load, 2, 0).gset(G.w)
2322
+ b.get(h).op(Op.return)
2323
+ })
2324
+ // No: install them under a new version
2325
+ b.get(addr).mem(Op.i32Load, 2, 0).i32(1).op(Op.i32Add).set(ver)
2326
+ b.get(addr).get(ver).mem(Op.i32Store, 2, 0)
2327
+ b.get(addr).get(len).mem(Op.i32Store, 2, 4)
2328
+ b.i32(0).set(k)
2329
+ b.block((done) =>
2330
+ b.loop((again) => {
2331
+ b.get(k).get(len).op(Op.i32GeU).brIf(done)
2332
+ b.get(addr).get(k)
2333
+ if (width === 2) b.i32(1).op(Op.i32Shl)
2334
+ b.op(Op.i32Add)
2335
+ unitAt(() => b.get(start).get(k).op(Op.i32Add))
2336
+ b.mem(width === 2 ? Op.i32Store16 : Op.i32Store8, width === 2 ? 1 : 0, 8)
2337
+ b.get(k).i32(1).op(Op.i32Add).set(k)
2338
+ b.br(again)
2339
+ })
2340
+ )
2341
+ b.get(ver).gset(G.w)
2342
+ b.get(h)
2343
+ return index
2344
+ }
2345
+
2346
+ // ---------------------------------------------------------------------------
2347
+ // Helper functions
2348
+ // ---------------------------------------------------------------------------
2349
+
2350
+ /**
2351
+ * `(pos, end, len, tp, tlim) -> p | -1`: sets up the globals of a parse
2352
+ * and runs the root, in one call from JavaScript (setting a global from
2353
+ * JavaScript costs about as much as a call).
2354
+ */
2355
+ starter(root: number): number {
2356
+ const [index, b] = this.reserve(Ty.start, 5)
2357
+ b.get(1).gset(G.end).get(2).gset(G.len).get(3).gset(G.tp).get(4).gset(G.tlim)
2358
+ b.i32(0).gset(G.nv).i32(0).gset(G.mode)
2359
+ b.get(0).call(root)
2360
+ return index
2361
+ }
2362
+
2363
+ /**
2364
+ * `(src, n) -> count`, for string input: widens the `n` bytes of UTF-8 at
2365
+ * `src`, which `TextEncoder#encodeInto` wrote, to the string's UTF-16
2366
+ * units at `Input`, sixteen ASCII bytes at a time with SIMD (where it is
2367
+ * available) and eight without. `encodeInto` writes each
2368
+ * lone surrogate as U+FFFD, which is one unit as well, so every unit is in
2369
+ * place; the units that are U+FFFD (or were a lone surrogate) are listed
2370
+ * as 32-bit indices from `(src + n + 3) & -4`, for JavaScript to copy from
2371
+ * the string. Returns their number.
2372
+ */
2373
+ widener(): number {
2374
+ const [index, b] = this.reserve(Ty.pair, 2)
2375
+ const src = 0
2376
+ const n = 1
2377
+ const end = b.local()
2378
+ const k = b.local()
2379
+ const c = b.local()
2380
+ const cp = b.local()
2381
+ const count = b.local()
2382
+ const list = b.local()
2383
+ const x = b.localI64()
2384
+ const byte = (at: number) => b.get(src).mem(Op.i32Load8U, 0, at)
2385
+ const cont = (at: number, shift: number) => {
2386
+ byte(at)
2387
+ b.i32(0x3f).op(Op.i32And)
2388
+ if (shift > 0) b.i32(shift).op(Op.i32Shl)
2389
+ b.op(Op.i32Or)
2390
+ }
2391
+ const unit = (delta: number, push: () => void) => {
2392
+ b.get(k).i32(1).op(Op.i32Shl)
2393
+ push()
2394
+ b.mem(Op.i32Store16, 1, Input + 2 * delta)
2395
+ }
2396
+ const step = (bytes: number, units: number, again: Label) => {
2397
+ b.get(src).i32(bytes).op(Op.i32Add).set(src)
2398
+ b.get(k).i32(units).op(Op.i32Add).set(k)
2399
+ b.br(again)
2400
+ }
2401
+ /** Spreads four bytes (the low 32 bits of `x`, shifted right by `shift`) to four 16-bit units. */
2402
+ const spread = (shift: number) => {
2403
+ b.get(x)
2404
+ if (shift > 0) b.i64(BigInt(shift)).op(Op.i64ShrU)
2405
+ b.i64(0xffffffffn).op(Op.i64And)
2406
+ b.tee(x).get(x).i64(16n).op(Op.i64Shl).op(Op.i64Or).i64(0x0000ffff0000ffffn).op(Op.i64And)
2407
+ b.tee(x).get(x).i64(8n).op(Op.i64Shl).op(Op.i64Or).i64(0x00ff00ff00ff00ffn).op(Op.i64And)
2408
+ }
2409
+ b.get(src).get(n).op(Op.i32Add).tee(end).i32(3).op(Op.i32Add).i32(-4).op(Op.i32And).set(list)
2410
+ b.block((done) =>
2411
+ b.loop((again) => {
2412
+ b.get(src).get(end).op(Op.i32GeU).brIf(done)
2413
+ // Eight ASCII bytes
2414
+ if (simd()) {
2415
+ // Sixteen ASCII bytes, with SIMD
2416
+ b.get(src).i32(16).op(Op.i32Add).get(end).op(Op.i32LeU)
2417
+ b.if(() => {
2418
+ b.get(src).simd(Simd.v128Load, 0, 0).simd(Simd.i8x16Bitmask).op(Op.i32Eqz)
2419
+ b.if(() => {
2420
+ b.get(k).i32(1).op(Op.i32Shl)
2421
+ b.get(src).simd(Simd.v128Load, 0, 0).simd(Simd.i16x8ExtendLowI8x16U).simd(Simd.v128Store, 0, Input)
2422
+ b.get(k).i32(1).op(Op.i32Shl)
2423
+ b.get(src)
2424
+ .simd(Simd.v128Load, 0, 0)
2425
+ .simd(Simd.i16x8ExtendHighI8x16U)
2426
+ .simd(Simd.v128Store, 0, Input + 16)
2427
+ step(16, 16, again)
2428
+ })
2429
+ })
2430
+ }
2431
+ b.get(src).i32(8).op(Op.i32Add).get(end).op(Op.i32LeU)
2432
+ b.if(() => {
2433
+ b.get(src).mem(Op.i64Load, 0, 0).tee(x).i64(-0x7f7f7f7f7f7f7f80n).op(Op.i64And).op(Op.i64Eqz)
2434
+ b.if(() => {
2435
+ const word = b.localI64()
2436
+ b.get(x).set(word)
2437
+ b.get(k).i32(1).op(Op.i32Shl)
2438
+ spread(0)
2439
+ b.mem(Op.i64Store, 0, Input)
2440
+ b.get(word).set(x)
2441
+ b.get(k).i32(1).op(Op.i32Shl)
2442
+ spread(32)
2443
+ b.mem(Op.i64Store, 0, Input + 8)
2444
+ step(8, 8, again)
2445
+ })
2446
+ })
2447
+ byte(0)
2448
+ b.tee(c).i32(0x80).op(Op.i32LtU)
2449
+ b.if(() => {
2450
+ unit(0, () => b.get(c))
2451
+ step(1, 1, again)
2452
+ })
2453
+ b.get(c).i32(0xe0).op(Op.i32LtU)
2454
+ b.if(() => {
2455
+ unit(0, () => {
2456
+ b.get(c).i32(0x1f).op(Op.i32And).i32(6).op(Op.i32Shl)
2457
+ cont(1, 0)
2458
+ })
2459
+ step(2, 1, again)
2460
+ })
2461
+ b.get(c).i32(0xf0).op(Op.i32LtU)
2462
+ b.if(() => {
2463
+ b.get(c).i32(0x0f).op(Op.i32And).i32(12).op(Op.i32Shl)
2464
+ cont(1, 6)
2465
+ cont(2, 0)
2466
+ b.tee(cp).i32(0xfffd).op(Op.i32Eq)
2467
+ b.if(() => {
2468
+ b.get(count).i32(2).op(Op.i32Shl).get(list).op(Op.i32Add).get(k).mem(Op.i32Store, 2, 0)
2469
+ b.get(count).i32(1).op(Op.i32Add).set(count)
2470
+ })
2471
+ unit(0, () => b.get(cp))
2472
+ step(3, 1, again)
2473
+ })
2474
+ b.get(c).i32(0x07).op(Op.i32And).i32(18).op(Op.i32Shl)
2475
+ cont(1, 12)
2476
+ cont(2, 6)
2477
+ cont(3, 0)
2478
+ b.i32(0x10000).op(Op.i32Sub).set(cp)
2479
+ unit(0, () => b.get(cp).i32(10).op(Op.i32ShrU).i32(0xd800).op(Op.i32Or))
2480
+ unit(1, () => b.get(cp).i32(0x3ff).op(Op.i32And).i32(0xdc00).op(Op.i32Or))
2481
+ step(4, 2, again)
2482
+ })
2483
+ )
2484
+ b.get(count)
2485
+ return index
2486
+ }
2487
+
2488
+ /** The decoder of a built-in encoding: `(p) -> code point | -1 | -2`, the width in `G.w`. */
2489
+ private decoder(enc: Encoding<any>): number {
2490
+ const known = this.decoders.get(enc)
2491
+ if (known !== undefined) return known
2492
+ const [index, b] = this.reserve(Ty.rule)
2493
+ this.decoders.set(enc, index)
2494
+ const c = b.local()
2495
+ const d = b.local()
2496
+ const ret = (code: () => void, width: number) => {
2497
+ b.i32(width).gset(G.w)
2498
+ code()
2499
+ b.op(Op.return)
2500
+ }
2501
+ const fail = (code: number) => b.i32(code).op(Op.return)
2502
+ const at = (k: number) => this.load(b, 0, k)
2503
+ const inRange = (k: number) => this.end(b.get(0).i32(k).op(Op.i32Add)).op(Op.i32GeU)
2504
+ if (enc === string) {
2505
+ at(0)
2506
+ b.tee(c).i32(0xf800).op(Op.i32And).i32(0xd800).op(Op.i32Ne)
2507
+ b.if(() => ret(() => b.get(c), 1))
2508
+ b.get(c).i32(0xdbff).op(Op.i32LeU)
2509
+ b.if(() => {
2510
+ inRange(1)
2511
+ b.if(() => fail(-2))
2512
+ at(1)
2513
+ b.tee(d).i32(0xfc00).op(Op.i32And).i32(0xdc00).op(Op.i32Eq)
2514
+ b.if(() => ret(() => pair(b, c, d), 2))
2515
+ })
2516
+ fail(-1)
2517
+ } else if (enc === utf16le || enc === utf16be) {
2518
+ const [lo, hi] = enc === utf16le ? [0, 1] : [1, 0]
2519
+ const unitAt = (k: number) => {
2520
+ at(k + lo)
2521
+ at(k + hi)
2522
+ b.i32(8).op(Op.i32Shl, Op.i32Or)
2523
+ }
2524
+ inRange(1)
2525
+ b.if(() => fail(-2))
2526
+ unitAt(0)
2527
+ b.tee(c).i32(0xf800).op(Op.i32And).i32(0xd800).op(Op.i32Ne)
2528
+ b.if(() => ret(() => b.get(c), 2))
2529
+ b.get(c).i32(0xdbff).op(Op.i32GtU)
2530
+ b.if(() => fail(-1))
2531
+ inRange(3)
2532
+ b.if(() => fail(-2))
2533
+ unitAt(2)
2534
+ b.tee(d).i32(0xfc00).op(Op.i32And).i32(0xdc00).op(Op.i32Eq)
2535
+ b.if(() => ret(() => pair(b, c, d), 4))
2536
+ fail(-1)
2537
+ } else {
2538
+ // UTF-8, as `decodeUtf8`
2539
+ const e = b.local()
2540
+ const f = b.local()
2541
+ const cont = (l: number) => b.get(l).i32(0xc0).op(Op.i32And).i32(0x80).op(Op.i32Ne)
2542
+ /** Fails unless the second byte, in `d`, may follow the lead byte: `a` and `b` are the leads with a range of their own. */
2543
+ const second = (a: readonly [number, number, number], z: readonly [number, number, number]) => {
2544
+ b.get(c).i32(a[0]).op(Op.i32Eq)
2545
+ b.ifI32(
2546
+ () => b.get(d).i32(a[1]).op(Op.i32Sub).i32(a[2]).op(Op.i32GtU),
2547
+ () => {
2548
+ b.get(c).i32(z[0]).op(Op.i32Eq)
2549
+ b.ifI32(
2550
+ () => b.get(d).i32(z[1]).op(Op.i32Sub).i32(z[2]).op(Op.i32GtU),
2551
+ () => cont(d)
2552
+ )
2553
+ }
2554
+ )
2555
+ b.if(() => fail(-1))
2556
+ }
2557
+ /** Reads the continuation byte `k` into local `l`, failing if it is missing or malformed. */
2558
+ const next = (k: number, l: number) => {
2559
+ inRange(k)
2560
+ b.if(() => fail(-2))
2561
+ at(k)
2562
+ b.set(l)
2563
+ cont(l)
2564
+ b.if(() => fail(-1))
2565
+ }
2566
+ at(0)
2567
+ b.tee(c).i32(0x80).op(Op.i32LtU)
2568
+ b.if(() => ret(() => b.get(c), 1))
2569
+ b.get(c).i32(0xc2).op(Op.i32LtU)
2570
+ b.if(() => fail(-1))
2571
+ inRange(1)
2572
+ b.if(() => fail(-2))
2573
+ at(1)
2574
+ b.set(d)
2575
+ b.get(c).i32(0xe0).op(Op.i32LtU)
2576
+ b.if(() => {
2577
+ cont(d)
2578
+ b.if(() => fail(-1))
2579
+ ret(() => {
2580
+ b.get(c).i32(0x1f).op(Op.i32And).i32(6).op(Op.i32Shl)
2581
+ b.get(d).i32(0x3f).op(Op.i32And).op(Op.i32Or)
2582
+ }, 2)
2583
+ })
2584
+ b.get(c).i32(0xf0).op(Op.i32LtU)
2585
+ b.if(() => {
2586
+ // Overlong (E0 80..9F) and surrogates (ED A0..BF)
2587
+ second([0xe0, 0xa0, 0x1f], [0xed, 0x80, 0x1f])
2588
+ next(2, e)
2589
+ ret(() => {
2590
+ b.get(c).i32(0x0f).op(Op.i32And).i32(12).op(Op.i32Shl)
2591
+ b.get(d).i32(0x3f).op(Op.i32And).i32(6).op(Op.i32Shl).op(Op.i32Or)
2592
+ b.get(e).i32(0x3f).op(Op.i32And).op(Op.i32Or)
2593
+ }, 3)
2594
+ })
2595
+ b.get(c).i32(0xf5).op(Op.i32LtU)
2596
+ b.if(() => {
2597
+ // Overlong (F0 80..8F) and above U+10FFFF (F4 90..BF)
2598
+ second([0xf0, 0x90, 0x2f], [0xf4, 0x80, 0x0f])
2599
+ next(2, e)
2600
+ next(3, f)
2601
+ ret(() => {
2602
+ b.get(c).i32(0x07).op(Op.i32And).i32(18).op(Op.i32Shl)
2603
+ b.get(d).i32(0x3f).op(Op.i32And).i32(12).op(Op.i32Shl).op(Op.i32Or)
2604
+ b.get(e).i32(0x3f).op(Op.i32And).i32(6).op(Op.i32Shl).op(Op.i32Or)
2605
+ b.get(f).i32(0x3f).op(Op.i32And).op(Op.i32Or)
2606
+ }, 4)
2607
+ })
2608
+ fail(-1)
2609
+ }
2610
+ return index
2611
+ }
2612
+
2613
+ /**
2614
+ * `scanFloatString` / `scanFloatBytes` at `p`: returns where the number
2615
+ * ends, with its value in `G.f`; -1 when the text doesn't start like a
2616
+ * decimal number; -2 when the float parser fails there. Numbers with more
2617
+ * than 15 significant digits or an exponent beyond ±22 are converted by
2618
+ * the JavaScript scan, so the value is always `Number`'s.
2619
+ */
2620
+ private scanFloat(enc: Enc): number {
2621
+ const known = enc === undefined ? this.tokenFloatScanner : (this.floatScanners.get(enc) ?? -1)
2622
+ if (known >= 0) return known
2623
+ const [index, b] = this.reserve(Ty.rule)
2624
+ if (enc === undefined) this.tokenFloatScanner = index
2625
+ else this.floatScanners.set(enc, index)
2626
+ // Decimal notation is ASCII: one unit per character in every encoding read here
2627
+ const r = enc === undefined ? undefined : this.reader(enc)
2628
+ const width = r?.width ?? 1
2629
+ const unit = (k: number) => (r === undefined ? this.load(b, k) : r.unit(b, k))
2630
+ const i = 0
2631
+ const c = b.local()
2632
+ const digits = b.local()
2633
+ const e10 = b.local()
2634
+ const e = b.local()
2635
+ const j = b.local()
2636
+ const neg = b.local()
2637
+ const start = b.local()
2638
+ const high = b.localF64()
2639
+ const low = b.localF64()
2640
+ const v = b.localF64()
2641
+ const isDigit = () => b.get(c).i32(48).op(Op.i32Sub).i32(9).op(Op.i32LeU)
2642
+ /** `c = unit(k)`, or -1 if there is no whole unit at `k` */
2643
+ const read = (k: number) => {
2644
+ if (r === undefined) this.end(b.get(k)).op(Op.i32LtU)
2645
+ else r.guard(b, k)
2646
+ b.ifI32(
2647
+ () => unit(k),
2648
+ () => b.i32(-1)
2649
+ )
2650
+ b.set(c)
2651
+ }
2652
+ const adv = (l: number) => b.get(l).i32(width).op(Op.i32Add).set(l)
2653
+ const inc = (l: number) => b.get(l).i32(1).op(Op.i32Add).set(l)
2654
+ const digit = (frac: boolean) => {
2655
+ b.get(digits).i32(15).op(Op.i32LtU)
2656
+ b.if(
2657
+ () => {
2658
+ b.get(c).i32(48).op(Op.i32Ne).get(high).f64(0).op(Op.f64Ne).op(Op.i32Or)
2659
+ b.if(() => {
2660
+ b.get(high).f64(10).op(Op.f64Mul).get(c).i32(48).op(Op.i32Sub).op(Op.f64ConvertI32U).op(Op.f64Add)
2661
+ b.set(high)
2662
+ inc(digits)
2663
+ })
2664
+ if (frac) b.get(e10).i32(1).op(Op.i32Sub).set(e10)
2665
+ },
2666
+ () => {
2667
+ // Digits 16 to 19 go to `low`; the rest only scale the number
2668
+ const addLow = () =>
2669
+ b.get(low).f64(10).op(Op.f64Mul).get(c).i32(48).op(Op.i32Sub).op(Op.f64ConvertI32U).op(Op.f64Add).set(low)
2670
+ b.get(digits).i32(19).op(Op.i32LtU)
2671
+ if (frac) {
2672
+ b.if(() => {
2673
+ addLow()
2674
+ b.get(e10).i32(1).op(Op.i32Sub).set(e10)
2675
+ })
2676
+ } else b.if(addLow, () => inc(e10))
2677
+ inc(digits)
2678
+ }
2679
+ )
2680
+ }
2681
+ b.get(i).set(start)
2682
+ read(i)
2683
+ b.get(c).i32(45).op(Op.i32Eq).get(c).i32(43).op(Op.i32Eq).op(Op.i32Or)
2684
+ b.if(
2685
+ () => {
2686
+ b.get(i).i32(width).op(Op.i32Add).set(j)
2687
+ read(j)
2688
+ isDigit()
2689
+ b.get(c).i32(46).op(Op.i32Eq).op(Op.i32Or).op(Op.i32Eqz)
2690
+ b.if(() => b.i32(-1).op(Op.return))
2691
+ adv(i)
2692
+ },
2693
+ () => {
2694
+ isDigit()
2695
+ b.get(c).i32(46).op(Op.i32Eq).op(Op.i32Or).op(Op.i32Eqz)
2696
+ b.if(() => b.i32(-1).op(Op.return))
2697
+ }
2698
+ )
2699
+ b.get(c).i32(46).op(Op.i32Ne)
2700
+ b.if(
2701
+ () => {
2702
+ b.loop((more) => {
2703
+ digit(false)
2704
+ adv(i)
2705
+ read(i)
2706
+ isDigit()
2707
+ b.brIf(more)
2708
+ })
2709
+ b.get(c).i32(46).op(Op.i32Eq)
2710
+ b.if(() => {
2711
+ adv(i)
2712
+ read(i)
2713
+ })
2714
+ },
2715
+ () => {
2716
+ adv(i)
2717
+ read(i)
2718
+ isDigit()
2719
+ b.op(Op.i32Eqz)
2720
+ b.if(() => b.i32(-2).op(Op.return))
2721
+ }
2722
+ )
2723
+ b.block((done) =>
2724
+ b.loop((more) => {
2725
+ isDigit()
2726
+ b.op(Op.i32Eqz).brIf(done)
2727
+ digit(true)
2728
+ adv(i)
2729
+ read(i)
2730
+ b.br(more)
2731
+ })
2732
+ )
2733
+ b.get(c).i32(32).op(Op.i32Or).i32(101).op(Op.i32Eq)
2734
+ b.if(() => {
2735
+ b.get(i).i32(width).op(Op.i32Add).set(j)
2736
+ read(j)
2737
+ b.get(c).i32(45).op(Op.i32Eq).set(neg)
2738
+ b.get(neg).get(c).i32(43).op(Op.i32Eq).op(Op.i32Or)
2739
+ b.if(() => {
2740
+ adv(j)
2741
+ read(j)
2742
+ })
2743
+ isDigit()
2744
+ b.if(() => {
2745
+ b.i32(0).set(e)
2746
+ b.loop((more) => {
2747
+ b.get(e).i32(1000000).op(Op.i32LtS)
2748
+ b.if(() => b.get(e).i32(10).op(Op.i32Mul).get(c).i32(48).op(Op.i32Sub).op(Op.i32Add).set(e))
2749
+ adv(j)
2750
+ read(j)
2751
+ isDigit()
2752
+ b.brIf(more)
2753
+ })
2754
+ b.get(e10).i32(0).get(e).op(Op.i32Sub).get(e).get(neg).op(Op.select).op(Op.i32Add).set(e10)
2755
+ b.get(j).set(i)
2756
+ })
2757
+ })
2758
+ // The value
2759
+ unit(start)
2760
+ b.i32(45).op(Op.i32Eq).set(neg)
2761
+ b.get(high).f64(0).op(Op.f64Eq)
2762
+ b.if(() => {
2763
+ b.f64(-0).f64(0).get(neg).op(Op.select).gset(G.f)
2764
+ b.get(i).op(Op.return)
2765
+ })
2766
+ /** `exact[k]`, for `k` in local `l` (or `-l` when `negate`). */
2767
+ const pow = (l: number, negate = false) => {
2768
+ if (negate) b.i32(0).get(l).op(Op.i32Sub)
2769
+ else b.get(l)
2770
+ b.i32(3).op(Op.i32Shl).mem(Op.f64Load, 3, Pow10)
2771
+ }
2772
+ // As `decimal` (internal/numbers.ts): the same double operations, so the same doubles
2773
+ const [ph, pl, t, ah, al, bh, bl, wh, wl, xh, xl, pp, err] = Array.from({ length: 13 }, () => b.localF64())
2774
+ const lowDigits = b.local()
2775
+ /** `twoProduct(a, b)`: `ph + pl` is exactly `a * b`. */
2776
+ const twoProduct = (x: () => void, y: () => void) => {
2777
+ x()
2778
+ y()
2779
+ b.op(Op.f64Mul).set(ph)
2780
+ b.f64(134217729)
2781
+ x()
2782
+ b.op(Op.f64Mul).set(t)
2783
+ b.get(t).get(t)
2784
+ x()
2785
+ b.op(Op.f64Sub).op(Op.f64Sub).set(ah)
2786
+ x()
2787
+ b.get(ah).op(Op.f64Sub).set(al)
2788
+ b.f64(134217729)
2789
+ y()
2790
+ b.op(Op.f64Mul).set(t)
2791
+ b.get(t).get(t)
2792
+ y()
2793
+ b.op(Op.f64Sub).op(Op.f64Sub).set(bh)
2794
+ y()
2795
+ b.get(bh).op(Op.f64Sub).set(bl)
2796
+ b.get(ah).get(bh).op(Op.f64Mul).get(ph).op(Op.f64Sub)
2797
+ b.get(ah).get(bl).op(Op.f64Mul).op(Op.f64Add)
2798
+ b.get(al).get(bh).op(Op.f64Mul).op(Op.f64Add)
2799
+ b.get(al).get(bl).op(Op.f64Mul).op(Op.f64Add).set(pl)
2800
+ }
2801
+ b.block((slow) => {
2802
+ b.get(digits).i32(19).op(Op.i32GtU).brIf(slow)
2803
+ b.get(e10).i32(22).op(Op.i32Add).i32(44).op(Op.i32GtU).brIf(slow)
2804
+ b.get(digits).i32(15).op(Op.i32LeU)
2805
+ b.if(
2806
+ () => {
2807
+ // Up to 15 digits: one exact multiplication or division (Clinger's fast path)
2808
+ b.get(e10).i32(0).op(Op.i32Eq)
2809
+ b.if(
2810
+ () => b.get(high).set(v),
2811
+ () => {
2812
+ b.get(e10).i32(0).op(Op.i32GtS)
2813
+ b.if(
2814
+ () => {
2815
+ b.get(high)
2816
+ pow(e10)
2817
+ b.op(Op.f64Mul).set(v)
2818
+ },
2819
+ () => {
2820
+ b.get(high)
2821
+ pow(e10, true)
2822
+ b.op(Op.f64Div).set(v)
2823
+ }
2824
+ )
2825
+ }
2826
+ )
2827
+ },
2828
+ () => {
2829
+ // 16 to 19 digits: double-double, kept when its error bound rounds to one double
2830
+ b.get(digits).i32(15).op(Op.i32Sub).set(lowDigits)
2831
+ twoProduct(
2832
+ () => b.get(high),
2833
+ () => pow(lowDigits)
2834
+ )
2835
+ b.get(ph).get(low).op(Op.f64Add).set(wh)
2836
+ b.get(pl).get(low).get(wh).get(ph).op(Op.f64Sub).op(Op.f64Sub).op(Op.f64Add).set(wl)
2837
+ b.get(e10).i32(0).op(Op.i32GeS)
2838
+ b.if(
2839
+ () => {
2840
+ pow(e10)
2841
+ b.set(pp)
2842
+ twoProduct(
2843
+ () => b.get(wh),
2844
+ () => b.get(pp)
2845
+ )
2846
+ b.get(ph).set(xh)
2847
+ b.get(pl).get(wl).get(pp).op(Op.f64Mul).op(Op.f64Add).set(xl)
2848
+ },
2849
+ () => {
2850
+ pow(e10, true)
2851
+ b.set(pp)
2852
+ b.get(wh).get(pp).op(Op.f64Div).set(xh)
2853
+ twoProduct(
2854
+ () => b.get(xh),
2855
+ () => b.get(pp)
2856
+ )
2857
+ b.get(wh).get(ph).op(Op.f64Sub).get(pl).op(Op.f64Sub).get(wl).op(Op.f64Add).get(pp).op(Op.f64Div).set(xl)
2858
+ }
2859
+ )
2860
+ b.get(xh).get(xl).op(Op.f64Add).set(v)
2861
+ b.get(xh)
2862
+ .op(Op.f64Abs)
2863
+ .f64(1 / 2 ** 96)
2864
+ .op(Op.f64Mul)
2865
+ .set(err)
2866
+ b.get(xh).get(xl).get(err).op(Op.f64Add).op(Op.f64Add).get(v).op(Op.f64Ne).brIf(slow)
2867
+ b.get(xh).get(xl).get(err).op(Op.f64Sub).op(Op.f64Add).get(v).op(Op.f64Ne).brIf(slow)
2868
+ }
2869
+ )
2870
+ b.get(v).op(Op.f64Neg).get(v).get(neg).op(Op.select).gset(G.f)
2871
+ b.get(i).op(Op.return)
2872
+ })
2873
+ // Anything else: `Number`, in JavaScript
2874
+ b.get(start).get(i).call(Imp.float).gset(G.f)
2875
+ b.get(i)
2876
+ return index
2877
+ }
2878
+ }
2879
+
2880
+ const unitsOf = (s: string): Array<number> => Array.from({ length: s.length }, (_, i) => s.charCodeAt(i))
2881
+
2882
+ const utf8Encoder = /*#__PURE__*/ new TextEncoder()
2883
+
2884
+ /** The powers of ten that doubles represent exactly, as stored at `Pow10`. */
2885
+ const powersOfTen: Uint8Array = /*#__PURE__*/ new Uint8Array(
2886
+ /*#__PURE__*/ new Float64Array(/*#__PURE__*/ Array.from({ length: 23 }, (_, k) => 10 ** k)).buffer
2887
+ )
2888
+
2889
+ // -----------------------------------------------------------------------------
2890
+ // Building and running
2891
+ // -----------------------------------------------------------------------------
2892
+
2893
+ /** Runtime helpers of the builder (`R`). */
2894
+ const fromCharCode = String.fromCharCode
2895
+
2896
+ /**
2897
+ * The text of ASCII bytes. Short ones are built from their codes, about a
2898
+ * third faster than `utf8.text`, which tests every byte for a longer
2899
+ * sequence; long ones are decoded by `TextDecoder`.
2900
+ */
2901
+ const ascii = (b: Uint8Array, start: number, end: number): string => {
2902
+ const n = end - start
2903
+ if (n > 24) return utf8.text(b, start, end)
2904
+ if (n <= 4) {
2905
+ if (n === 0) return ""
2906
+ if (n === 1) return charOf(b[start])
2907
+ if (n === 2) return fromCharCode(b[start], b[start + 1])
2908
+ if (n === 3) return fromCharCode(b[start], b[start + 1], b[start + 2])
2909
+ return fromCharCode(b[start], b[start + 1], b[start + 2], b[start + 3])
2910
+ }
2911
+ let s = fromCharCode(b[start], b[start + 1], b[start + 2], b[start + 3])
2912
+ for (let i = start + 4; i < end; i++) s += charOf(b[i])
2913
+ return s
2914
+ }
2915
+
2916
+ const runtime = { some: Option.some, none: Option.none, charOf, isPlainKey, addEntry, flushEntries, ascii }
2917
+
2918
+ /** A parser's value, as steps return it. */
2919
+ type Value = ReturnType<Step>
2920
+
2921
+ /**
2922
+ * The generated builder. `bind` gives it the views of the memory, the
2923
+ * input and the state of a parse (again whenever the memory grows, which
2924
+ * replaces the views), `unbind` drops them; then `root` builds the value of
2925
+ * the root and `value` that of value island `w`, from the trace at index `i`.
2926
+ */
2927
+ interface Builder {
2928
+ readonly bind: (
2929
+ t: Int32Array,
2930
+ f: Float64Array,
2931
+ input: string | Uint8Array,
2932
+ end: number,
2933
+ values: Array<Value>,
2934
+ state: State<any>
2935
+ ) => void
2936
+ readonly unbind: () => void
2937
+ readonly root: (i: number) => Value
2938
+ readonly value: (w: number, i: number) => Value
2939
+ }
2940
+
2941
+ /** The state outside a parse: islands only run during one. */
2942
+ const idle = /*#__PURE__*/ new State("")
2943
+
2944
+ const compileWasm = (root: ParserLike, kind: 0 | 1): Result.Result<Compiled, string> => {
2945
+ const c = new WasmCompiler(kind)
2946
+ c.countRefs(root)
2947
+ const rootIndex = c.rule(root, true)
2948
+ c.flush()
2949
+ const widen = kind === 0 ? c.widener() : -1
2950
+ const start = c.starter(rootIndex)
2951
+ if (c.functions.some(([, body]) => body.invalid)) return Result.fail("a branch to a label out of scope")
2952
+ const globals = [
2953
+ { type: I32, init: 0 },
2954
+ { type: I32, init: 0 },
2955
+ { type: I32, init: 0 },
2956
+ { type: I32, init: 0 },
2957
+ { type: I32, init: 0 },
2958
+ { type: I32, init: 0 },
2959
+ { type: I32, init: 0 },
2960
+ { type: F64, init: 0 }
2961
+ ]
2962
+ const tables = new Uint8Array(c.tables.length * TableSize)
2963
+ c.tables.forEach((t, i) => tables.set(t, i * TableSize))
2964
+ const bytes = encodeModule({
2965
+ types: [
2966
+ { params: [I32], results: [I32] },
2967
+ { params: [I32, I32], results: [I32] },
2968
+ { params: [], results: [I32] },
2969
+ { params: [I32, I32], results: [F64] },
2970
+ { params: [I32, I32, I32, I32, I32], results: [I32] }
2971
+ ],
2972
+ imports: [
2973
+ ["env", "island", Ty.pair],
2974
+ ["env", "grow", Ty.grow],
2975
+ ["env", "test", Ty.pair],
2976
+ ["env", "float", Ty.toF64],
2977
+ ["env", "check", Ty.pair]
2978
+ ],
2979
+ memoryPages: 2,
2980
+ functions: c.functions,
2981
+ globals,
2982
+ exports: [
2983
+ ["root", 0, rootIndex],
2984
+ ...(widen < 0 ? [] : [["widen", 0, widen] as const]),
2985
+ ["start", 0, start],
2986
+ ["mode", 3, G.mode],
2987
+ ["end", 3, G.end],
2988
+ ["len", 3, G.len],
2989
+ ["tp", 3, G.tp],
2990
+ ["tlim", 3, G.tlim],
2991
+ ["nv", 3, G.nv]
2992
+ ],
2993
+ data: [
2994
+ [0, tables],
2995
+ [Pow10, powersOfTen]
2996
+ ]
2997
+ })
2998
+ const source =
2999
+ `"use strict";let T,F,I,E,V,S,N=0;${c.builders.join("")}${c.valueFns.join("")}` +
3000
+ `const W=[${c.valueFns.map((_, k) => `w${k}`).join(",")}];` +
3001
+ `return{bind(t,f,inp,e,vals,s){T=t;F=f;I=inp;E=e;V=vals;S=s},unbind(){T=F=I=V=S=void 0},` +
3002
+ `root(i){return b${rootIndex}(i)},value(w,i){return W[w](i)}}`
3003
+ const build: Builder = new globalThis.Function("C", "R", source)(c.constants, runtime)
3004
+ return run(root, c, bytes, kind, build, source)
3005
+ }
3006
+
3007
+ /** A global of a module's exports. */
3008
+ const exportedGlobal = (exports: WebAssembly.Exports, name: string): Result.Result<WebAssembly.Global, string> => {
3009
+ const g = exports[name]
3010
+ return g instanceof WebAssembly.Global ? Result.succeed(g) : Result.fail(`no global ${name}`)
3011
+ }
3012
+
3013
+ const run = (
3014
+ root: ParserLike,
3015
+ c: WasmCompiler,
3016
+ bytes: Uint8Array<ArrayBuffer>,
3017
+ kind: 0 | 1,
3018
+ build: Builder,
3019
+ source: string
3020
+ ): Result.Result<Compiled, string> => {
3021
+ const memory = new WebAssembly.Memory({ initial: 2 })
3022
+ // `memory.buffer` is slow to read: its size, as of the latest growth
3023
+ let size = memory.buffer.byteLength
3024
+ let i32 = new Int32Array(memory.buffer)
3025
+ let f64 = new Float64Array(memory.buffer)
3026
+ let u8 = new Uint8Array(memory.buffer)
3027
+ const views = () => {
3028
+ i32 = new Int32Array(memory.buffer)
3029
+ f64 = new Float64Array(memory.buffer)
3030
+ u8 = new Uint8Array(memory.buffer)
3031
+ }
3032
+ /** Grows the memory to at least `bytes`, doubling it. */
3033
+ const reserve = (bytes: number) => {
3034
+ const have = size
3035
+ if (have >= bytes) return
3036
+ memory.grow(Math.ceil((Math.max(bytes, have * 2) - have) / PageSize))
3037
+ size = memory.buffer.byteLength
3038
+ views()
3039
+ }
3040
+ const steps: Array<Step> = c.islands.map(({ parser }) => jitBackend.build(parser, kind, false).step ?? parser.step)
3041
+ const used = c.islands.map(({ used }) => used)
3042
+ const tests = c.tests
3043
+ const checks = c.checks
3044
+ let state: State<any> = idle
3045
+ const values: Array<Value> = []
3046
+ const imports = {
3047
+ env: {
3048
+ mem: memory,
3049
+ island: (id: number, pos: number): number => {
3050
+ const s = state
3051
+ s.pos = pos
3052
+ const a = steps[id](s)
3053
+ if (a === Fail) return s.mode === "Cut" ? -2 : -1
3054
+ if (used[id]) values.push(a)
3055
+ return s.pos
3056
+ },
3057
+ // Doubles the memory, returning the new limit of the trace
3058
+ grow: (): number => {
3059
+ reserve(size * 2)
3060
+ // The views changed
3061
+ build.bind(i32, f64, state.input, state.end, values, state)
3062
+ return size - Slack
3063
+ },
3064
+ test: (id: number, cp: number): number => tests[id](cp),
3065
+ // A value island: the value of its parser, built from its trace at `from`, and the callback
3066
+ check: (id: number, from: number): number => {
3067
+ const s = state
3068
+ const a = checks[id](build.value(id, from >> 2))
3069
+ if (a === Fail) {
3070
+ s.mode = "Backtrack"
3071
+ return 0
3072
+ }
3073
+ values.push(a)
3074
+ return 1
3075
+ },
3076
+ // The value of the float text at `[start, end)`, which is ASCII: one byte each, or a
3077
+ // byte and a zero byte in UTF-16
3078
+ float: (start: number, end: number): number => {
3079
+ const input: string | Uint8Array = state.input
3080
+ return toFloat(
3081
+ Predicate.isString(input) ? input.slice(start, end) : latin1.text(input, start, end).replace(/\0/g, "")
3082
+ )
3083
+ }
3084
+ }
3085
+ }
3086
+ const instance = new WebAssembly.Instance(new WebAssembly.Module(bytes), imports)
3087
+ const exported = instance.exports
3088
+ const rootFn = exported.root
3089
+ if (!Predicate.isFunction(rootFn)) return Result.fail("no root function")
3090
+ const widenFn = kind === 0 ? exported.widen : rootFn
3091
+ if (!Predicate.isFunction(widenFn)) return Result.fail("no widen function")
3092
+ const startFn = exported.start
3093
+ if (!Predicate.isFunction(startFn)) return Result.fail("no start function")
3094
+ const names = ["mode"] as const
3095
+ const globals = Result.all(names.map((name) => exportedGlobal(exported, name)))
3096
+ if (Result.isFailure(globals)) return Result.fail(globals.failure)
3097
+ const [mode] = globals.success
3098
+ let busy = false
3099
+ /** The JIT's step for the parser, built on first use. */
3100
+ let fallback: Step = (s) => {
3101
+ fallback = jitBackend.build(root, kind, false).step ?? root.step
3102
+ return fallback(s)
3103
+ }
3104
+ /** Copies the input into memory, scans it and builds the value. */
3105
+ /**
3106
+ * Copies a string's UTF-16 units into memory, at `staging` for its UTF-8
3107
+ * (see `widener`): standard `ArrayBuffer` views and `TextEncoder`, so the
3108
+ * same code runs everywhere, and a memory backed by a `SharedArrayBuffer`
3109
+ * would take it as well.
3110
+ */
3111
+ const copyString = (input: string, staging: number) => {
3112
+ const { written } = utf8Encoder.encodeInto(input, u8.subarray(staging, staging + 3 * input.length))
3113
+ const replaced: number = widenFn(staging, written)
3114
+ if (replaced === 0) return
3115
+ // U+FFFD in the string, or a lone surrogate that `encodeInto` replaced by it: its unit, as it is
3116
+ const view = new DataView(memory.buffer)
3117
+ const list = (staging + written + 3) >> 2
3118
+ for (let r = 0; r < replaced; r++) {
3119
+ const at = i32[list + r]
3120
+ view.setUint16(Input + 2 * at, input.charCodeAt(at), true)
3121
+ }
3122
+ }
3123
+ /** The string whose units are in memory, if any (`""`: none). */
3124
+ let copied = ""
3125
+ const parse = (s: State<any>): Value => {
3126
+ const input: string | Uint8Array = s.input
3127
+ const n = input.length
3128
+ const inputBytes = kind === 0 ? 2 * n : n
3129
+ const traceStart = (Input + inputBytes + 15) & -16
3130
+ // Strings are staged where the trace goes: 3 bytes of UTF-8 and 4 of list at most per unit
3131
+ reserve(traceStart + Math.max(Slack * 4, inputBytes * 4, kind === 0 ? 7 * n + 16 : 0))
3132
+ if (Predicate.isString(input)) {
3133
+ // Strings never change: one already in memory (parsed again, or from another offset) stays there
3134
+ if (input !== copied) {
3135
+ copied = ""
3136
+ copyString(input, traceStart)
3137
+ copied = input
3138
+ }
3139
+ } else {
3140
+ // Bytes may change between parses: copy what the scan can read, from the cursor to the end
3141
+ u8.set(s.pos === 0 && s.end === n ? input : input.subarray(s.pos, s.end), Input + s.pos)
3142
+ }
3143
+ values.length = 0
3144
+ state = s
3145
+ build.bind(i32, f64, input, s.end, values, s)
3146
+ const r: number = startFn(s.pos, s.end, n, traceStart, size - Slack)
3147
+ if (r < 0) {
3148
+ s.mode = mode.value === Cut ? "Cut" : "Backtrack"
3149
+ return Fail
3150
+ }
3151
+ s.pos = r
3152
+ return build.root(traceStart >> 2)
3153
+ }
3154
+ /** The bytes copied by the latest parse: parsing them again from further on is a driver at work. */
3155
+ let lastBytes: Uint8Array = new Uint8Array(0)
3156
+ /** The state of the parse `parsePending` runs: `Result.try` takes a function without arguments. */
3157
+ let pending: State<any> = idle
3158
+ const parsePending = () => parse(pending)
3159
+ const step: Step = (s) => {
3160
+ // A callback that parses with the same parser, while this one runs, runs the JIT
3161
+ if (busy) return fallback(s)
3162
+ const input: string | Uint8Array = s.input
3163
+ if (!Predicate.isString(input)) {
3164
+ // Bytes can change, so they are copied on every parse; a driver parsing one token at a time
3165
+ // from a large buffer would copy the rest of it for each token, where the JIT copies nothing
3166
+ const again = input === lastBytes && s.pos > 0 && s.end - s.pos > LargeRest
3167
+ lastBytes = input
3168
+ if (again) return fallback(s)
3169
+ }
3170
+ busy = true
3171
+ // Whatever happens (a callback may throw), the next parse starts afresh
3172
+ pending = s
3173
+ const result = Result.try(parsePending)
3174
+ build.unbind()
3175
+ state = idle
3176
+ pending = idle
3177
+ busy = false
3178
+ values.length = 0
3179
+ if (Result.isFailure(result)) throw result.failure
3180
+ return result.success
3181
+ }
3182
+ return Result.succeed({ backend: "wasm", step, guarded: false, fallbacks: c.fallbacks, error: undefined, source })
3183
+ }
3184
+
3185
+ /**
3186
+ * The WebAssembly compiler as a backend. Streaming builds, array input and
3187
+ * environments without `WebAssembly` or `new Function` use the JIT, as does
3188
+ * a parser whose module fails to build.
3189
+ */
3190
+ export const backend: Backend = {
3191
+ build: (p, kind, resumable) => {
3192
+ if (resumable || kind === 2 || !supported()) return jitBackend.build(p, kind, resumable)
3193
+ // `compileWasm` fails when the module can't be built here; a throw is a bug, and both use the JIT
3194
+ const built = Result.flatMap(
3195
+ Result.try(() => compileWasm(p, kind)),
3196
+ (r) => r
3197
+ )
3198
+ return Result.match(built, {
3199
+ onSuccess: (compiled) => compiled,
3200
+ onFailure: (error) => ({ ...jitBackend.build(p, kind, resumable), wasmError: error })
3201
+ })
3202
+ }
3203
+ }