@thomasfosterau/effect-parse 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +37 -13
  2. package/dist/Ascii.d.ts +11 -4
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +7 -1
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts.map +1 -1
  7. package/dist/Binary.js +64 -35
  8. package/dist/Binary.js.map +1 -1
  9. package/dist/Bits.d.ts.map +1 -1
  10. package/dist/Bits.js +7 -4
  11. package/dist/Bits.js.map +1 -1
  12. package/dist/Compiler.d.ts +74 -2
  13. package/dist/Compiler.d.ts.map +1 -1
  14. package/dist/Compiler.js +78 -1
  15. package/dist/Compiler.js.map +1 -1
  16. package/dist/Expression.d.ts.map +1 -1
  17. package/dist/Expression.js +86 -74
  18. package/dist/Expression.js.map +1 -1
  19. package/dist/Input.d.ts +37 -4
  20. package/dist/Input.d.ts.map +1 -1
  21. package/dist/Input.js +42 -0
  22. package/dist/Input.js.map +1 -1
  23. package/dist/Nom.d.ts +5 -15
  24. package/dist/Nom.d.ts.map +1 -1
  25. package/dist/Nom.js.map +1 -1
  26. package/dist/ParseError.d.ts.map +1 -1
  27. package/dist/ParseError.js.map +1 -1
  28. package/dist/Parser.d.ts +366 -26
  29. package/dist/Parser.d.ts.map +1 -1
  30. package/dist/Parser.js +390 -19
  31. package/dist/Parser.js.map +1 -1
  32. package/dist/ParserSchema.js +1 -1
  33. package/dist/ParserStream.d.ts +3 -2
  34. package/dist/ParserStream.d.ts.map +1 -1
  35. package/dist/ParserStream.js +13 -9
  36. package/dist/ParserStream.js.map +1 -1
  37. package/dist/Syntax.d.ts +23 -5
  38. package/dist/Syntax.d.ts.map +1 -1
  39. package/dist/Syntax.js.map +1 -1
  40. package/dist/Text.d.ts.map +1 -1
  41. package/dist/Text.js +9 -9
  42. package/dist/Text.js.map +1 -1
  43. package/dist/Token.d.ts +55 -10
  44. package/dist/Token.d.ts.map +1 -1
  45. package/dist/Token.js +59 -21
  46. package/dist/Token.js.map +1 -1
  47. package/dist/internal/compiler/closure.d.ts.map +1 -1
  48. package/dist/internal/compiler/closure.js +257 -97
  49. package/dist/internal/compiler/closure.js.map +1 -1
  50. package/dist/internal/compiler/entries.d.ts +3 -1
  51. package/dist/internal/compiler/entries.d.ts.map +1 -1
  52. package/dist/internal/compiler/entries.js +3 -2
  53. package/dist/internal/compiler/entries.js.map +1 -1
  54. package/dist/internal/compiler/first.d.ts.map +1 -1
  55. package/dist/internal/compiler/first.js +7 -1
  56. package/dist/internal/compiler/first.js.map +1 -1
  57. package/dist/internal/compiler/graph.d.ts +17 -0
  58. package/dist/internal/compiler/graph.d.ts.map +1 -0
  59. package/dist/internal/compiler/graph.js +56 -0
  60. package/dist/internal/compiler/graph.js.map +1 -0
  61. package/dist/internal/compiler/jit.d.ts +5 -0
  62. package/dist/internal/compiler/jit.d.ts.map +1 -1
  63. package/dist/internal/compiler/jit.js +185 -96
  64. package/dist/internal/compiler/jit.js.map +1 -1
  65. package/dist/internal/compiler/registry.d.ts +10 -2
  66. package/dist/internal/compiler/registry.d.ts.map +1 -1
  67. package/dist/internal/compiler/registry.js.map +1 -1
  68. package/dist/internal/compiler/search.d.ts +32 -0
  69. package/dist/internal/compiler/search.d.ts.map +1 -0
  70. package/dist/internal/compiler/search.js +68 -0
  71. package/dist/internal/compiler/search.js.map +1 -0
  72. package/dist/internal/compiler/sets.d.ts.map +1 -1
  73. package/dist/internal/compiler/sets.js +1 -1
  74. package/dist/internal/compiler/sets.js.map +1 -1
  75. package/dist/internal/compiler/wasm.d.ts +8 -0
  76. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  77. package/dist/internal/compiler/wasm.js +2769 -0
  78. package/dist/internal/compiler/wasm.js.map +1 -0
  79. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  80. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  81. package/dist/internal/compiler/wasmAsm.js +427 -0
  82. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  83. package/dist/internal/entries.d.ts +3 -1
  84. package/dist/internal/entries.d.ts.map +1 -1
  85. package/dist/internal/entries.js.map +1 -1
  86. package/dist/internal/node.d.ts +58 -2
  87. package/dist/internal/node.d.ts.map +1 -1
  88. package/dist/internal/node.js +13 -3
  89. package/dist/internal/node.js.map +1 -1
  90. package/dist/internal/numbers.d.ts.map +1 -1
  91. package/dist/internal/numbers.js.map +1 -1
  92. package/dist/internal/run.d.ts +12 -0
  93. package/dist/internal/run.d.ts.map +1 -1
  94. package/dist/internal/run.js +51 -11
  95. package/dist/internal/run.js.map +1 -1
  96. package/dist/internal/stack.d.ts +6 -0
  97. package/dist/internal/stack.d.ts.map +1 -1
  98. package/dist/internal/stack.js +8 -2
  99. package/dist/internal/stack.js.map +1 -1
  100. package/dist/internal/syntax.d.ts +4 -4
  101. package/dist/internal/syntax.d.ts.map +1 -1
  102. package/dist/internal/syntax.js +18 -10
  103. package/dist/internal/syntax.js.map +1 -1
  104. package/dist/internal/text.d.ts +32 -0
  105. package/dist/internal/text.d.ts.map +1 -1
  106. package/dist/internal/text.js +68 -14
  107. package/dist/internal/text.js.map +1 -1
  108. package/dist/internal/tokens.d.ts +13 -0
  109. package/dist/internal/tokens.d.ts.map +1 -1
  110. package/dist/internal/tokens.js +59 -39
  111. package/dist/internal/tokens.js.map +1 -1
  112. package/package.json +5 -4
  113. package/src/Ascii.ts +42 -9
  114. package/src/Binary.ts +151 -71
  115. package/src/Bits.ts +12 -6
  116. package/src/Compiler.ts +101 -10
  117. package/src/Expression.ts +94 -81
  118. package/src/Input.ts +65 -6
  119. package/src/Nom.ts +15 -5
  120. package/src/ParseError.ts +3 -0
  121. package/src/Parser.ts +678 -127
  122. package/src/ParserStream.ts +37 -18
  123. package/src/Syntax.ts +60 -36
  124. package/src/Text.ts +16 -15
  125. package/src/Token.ts +138 -29
  126. package/src/internal/compiler/closure.ts +362 -135
  127. package/src/internal/compiler/entries.ts +4 -2
  128. package/src/internal/compiler/first.ts +10 -0
  129. package/src/internal/compiler/graph.ts +68 -0
  130. package/src/internal/compiler/jit.ts +284 -154
  131. package/src/internal/compiler/registry.ts +10 -2
  132. package/src/internal/compiler/search.ts +68 -0
  133. package/src/internal/compiler/sets.ts +7 -3
  134. package/src/internal/compiler/wasm.ts +3203 -0
  135. package/src/internal/compiler/wasmAsm.ts +484 -0
  136. package/src/internal/entries.ts +8 -2
  137. package/src/internal/node.ts +54 -4
  138. package/src/internal/numbers.ts +3 -1
  139. package/src/internal/run.ts +71 -12
  140. package/src/internal/stack.ts +8 -1
  141. package/src/internal/syntax.ts +39 -18
  142. package/src/internal/text.ts +97 -9
  143. package/src/internal/tokens.ts +96 -14
@@ -9,16 +9,16 @@
9
9
  */
10
10
  import * as Predicate from "effect/Predicate"
11
11
  import type { Encoding } from "../../Encoding.js"
12
- import type { ErrMode, State } from "../../Input.js"
12
+ import type { ErrMode, Input, State } from "../../Input.js"
13
13
  import { Fail } from "../../Input.js"
14
14
  import * as Option from "effect/Option"
15
15
  import * as Result from "effect/Result"
16
16
  import type { Node, NodeOf, ParserLike } from "../node.js"
17
17
  import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js"
18
- import { nodeOf, scansFloat } from "../node.js"
18
+ import { Defer, nodeOf, scansFloat } from "../node.js"
19
19
  import { floatScan, floatTexts, scanFloatBytes, scanFloatString, toFloat } from "../numbers.js"
20
- import { charOf, fastLimit, firstCodePoint, fold, utf16be, utf16le, validate } from "../text.js"
21
- import { countTokens, indexOf } from "../tokens.js"
20
+ import { charOf, fastLimit, firstCodePoint, fold, textIn, utf16be, utf16le, utf8, utf8Text, validate } from "../text.js"
21
+ import { countTokens, indexOf, indexOfAny } from "../tokens.js"
22
22
  import type { AltPlan } from "./first.js"
23
23
  import { Key } from "../key.js"
24
24
  import * as Resume from "../resume.js"
@@ -26,6 +26,7 @@ import type { Backend } from "./registry.js"
26
26
  import type { EntryPair } from "./entries.js"
27
27
  import { buildsEntries, pairOf } from "./entries.js"
28
28
  import { FirstSets, planAlt } from "./first.js"
29
+ import { nextStop, SearchHead, StopCache, stopChars } from "./search.js"
29
30
  import { acceptTable, kindOfEncoding, TableSize, textSet, tokenSet } from "./sets.js"
30
31
 
31
32
  export type Step = (state: State<any>) => any
@@ -137,6 +138,11 @@ class Compiler {
137
138
  switch (node._tag) {
138
139
  case "Opaque":
139
140
  return this.fallback(p, node, "not describable (Parser.make, gen, ...)")
141
+ case "Fixed":
142
+ // `Binary`'s numbers: the step is already what a closure would be
143
+ return p.step
144
+ case "Combinator":
145
+ return node.build(node.parsers.map((q) => this.compile(q, true)))
140
146
  case "Succeed": {
141
147
  const value = node.value
142
148
  return () => value
@@ -158,10 +164,8 @@ class Compiler {
158
164
  s.mode = BT
159
165
  return Fail
160
166
  }
161
- if (s.partial) {
162
- s.mode = INC
163
- return Fail
164
- }
167
+ // `eof`'s own failure: `Incomplete`, needing one more unit
168
+ if (s.partial) return s.incomplete(1)
165
169
  return undefined
166
170
  }
167
171
  case "Location":
@@ -176,14 +180,13 @@ class Compiler {
176
180
  case "Take":
177
181
  case "TakeWhile":
178
182
  case "TakeUntil":
183
+ case "TakeUntilAny":
179
184
  case "Rest":
180
185
  if (!this.native(node.encoding)) return this.fallback(p, node, "token parser on array input")
181
186
  return this.partialSafe(
182
187
  p,
183
188
  node.encoding === undefined ? this.token(p, node, used) : this.text(p, node, node.encoding, used),
184
- Predicate.isTagged(node, "Literal") || Predicate.isTagged(node, "TakeUntil")
185
- ? node.literal.length * 4
186
- : Margin
189
+ marginOf(node)
187
190
  )
188
191
  case "TillLineEnding":
189
192
  case "Identifier":
@@ -199,7 +202,7 @@ class Compiler {
199
202
  if (step !== undefined) return step
200
203
  }
201
204
  // Streaming builds keep the array, which their checkpoints hold
202
- if (node.f === fromEntries && !this.resumable && buildsEntries(node.self)) return this.entries(node.self)
205
+ if (node.f === fromEntries && buildsEntries(node.self)) return this.entries(node.self)
203
206
  const c = this.compile(node.self, true)
204
207
  const f = node.f
205
208
  return (s) => {
@@ -246,6 +249,28 @@ class Compiler {
246
249
  return Fail
247
250
  }
248
251
  }
252
+ case "FastPath": {
253
+ const c = this.compile(node.self, used)
254
+ const fast = node.fast
255
+ return (s) => {
256
+ const start = s.pos
257
+ const a = fast(s)
258
+ if (a !== Defer) return a
259
+ s.pos = start
260
+ return c(s)
261
+ }
262
+ }
263
+ case "MapOrFail": {
264
+ const c = this.compile(node.self, true)
265
+ const f = node.f
266
+ return (s) => {
267
+ const a = c(s)
268
+ if (a === Fail) return Fail
269
+ const b = f(a)
270
+ if (b === Fail) s.mode = BT
271
+ return b
272
+ }
273
+ }
249
274
  case "Filter": {
250
275
  const c = this.compile(node.self, true)
251
276
  const predicate = node.predicate
@@ -280,6 +305,16 @@ class Compiler {
280
305
  return a === Fail ? Fail : [a, s.slice(start, s.pos)]
281
306
  }
282
307
  }
308
+ case "MapSpan": {
309
+ const c = this.compile(node.self, used)
310
+ if (!used) return c
311
+ const f = node.f
312
+ return (s) => {
313
+ const start = s.pos
314
+ const a = c(s)
315
+ return a === Fail ? Fail : f(a, start, s.pos)
316
+ }
317
+ }
283
318
  case "Span": {
284
319
  const c = this.compile(node.self, false)
285
320
  if (!used) return c
@@ -308,6 +343,7 @@ class Compiler {
308
343
  }
309
344
  case "Context":
310
345
  case "Expected":
346
+ case "Quiet":
311
347
  case "MapError":
312
348
  case "OrFail":
313
349
  case "Trace":
@@ -377,6 +413,11 @@ class Compiler {
377
413
  return out
378
414
  }
379
415
  }
416
+ case "SeqWith": {
417
+ const steps = node.parsers.map((q) => this.compile(q, used))
418
+ if (!used) return this.chain(steps)
419
+ return this.seqWith(steps, node.f)
420
+ }
380
421
  case "ZipWith": {
381
422
  const c1 = this.compile(node.self, used)
382
423
  const c2 = this.compile(node.that, used)
@@ -394,8 +435,26 @@ class Compiler {
394
435
  case "Dispatch":
395
436
  return this.dispatch(p, node, used)
396
437
  case "Repeat":
397
- case "Fold":
438
+ case "Fold": {
439
+ // A run of a character set whose characters are not kept scans as a `takeWhile` (#79)
440
+ const element = Predicate.isTagged(node, "Repeat") && !used ? nodeOf(node.self) : undefined
441
+ if (
442
+ element !== undefined &&
443
+ Predicate.isTagged(element, "OneOf") &&
444
+ this.kind !== 2 &&
445
+ this.native(element.encoding)
446
+ ) {
447
+ const { encoding, negate, set } = element
448
+ const run: NodeOf<"TakeWhile"> = { _tag: "TakeWhile", set, negate, encoding, min: node.min, max: node.max }
449
+ // The repetition's own step is the exact fallback near the end of a partial input
450
+ return this.partialSafe(
451
+ p,
452
+ encoding === undefined ? this.token(p, run, false) : this.text(p, run, encoding, false),
453
+ Margin
454
+ )
455
+ }
398
456
  return this.repeat(node, used)
457
+ }
399
458
  case "RepeatTill":
400
459
  return this.repeatTill(node)
401
460
  case "Separated":
@@ -408,13 +467,51 @@ class Compiler {
408
467
  }
409
468
  }
410
469
 
470
+ /** `seqWith`: up to three parsers without an array of values. */
471
+ seqWith(steps: ReadonlyArray<Step>, f: NodeOf<"SeqWith">["f"]): Step {
472
+ const n = steps.length
473
+ if (n === 0) return () => f()
474
+ const [c1, c2, c3] = steps
475
+ if (n === 1)
476
+ return (s) => {
477
+ const a = c1(s)
478
+ return a === Fail ? Fail : f(a)
479
+ }
480
+ if (n === 2)
481
+ return (s) => {
482
+ const a = c1(s)
483
+ if (a === Fail) return Fail
484
+ const b = c2(s)
485
+ return b === Fail ? Fail : f(a, b)
486
+ }
487
+ if (n === 3)
488
+ return (s) => {
489
+ const a = c1(s)
490
+ if (a === Fail) return Fail
491
+ const b = c2(s)
492
+ if (b === Fail) return Fail
493
+ const c = c3(s)
494
+ return c === Fail ? Fail : f(a, b, c)
495
+ }
496
+ return (s) => {
497
+ const values = new Array(n)
498
+ for (let i = 0; i < n; i++) {
499
+ const a = steps[i](s)
500
+ if (a === Fail) return Fail
501
+ values[i] = a
502
+ }
503
+ return f(...values)
504
+ }
505
+ }
506
+
411
507
  /** Runs steps in order, for their effect on the cursor only. */
412
508
  chain(steps: ReadonlyArray<Step>): Step {
413
509
  const n = steps.length
414
510
  if (n === 0) return () => undefined
415
511
  if (n === 1) return steps[0]
416
512
  if (n === 2) {
417
- const [a, b] = steps as [Step, Step]
513
+ const a = steps[0]
514
+ const b = steps[1]
418
515
  return (s) => (a(s) === Fail ? Fail : b(s))
419
516
  }
420
517
  return (s) => {
@@ -443,7 +540,8 @@ class Compiler {
443
540
  }
444
541
  if (Predicate.isNumber(output)) {
445
542
  if (n === 2) {
446
- const [a, b] = steps as [Step, Step]
543
+ const a = steps[0]
544
+ const b = steps[1]
447
545
  return output === 0
448
546
  ? (s) => {
449
547
  const x = a(s)
@@ -479,8 +577,8 @@ class Compiler {
479
577
  /** The unit at the cursor, or -1 at the end of the input. */
480
578
  private unitReader(): (s: State<any>) => number {
481
579
  return this.kind === 0
482
- ? (s) => (s.pos < s.end ? (s.input as string).charCodeAt(s.pos) : -1)
483
- : (s) => (s.pos < s.end ? (s.input as Uint8Array)[s.pos] : -1)
580
+ ? (s: State<string>) => (s.pos < s.end ? s.input.charCodeAt(s.pos) : -1)
581
+ : (s: State<Uint8Array>) => (s.pos < s.end ? s.input[s.pos] : -1)
484
582
  }
485
583
 
486
584
  alt(node: NodeOf<"Alt">, used: boolean): Step {
@@ -533,7 +631,7 @@ class Compiler {
533
631
  const compiled = new Map<ParserLike, Step>()
534
632
  if (node.cases !== undefined) {
535
633
  for (const key of Reflect.ownKeys(node.cases)) {
536
- const q = node.cases[key as any]
634
+ const q = node.cases[key]
537
635
  if (!compiled.has(q)) compiled.set(q, this.compile(q, used))
538
636
  }
539
637
  }
@@ -557,9 +655,9 @@ class Compiler {
557
655
  return general
558
656
  }
559
657
  // Switch on the next unit when the selector is `any` or `peek(any)`
560
- let sel = nodeOf(node.selector)
561
- const peek = Predicate.isTagged(sel, "Peek")
562
- if (peek) sel = nodeOf((sel as NodeOf<"Peek">).self)
658
+ const top = nodeOf(node.selector)
659
+ const peek = Predicate.isTagged(top, "Peek")
660
+ const sel = peek ? nodeOf(top.self) : top
563
661
  if (!Predicate.isTagged(sel, "Any") || !this.native(sel.encoding)) return general
564
662
  const enc = sel.encoding
565
663
  // Units below `limit` are whole tokens, so the key is known in advance
@@ -597,17 +695,17 @@ class Compiler {
597
695
  const { max, min } = node
598
696
  const checkLoop = max === Infinity
599
697
  const isFold = Predicate.isTagged(node, "Fold")
600
- const cond = isFold ? (node as NodeOf<"Fold">).while : undefined
698
+ const cond = isFold ? node.while : undefined
601
699
  const c = this.compile(node.self, used)
602
700
  const resumable = this.resumable
603
701
  const key = new Key()
604
702
  // The accumulator: an array, the fold's value, or nothing when unused (and unneeded by `while`)
605
703
  const tracked = used || cond !== undefined
606
- const init: () => unknown = !tracked ? () => undefined : isFold ? (node as NodeOf<"Fold">).init : () => []
704
+ const init: () => unknown = !tracked ? () => undefined : isFold ? node.init : () => []
607
705
  const add: (acc: any, a: unknown) => unknown = !tracked
608
706
  ? (acc) => acc
609
707
  : isFold
610
- ? (node as NodeOf<"Fold">).f
708
+ ? node.f
611
709
  : (acc, a) => {
612
710
  acc.push(a)
613
711
  return acc
@@ -618,6 +716,7 @@ class Compiler {
618
716
  let count = 0
619
717
  let resumed = false
620
718
  if (resumable && s.resume !== undefined) {
719
+ // SAFETY: only this loop saves checkpoints under `key`: `{ acc, count, pos }` when it runs out, a `Done` when it completes
621
720
  const cp = Resume.take(s, key, entry) as Resume.Done | { acc: unknown; count: number; pos: number } | undefined
622
721
  if (cp instanceof Resume.Done) {
623
722
  s.pos = cp.pos
@@ -657,44 +756,43 @@ class Compiler {
657
756
  */
658
757
  private entries(p: ParserLike): Step {
659
758
  const node = nodeOf(p)
660
- switch (node._tag) {
661
- case "Mode": {
662
- const c = this.entries(node.self)
663
- const from = node.from
664
- const to = node.to
665
- return (s) => {
666
- const a = c(s)
667
- if (a === Fail && s.mode === from) s.mode = to
668
- return a
669
- }
759
+ if (Predicate.isTagged(node, "Mode")) {
760
+ const c = this.entries(node.self)
761
+ const from = node.from
762
+ const to = node.to
763
+ return (s) => {
764
+ const a = c(s)
765
+ if (a === Fail && s.mode === from) s.mode = to
766
+ return a
670
767
  }
671
- case "Seq": {
672
- const i = node.output as number
673
- const steps = node.parsers.map((q, j) => (j === i ? this.entries(q) : this.compile(q, false)))
674
- return (s) => {
675
- let out: unknown
676
- for (let j = 0; j < steps.length; j++) {
677
- const a = steps[j](s)
678
- if (a === Fail) return Fail
679
- if (j === i) out = a
680
- }
681
- return out
768
+ }
769
+ if (Predicate.isTagged(node, "Seq")) {
770
+ // SAFETY: `buildsEntries` passes through a sequence only when it keeps one output, by index
771
+ const i = node.output as number
772
+ const steps = node.parsers.map((q, j) => (j === i ? this.entries(q) : this.compile(q, false)))
773
+ return (s) => {
774
+ let out: unknown
775
+ for (let j = 0; j < steps.length; j++) {
776
+ const a = steps[j](s)
777
+ if (a === Fail) return Fail
778
+ if (j === i) out = a
682
779
  }
780
+ return out
683
781
  }
684
- case "Repeat":
685
- case "Separated": {
686
- const pair = pairOf(node.self)
687
- if (pair !== undefined) return this.entriesLoop(node, pair)
688
- // Not reached (`buildsEntries` found the pair): build the pairs, then the object
689
- const c = this.compile(p, true)
690
- return (s) => {
691
- const a = c(s)
692
- return a === Fail ? Fail : fromEntries(a as Iterable<readonly [PropertyKey, unknown]>)
693
- }
782
+ }
783
+ if (Predicate.isTagged(node, "Repeat") || Predicate.isTagged(node, "Separated")) {
784
+ const pair = pairOf(node.self)
785
+ if (pair !== undefined) return this.entriesLoop(node, pair)
786
+ // Not reached (`buildsEntries` found the pair): build the pairs, then the object
787
+ const c = this.compile(p, true)
788
+ return (s) => {
789
+ const a = c(s)
790
+ // SAFETY: a used `repeat` or `separated` returns the array of its elements, here the pairs
791
+ return a === Fail ? Fail : fromEntries(a as Iterable<readonly [PropertyKey, unknown]>)
694
792
  }
695
- default:
696
- return this.entries((node as { readonly self: ParserLike }).self)
697
793
  }
794
+ // SAFETY: the other nodes `buildsEntries` passes through are diagnostics, which all wrap a `self`
795
+ return this.entries((node as { readonly self: ParserLike }).self)
698
796
  }
699
797
 
700
798
  private entriesLoop(node: NodeOf<"Repeat"> | NodeOf<"Separated">, pair: EntryPair): Step {
@@ -715,10 +813,17 @@ class Compiler {
715
813
  }
716
814
  const { max, min } = node
717
815
  const checkLoop = max === Infinity
816
+ // A streaming build's checkpoint holds the object built so far (see `repeat`)
817
+ const resumable = this.resumable
818
+ const site = new Key()
718
819
  if (Predicate.isTagged(node, "Repeat")) {
719
820
  return (s) => {
720
- const acc = new EntriesAcc()
721
- for (let count = 0; count < max; count++) {
821
+ const at = s.pos
822
+ const cp = resumable && s.resume !== undefined ? resumeEntries(s, site, at) : undefined
823
+ if (cp instanceof Resume.Done) return cp.value
824
+ const resumed = cp !== undefined
825
+ const acc = cp?.acc ?? new EntriesAcc()
826
+ for (let count = cp?.count ?? 0; count < max; count++) {
722
827
  const start = s.pos
723
828
  let key: unknown
724
829
  let value: unknown
@@ -733,9 +838,7 @@ class Compiler {
733
838
  else if (j === vi) value = a
734
839
  }
735
840
  if (!ok) {
736
- if (s.mode !== BT || count < min) return Fail
737
- s.pos = start
738
- return acc.result()
841
+ return stopEntries(s, site, at, resumed, acc, count, start, min)
739
842
  }
740
843
  if (checkLoop && s.pos === start) {
741
844
  s.mode = CUT
@@ -743,40 +846,42 @@ class Compiler {
743
846
  }
744
847
  acc.add(key, value)
745
848
  }
746
- return acc.result()
849
+ return entriesDone(s, site, at, resumed, acc)
747
850
  }
748
851
  }
749
852
  const sep = this.compile(node.separator, false)
750
853
  return (s) => {
751
- const acc = new EntriesAcc()
752
- let count = 0
753
- if (max === 0) return acc.result()
754
- let start = s.pos
755
- if (!entry(s, acc)) {
756
- if (s.mode !== BT || min > 0) return Fail
757
- s.pos = start
758
- return acc.result()
854
+ const at = s.pos
855
+ const cp = resumable && s.resume !== undefined ? resumeEntries(s, site, at) : undefined
856
+ if (cp instanceof Resume.Done) return cp.value
857
+ const resumed = cp !== undefined
858
+ const acc = cp?.acc ?? new EntriesAcc()
859
+ let count = cp?.count ?? 0
860
+ if (!resumed) {
861
+ if (max === 0) return acc.result()
862
+ const start = s.pos
863
+ if (!entry(s, acc)) {
864
+ if (s.mode !== BT || min > 0) return Fail
865
+ s.pos = start
866
+ return acc.result()
867
+ }
868
+ count++
759
869
  }
760
- count++
761
870
  while (count < max) {
762
- start = s.pos
871
+ const start = s.pos
763
872
  if (sep(s) === Fail) {
764
- if (s.mode !== BT || count < min) return Fail
765
- s.pos = start
766
- return acc.result()
873
+ return stopEntries(s, site, at, resumed, acc, count, start, min)
767
874
  }
768
875
  if (checkLoop && s.pos === start) {
769
876
  s.mode = CUT
770
877
  return Fail
771
878
  }
772
879
  if (!entry(s, acc)) {
773
- if (s.mode !== BT || count < min) return Fail
774
- s.pos = start
775
- return acc.result()
880
+ return stopEntries(s, site, at, resumed, acc, count, start, min)
776
881
  }
777
882
  count++
778
883
  }
779
- return acc.result()
884
+ return entriesDone(s, site, at, resumed, acc)
780
885
  }
781
886
  }
782
887
 
@@ -819,6 +924,7 @@ class Compiler {
819
924
  let count = 0
820
925
  let resumed = false
821
926
  if (resumable && s.resume !== undefined) {
927
+ // SAFETY: only this loop saves checkpoints under `key`: `{ acc, count, pos }` when it runs out, a `Done` when it completes
822
928
  const cp = Resume.take(s, key, entry) as
823
929
  | Resume.Done
824
930
  | { acc: Array<unknown>; count: number; pos: number }
@@ -885,6 +991,7 @@ class Compiler {
885
991
  let acc: unknown
886
992
  let resumed = false
887
993
  if (resumable && s.resume !== undefined) {
994
+ // SAFETY: only this loop saves checkpoints under `key`: `{ acc, pos }` when it runs out, a `Done` when it completes
888
995
  const cp = Resume.take(s, key, entry) as Resume.Done | { acc: unknown; pos: number } | undefined
889
996
  if (cp instanceof Resume.Done) {
890
997
  s.pos = cp.pos
@@ -961,21 +1068,22 @@ class Compiler {
961
1068
  // ---------------------------------------------------------------------------
962
1069
 
963
1070
  token(p: ParserLike, node: TokenNode, used: boolean): Step {
1071
+ // SAFETY: `build` compiles a token node without an encoding only when `native` holds, that is when `kind` is not 2
964
1072
  const kind = this.kind as 0 | 1
965
1073
  switch (node._tag) {
966
1074
  case "Any":
967
1075
  if (kind === 1) {
968
- return (s) => {
1076
+ return (s: State<Uint8Array>) => {
969
1077
  const pos = s.pos
970
1078
  if (pos >= s.end) return fail(s)
971
1079
  s.pos = pos + 1
972
- return (s.input as Uint8Array)[pos]
1080
+ return s.input[pos]
973
1081
  }
974
1082
  }
975
1083
  return (s) => {
976
1084
  const pos = s.pos
977
1085
  if (pos >= s.end) return fail(s)
978
- const str = s.input as string
1086
+ const str: string = s.input
979
1087
  const c = str.charCodeAt(pos)
980
1088
  if (c >= 0xd800 && c <= 0xdbff && pos + 1 < str.length && (str.charCodeAt(pos + 1) & 0xfc00) === 0xdc00) {
981
1089
  s.pos = pos + 2
@@ -992,10 +1100,10 @@ class Compiler {
992
1100
  const accept = acceptTable(set, negate)
993
1101
  const test = set.test
994
1102
  if (kind === 1) {
995
- return (s) => {
1103
+ return (s: State<Uint8Array>) => {
996
1104
  const pos = s.pos
997
1105
  if (pos >= s.end) return fail(s)
998
- const b = (s.input as Uint8Array)[pos]
1106
+ const b = s.input[pos]
999
1107
  if (accept[b] === 0) return fail(s)
1000
1108
  s.pos = pos + 1
1001
1109
  return b
@@ -1004,7 +1112,7 @@ class Compiler {
1004
1112
  return (s) => {
1005
1113
  const pos = s.pos
1006
1114
  if (pos >= s.end) return fail(s)
1007
- const str = s.input as string
1115
+ const str: string = s.input
1008
1116
  const c = str.charCodeAt(pos)
1009
1117
  if (c < TableSize) {
1010
1118
  if (accept[c] === 0) return fail(s)
@@ -1025,15 +1133,15 @@ class Compiler {
1025
1133
  case "Take": {
1026
1134
  const count = node.count
1027
1135
  if (kind === 1) {
1028
- return (s) => {
1136
+ return (s: State<Uint8Array>) => {
1029
1137
  const start = s.pos
1030
1138
  if (s.end - start < count) return failAt(s, s.end)
1031
1139
  s.pos = start + count
1032
- return used ? (s.input as Uint8Array).subarray(start, start + count) : undefined
1140
+ return used ? s.input.subarray(start, start + count) : undefined
1033
1141
  }
1034
1142
  }
1035
1143
  return (s) => {
1036
- const str = s.input as string
1144
+ const str: string = s.input
1037
1145
  const start = s.pos
1038
1146
  const end = s.end
1039
1147
  let i = start
@@ -1050,12 +1158,16 @@ class Compiler {
1050
1158
  }
1051
1159
  case "TakeWhile":
1052
1160
  return this.tokenTakeWhile(node, used)
1053
- case "TakeUntil": {
1054
- const { literal, max, min } = node
1161
+ case "TakeUntil":
1162
+ case "TakeUntilAny": {
1163
+ const { max, min } = node
1055
1164
  const bounded = min > 0 || max !== Infinity
1165
+ const search: (s: State<Input>, from: number) => number = Predicate.isTagged(node, "TakeUntil")
1166
+ ? (s, from) => indexOf(s, node.literal, from)
1167
+ : (s, from) => indexOfAny(s, node.literals, from)
1056
1168
  return (s) => {
1057
1169
  const start = s.pos
1058
- const idx = indexOf(s, literal, start)
1170
+ const idx = search(s, start)
1059
1171
  if (idx < 0) return failAt(s, s.end)
1060
1172
  if (bounded) {
1061
1173
  const count = countTokens(s, start, idx)
@@ -1078,15 +1190,15 @@ class Compiler {
1078
1190
  const lit = node.literal
1079
1191
  const literalKind = Predicate.isString(lit) ? 0 : lit instanceof Uint8Array ? 1 : 2
1080
1192
  if (this.kind === 0) {
1081
- if (literalKind !== 0) return this.fallback(p, node, "byte or array literal on string input")
1082
- const str = lit as string
1193
+ if (!Predicate.isString(lit)) return this.fallback(p, node, "byte or array literal on string input")
1194
+ const str = lit
1083
1195
  const len = str.length
1084
1196
  if (node.caseless) {
1085
1197
  const lowered = Array.from({ length: len }, (_, i) => lowerAscii(str.charCodeAt(i)))
1086
1198
  return (s) => {
1087
1199
  const pos = s.pos
1088
1200
  if (s.end - pos < len) return fail(s)
1089
- const input = s.input as string
1201
+ const input: string = s.input
1090
1202
  for (let i = 0; i < len; i++) {
1091
1203
  if (lowerAscii(input.charCodeAt(pos + i)) !== lowered[i]) return fail(s)
1092
1204
  }
@@ -1096,8 +1208,8 @@ class Compiler {
1096
1208
  }
1097
1209
  return stringLiteral(str, str)
1098
1210
  }
1099
- if (literalKind === 2) return this.fallback(p, node, "array literal on byte input")
1100
- const bytes = literalKind === 0 ? new TextEncoder().encode(lit as string) : (lit as Uint8Array)
1211
+ const bytes = Predicate.isString(lit) ? new TextEncoder().encode(lit) : lit instanceof Uint8Array ? lit : undefined
1212
+ if (bytes === undefined) return this.fallback(p, node, "array literal on byte input")
1101
1213
  const len = bytes.length
1102
1214
  const sameKind = literalKind === 1 && !node.caseless
1103
1215
  const expect = node.caseless ? Array.from(bytes, lowerAscii) : Array.from(bytes)
@@ -1105,7 +1217,7 @@ class Compiler {
1105
1217
  return (s) => {
1106
1218
  const pos = s.pos
1107
1219
  if (s.end - pos < len) return fail(s)
1108
- const input = s.input as Uint8Array
1220
+ const input: Uint8Array = s.input
1109
1221
  for (let i = 0; i < len; i++) {
1110
1222
  const b = input[pos + i]
1111
1223
  if ((caseless ? lowerAscii(b) : b) !== expect[i]) return fail(s)
@@ -1116,13 +1228,14 @@ class Compiler {
1116
1228
  }
1117
1229
 
1118
1230
  tokenTakeWhile(node: NodeOf<"TakeWhile">, used: boolean): Step {
1231
+ // SAFETY: called from `token` only, whose `kind` is not 2
1119
1232
  const kind = this.kind as 0 | 1
1120
1233
  const { max, min, negate } = node
1121
1234
  const set = tokenSet(node.set, kind)
1122
1235
  const accept = acceptTable(set, negate)
1123
1236
  if (kind === 1) {
1124
1237
  return (s) => {
1125
- const bytes = s.input as Uint8Array
1238
+ const bytes: Uint8Array = s.input
1126
1239
  const start = s.pos
1127
1240
  const limit = max === Infinity ? s.end : Math.min(s.end, start + max)
1128
1241
  let i = start
@@ -1132,9 +1245,11 @@ class Compiler {
1132
1245
  return used ? bytes.subarray(start, i) : undefined
1133
1246
  }
1134
1247
  }
1248
+ const stops = negate && min <= 1 && max === Infinity ? stopChars(set) : undefined
1249
+ if (stops !== undefined) return stopTakeTill(stops, min, used)
1135
1250
  const test = set.test
1136
1251
  return (s) => {
1137
- const str = s.input as string
1252
+ const str: string = s.input
1138
1253
  const start = s.pos
1139
1254
  const end = s.end
1140
1255
  let i = start
@@ -1172,7 +1287,9 @@ class Compiler {
1172
1287
  const { fast, unitAt, width: unitWidth } = unitReader(this.kind, enc)
1173
1288
  const decode = enc.decode
1174
1289
  const width = enc.width
1175
- const toText = enc.text
1290
+ // UTF-8 decodes through the state's ASCII run (see `utf8Text`)
1291
+ const toText: (s: State<any>, start: number, end: number) => string =
1292
+ enc === utf8 ? utf8Text : (s, start, end) => enc.text(s.input, start, end)
1176
1293
  switch (node._tag) {
1177
1294
  case "Any":
1178
1295
  return (s) => {
@@ -1190,6 +1307,7 @@ class Compiler {
1190
1307
  return used ? charOf(cp) : undefined
1191
1308
  }
1192
1309
  case "Literal": {
1310
+ // SAFETY: a `Literal` with an encoding is a `Text` literal, which records the string it encodes
1193
1311
  const lit = node.literal as string
1194
1312
  if (node.caseless) {
1195
1313
  const folded = Array.from(lit, (c) => fold(firstCodePoint(c)))
@@ -1214,18 +1332,18 @@ class Compiler {
1214
1332
  if (fold(cp) !== folded[i]) return failAt(s, pos)
1215
1333
  }
1216
1334
  s.pos = pos
1217
- return used ? toText(input, start, pos) : undefined
1335
+ return used ? toText(s, start, pos) : undefined
1218
1336
  }
1219
1337
  }
1220
1338
  const encoded = enc.encode(lit)
1221
1339
  if (Predicate.isString(encoded)) return stringLiteral(encoded, lit)
1222
- const bytes = encoded as Uint8Array
1340
+ const bytes: Uint8Array = encoded
1223
1341
  const len = bytes.length
1224
1342
  if (len === 1) {
1225
1343
  const b0 = bytes[0]
1226
- return (s) => {
1344
+ return (s: State<Uint8Array>) => {
1227
1345
  const pos = s.pos
1228
- if (pos < s.end && (s.input as Uint8Array)[pos] === b0) {
1346
+ if (pos < s.end && s.input[pos] === b0) {
1229
1347
  s.pos = pos + 1
1230
1348
  return lit
1231
1349
  }
@@ -1235,7 +1353,7 @@ class Compiler {
1235
1353
  return (s) => {
1236
1354
  const pos = s.pos
1237
1355
  if (s.end - pos < len) return fail(s)
1238
- const input = s.input as Uint8Array
1356
+ const input: Uint8Array = s.input
1239
1357
  for (let i = 0; i < len; i++) if (input[pos + i] !== bytes[i]) return fail(s)
1240
1358
  s.pos = pos + len
1241
1359
  return lit
@@ -1280,7 +1398,7 @@ class Compiler {
1280
1398
  }
1281
1399
  }
1282
1400
  s.pos = pos
1283
- return used ? toText(input, start, pos) : undefined
1401
+ return used ? toText(s, start, pos) : undefined
1284
1402
  }
1285
1403
  }
1286
1404
  case "TakeWhile": {
@@ -1296,6 +1414,7 @@ class Compiler {
1296
1414
  return this.kind === 0 ? takeWhileString(loop, enc) : takeWhileBytes(loop, enc)
1297
1415
  }
1298
1416
  case "TakeUntil":
1417
+ case "TakeUntilAny":
1299
1418
  case "Rest":
1300
1419
  return this.fallback(p, node, "Text parser without a compiled form")
1301
1420
  }
@@ -1307,9 +1426,9 @@ class Compiler {
1307
1426
  if (enc === undefined) {
1308
1427
  if (!used) return c
1309
1428
  if (this.kind === 0) {
1310
- return (s) => {
1429
+ return (s: State<string>) => {
1311
1430
  const start = s.pos
1312
- return c(s) === Fail ? Fail : (s.input as string).slice(start, s.pos)
1431
+ return c(s) === Fail ? Fail : s.input.slice(start, s.pos)
1313
1432
  }
1314
1433
  }
1315
1434
  return (s) => {
@@ -1319,7 +1438,9 @@ class Compiler {
1319
1438
  }
1320
1439
  // `Text.taken` checks that the text is well formed, then decodes it
1321
1440
  const { fast, unitAt, width } = unitReader(this.kind, enc)
1322
- const toText = enc.text
1441
+ // UTF-8 decodes through the state's ASCII run (see `utf8Text`)
1442
+ const toText: (s: State<any>, start: number, end: number) => string =
1443
+ enc === utf8 ? utf8Text : (s, start, end) => enc.text(s.input, start, end)
1323
1444
  return (s) => {
1324
1445
  const start = s.pos
1325
1446
  if (c(s) === Fail) return Fail
@@ -1328,7 +1449,7 @@ class Compiler {
1328
1449
  let i = start
1329
1450
  while (i < end && unitAt(input, i, end) < fast) i += width
1330
1451
  if (i < end && validate(enc, s, i, end) < 0) return fail(s)
1331
- return used ? toText(input, start, end) : undefined
1452
+ return used ? toText(s, start, end) : undefined
1332
1453
  }
1333
1454
  }
1334
1455
 
@@ -1341,11 +1462,7 @@ class Compiler {
1341
1462
  if (!floatTexts.has(node.self) || this.kind === 2) return undefined
1342
1463
  if (!scansFloat(node.self, (enc) => this.native(enc))) return undefined
1343
1464
  const c = this.compile(node.self, true)
1344
- const scan = (this.kind === 0 ? scanFloatString : scanFloatBytes) as (
1345
- input: unknown,
1346
- i: number,
1347
- end: number
1348
- ) => unknown
1465
+ const scan = this.kind === 0 ? scanFloatString : scanFloatBytes
1349
1466
  const resumable = this.resumable
1350
1467
  return (s) => {
1351
1468
  const v = scan(s.input, s.pos, s.end)
@@ -1372,15 +1489,15 @@ class Compiler {
1372
1489
  if (enc === undefined || (ctrl < fastLimit(enc) && ctrl < TableSize)) {
1373
1490
  control =
1374
1491
  kind === 0
1375
- ? (s) => {
1376
- if (s.pos < s.end && (s.input as string).charCodeAt(s.pos) === ctrl) {
1492
+ ? (s: State<string>) => {
1493
+ if (s.pos < s.end && s.input.charCodeAt(s.pos) === ctrl) {
1377
1494
  s.pos++
1378
1495
  return true
1379
1496
  }
1380
1497
  return false
1381
1498
  }
1382
- : (s) => {
1383
- if (s.pos < s.end && (s.input as Uint8Array)[s.pos] === ctrl) {
1499
+ : (s: State<Uint8Array>) => {
1500
+ if (s.pos < s.end && s.input[s.pos] === ctrl) {
1384
1501
  s.pos++
1385
1502
  return true
1386
1503
  }
@@ -1407,19 +1524,20 @@ class Compiler {
1407
1524
  const resumable = this.resumable
1408
1525
  const key = new Key()
1409
1526
  /** Fails as incomplete, keeping a checkpoint in a streaming parse. */
1410
- const incomplete = (s: State<any>, start: number, checkpoint: unknown): typeof Fail => {
1527
+ const incomplete = (s: State<any>, start: number, checkpoint: EscapedCheckpoint): typeof Fail => {
1411
1528
  if (s.resume !== undefined) Resume.save(s, key, start, checkpoint)
1412
1529
  s.mode = INC
1413
1530
  return Fail
1414
1531
  }
1415
1532
  if (raw) {
1416
- const slice: (s: State<any>, start: number) => unknown =
1417
- enc === undefined ? (s, start) => s.slice(start, s.pos) : (s, start) => enc.text(s.input, start, s.pos)
1533
+ const slice: (s: State<any>, start: number) => Input =
1534
+ enc === undefined ? (s, start) => s.slice(start, s.pos) : (s, start) => textIn(enc, s, start, s.pos)
1418
1535
  return (s) => {
1419
1536
  const start = s.pos
1420
1537
  let resumed = false
1421
1538
  if (resumable && s.resume !== undefined) {
1422
- const cp = Resume.take(s, key, start) as Resume.Done | number | undefined
1539
+ // SAFETY: only this raw loop saves checkpoints under `key`: the position it stopped at, or a `Done` when it completes
1540
+ const cp = Resume.take(s, key, start) as Resume.Done | Extract<EscapedCheckpoint, number> | undefined
1423
1541
  if (cp instanceof Resume.Done) {
1424
1542
  s.pos = cp.pos
1425
1543
  return cp.value
@@ -1447,7 +1565,11 @@ class Compiler {
1447
1565
  return resumed ? Resume.done(s, key, start, true, slice(s, start)) : slice(s, start)
1448
1566
  }
1449
1567
  }
1450
- if (s.partial) return incomplete(s, start, s.pos)
1568
+ if (s.partial) {
1569
+ // The text ran out of input: `escaped`'s own failure, needing more
1570
+ s.incomplete(0)
1571
+ return incomplete(s, start, s.pos)
1572
+ }
1451
1573
  return resumed ? Resume.done(s, key, start, true, slice(s, start)) : slice(s, start)
1452
1574
  }
1453
1575
  }
@@ -1456,7 +1578,8 @@ class Compiler {
1456
1578
  let out = ""
1457
1579
  let resumed = false
1458
1580
  if (resumable && s.resume !== undefined) {
1459
- const cp = Resume.take(s, key, start) as Resume.Done | { out: string; pos: number } | undefined
1581
+ // SAFETY: only this loop saves checkpoints under `key`: `{ out, pos }` when it runs out, a `Done` when it completes
1582
+ const cp = Resume.take(s, key, start) as Resume.Done | Exclude<EscapedCheckpoint, number> | undefined
1460
1583
  if (cp instanceof Resume.Done) {
1461
1584
  s.pos = cp.pos
1462
1585
  return cp.value
@@ -1488,13 +1611,31 @@ class Compiler {
1488
1611
  return resumed ? Resume.done(s, key, start, true, out) : out
1489
1612
  }
1490
1613
  }
1491
- if (s.partial) return incomplete(s, start, { out, pos: s.pos })
1614
+ if (s.partial) {
1615
+ // The text ran out of input: `escaped`'s own failure, needing more
1616
+ s.incomplete(0)
1617
+ return incomplete(s, start, { out, pos: s.pos })
1618
+ }
1492
1619
  return resumed ? Resume.done(s, key, start, true, out) : out
1493
1620
  }
1494
1621
  }
1495
1622
  }
1496
1623
 
1497
- type TokenNode = NodeOf<"Any" | "Literal" | "OneOf" | "Take" | "TakeWhile" | "TakeUntil" | "Rest">
1624
+ /** How far past the cursor a token parser may read: its literals' length in UTF-8 at most, or `Margin`. */
1625
+ const marginOf = (node: TokenNode): number => {
1626
+ if (Predicate.isTagged(node, "Literal") || Predicate.isTagged(node, "TakeUntil")) return node.literal.length * 4
1627
+ if (Predicate.isTagged(node, "TakeUntilAny")) return Math.max(0, ...node.literals.map((lit) => lit.length)) * 4
1628
+ return Margin
1629
+ }
1630
+
1631
+ type TokenNode = NodeOf<"Any" | "Literal" | "OneOf" | "Take" | "TakeWhile" | "TakeUntil" | "TakeUntilAny" | "Rest">
1632
+
1633
+ /**
1634
+ * Where a compiled `escaped` that ran out of input resumes: the position
1635
+ * (`takeEscaped`, which slices the input), or the text unescaped so far and
1636
+ * the position after it.
1637
+ */
1638
+ type EscapedCheckpoint = number | { readonly out: string; readonly pos: number }
1498
1639
 
1499
1640
  const fail = (s: State<any>): typeof Fail => {
1500
1641
  s.mode = BT
@@ -1651,18 +1792,18 @@ const stringLiteral = (str: string, value: unknown): Step => {
1651
1792
  if (len === 0) return () => value
1652
1793
  if (len === 1) {
1653
1794
  const c0 = str.charCodeAt(0)
1654
- return (s) => {
1795
+ return (s: State<string>) => {
1655
1796
  const pos = s.pos
1656
- if (pos < s.end && (s.input as string).charCodeAt(pos) === c0) {
1797
+ if (pos < s.end && s.input.charCodeAt(pos) === c0) {
1657
1798
  s.pos = pos + 1
1658
1799
  return value
1659
1800
  }
1660
1801
  return fail(s)
1661
1802
  }
1662
1803
  }
1663
- return (s) => {
1804
+ return (s: State<string>) => {
1664
1805
  const pos = s.pos
1665
- if (s.end - pos >= len && (s.input as string).startsWith(str, pos)) {
1806
+ if (s.end - pos >= len && s.input.startsWith(str, pos)) {
1666
1807
  s.pos = pos + len
1667
1808
  return value
1668
1809
  }
@@ -1670,7 +1811,81 @@ const stringLiteral = (str: string, value: unknown): Step => {
1670
1811
  }
1671
1812
  }
1672
1813
 
1673
- /** A closure compiler for one input kind; it memoizes what it compiles. */
1814
+ /**
1815
+ * `Token.takeTill` on a string, of one to three stop characters that are
1816
+ * whole code points (see `search.ts`): every other unit, surrogates
1817
+ * included, is accepted, so the run ends at the first stop character, or
1818
+ * at the state's end.
1819
+ */
1820
+ const stopTakeTill = (stops: ReadonlyArray<number>, min: number, used: boolean): Step => {
1821
+ const head = stops.length === 1 ? SearchHead : 2 * SearchHead
1822
+ const c0 = stops[0]
1823
+ const c1 = stops.length > 1 ? stops[1] : -1
1824
+ const c2 = stops.length > 2 ? stops[2] : -1
1825
+ const one = String.fromCharCode(c0)
1826
+ const site = new StopCache(stops.map((u) => String.fromCharCode(u)))
1827
+ return (s: State<string>) => {
1828
+ const str = s.input
1829
+ const start = s.pos
1830
+ const end = s.end
1831
+ const h = start + head < end ? start + head : end
1832
+ let i = start
1833
+ let c = -1
1834
+ while (i < h && (c = str.charCodeAt(i)) !== c0 && c !== c1 && c !== c2) i++
1835
+ // A run that ends right after the head needs no search
1836
+ if (i === h && i < end && (c = str.charCodeAt(i)) !== c0 && c !== c1 && c !== c2) {
1837
+ if (stops.length === 1) {
1838
+ const at = str.indexOf(one, i)
1839
+ i = at < 0 || at > end ? end : at
1840
+ } else i = nextStop(site, s, i)
1841
+ }
1842
+ if (i - start < min) return failAt(s, i)
1843
+ s.pos = i
1844
+ return used ? str.slice(start, i) : undefined
1845
+ }
1846
+ }
1847
+
1848
+ /** A checkpoint of `entriesLoop`: the object so far, the entries parsed and where the next one starts. */
1849
+ interface EntriesCheckpoint {
1850
+ readonly acc: EntriesAcc
1851
+ readonly count: number
1852
+ readonly pos: number
1853
+ }
1854
+
1855
+ /** Takes the checkpoint `entriesLoop` saved at `at`, restoring the position of a completed one. */
1856
+ const resumeEntries = (s: State<any>, key: Key, at: number): Resume.Done | EntriesCheckpoint | undefined => {
1857
+ // SAFETY: only `entriesLoop` saves checkpoints under its `key`: an `EntriesCheckpoint` when it runs out, a `Done` when it completes
1858
+ const cp = Resume.take(s, key, at) as Resume.Done | EntriesCheckpoint | undefined
1859
+ if (cp instanceof Resume.Done) s.pos = cp.pos
1860
+ else if (cp !== undefined) s.pos = cp.pos
1861
+ return cp
1862
+ }
1863
+
1864
+ /** The object `entriesLoop` built, recorded as done when the loop resumed. */
1865
+ const entriesDone = (s: State<any>, key: Key, at: number, resumed: boolean, acc: EntriesAcc): object =>
1866
+ resumed ? Resume.done(s, key, at, true, acc.result()) : acc.result()
1867
+
1868
+ /**
1869
+ * Where `entriesLoop` stops at an entry (or separator) that failed at
1870
+ * `start`: a checkpoint when the input ran out, `Fail` unless the failure
1871
+ * backtracks after `min` entries, otherwise the object.
1872
+ */
1873
+ const stopEntries = (
1874
+ s: State<any>,
1875
+ site: Key,
1876
+ at: number,
1877
+ resumed: boolean,
1878
+ acc: EntriesAcc,
1879
+ count: number,
1880
+ start: number,
1881
+ min: number
1882
+ ): object | typeof Fail => {
1883
+ if (s.mode === INC && s.resume !== undefined) Resume.save(s, site, at, { acc, count, pos: start })
1884
+ if (s.mode !== BT || count < min) return Fail
1885
+ s.pos = start
1886
+ return entriesDone(s, site, at, resumed, acc)
1887
+ }
1888
+
1674
1889
  /** The object of `Object.fromEntries`, built an entry at a time (see `internal/entries.ts`). */
1675
1890
  class EntriesAcc {
1676
1891
  readonly o: Record<string, unknown> = {}
@@ -1721,8 +1936,20 @@ export const backend: Backend = {
1721
1936
  Result.match(
1722
1937
  Result.try(() => compileClosure(p, kind, resumable)),
1723
1938
  {
1724
- onSuccess: ({ fallbacks, step }) => ({ backend: "closure" as const, step, fallbacks, error: undefined }),
1725
- onFailure: (error) => ({ backend: undefined, step: undefined, fallbacks: [], error })
1939
+ onSuccess: ({ fallbacks, step }) => ({
1940
+ backend: "closure" as const,
1941
+ step,
1942
+ guarded: false,
1943
+ fallbacks,
1944
+ error: undefined
1945
+ }),
1946
+ onFailure: (error) => ({
1947
+ backend: "interpreted" as const,
1948
+ step: undefined,
1949
+ guarded: false,
1950
+ fallbacks: [],
1951
+ error
1952
+ })
1726
1953
  }
1727
1954
  )
1728
1955
  }