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