@thomasfosterau/effect-parse 0.0.0-stage → 0.0.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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +550 -2
  3. package/dist/Ascii.d.ts +392 -0
  4. package/dist/Ascii.d.ts.map +1 -0
  5. package/dist/Ascii.js +610 -0
  6. package/dist/Ascii.js.map +1 -0
  7. package/dist/Binary.d.ts +153 -0
  8. package/dist/Binary.d.ts.map +1 -0
  9. package/dist/Binary.js +171 -0
  10. package/dist/Binary.js.map +1 -0
  11. package/dist/Bits.d.ts +60 -0
  12. package/dist/Bits.d.ts.map +1 -0
  13. package/dist/Bits.js +144 -0
  14. package/dist/Bits.js.map +1 -0
  15. package/dist/Encoding.d.ts +116 -0
  16. package/dist/Encoding.d.ts.map +1 -0
  17. package/dist/Encoding.js +85 -0
  18. package/dist/Encoding.js.map +1 -0
  19. package/dist/Expression.d.ts +124 -0
  20. package/dist/Expression.d.ts.map +1 -0
  21. package/dist/Expression.js +139 -0
  22. package/dist/Expression.js.map +1 -0
  23. package/dist/Input.d.ts +301 -0
  24. package/dist/Input.d.ts.map +1 -0
  25. package/dist/Input.js +312 -0
  26. package/dist/Input.js.map +1 -0
  27. package/dist/Nom.d.ts +518 -0
  28. package/dist/Nom.d.ts.map +1 -0
  29. package/dist/Nom.js +519 -0
  30. package/dist/Nom.js.map +1 -0
  31. package/dist/ParseError.d.ts +120 -0
  32. package/dist/ParseError.d.ts.map +1 -0
  33. package/dist/ParseError.js +301 -0
  34. package/dist/ParseError.js.map +1 -0
  35. package/dist/Parser.d.ts +1312 -0
  36. package/dist/Parser.d.ts.map +1 -0
  37. package/dist/Parser.js +1936 -0
  38. package/dist/Parser.js.map +1 -0
  39. package/dist/ParserSchema.d.ts +31 -0
  40. package/dist/ParserSchema.d.ts.map +1 -0
  41. package/dist/ParserSchema.js +56 -0
  42. package/dist/ParserSchema.js.map +1 -0
  43. package/dist/ParserStream.d.ts +127 -0
  44. package/dist/ParserStream.d.ts.map +1 -0
  45. package/dist/ParserStream.js +270 -0
  46. package/dist/ParserStream.js.map +1 -0
  47. package/dist/Text.d.ts +330 -0
  48. package/dist/Text.d.ts.map +1 -0
  49. package/dist/Text.js +529 -0
  50. package/dist/Text.js.map +1 -0
  51. package/dist/Token.d.ts +225 -0
  52. package/dist/Token.d.ts.map +1 -0
  53. package/dist/Token.js +294 -0
  54. package/dist/Token.js.map +1 -0
  55. package/dist/index.d.ts +17 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +17 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/internal/compiler/closure.d.ts +25 -0
  60. package/dist/internal/compiler/closure.d.ts.map +1 -0
  61. package/dist/internal/compiler/closure.js +1668 -0
  62. package/dist/internal/compiler/closure.js.map +1 -0
  63. package/dist/internal/compiler/entries.d.ts +19 -0
  64. package/dist/internal/compiler/entries.d.ts.map +1 -0
  65. package/dist/internal/compiler/entries.js +32 -0
  66. package/dist/internal/compiler/entries.js.map +1 -0
  67. package/dist/internal/compiler/first.d.ts +33 -0
  68. package/dist/internal/compiler/first.d.ts.map +1 -0
  69. package/dist/internal/compiler/first.js +235 -0
  70. package/dist/internal/compiler/first.js.map +1 -0
  71. package/dist/internal/compiler/jit.d.ts +20 -0
  72. package/dist/internal/compiler/jit.d.ts.map +1 -0
  73. package/dist/internal/compiler/jit.js +1030 -0
  74. package/dist/internal/compiler/jit.js.map +1 -0
  75. package/dist/internal/compiler/registry.d.ts +34 -0
  76. package/dist/internal/compiler/registry.d.ts.map +1 -0
  77. package/dist/internal/compiler/registry.js +46 -0
  78. package/dist/internal/compiler/registry.js.map +1 -0
  79. package/dist/internal/compiler/sets.d.ts +29 -0
  80. package/dist/internal/compiler/sets.d.ts.map +1 -0
  81. package/dist/internal/compiler/sets.js +85 -0
  82. package/dist/internal/compiler/sets.js.map +1 -0
  83. package/dist/internal/entries.d.ts +27 -0
  84. package/dist/internal/entries.d.ts.map +1 -0
  85. package/dist/internal/entries.js +42 -0
  86. package/dist/internal/entries.js.map +1 -0
  87. package/dist/internal/node.d.ts +251 -0
  88. package/dist/internal/node.d.ts.map +1 -0
  89. package/dist/internal/node.js +73 -0
  90. package/dist/internal/node.js.map +1 -0
  91. package/dist/internal/numbers.d.ts +41 -0
  92. package/dist/internal/numbers.d.ts.map +1 -0
  93. package/dist/internal/numbers.js +302 -0
  94. package/dist/internal/numbers.js.map +1 -0
  95. package/dist/internal/resume.d.ts +39 -0
  96. package/dist/internal/resume.d.ts.map +1 -0
  97. package/dist/internal/resume.js +37 -0
  98. package/dist/internal/resume.js.map +1 -0
  99. package/dist/internal/text.d.ts +72 -0
  100. package/dist/internal/text.d.ts.map +1 -0
  101. package/dist/internal/text.js +391 -0
  102. package/dist/internal/text.js.map +1 -0
  103. package/dist/internal/tokens.d.ts +93 -0
  104. package/dist/internal/tokens.d.ts.map +1 -0
  105. package/dist/internal/tokens.js +400 -0
  106. package/dist/internal/tokens.js.map +1 -0
  107. package/package.json +115 -3
  108. package/src/Ascii.ts +741 -0
  109. package/src/Binary.ts +228 -0
  110. package/src/Bits.ts +192 -0
  111. package/src/Encoding.ts +128 -0
  112. package/src/Expression.ts +245 -0
  113. package/src/Input.ts +429 -0
  114. package/src/Nom.ts +642 -0
  115. package/src/ParseError.ts +378 -0
  116. package/src/Parser.ts +2763 -0
  117. package/src/ParserSchema.ts +87 -0
  118. package/src/ParserStream.ts +390 -0
  119. package/src/Text.ts +757 -0
  120. package/src/Token.ts +416 -0
  121. package/src/index.ts +16 -0
  122. package/src/internal/compiler/closure.ts +1583 -0
  123. package/src/internal/compiler/entries.ts +36 -0
  124. package/src/internal/compiler/first.ts +252 -0
  125. package/src/internal/compiler/jit.ts +1091 -0
  126. package/src/internal/compiler/registry.ts +79 -0
  127. package/src/internal/compiler/sets.ts +100 -0
  128. package/src/internal/entries.ts +51 -0
  129. package/src/internal/node.ts +207 -0
  130. package/src/internal/numbers.ts +291 -0
  131. package/src/internal/resume.ts +55 -0
  132. package/src/internal/text.ts +411 -0
  133. package/src/internal/tokens.ts +409 -0
@@ -0,0 +1,1091 @@
1
+ /**
2
+ * The JIT: emits JavaScript source for a parser's description and builds
3
+ * it with `new Function`.
4
+ *
5
+ * Every rule (the root, every `suspend` target and every non-leaf parser
6
+ * with several parents) becomes a function `f(p)` taking the cursor and
7
+ * returning the value, or `Fail`, leaving the new cursor in the register
8
+ * `P`. Everything else is inlined as straight-line code, with labelled
9
+ * blocks for failure. The cursor is a local; the input and its end are
10
+ * hoisted into locals. Failures only set `state.mode`, as in the closure
11
+ * compiler.
12
+ *
13
+ * Nodes without an inline form run their closure-compiled steps (which run
14
+ * the interpreter for what they can't compile) on the shared state, with
15
+ * `state.pos` kept in sync.
16
+ */
17
+ import * as Option from "effect/Option"
18
+ import type { Encoding } from "../../Encoding.js"
19
+ import type { State } from "../../Input.js"
20
+ import { Fail } from "../../Input.js"
21
+ import type { Node, NodeOf, ParserLike } from "../node.js"
22
+ import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js"
23
+ import { children, nodeOf } from "../node.js"
24
+ import { floatScan, floatTexts, scanFloatBytes, scanFloatString, toFloat } from "../numbers.js"
25
+ import * as Resume from "../resume.js"
26
+ import { charOf, fastLimit, latin1, string, utf16be, utf16le, utf8, validate } from "../text.js"
27
+ import type { ClosureCompiler, Fallback, Step } from "./closure.js"
28
+ import { createClosureCompiler } from "./closure.js"
29
+ import { entriesShape, pairOf } from "./entries.js"
30
+ import type { First } from "./first.js"
31
+ import { planAlt, Strict } from "./first.js"
32
+ import type { CharSet } from "./sets.js"
33
+ import { kindOfEncoding, reachesAbove, TableSize, textSet, tokenSet } from "./sets.js"
34
+
35
+ /** Helpers available to generated code as `R`. */
36
+ const runtime = {
37
+ take: Resume.take,
38
+ save: Resume.save,
39
+ Done: Resume.Done,
40
+ /** Records the result of a resumed repetition (see `Resume.done`). */
41
+ done: (state: State<any>, key: object, entry: number, value: unknown, pos: number): void =>
42
+ Resume.save(state, key, entry, new Resume.Done(value, pos)),
43
+ some: Option.some,
44
+ none: Option.none,
45
+ charOf,
46
+ scanFloatString,
47
+ scanFloatBytes,
48
+ isPlainKey,
49
+ addEntry,
50
+ flushEntries,
51
+ /** `Text.taken`: the text of `[start, end)`, or `Fail` if it is malformed. */
52
+ textOf: (enc: Encoding<any>, fast: number, s: State<any>, start: number, end: number): unknown => {
53
+ const input = s.input
54
+ let i = start
55
+ if (s.inputKind === 0) while (i < end && (input as string).charCodeAt(i) < fast) i++
56
+ else while (i < end && (input as Uint8Array)[i]! < fast) i++
57
+ if (i < end && validate(enc, s, i, end) < 0) {
58
+ s.mode = "Backtrack"
59
+ return Fail
60
+ }
61
+ return enc.text(input, start, end)
62
+ }
63
+ }
64
+
65
+ let checked: FunctionConstructor | undefined
66
+ let supported = false
67
+
68
+ /**
69
+ * Whether `new Function` works here (it doesn't under a Content Security
70
+ * Policy). Checked once, and again if `globalThis.Function` is replaced.
71
+ */
72
+ export const jitSupported = (): boolean => {
73
+ if (checked !== globalThis.Function) {
74
+ checked = globalThis.Function
75
+ try {
76
+ supported = checked("return true")() === true
77
+ } catch {
78
+ supported = false
79
+ }
80
+ }
81
+ return supported
82
+ }
83
+
84
+ const BT = `$S.mode="Backtrack";`
85
+
86
+ const builtinEncodings: ReadonlySet<Encoding<any>> = new Set([string, utf8, utf16le, utf16be, latin1])
87
+
88
+ /** How many units past the cursor a single token can depend on (see the closure compiler). */
89
+ const Margin = 4
90
+
91
+ /** How generated code reads the code units of an encoding (see `Emitter.reader`). */
92
+ interface Reader {
93
+ readonly width: number
94
+ readonly fast: number
95
+ readonly guard: (i: string) => string
96
+ readonly unit: (i: string) => string
97
+ }
98
+
99
+ /** Per generated function: its local variables. */
100
+ class Fn {
101
+ readonly vars: Array<string> = []
102
+ }
103
+
104
+ const isLeaf = (node: Node): boolean => {
105
+ switch (node._tag) {
106
+ case "Succeed":
107
+ case "Fail":
108
+ case "Eof":
109
+ case "Location":
110
+ case "RestLen":
111
+ case "Any":
112
+ case "Literal":
113
+ case "OneOf":
114
+ case "TakeWhile":
115
+ return true
116
+ default:
117
+ return false
118
+ }
119
+ }
120
+
121
+ const lowerAscii = (c: number): number => c >= 65 && c <= 90 ? c + 32 : c
122
+ const isLetter = (c: number): boolean => (c >= 65 && c <= 90) || (c >= 97 && c <= 122)
123
+
124
+ /** The inclusive ranges of 1s in a table. */
125
+ const runsOf = (table: Uint8Array): Array<[number, number]> => {
126
+ const out: Array<[number, number]> = []
127
+ for (let i = 0; i < table.length; i++) {
128
+ if (table[i] !== 1) continue
129
+ let j = i
130
+ while (j + 1 < table.length && table[j + 1] === 1) j++
131
+ out.push([i, j])
132
+ i = j
133
+ }
134
+ return out
135
+ }
136
+
137
+ /** A JavaScript expression testing `v` against ranges, or `undefined` if too long. */
138
+ const rangeTest = (runs: ReadonlyArray<readonly [number, number]>, v: string): string | undefined => {
139
+ if (runs.length === 0) return "false"
140
+ if (runs.length > 4) return undefined
141
+ return runs.map(([a, b]) => a === b ? `${v}===${a}` : `(${v}>=${a}&&${v}<=${b})`).join("||")
142
+ }
143
+
144
+ /** The inclusive ranges of `[0, size)` where `table[c] === value`. */
145
+ const runsWhere = (table: Uint8Array, size: number, value: 0 | 1): Array<[number, number]> => {
146
+ const out: Array<[number, number]> = []
147
+ for (let i = 0; i < size; i++) {
148
+ if (table[i] !== value) continue
149
+ let j = i
150
+ while (j + 1 < size && table[j + 1] === value) j++
151
+ out.push([i, j])
152
+ i = j
153
+ }
154
+ return out
155
+ }
156
+
157
+ /** How many comparisons it takes to test a run of `[0, size)`. */
158
+ const runCost = ([a, b]: readonly [number, number], size: number): number =>
159
+ a === b || a === 0 || b === size - 1 ? 1 : 2
160
+
161
+ /**
162
+ * A JavaScript expression testing whether `v`, known to be in `[0, size)`,
163
+ * is accepted by `table`, or `undefined` if that takes too many comparisons.
164
+ * It tests the accepted runs or the rejected ones, whichever is cheaper:
165
+ * `c!==34&&c!==92&&c>=32` rather than three two-sided ranges.
166
+ */
167
+ const tableTest = (table: Uint8Array, v: string, size: number): string | undefined => {
168
+ const accept = runsWhere(table, size, 1)
169
+ const reject = runsWhere(table, size, 0)
170
+ if (accept.length === 0) return "false"
171
+ if (reject.length === 0) return "true"
172
+ const cost = (runs: ReadonlyArray<readonly [number, number]>) => runs.reduce((n, r) => n + runCost(r, size), 0)
173
+ const acceptCost = cost(accept)
174
+ const rejectCost = cost(reject)
175
+ if (Math.min(acceptCost, rejectCost) > 6) return undefined
176
+ const terms = acceptCost <= rejectCost
177
+ ? accept.map(([a, b]) =>
178
+ a === b ? `${v}===${a}` : a === 0 ? `${v}<=${b}` : b === size - 1 ? `${v}>=${a}` : `(${v}>=${a}&&${v}<=${b})`
179
+ ).join("||")
180
+ : reject.map(([a, b]) =>
181
+ a === b ? `${v}!==${a}` : a === 0 ? `${v}>${b}` : b === size - 1 ? `${v}<${a}` : `(${v}<${a}||${v}>${b})`
182
+ ).join("&&")
183
+ return `(${terms})`
184
+ }
185
+
186
+ /**
187
+ * A repetition that builds its own result (see `Emitter.entries`): `init`
188
+ * starts it, `element(fail, check)` parses one element, runs `check` (the
189
+ * repetition's loop check) and adds the element, and `finish` ends it.
190
+ */
191
+ interface Collect {
192
+ readonly init: string
193
+ readonly element: (fail: string, check: string) => string
194
+ readonly finish: string
195
+ }
196
+
197
+ class Emitter {
198
+ readonly constants: Array<unknown> = []
199
+ private readonly constantIndex = new Map<unknown, number>()
200
+ readonly functions: Array<string> = []
201
+ private readonly rules = [new Map<ParserLike, string>(), new Map<ParserLike, string>()]
202
+ private readonly refs = new Map<ParserLike, number>()
203
+ private readonly closure: ClosureCompiler
204
+ private next = 0
205
+
206
+ constructor(readonly root: ParserLike, readonly kind: 0 | 1 | 2, readonly resumable = false) {
207
+ this.closure = createClosureCompiler(kind, resumable)
208
+ this.countRefs(root)
209
+ }
210
+
211
+ get fallbacks(): ReadonlyArray<Fallback> {
212
+ return this.closure.fallbacks
213
+ }
214
+
215
+ /** Counts the distinct parents of each parser. */
216
+ private countRefs(root: ParserLike): void {
217
+ const seen = new Set<ParserLike>()
218
+ const stack = [root]
219
+ while (stack.length > 0) {
220
+ const p = stack.pop()!
221
+ if (seen.has(p)) continue
222
+ seen.add(p)
223
+ for (const c of new Set(children(nodeOf(p)))) {
224
+ this.refs.set(c, (this.refs.get(c) ?? 0) + 1)
225
+ stack.push(c)
226
+ }
227
+ }
228
+ }
229
+
230
+ const(value: unknown): string {
231
+ let i = this.constantIndex.get(value)
232
+ if (i === undefined) {
233
+ i = this.constants.length
234
+ this.constants.push(value)
235
+ this.constantIndex.set(value, i)
236
+ }
237
+ return `C[${i}]`
238
+ }
239
+
240
+ /** A JavaScript literal for simple values, otherwise a constant. */
241
+ lit(value: unknown): string {
242
+ if (value === undefined) return "void 0"
243
+ if (value === null || typeof value === "boolean") return String(value)
244
+ if (typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0)) return String(value)
245
+ if (typeof value === "string" && value.length < 64) return JSON.stringify(value)
246
+ return this.const(value)
247
+ }
248
+
249
+ v(fn: Fn): string {
250
+ const name = `v${this.next++}`
251
+ fn.vars.push(name)
252
+ return name
253
+ }
254
+
255
+ label(): string {
256
+ return `L${this.next++}`
257
+ }
258
+
259
+ unit(i: string): string {
260
+ return this.kind === 0 ? `I.charCodeAt(${i})` : `I[${i}]`
261
+ }
262
+
263
+ /**
264
+ * How generated code reads an encoding's code units: units below `fast`
265
+ * are whole code points, `width` input units long. `guard(i)` says that a
266
+ * whole unit is available at `i`.
267
+ */
268
+ reader(enc: Encoding<any>): Reader {
269
+ if (enc === utf16le || enc === utf16be) {
270
+ const [lo, hi] = enc === utf16le ? ["", "+1"] : ["+1", ""]
271
+ return {
272
+ width: 2,
273
+ fast: 0xd800,
274
+ guard: (i) => `${i}+1<E`,
275
+ unit: (i) => `(I[${i}${lo}]|I[${i}${hi}]<<8)`
276
+ }
277
+ }
278
+ return { width: 1, fast: enc.unit === 1 ? fastLimit(enc) : 0, guard: (i) => `${i}<E`, unit: (i) => this.unit(i) }
279
+ }
280
+
281
+ /** The function implementing a rule, generating it on first use. */
282
+ rule(p: ParserLike, used: boolean): string {
283
+ const rules = this.rules[used ? 1 : 0]!
284
+ let name = rules.get(p)
285
+ if (name !== undefined) return name
286
+ name = `f${this.next++}`
287
+ rules.set(p, name)
288
+ const fn = new Fn()
289
+ const out = used ? this.v(fn) : null
290
+ const done = this.label()
291
+ const body = this.inline(p, used, out, `break ${done};`, fn, 0)
292
+ this.functions.push(
293
+ `function ${name}(p){const I=$I,E=$E;${fn.vars.length > 0 ? `let ${fn.vars.join(",")};` : ""}` +
294
+ `${done}:{${body}P=p;return ${out ?? "void 0"}}return F}`
295
+ )
296
+ return name
297
+ }
298
+
299
+ /**
300
+ * Code for `p`: on success it falls through with `p` advanced and, when
301
+ * `out` is given, the value in `out`; on failure it runs `fail`.
302
+ */
303
+ gen(p: ParserLike, used: boolean, out: string | null, fail: string, fn: Fn, depth: number): string {
304
+ const node = nodeOf(p)
305
+ if (node._tag === "Suspend") return this.call(this.rule(node.resolve(), used), out, fail)
306
+ if (!isLeaf(node) && (((this.refs.get(p) ?? 0) > 1 && !this.small(p)) || depth > 32)) {
307
+ return this.call(this.rule(p, used), out, fail)
308
+ }
309
+ return this.inline(p, used, out, fail, fn, depth)
310
+ }
311
+
312
+ /** Whether a parser is small enough to inline wherever it is used. */
313
+ private small(p: ParserLike): boolean {
314
+ let budget = 6
315
+ const visit = (q: ParserLike): boolean => {
316
+ const node = nodeOf(q)
317
+ if (--budget < 0 || node._tag === "Suspend" || node._tag === "Escaped" || node._tag === "Dispatch") return false
318
+ return children(node).every(visit)
319
+ }
320
+ return visit(p)
321
+ }
322
+
323
+ /**
324
+ * In a resumable build: token code (which fails by breaking out of `bad`)
325
+ * that read within `lookahead` units of the end of a partial input re-runs
326
+ * interpreted, since it may have to report `Incomplete` there. Token code
327
+ * leaves `p` where it stopped reading, even when it fails.
328
+ */
329
+ private partialSafe(
330
+ p: ParserLike,
331
+ code: string,
332
+ bad: string,
333
+ lookahead: number,
334
+ out: string | null,
335
+ fail: string,
336
+ fn: Fn
337
+ ): string {
338
+ const ahead = Math.max(lookahead, Margin)
339
+ const st = this.v(fn)
340
+ const ok = this.v(fn)
341
+ const t = this.v(fn)
342
+ const leaf = this.label()
343
+ return `${st}=p;${ok}=false;${leaf}:{${bad}:{${code}${ok}=true;}` +
344
+ `if(!$S.partial||p+${ahead}<=E){if(${ok})break ${leaf};${fail}}` +
345
+ `p=${st};$S.pos=p;${t}=${this.const(p.step)}($S);if(${t}===F){${fail}}p=$S.pos;${out === null ? "" : `${out}=${t};`}}`
346
+ }
347
+
348
+ call(name: string, out: string | null, fail: string): string {
349
+ if (out === null) return `if(${name}(p)===F){${fail}}p=P;`
350
+ return `${out}=${name}(p);if(${out}===F){${fail}}p=P;`
351
+ }
352
+
353
+ /** Runs the closure-compiled step of `p` on the shared state. */
354
+ delegate(p: ParserLike, used: boolean, out: string | null, fail: string, fn: Fn): string {
355
+ const step = this.const(this.closure.compile(p, used))
356
+ const t = out ?? this.v(fn)
357
+ return `$S.pos=p;${t}=${step}($S);if(${t}===F){${fail}}p=$S.pos;`
358
+ }
359
+
360
+ private native(enc: Encoding<any> | undefined): boolean {
361
+ return enc === undefined ? this.kind !== 2 : kindOfEncoding(enc) === this.kind
362
+ }
363
+
364
+ inline(p: ParserLike, used: boolean, out: string | null, fail: string, fn: Fn, depth: number): string {
365
+ const node = nodeOf(p)
366
+ const set = (value: string) => out === null ? "" : `${out}=${value};`
367
+ const d = depth + 1
368
+ switch (node._tag) {
369
+ case "Succeed":
370
+ return set(this.lit(node.value))
371
+ case "Sync":
372
+ return used ? set(`${this.const(node.evaluate)}()`) : ""
373
+ case "Fail":
374
+ return `$S.mode=${JSON.stringify(node.mode)};${fail}`
375
+ case "Eof":
376
+ return `if(p<E){${BT}${fail}}` + (this.resumable ? `if($S.partial){$S.mode="Incomplete";${fail}}` : "") +
377
+ set("void 0")
378
+ case "Location":
379
+ return set("p")
380
+ case "RestLen":
381
+ return set("E-p")
382
+ case "Suspend":
383
+ return this.call(this.rule(node.resolve(), used), out, fail)
384
+ case "Any":
385
+ case "Literal":
386
+ case "OneOf":
387
+ case "TakeWhile": {
388
+ if (!this.native(node.encoding)) return this.delegate(p, used, out, fail, fn)
389
+ if (!this.resumable) return this.token(node, out, fail, fn) ?? this.delegate(p, used, out, fail, fn)
390
+ const bad = this.label()
391
+ const code = this.token(node, out, `break ${bad};`, fn)
392
+ if (code === undefined) return this.delegate(p, used, out, fail, fn)
393
+ return this.partialSafe(p, code, bad, node._tag === "Literal" ? node.literal.length * 4 : Margin, out, fail, fn)
394
+ }
395
+ case "Escaped": {
396
+ // Streaming builds use the closure compiler's escapes, which keep checkpoints
397
+ if (!this.native(node.encoding) || this.resumable) return this.delegate(p, used, out, fail, fn)
398
+ return this.escaped(p, node, out, fail, fn, d)
399
+ }
400
+ case "Map": {
401
+ if (!used) return this.gen(node.self, false, null, fail, fn, d)
402
+ if (node.f === toFloat && out !== null) {
403
+ const code = this.float(node, out, fail, fn, d)
404
+ if (code !== undefined) return code
405
+ }
406
+ // Streaming builds keep the array, which their checkpoints hold
407
+ if (node.f === fromEntries && out !== null && !this.resumable && entriesShape(node.self)) {
408
+ return this.entries(node.self, out, fail, fn, d)
409
+ }
410
+ const t = this.v(fn)
411
+ return this.gen(node.self, true, t, fail, fn, d) + set(`${this.const(node.f)}(${t})`)
412
+ }
413
+ case "As":
414
+ return this.gen(node.self, false, null, fail, fn, d) + (used ? set(this.lit(node.value)) : "")
415
+ case "Tap": {
416
+ const t = out ?? this.v(fn)
417
+ return this.gen(node.self, true, t, fail, fn, d) + `${this.const(node.f)}(${t});`
418
+ }
419
+ case "TryMap":
420
+ case "FilterMap": {
421
+ const t = this.v(fn)
422
+ const r = this.v(fn)
423
+ const [tag, field] = node._tag === "TryMap" ? ["Success", "success"] : ["Some", "value"]
424
+ return this.gen(node.self, true, t, fail, fn, d) +
425
+ `${r}=${this.const(node.f)}(${t});if(${r}._tag!=="${tag}"){${BT}${fail}}${set(`${r}.${field}`)}`
426
+ }
427
+ case "Filter": {
428
+ const t = out ?? this.v(fn)
429
+ return this.gen(node.self, true, t, fail, fn, d) + `if(!${this.const(node.predicate)}(${t})){${BT}${fail}}`
430
+ }
431
+ case "FlatMap": {
432
+ const t = this.v(fn)
433
+ const r = out ?? this.v(fn)
434
+ this.closure.note(p, node, "the parser returned by the function runs interpreted")
435
+ return this.gen(node.self, true, t, fail, fn, d) +
436
+ `$S.pos=p;${r}=${this.const(node.f)}(${t}).step($S);if(${r}===F){${fail}}p=$S.pos;`
437
+ }
438
+ case "Taken": {
439
+ if (node.encoding !== undefined && !this.native(node.encoding)) return this.delegate(p, used, out, fail, fn)
440
+ const st = this.v(fn)
441
+ const body = `${st}=p;` + this.gen(node.self, false, null, fail, fn, d)
442
+ if (out === null && node.encoding === undefined) return body
443
+ if (node.encoding === undefined) {
444
+ return body + set(this.kind === 0 ? `I.slice(${st},p)` : this.kind === 1 ? `I.subarray(${st},p)` : `$S.slice(${st},p)`)
445
+ }
446
+ const t = out ?? this.v(fn)
447
+ const enc = this.const(node.encoding)
448
+ return body + `${t}=R.textOf(${enc},${fastLimit(node.encoding)},$S,${st},p);if(${t}===F){${fail}}`
449
+ }
450
+ case "WithTaken":
451
+ case "WithSpan": {
452
+ const st = this.v(fn)
453
+ const t = this.v(fn)
454
+ const extra = node._tag === "WithSpan" ? `{start:${st},end:p}` : `$S.slice(${st},p)`
455
+ return `${st}=p;` + this.gen(node.self, true, t, fail, fn, d) + set(`[${t},${extra}]`)
456
+ }
457
+ case "Span": {
458
+ const st = this.v(fn)
459
+ return `${st}=p;` + this.gen(node.self, false, null, fail, fn, d) + set(`{start:${st},end:p}`)
460
+ }
461
+ case "Mode": {
462
+ const ok = this.label()
463
+ const bad = this.label()
464
+ return `${ok}:{${bad}:{${this.gen(node.self, used, out, `break ${bad};`, fn, d)}break ${ok};}` +
465
+ `if($S.mode===${JSON.stringify(node.from)})$S.mode=${JSON.stringify(node.to)};${fail}}`
466
+ }
467
+ case "Context":
468
+ case "Expected":
469
+ case "MapError":
470
+ case "OrFail":
471
+ case "Trace":
472
+ case "RetryAfter":
473
+ return this.gen(node.self, used, out, fail, fn, d)
474
+ case "ResumeAfter": {
475
+ if (!used) return this.gen(node.self, false, null, fail, fn, d)
476
+ const t = this.v(fn)
477
+ return this.gen(node.self, true, t, fail, fn, d) + set(`R.some(${t})`)
478
+ }
479
+ case "Opt":
480
+ case "OrElseSucceed": {
481
+ const st = this.v(fn)
482
+ const ok = this.label()
483
+ const bad = this.label()
484
+ const t = out === null ? null : this.v(fn)
485
+ const some = node._tag === "Opt" ? `R.some(${t})` : t!
486
+ const orElse = node._tag === "Opt" ? "R.none()" : `${this.const(node.value)}()`
487
+ return `${st}=p;${ok}:{${bad}:{${this.gen(node.self, used, t, `break ${bad};`, fn, d)}${set(some)}break ${ok};}` +
488
+ `if($S.mode!=="Backtrack"){${fail}}p=${st};${set(orElse)}}`
489
+ }
490
+ case "Peek": {
491
+ const st = this.v(fn)
492
+ return `${st}=p;${this.gen(node.self, used, out, fail, fn, d)}p=${st};`
493
+ }
494
+ case "Not": {
495
+ const st = this.v(fn)
496
+ const ok = this.label()
497
+ const bad = this.label()
498
+ return `${st}=p;${ok}:{${bad}:{${this.gen(node.self, false, null, `break ${bad};`, fn, d)}p=${st};${BT}${fail}}` +
499
+ `if($S.mode!=="Backtrack"){${fail}}p=${st};}${set("void 0")}`
500
+ }
501
+ case "Seq":
502
+ return this.seq(node, used, out, fail, fn, d)
503
+ case "Struct": {
504
+ if (out === null) return node.parsers.map((q) => this.gen(q, false, null, fail, fn, d)).join("")
505
+ const vars = node.parsers.map(() => this.v(fn))
506
+ const code = node.parsers.map((q, i) => this.gen(q, true, vars[i]!, fail, fn, d)).join("")
507
+ if (node.keys.includes("__proto__")) {
508
+ return code + `${out}={};` + node.keys.map((k, i) => `${out}[${JSON.stringify(k)}]=${vars[i]};`).join("")
509
+ }
510
+ return code + `${out}={${node.keys.map((k, i) => `${JSON.stringify(k)}:${vars[i]}`).join(",")}};`
511
+ }
512
+ case "ZipWith": {
513
+ if (out === null) return this.gen(node.self, false, null, fail, fn, d) + this.gen(node.that, false, null, fail, fn, d)
514
+ const x = this.v(fn)
515
+ const y = this.v(fn)
516
+ return this.gen(node.self, true, x, fail, fn, d) + this.gen(node.that, true, y, fail, fn, d) +
517
+ `${out}=${this.const(node.f)}(${x},${y});`
518
+ }
519
+ case "Alt":
520
+ return this.alt(node, used, out, fail, fn, d)
521
+ case "Dispatch":
522
+ return this.dispatch(p, node, used, out, fail, fn, d)
523
+ case "Repeat":
524
+ case "Fold":
525
+ return this.repeat(node, used, out, fail, fn, d)
526
+ case "Separated":
527
+ return this.separated(node, used, out, fail, fn, d)
528
+ default:
529
+ return this.delegate(p, used, out, fail, fn)
530
+ }
531
+ }
532
+
533
+ /**
534
+ * `Ascii.float` and `Text.float` (`map(floatText, toFloat)`): numbers in
535
+ * decimal notation are matched and converted in one pass by
536
+ * `scanFloatString` or `scanFloatBytes`, without slicing the input; the
537
+ * rest (`nan`, `inf`, failures before a digit) runs the parser. Returns
538
+ * `undefined` when this does not apply.
539
+ */
540
+ private float(node: NodeOf<"Map">, out: string, fail: string, fn: Fn, d: number): string | undefined {
541
+ if (!floatTexts.has(node.self) || this.kind === 2) return undefined
542
+ let taken = nodeOf(node.self)
543
+ while (taken._tag === "Expected") taken = nodeOf(taken.self)
544
+ if (taken._tag !== "Taken") return undefined
545
+ const enc = taken.encoding
546
+ // Decimal notation is ASCII, which these encodings read unit by unit
547
+ if (enc !== undefined && !(this.native(enc) && enc.unit === 1 && enc.asciiCompatible)) return undefined
548
+ const t = this.v(fn)
549
+ const scan = this.const(floatScan)
550
+ // Near the end of a partial input, the parser may have to report `Incomplete`
551
+ const near = this.resumable ? `||$S.partial&&${scan}.read+${Margin}>E` : ""
552
+ return `${out}=${this.kind === 0 ? "R.scanFloatString" : "R.scanFloatBytes"}(I,p,E);` +
553
+ `if(${out}===void 0${near}){${this.gen(node.self, true, t, fail, fn, d)}${out}=${this.const(toFloat)}(${t})}` +
554
+ `else if(${out}===F){${BT}${fail}}else p=${scan}.end;`
555
+ }
556
+
557
+ seq(node: NodeOf<"Seq">, used: boolean, out: string | null, fail: string, fn: Fn, d: number): string {
558
+ const output = node.output
559
+ if (out === null || !used) return node.parsers.map((q) => this.gen(q, false, null, fail, fn, d)).join("")
560
+ if (typeof output === "number") {
561
+ return node.parsers.map((q, i) => this.gen(q, i === output, i === output ? out : null, fail, fn, d)).join("")
562
+ }
563
+ const keep = (i: number) => output === "tuple" || output.includes(i)
564
+ const vars = node.parsers.map((_, i) => keep(i) ? this.v(fn) : null)
565
+ const code = node.parsers.map((q, i) => this.gen(q, keep(i), vars[i]!, fail, fn, d)).join("")
566
+ return code + `${out}=[${vars.filter((v) => v !== null).join(",")}];`
567
+ }
568
+
569
+ /** A test that the unit `u` may start `first` (false for -1, the end of the input). */
570
+ private startTest(first: First, u: string): string {
571
+ const runs = runsOf(first.units)
572
+ const small = rangeTest(runs, u)
573
+ const low = small ?? `${this.const(first.units)}[${u}]===1`
574
+ if (!first.high) return small !== undefined ? `(${low})` : `(${u}<${TableSize}&&${low})`
575
+ return `(${u}>=${TableSize}||${low})`
576
+ }
577
+
578
+ alt(node: NodeOf<"Alt">, used: boolean, out: string | null, fail: string, fn: Fn, d: number): string {
579
+ const parsers = node.parsers
580
+ if (parsers.length === 0) return `${BT}${fail}`
581
+ if (parsers.length === 1) return this.gen(parsers[0]!, used, out, fail, fn, d)
582
+ const firsts = this.kind === 2 ? undefined : parsers.map((q) => this.closure.firsts.of(q))
583
+ const prune = firsts !== undefined && planAlt(firsts) !== undefined
584
+ const st = this.v(fn)
585
+ const u = prune ? this.v(fn) : ""
586
+ const done = this.label()
587
+ // Near the end of a partial input, no branch is skipped: it may be incomplete rather than fail
588
+ const read = this.resumable ? `$S.partial&&p+${Margin}>E?-2:p<E?${this.unit("p")}:-1` : `p<E?${this.unit("p")}:-1`
589
+ let code = `${st}=p;${prune ? `${u}=${read};` : ""}${done}:{`
590
+ parsers.forEach((q, i) => {
591
+ const bad = this.label()
592
+ const branch = `${bad}:{${this.gen(q, used, out, `break ${bad};`, fn, d)}break ${done};}` +
593
+ `if($S.mode!=="Backtrack"){${fail}}p=${st};`
594
+ const f = firsts?.[i]
595
+ code += prune && f!.mode === Strict
596
+ ? `if(${this.resumable ? `${u}===-2||` : ""}${this.startTest(f!, u)}){${branch}}`
597
+ : branch
598
+ })
599
+ return code + `${BT}${fail}}`
600
+ }
601
+
602
+ dispatch(p: ParserLike, node: NodeOf<"Dispatch">, used: boolean, out: string | null, fail: string, fn: Fn, d: number): string {
603
+ const choose = this.const(node.choose)
604
+ // The general path: run the selector, choose, then run the case's compiled step
605
+ const cases = new Map<ParserLike, Step>()
606
+ const caseStep = (q: ParserLike) => {
607
+ let s = cases.get(q)
608
+ if (s === undefined) cases.set(q, s = this.closure.compile(q, used))
609
+ return s
610
+ }
611
+ if (node.cases !== undefined) {
612
+ for (const key of Reflect.ownKeys(node.cases)) caseStep(node.cases[key as any]!)
613
+ if (node.otherwise !== undefined) caseStep(node.otherwise)
614
+ }
615
+ const table = this.const(cases)
616
+ const general = (): string => {
617
+ const st = this.v(fn)
618
+ const k = this.v(fn)
619
+ const q = this.v(fn)
620
+ const r = out ?? this.v(fn)
621
+ return `${st}=p;${this.gen(node.selector, true, k, fail, fn, d)}${q}=${choose}(${k});` +
622
+ `if(${q}===void 0){p=${st};${BT}${fail}}$S.pos=p;${r}=(${table}.get(${q})??${q}.step)($S);` +
623
+ `if(${r}===F){${fail}}p=$S.pos;`
624
+ }
625
+ if (node.cases === undefined) {
626
+ this.closure.note(p, node, "the parser returned by the function of cases runs interpreted")
627
+ return general()
628
+ }
629
+ let sel = nodeOf(node.selector)
630
+ const peek = sel._tag === "Peek"
631
+ if (peek) sel = nodeOf((sel as NodeOf<"Peek">).self)
632
+ if (sel._tag !== "Any" || !this.native(sel.encoding)) return general()
633
+ const enc = sel.encoding
634
+ const r = enc === undefined ? undefined : this.reader(enc)
635
+ const limit = r === undefined ? (this.kind === 0 ? 0xd800 : TableSize) : Math.min(r.fast, 0xd800)
636
+ const size = Math.min(limit, TableSize)
637
+ if (size === 0) return general()
638
+ // Group the units by the case they select
639
+ const groups = new Map<ParserLike, Array<number>>()
640
+ for (let unit = 0; unit < size; unit++) {
641
+ const q = (node.choose)(enc === undefined && this.kind === 1 ? unit : charOf(unit))
642
+ if (q === undefined) continue
643
+ let g = groups.get(q)
644
+ if (g === undefined) groups.set(q, g = [])
645
+ g.push(unit)
646
+ }
647
+ const u = this.v(fn)
648
+ const sw = this.label()
649
+ // A unit cut short by the end of the input takes the general path
650
+ const read = r === undefined
651
+ ? `p<E?${this.unit("p")}:-1`
652
+ : `${r.guard("p")}?${r.unit("p")}:p<E?${size}:-1`
653
+ // Near the end of a partial input, the general path handles incomplete units
654
+ const near = this.resumable ? `$S.partial&&p+${Margin}>E?${size}:` : ""
655
+ let code = `${u}=${near}${read};${sw}:switch(${u}){`
656
+ for (const [q, units] of groups) {
657
+ code += units.map((x) => `case ${x}:`).join("") +
658
+ `{${peek ? "" : `p+=${r?.width ?? 1};`}${this.gen(q, used, out, fail, fn, d)}break ${sw};}`
659
+ }
660
+ code += `case -1:{${BT}${fail}}default:{if(${u}<${size}){${BT}${fail}}${general()}}}`
661
+ return code
662
+ }
663
+
664
+ repeat(
665
+ node: NodeOf<"Repeat"> | NodeOf<"Fold">,
666
+ used: boolean,
667
+ out: string | null,
668
+ fail: string,
669
+ fn: Fn,
670
+ d: number,
671
+ collect?: Collect
672
+ ): string {
673
+ const { max, min } = node
674
+ const loop = this.label()
675
+ const bad = this.label()
676
+ const n = this.v(fn)
677
+ const st = this.v(fn)
678
+ const isFold = node._tag === "Fold"
679
+ const keep = out !== null && used && collect === undefined
680
+ const x = keep ? this.v(fn) : null
681
+ const acc = keep ? out! : this.v(fn)
682
+ let init = collect?.init ?? ""
683
+ let add = ""
684
+ if (keep) {
685
+ if (isFold) {
686
+ init = `${acc}=${this.const((node as NodeOf<"Fold">).init)}();`
687
+ add = `${acc}=${this.const((node as NodeOf<"Fold">).f)}(${acc},${x});`
688
+ } else {
689
+ init = `${acc}=[];`
690
+ add = `${acc}.push(${x});`
691
+ }
692
+ }
693
+ const check = max === Infinity ? `if(p===${st}){$S.mode="Cut";${fail}}` : ""
694
+ const bound = max === Infinity ? "" : `${n}<${max}`
695
+ const r = this.resumable ? this.resumePoint(fn) : undefined
696
+ const save = r === undefined
697
+ ? ""
698
+ : `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},{acc:${acc},count:${n},pos:${st}});`
699
+ // With `collect` (never in resumable builds), the element adds itself once the loop check passed
700
+ const element = collect === undefined
701
+ ? `${this.gen(node.self, keep, x, `break ${bad};`, fn, d)}${check}${add}`
702
+ : collect.element(`break ${bad};`, check)
703
+ const body = `${loop}:for(;${bound};${n}++){${st}=p;` +
704
+ `${bad}:{${element}continue ${loop};}` +
705
+ `${save}if($S.mode!=="Backtrack"${min > 0 ? `||${n}<${min}` : ""}){${fail}}p=${st};break ${loop};}`
706
+ if (r === undefined) return `${init}${n}=0;${body}${collect?.finish ?? ""}`
707
+ return r.wrap(acc, `${acc}=cp.acc;${n}=cp.count;p=cp.pos;`, `${init}${n}=0;`, body)
708
+ }
709
+
710
+ /**
711
+ * In a resumable build, a repetition entered at `entry` may restart from a
712
+ * checkpoint (`restore`, with the checkpoint in `cp`), or return the result
713
+ * of a `Done` one; a resumed repetition records its result when it ends.
714
+ */
715
+ private resumePoint(fn: Fn) {
716
+ const key = this.const({})
717
+ const entry = this.v(fn)
718
+ const resumed = this.v(fn)
719
+ const done = this.label()
720
+ return {
721
+ key,
722
+ entry,
723
+ resumed,
724
+ wrap: (value: string, restore: string, fresh: string, body: string) =>
725
+ `${entry}=p;${resumed}=false;${done}:{if($S.resume!==void 0){const cp=R.take($S,${key},p);` +
726
+ `if(cp instanceof R.Done){p=cp.pos;${value}=cp.value;break ${done};}` +
727
+ `if(cp!==void 0){${restore}${resumed}=true;}}if(!${resumed}){${fresh}}${body}` +
728
+ `if(${resumed})R.done($S,${key},${entry},${value},p);}`
729
+ }
730
+ }
731
+
732
+ separated(
733
+ node: NodeOf<"Separated">,
734
+ used: boolean,
735
+ out: string | null,
736
+ fail: string,
737
+ fn: Fn,
738
+ d: number,
739
+ collect?: Collect
740
+ ): string {
741
+ const { max, min } = node
742
+ const keep = out !== null && used && collect === undefined
743
+ if (max === 0) return collect !== undefined ? collect.init + collect.finish : keep ? `${out}=[];` : ""
744
+ const x = keep ? this.v(fn) : null
745
+ const acc = keep ? out! : this.v(fn)
746
+ const n = this.v(fn)
747
+ const st = this.v(fn)
748
+ const done = this.label()
749
+ const loop = this.label()
750
+ const push = keep ? `${acc}.push(${x});` : ""
751
+ const r = this.resumable ? this.resumePoint(fn) : undefined
752
+ const save = r === undefined
753
+ ? ""
754
+ : `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},{acc:${acc},count:${n},pos:${st}});`
755
+ // Runs `body`; on its failure, stops the repetition (or fails if `fatal`)
756
+ const attempt = (body: (fail: string) => string, success: string, fatal: string, checkpoint: string) => {
757
+ const ok = this.label()
758
+ const bad = this.label()
759
+ return `${ok}:{${bad}:{${body(`break ${bad};`)}${success}break ${ok};}` +
760
+ `${checkpoint}if($S.mode!=="Backtrack"${fatal}){${fail}}p=${st};break ${done};}`
761
+ }
762
+ const self = (f: string) => collect === undefined ? this.gen(node.self, keep, x, f, fn, d) : collect.element(f, "")
763
+ const sep = (f: string) => this.gen(node.separator, false, null, f, fn, d)
764
+ const check = max === Infinity ? `if(p===${st}){$S.mode="Cut";${fail}}` : ""
765
+ const first = `${collect?.init ?? (keep ? `${acc}=[];` : "")}${n}=0;${st}=p;` + attempt(self, `${push}${n}++;`, min > 0 ? "||true" : "", "")
766
+ const rest = `${loop}:while(${max === Infinity ? "true" : `${n}<${max}`}){${st}=p;` +
767
+ attempt(sep, "", `||${n}<${min}`, save) + check +
768
+ attempt(self, `${push}${n}++;continue ${loop};`, `||${n}<${min}`, save) +
769
+ `}`
770
+ if (r === undefined) return `${done}:{${first}${rest}}${collect?.finish ?? ""}`
771
+ // `done` ends the repetition; the checkpoint wrapper records the result after it
772
+ return r.wrap(acc, `${acc}=cp.acc;${n}=cp.count;p=cp.pos;`, "", `${done}:{if(!${r.resumed}){${first}}${rest}}`)
773
+ }
774
+
775
+ /**
776
+ * `map(self, Object.fromEntries)`, where `entriesShape(self)`: builds the
777
+ * object directly, assigning each entry as it is parsed (see
778
+ * `internal/entries.ts`), without the pairs and their array. `cut`,
779
+ * `preceded`, `terminated` and diagnostics around the repetition are
780
+ * compiled as usual around it.
781
+ */
782
+ private entries(p: ParserLike, out: string, fail: string, fn: Fn, d: number): string {
783
+ const node = nodeOf(p)
784
+ switch (node._tag) {
785
+ case "Mode": {
786
+ const ok = this.label()
787
+ const bad = this.label()
788
+ return `${ok}:{${bad}:{${this.entries(node.self, out, `break ${bad};`, fn, d)}break ${ok};}` +
789
+ `if($S.mode===${JSON.stringify(node.from)})$S.mode=${JSON.stringify(node.to)};${fail}}`
790
+ }
791
+ case "Seq": {
792
+ const i = node.output as number
793
+ return node.parsers.map((q, j) =>
794
+ j === i ? this.entries(q, out, fail, fn, d) : this.gen(q, false, null, fail, fn, d)
795
+ ).join("")
796
+ }
797
+ case "Repeat":
798
+ case "Separated": {
799
+ const pair = pairOf(node.self)!
800
+ const deferred = this.v(fn)
801
+ const key = this.v(fn)
802
+ const value = this.v(fn)
803
+ const collect: Collect = {
804
+ init: `${out}={};${deferred}=void 0;`,
805
+ element: (f, check) =>
806
+ pair.parsers.map((q, j) =>
807
+ this.gen(q, j === pair.key || j === pair.value, j === pair.key ? key : j === pair.value ? value : null, f, fn, d)
808
+ ).join("") + check +
809
+ `if(${deferred}===void 0&&R.isPlainKey(${key}))${out}[${key}]=${value};` +
810
+ `else ${deferred}=R.addEntry(${out},${deferred},${key},${value});`,
811
+ finish: `if(${deferred}!==void 0)R.flushEntries(${out},${deferred});`
812
+ }
813
+ return node._tag === "Repeat"
814
+ ? this.repeat(node, true, out, fail, fn, d, collect)
815
+ : this.separated(node, true, out, fail, fn, d, collect)
816
+ }
817
+ default:
818
+ return this.entries((node as { readonly self: ParserLike }).self, out, fail, fn, d)
819
+ }
820
+ }
821
+
822
+ // ---------------------------------------------------------------------------
823
+ // Tokens
824
+ // ---------------------------------------------------------------------------
825
+
826
+ /** An expression: is the unit `c`, known to be below `size` (at most `TableSize`), accepted? */
827
+ private member(accept: Uint8Array, c: string, size: number = TableSize): string {
828
+ return tableTest(accept, c, size) ?? `${this.const(accept)}[${c}]===1`
829
+ }
830
+
831
+ /** An expression: is the code point `c` (at least `TableSize`) accepted? */
832
+ private memberHigh(set: CharSet, negate: boolean, c: string): string {
833
+ if (set.ranges !== undefined && !set.ranges.some(([, b]) => b >= TableSize)) return negate ? "true" : "false"
834
+ return `${this.const(set.test)}(${c})!==${negate}`
835
+ }
836
+
837
+ /**
838
+ * An expression: does a `Text` set accept the unit `c`, known to be below
839
+ * `r.fast`? Code points from `TableSize` up are tested by the set's
840
+ * function, unless its ranges show they are all rejected.
841
+ */
842
+ private textAccepts(r: Reader, cs: CharSet, accept: Uint8Array, negate: boolean, c: string): string {
843
+ if (r.fast <= TableSize) return `(${this.member(accept, c, r.fast)})`
844
+ const member = this.member(accept, c)
845
+ if (reachesAbove(cs, TableSize)) return `(${c}<${TableSize}?${member}:${this.const(cs.test)}(${c})!==${negate})`
846
+ return negate ? `(${c}>=${TableSize}||${member})` : `(${c}<${TableSize}&&${member})`
847
+ }
848
+
849
+ /**
850
+ * An expression: does a `Text` set reject `c`, a code point that `decode`
851
+ * returned? The built-in encodings only decode code points from `r.fast`
852
+ * up there.
853
+ */
854
+ private textRejectsDecoded(enc: Encoding<any>, r: Reader, cs: CharSet, negate: boolean, c: string): string {
855
+ if (r.fast > 0 && builtinEncodings.has(enc) && !reachesAbove(cs, r.fast)) return String(!negate)
856
+ return `${this.const(cs.test)}(${c})===${negate}`
857
+ }
858
+
859
+ token(
860
+ node: NodeOf<"Any" | "Literal" | "OneOf" | "TakeWhile">,
861
+ out: string | null,
862
+ fail: string,
863
+ fn: Fn
864
+ ): string | undefined {
865
+ const set = (value: string) => out === null ? "" : `${out}=${value};`
866
+ const enc = node.encoding
867
+ const kind = this.kind as 0 | 1
868
+ const failBT = `{${BT}${fail}}`
869
+ switch (node._tag) {
870
+ case "Any": {
871
+ if (enc === undefined) {
872
+ if (kind === 1) return `if(p>=E)${failBT}${set("I[p]")}p++;`
873
+ const c = this.v(fn)
874
+ return `if(p>=E)${failBT}${c}=I.charCodeAt(p);` +
875
+ `if(${c}>=55296&&${c}<=56319&&p+1<I.length&&(I.charCodeAt(p+1)&64512)===56320){${set("I.slice(p,p+2)")}p+=2}` +
876
+ `else{${set("I[p]")}p++}`
877
+ }
878
+ const r = this.reader(enc)
879
+ const c = this.v(fn)
880
+ const dec = `${this.const(enc.decode)}(I,p,E)`
881
+ return `if(p>=E)${failBT}` +
882
+ (r.fast > 0 ? `if(${r.guard("p")}&&(${c}=${r.unit("p")})<${r.fast}){${set(`R.charOf(${c})`)}p+=${r.width}}else` : "") +
883
+ `{${c}=${dec};if(${c}<0)${failBT}${set(`R.charOf(${c})`)}p+=${this.const(enc.width)}(${c})}`
884
+ }
885
+ case "Literal": {
886
+ const lit = node.literal
887
+ if (enc !== undefined) {
888
+ if (node.caseless) return undefined
889
+ const encoded = enc.encode(lit as string)
890
+ return this.literalUnits(
891
+ typeof encoded === "string" ? unitsOf(encoded) : Array.from(encoded),
892
+ false,
893
+ fail
894
+ ) + set(this.lit(lit))
895
+ }
896
+ const literalKind = typeof lit === "string" ? 0 : lit instanceof Uint8Array ? 1 : 2
897
+ if (kind === 0 && literalKind !== 0) return undefined
898
+ if (literalKind === 2) return undefined
899
+ const codes = kind === 0
900
+ ? unitsOf(lit as string)
901
+ : Array.from(literalKind === 0 ? new TextEncoder().encode(lit as string) : lit as Uint8Array)
902
+ const len = codes.length
903
+ const value = !node.caseless && literalKind === kind
904
+ ? this.lit(lit)
905
+ : kind === 0
906
+ ? `I.slice(p-${len},p)`
907
+ : `I.subarray(p-${len},p)`
908
+ return this.literalUnits(codes, node.caseless, fail) + set(value)
909
+ }
910
+ case "OneOf": {
911
+ const negate = node.negate
912
+ const cs = enc === undefined ? tokenSet(node.set, kind) : textSet(node.set)
913
+ const accept = acceptTable(cs, negate)
914
+ const c = this.v(fn)
915
+ const value = (len: string) => this.kind === 0 ? `I.slice(p,p+${len})` : `I[p]`
916
+ if (enc === undefined) {
917
+ if (kind === 1) return `if(p>=E)${failBT}${c}=I[p];if(!(${this.member(accept, c)}))${failBT}${set("I[p]")}p++;`
918
+ const cp = this.v(fn)
919
+ if (!negate && this.memberHigh(cs, negate, cp) === "false") {
920
+ return `if(p>=E)${failBT}${c}=I.charCodeAt(p);if(${c}>=${TableSize}||!(${this.member(accept, c)}))${failBT}${set("I[p]")}p++;`
921
+ }
922
+ return `if(p>=E)${failBT}${c}=I.charCodeAt(p);` +
923
+ `if(${c}<${TableSize}){if(!(${this.member(accept, c)}))${failBT}${set("I[p]")}p++}` +
924
+ `else{${cp}=${c};if(${c}>=55296&&${c}<=56319&&p+1<I.length){const d=I.charCodeAt(p+1);` +
925
+ `if((d&64512)===56320)${cp}=((${c}-55296)<<10)+(d-56320)+65536}` +
926
+ `if(!(${this.memberHigh(cs, negate, cp)}))${failBT}` +
927
+ `if(${cp}>65535){${set(value("2"))}p+=2}else{${set("I[p]")}p++}}`
928
+ }
929
+ const r = this.reader(enc)
930
+ return `if(p>=E)${failBT}` +
931
+ (r.fast > 0
932
+ ? `if(${r.guard("p")}&&(${c}=${r.unit("p")})<${r.fast}){if(!${this.textAccepts(r, cs, accept, negate, c)})${failBT}${set(`R.charOf(${c})`)}p+=${r.width}}else`
933
+ : "") +
934
+ `{${c}=${this.const(enc.decode)}(I,p,E);if(${c}<0||${this.textRejectsDecoded(enc, r, cs, negate, c)})${failBT}` +
935
+ `${set(`R.charOf(${c})`)}p+=${this.const(enc.width)}(${c})}`
936
+ }
937
+ case "TakeWhile":
938
+ return this.takeWhile(node, out, fail, fn)
939
+ }
940
+ }
941
+
942
+ /** Matches literal units at `p`, advancing past them. */
943
+ private literalUnits(codes: ReadonlyArray<number>, caseless: boolean, fail: string): string {
944
+ const len = codes.length
945
+ if (len === 0) return ""
946
+ const test = (c: number, i: number) => {
947
+ const at = this.unit(i === 0 ? "p" : `p+${i}`)
948
+ if (caseless && isLetter(c)) return `(${at}|32)===${lowerAscii(c)}`
949
+ return `${at}===${c}`
950
+ }
951
+ if (len <= 8) {
952
+ // Reading past the end gives NaN or undefined, which matches nothing
953
+ return `if(!(${codes.map(test).join("&&")})){${BT}${fail}}p+=${len};`
954
+ }
955
+ if (this.kind === 0 && !caseless) {
956
+ const str = String.fromCharCode(...codes)
957
+ return `if(!I.startsWith(${JSON.stringify(str)},p)){${BT}${fail}}p+=${len};`
958
+ }
959
+ const expect = this.const(Uint8Array.from(caseless ? codes.map(lowerAscii) : codes))
960
+ const lower = caseless ? `${this.const(lowerAscii)}` : ""
961
+ return `if(E-p<${len}){${BT}${fail}}for(let i=0;i<${len};i++)if(${lower}(${this.unit("p+i")})!==${expect}[i]){${BT}${fail}}p+=${len};`
962
+ }
963
+
964
+ private takeWhile(node: NodeOf<"TakeWhile">, out: string | null, fail: string, fn: Fn): string {
965
+ const enc = node.encoding
966
+ const { max, min, negate } = node
967
+ const kind = this.kind as 0 | 1
968
+ const cs = enc === undefined ? tokenSet(node.set, kind) : textSet(node.set)
969
+ const accept = acceptTable(cs, negate)
970
+ const st = this.v(fn)
971
+ const c = this.v(fn)
972
+ const n = this.v(fn)
973
+ const loop = this.label()
974
+ const counted = max !== Infinity || min > 1
975
+ const count = counted ? `${n}++;` : ""
976
+ const bound = max !== Infinity ? `&&${n}<${max}` : ""
977
+ const slice = enc !== undefined
978
+ ? `${this.const(enc.text)}(I,${st},p)`
979
+ : kind === 0
980
+ ? `I.slice(${st},p)`
981
+ : `I.subarray(${st},p)`
982
+ const finish = (min > 0 ? `if(${counted ? n : `p-${st}`}<${min}){${BT}${fail}}` : "") +
983
+ (out === null ? "" : `${out}=${slice};`)
984
+ const head = `${st}=p;${counted ? `${n}=0;` : ""}${loop}:while(p<E${bound}){${c}=${this.unit("p")};`
985
+ const member = this.member(accept, c)
986
+ if (kind === 1 && (enc === undefined || fastLimit(enc) >= TableSize)) {
987
+ // Bytes, one token each
988
+ return `${head}if(!(${member}))break;p++;${count}}${finish}`
989
+ }
990
+ if (enc !== undefined) {
991
+ // Text: inline the encoding's single-unit code points, decode the rest
992
+ const r = this.reader(enc)
993
+ // The loop condition already checks a unit of width 1
994
+ const guard = r.width === 1 ? "" : `${r.guard("p")}&&`
995
+ return `${st}=p;${counted ? `${n}=0;` : ""}${loop}:while(p<E${bound}){` +
996
+ (r.fast > 0
997
+ ? `if(${guard}(${c}=${r.unit("p")})<${r.fast}){if(!${this.textAccepts(r, cs, accept, negate, c)})break ${loop};p+=${r.width}}else`
998
+ : "") +
999
+ `{${c}=${this.const(enc.decode)}(I,p,E);if(${c}<0||${this.textRejectsDecoded(enc, r, cs, negate, c)})break ${loop};` +
1000
+ `p+=${this.const(enc.width)}(${c})}${count}}${finish}`
1001
+ }
1002
+ const fast = 0xd800
1003
+ const small = TableSize
1004
+ const highTest = (v: string) => this.memberHigh(cs, negate, v)
1005
+ // Token on strings: code points, with lenient surrogates
1006
+ const cp = this.v(fn)
1007
+ const never = highTest(c) === "false"
1008
+ // Every code point from `TableSize` up is accepted: without a count,
1009
+ // surrogate pairs can be skipped a unit at a time
1010
+ const always = highTest(c) === "true"
1011
+ const rest = never && !negate
1012
+ ? `break;`
1013
+ : always && !counted
1014
+ ? `p++;`
1015
+ : `if(${c}<${fast}||${c}>56319){if(!(${highTest(c)}))break ${loop};p++}` +
1016
+ `else{${cp}=${c};if(p+1<I.length){const d=I.charCodeAt(p+1);if((d&64512)===56320)${cp}=((${c}-55296)<<10)+(d-56320)+65536}` +
1017
+ `if(!(${highTest(cp)}))break ${loop};p+=${cp}>65535?2:1}`
1018
+ return `${head}if(${c}<${small}){if(!(${member}))break;p++}else ${rest}${count}}${finish}`
1019
+ }
1020
+
1021
+ escaped(p: ParserLike, node: NodeOf<"Escaped">, out: string | null, fail: string, fn: Fn, d: number): string {
1022
+ const enc = node.encoding
1023
+ const ctrl = node.control
1024
+ const r = enc === undefined ? undefined : this.reader(enc)
1025
+ if (r !== undefined && !(ctrl < r.fast)) return this.delegate(p, !node.raw, out, fail, fn)
1026
+ const raw = node.raw
1027
+ const acc = raw ? null : out ?? this.v(fn)
1028
+ const x = raw ? null : this.v(fn)
1029
+ const b = this.v(fn)
1030
+ const st = this.v(fn)
1031
+ const okv = this.v(fn)
1032
+ const loop = this.label()
1033
+ const bad = this.label()
1034
+ const normal = this.gen(node.normal, !raw, x, `break ${bad};`, fn, d)
1035
+ const escape = this.gen(node.escape, !raw, x, fail, fn, d)
1036
+ const onNormal = raw
1037
+ ? `if(p===${b}){$S.mode="Cut";${fail}}continue ${loop};`
1038
+ : `if(p!==${b}){${acc}+=${x};continue ${loop}}`
1039
+ const slice = enc !== undefined
1040
+ ? `${this.const(enc.text)}(I,${st},p)`
1041
+ : this.kind === 0
1042
+ ? `I.slice(${st},p)`
1043
+ : `I.subarray(${st},p)`
1044
+ const ctrlTest = r === undefined ? `p<E&&${this.unit("p")}===${ctrl}` : `${r.guard("p")}&&${r.unit("p")}===${ctrl}`
1045
+ return `${raw ? `${st}=p;` : `${acc}="";`}${loop}:while(p<E){${b}=p;${okv}=false;` +
1046
+ `${bad}:{${normal}${okv}=true;}` +
1047
+ `if(${okv}){${onNormal}}else{if($S.mode!=="Backtrack"){${fail}}p=${b}}` +
1048
+ `if(${ctrlTest}){p+=${r?.width ?? 1};${escape}${raw ? "" : `${acc}+=${x};`}}else break ${loop};}` +
1049
+ (raw && out !== null ? `${out}=${slice};` : "")
1050
+ }
1051
+ }
1052
+
1053
+ /** The UTF-16 code units of a string. */
1054
+ const unitsOf = (s: string): Array<number> => Array.from({ length: s.length }, (_, i) => s.charCodeAt(i))
1055
+
1056
+ /** 1 where a small code is accepted (taking `negate` into account). */
1057
+ const acceptTable = (set: CharSet, negate: boolean): Uint8Array => {
1058
+ const t = new Uint8Array(TableSize)
1059
+ for (let c = 0; c < TableSize; c++) t[c] = (set.table[c] === 1) !== negate ? 1 : 0
1060
+ return t
1061
+ }
1062
+
1063
+ /** Generates the source of a compiled parser, without building it. */
1064
+ export const generate = (root: ParserLike, kind: 0 | 1 | 2, resumable = false): {
1065
+ readonly source: string
1066
+ readonly constants: ReadonlyArray<unknown>
1067
+ readonly fallbacks: ReadonlyArray<Fallback>
1068
+ } => {
1069
+ const e = new Emitter(root, kind, resumable)
1070
+ const main = e.rule(root, true)
1071
+ const source = `"use strict";let $I,$E,$S,P;${e.functions.join("\n")}\n` +
1072
+ `return function run(state){const sI=$I,sE=$E,sS=$S,sP=P;$I=state.input;$E=state.end;$S=state;` +
1073
+ `try{const v=${main}(state.pos);if(v!==F)state.pos=P;return v}finally{$I=sI;$E=sE;$S=sS;P=sP}}`
1074
+ return { source, constants: e.constants, fallbacks: e.fallbacks }
1075
+ }
1076
+
1077
+ /** Compiles a root parser for one input kind with `new Function`. */
1078
+ export const compileJit = (root: ParserLike, kind: 0 | 1 | 2, resumable = false): {
1079
+ readonly step: Step
1080
+ readonly fallbacks: ReadonlyArray<Fallback>
1081
+ readonly source: string
1082
+ } => {
1083
+ const { constants, fallbacks, source } = generate(root, kind, resumable)
1084
+ const run = globalThis.Function("C", "F", "R", source)(constants, Fail, runtime) as Step
1085
+ // Generated code reads past `end` expecting nothing to match there, which
1086
+ // holds when `end` is the input's length, as it is in the runners.
1087
+ let closure: Step | undefined
1088
+ const step: Step = (state) =>
1089
+ state.end === state.input.length ? run(state) : (closure ??= createClosureCompiler(kind, resumable).compile(root, true))(state)
1090
+ return { step, fallbacks, source }
1091
+ }