@thomasfosterau/effect-parse 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README.md +56 -14
  3. package/dist/Ascii.d.ts +11 -4
  4. package/dist/Ascii.d.ts.map +1 -1
  5. package/dist/Ascii.js +12 -4
  6. package/dist/Ascii.js.map +1 -1
  7. package/dist/Binary.d.ts +5 -2
  8. package/dist/Binary.d.ts.map +1 -1
  9. package/dist/Binary.js +83 -37
  10. package/dist/Binary.js.map +1 -1
  11. package/dist/Bits.d.ts.map +1 -1
  12. package/dist/Bits.js +11 -4
  13. package/dist/Bits.js.map +1 -1
  14. package/dist/Compiler.d.ts +74 -2
  15. package/dist/Compiler.d.ts.map +1 -1
  16. package/dist/Compiler.js +78 -1
  17. package/dist/Compiler.js.map +1 -1
  18. package/dist/Expression.d.ts.map +1 -1
  19. package/dist/Expression.js +86 -74
  20. package/dist/Expression.js.map +1 -1
  21. package/dist/Input.d.ts +56 -5
  22. package/dist/Input.d.ts.map +1 -1
  23. package/dist/Input.js +70 -1
  24. package/dist/Input.js.map +1 -1
  25. package/dist/Nom.d.ts +5 -15
  26. package/dist/Nom.d.ts.map +1 -1
  27. package/dist/Nom.js.map +1 -1
  28. package/dist/ParseError.d.ts +69 -11
  29. package/dist/ParseError.d.ts.map +1 -1
  30. package/dist/ParseError.js +129 -28
  31. package/dist/ParseError.js.map +1 -1
  32. package/dist/Parser.d.ts +375 -29
  33. package/dist/Parser.d.ts.map +1 -1
  34. package/dist/Parser.js +394 -37
  35. package/dist/Parser.js.map +1 -1
  36. package/dist/ParserSchema.d.ts.map +1 -1
  37. package/dist/ParserSchema.js +2 -3
  38. package/dist/ParserSchema.js.map +1 -1
  39. package/dist/ParserStream.d.ts +122 -14
  40. package/dist/ParserStream.d.ts.map +1 -1
  41. package/dist/ParserStream.js +293 -62
  42. package/dist/ParserStream.js.map +1 -1
  43. package/dist/Syntax.d.ts +23 -5
  44. package/dist/Syntax.d.ts.map +1 -1
  45. package/dist/Syntax.js +1 -1
  46. package/dist/Syntax.js.map +1 -1
  47. package/dist/Text.d.ts.map +1 -1
  48. package/dist/Text.js +21 -15
  49. package/dist/Text.js.map +1 -1
  50. package/dist/Token.d.ts +65 -11
  51. package/dist/Token.d.ts.map +1 -1
  52. package/dist/Token.js +73 -26
  53. package/dist/Token.js.map +1 -1
  54. package/dist/internal/compiler/closure.d.ts +1 -1
  55. package/dist/internal/compiler/closure.d.ts.map +1 -1
  56. package/dist/internal/compiler/closure.js +263 -113
  57. package/dist/internal/compiler/closure.js.map +1 -1
  58. package/dist/internal/compiler/entries.d.ts +3 -1
  59. package/dist/internal/compiler/entries.d.ts.map +1 -1
  60. package/dist/internal/compiler/entries.js +3 -2
  61. package/dist/internal/compiler/entries.js.map +1 -1
  62. package/dist/internal/compiler/first.d.ts.map +1 -1
  63. package/dist/internal/compiler/first.js +9 -2
  64. package/dist/internal/compiler/first.js.map +1 -1
  65. package/dist/internal/compiler/graph.d.ts +39 -0
  66. package/dist/internal/compiler/graph.d.ts.map +1 -0
  67. package/dist/internal/compiler/graph.js +78 -0
  68. package/dist/internal/compiler/graph.js.map +1 -0
  69. package/dist/internal/compiler/jit.d.ts +5 -0
  70. package/dist/internal/compiler/jit.d.ts.map +1 -1
  71. package/dist/internal/compiler/jit.js +208 -106
  72. package/dist/internal/compiler/jit.js.map +1 -1
  73. package/dist/internal/compiler/registry.d.ts +11 -3
  74. package/dist/internal/compiler/registry.d.ts.map +1 -1
  75. package/dist/internal/compiler/registry.js.map +1 -1
  76. package/dist/internal/compiler/search.d.ts +32 -0
  77. package/dist/internal/compiler/search.d.ts.map +1 -0
  78. package/dist/internal/compiler/search.js +68 -0
  79. package/dist/internal/compiler/search.js.map +1 -0
  80. package/dist/internal/compiler/sets.d.ts.map +1 -1
  81. package/dist/internal/compiler/sets.js +1 -1
  82. package/dist/internal/compiler/sets.js.map +1 -1
  83. package/dist/internal/compiler/wasm.d.ts +8 -0
  84. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  85. package/dist/internal/compiler/wasm.js +2840 -0
  86. package/dist/internal/compiler/wasm.js.map +1 -0
  87. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  88. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  89. package/dist/internal/compiler/wasmAsm.js +427 -0
  90. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  91. package/dist/internal/entries.d.ts +3 -1
  92. package/dist/internal/entries.d.ts.map +1 -1
  93. package/dist/internal/entries.js.map +1 -1
  94. package/dist/internal/node.d.ts +77 -3
  95. package/dist/internal/node.d.ts.map +1 -1
  96. package/dist/internal/node.js +54 -10
  97. package/dist/internal/node.js.map +1 -1
  98. package/dist/internal/numbers.d.ts.map +1 -1
  99. package/dist/internal/numbers.js.map +1 -1
  100. package/dist/internal/run.d.ts +14 -0
  101. package/dist/internal/run.d.ts.map +1 -1
  102. package/dist/internal/run.js +53 -11
  103. package/dist/internal/run.js.map +1 -1
  104. package/dist/internal/stack.d.ts +14 -0
  105. package/dist/internal/stack.d.ts.map +1 -1
  106. package/dist/internal/stack.js +24 -2
  107. package/dist/internal/stack.js.map +1 -1
  108. package/dist/internal/syntax.d.ts +4 -4
  109. package/dist/internal/syntax.d.ts.map +1 -1
  110. package/dist/internal/syntax.js +18 -10
  111. package/dist/internal/syntax.js.map +1 -1
  112. package/dist/internal/text.d.ts +32 -0
  113. package/dist/internal/text.d.ts.map +1 -1
  114. package/dist/internal/text.js +68 -14
  115. package/dist/internal/text.js.map +1 -1
  116. package/dist/internal/tokens.d.ts +20 -0
  117. package/dist/internal/tokens.d.ts.map +1 -1
  118. package/dist/internal/tokens.js +72 -39
  119. package/dist/internal/tokens.js.map +1 -1
  120. package/dist/internal/tuple.d.ts +6 -0
  121. package/dist/internal/tuple.d.ts.map +1 -0
  122. package/dist/internal/tuple.js +170 -0
  123. package/dist/internal/tuple.js.map +1 -0
  124. package/package.json +18 -10
  125. package/src/Ascii.ts +47 -12
  126. package/src/Binary.ts +174 -73
  127. package/src/Bits.ts +16 -6
  128. package/src/Compiler.ts +101 -10
  129. package/src/Expression.ts +94 -81
  130. package/src/Input.ts +99 -7
  131. package/src/Nom.ts +15 -5
  132. package/src/ParseError.ts +209 -52
  133. package/src/Parser.ts +684 -143
  134. package/src/ParserSchema.ts +1 -2
  135. package/src/ParserStream.ts +361 -68
  136. package/src/Syntax.ts +60 -36
  137. package/src/Text.ts +32 -27
  138. package/src/Token.ts +152 -34
  139. package/src/internal/compiler/closure.ts +371 -153
  140. package/src/internal/compiler/entries.ts +4 -2
  141. package/src/internal/compiler/first.ts +16 -0
  142. package/src/internal/compiler/graph.ts +96 -0
  143. package/src/internal/compiler/jit.ts +319 -169
  144. package/src/internal/compiler/registry.ts +10 -2
  145. package/src/internal/compiler/search.ts +68 -0
  146. package/src/internal/compiler/sets.ts +7 -3
  147. package/src/internal/compiler/wasm.ts +3276 -0
  148. package/src/internal/compiler/wasmAsm.ts +484 -0
  149. package/src/internal/entries.ts +8 -2
  150. package/src/internal/node.ts +103 -7
  151. package/src/internal/numbers.ts +3 -1
  152. package/src/internal/run.ts +73 -12
  153. package/src/internal/stack.ts +25 -1
  154. package/src/internal/syntax.ts +39 -18
  155. package/src/internal/text.ts +97 -9
  156. package/src/internal/tokens.ts +106 -14
  157. package/src/internal/tuple.ts +141 -0
@@ -9,16 +9,17 @@
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
+ import { tupleStep } from "../tuple.js"
22
23
  import type { AltPlan } from "./first.js"
23
24
  import { Key } from "../key.js"
24
25
  import * as Resume from "../resume.js"
@@ -26,6 +27,7 @@ import type { Backend } from "./registry.js"
26
27
  import type { EntryPair } from "./entries.js"
27
28
  import { buildsEntries, pairOf } from "./entries.js"
28
29
  import { FirstSets, planAlt } from "./first.js"
30
+ import { nextStop, SearchHead, StopCache, stopChars } from "./search.js"
29
31
  import { acceptTable, kindOfEncoding, TableSize, textSet, tokenSet } from "./sets.js"
30
32
 
31
33
  export type Step = (state: State<any>) => any
@@ -137,6 +139,11 @@ class Compiler {
137
139
  switch (node._tag) {
138
140
  case "Opaque":
139
141
  return this.fallback(p, node, "not describable (Parser.make, gen, ...)")
142
+ case "Fixed":
143
+ // `Binary`'s numbers: the step is already what a closure would be
144
+ return p.step
145
+ case "Combinator":
146
+ return node.build(node.parsers.map((q) => this.compile(q, true)))
140
147
  case "Succeed": {
141
148
  const value = node.value
142
149
  return () => value
@@ -158,10 +165,8 @@ class Compiler {
158
165
  s.mode = BT
159
166
  return Fail
160
167
  }
161
- if (s.partial) {
162
- s.mode = INC
163
- return Fail
164
- }
168
+ // `eof`'s own failure: `Incomplete`, needing one more unit
169
+ if (s.partial) return s.incomplete(1)
165
170
  return undefined
166
171
  }
167
172
  case "Location":
@@ -176,14 +181,13 @@ class Compiler {
176
181
  case "Take":
177
182
  case "TakeWhile":
178
183
  case "TakeUntil":
184
+ case "TakeUntilAny":
179
185
  case "Rest":
180
186
  if (!this.native(node.encoding)) return this.fallback(p, node, "token parser on array input")
181
187
  return this.partialSafe(
182
188
  p,
183
189
  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
190
+ marginOf(node)
187
191
  )
188
192
  case "TillLineEnding":
189
193
  case "Identifier":
@@ -199,7 +203,7 @@ class Compiler {
199
203
  if (step !== undefined) return step
200
204
  }
201
205
  // Streaming builds keep the array, which their checkpoints hold
202
- if (node.f === fromEntries && !this.resumable && buildsEntries(node.self)) return this.entries(node.self)
206
+ if (node.f === fromEntries && buildsEntries(node.self)) return this.entries(node.self)
203
207
  const c = this.compile(node.self, true)
204
208
  const f = node.f
205
209
  return (s) => {
@@ -246,6 +250,28 @@ class Compiler {
246
250
  return Fail
247
251
  }
248
252
  }
253
+ case "FastPath": {
254
+ const c = this.compile(node.self, used)
255
+ const fast = node.fast
256
+ return (s) => {
257
+ const start = s.pos
258
+ const a = fast(s)
259
+ if (a !== Defer) return a
260
+ s.pos = start
261
+ return c(s)
262
+ }
263
+ }
264
+ case "MapOrFail": {
265
+ const c = this.compile(node.self, true)
266
+ const f = node.f
267
+ return (s) => {
268
+ const a = c(s)
269
+ if (a === Fail) return Fail
270
+ const b = f(a)
271
+ if (b === Fail) s.mode = BT
272
+ return b
273
+ }
274
+ }
249
275
  case "Filter": {
250
276
  const c = this.compile(node.self, true)
251
277
  const predicate = node.predicate
@@ -280,6 +306,16 @@ class Compiler {
280
306
  return a === Fail ? Fail : [a, s.slice(start, s.pos)]
281
307
  }
282
308
  }
309
+ case "MapSpan": {
310
+ const c = this.compile(node.self, used)
311
+ if (!used) return c
312
+ const f = node.f
313
+ return (s) => {
314
+ const start = s.pos
315
+ const a = c(s)
316
+ return a === Fail ? Fail : f(a, start, s.pos)
317
+ }
318
+ }
283
319
  case "Span": {
284
320
  const c = this.compile(node.self, false)
285
321
  if (!used) return c
@@ -308,6 +344,7 @@ class Compiler {
308
344
  }
309
345
  case "Context":
310
346
  case "Expected":
347
+ case "Quiet":
311
348
  case "MapError":
312
349
  case "OrFail":
313
350
  case "Trace":
@@ -377,6 +414,11 @@ class Compiler {
377
414
  return out
378
415
  }
379
416
  }
417
+ case "SeqWith": {
418
+ const steps = node.parsers.map((q) => this.compile(q, used))
419
+ if (!used) return this.chain(steps)
420
+ return this.seqWith(steps, node.f)
421
+ }
380
422
  case "ZipWith": {
381
423
  const c1 = this.compile(node.self, used)
382
424
  const c2 = this.compile(node.that, used)
@@ -394,8 +436,26 @@ class Compiler {
394
436
  case "Dispatch":
395
437
  return this.dispatch(p, node, used)
396
438
  case "Repeat":
397
- case "Fold":
439
+ case "Fold": {
440
+ // A run of a character set whose characters are not kept scans as a `takeWhile` (#79)
441
+ const element = Predicate.isTagged(node, "Repeat") && !used ? nodeOf(node.self) : undefined
442
+ if (
443
+ element !== undefined &&
444
+ Predicate.isTagged(element, "OneOf") &&
445
+ this.kind !== 2 &&
446
+ this.native(element.encoding)
447
+ ) {
448
+ const { encoding, negate, set } = element
449
+ const run: NodeOf<"TakeWhile"> = { _tag: "TakeWhile", set, negate, encoding, min: node.min, max: node.max }
450
+ // The repetition's own step is the exact fallback near the end of a partial input
451
+ return this.partialSafe(
452
+ p,
453
+ encoding === undefined ? this.token(p, run, false) : this.text(p, run, encoding, false),
454
+ Margin
455
+ )
456
+ }
398
457
  return this.repeat(node, used)
458
+ }
399
459
  case "RepeatTill":
400
460
  return this.repeatTill(node)
401
461
  case "Separated":
@@ -408,13 +468,51 @@ class Compiler {
408
468
  }
409
469
  }
410
470
 
471
+ /** `seqWith`: up to three parsers without an array of values. */
472
+ seqWith(steps: ReadonlyArray<Step>, f: NodeOf<"SeqWith">["f"]): Step {
473
+ const n = steps.length
474
+ if (n === 0) return () => f()
475
+ const [c1, c2, c3] = steps
476
+ if (n === 1)
477
+ return (s) => {
478
+ const a = c1(s)
479
+ return a === Fail ? Fail : f(a)
480
+ }
481
+ if (n === 2)
482
+ return (s) => {
483
+ const a = c1(s)
484
+ if (a === Fail) return Fail
485
+ const b = c2(s)
486
+ return b === Fail ? Fail : f(a, b)
487
+ }
488
+ if (n === 3)
489
+ return (s) => {
490
+ const a = c1(s)
491
+ if (a === Fail) return Fail
492
+ const b = c2(s)
493
+ if (b === Fail) return Fail
494
+ const c = c3(s)
495
+ return c === Fail ? Fail : f(a, b, c)
496
+ }
497
+ return (s) => {
498
+ const values = new Array(n)
499
+ for (let i = 0; i < n; i++) {
500
+ const a = steps[i](s)
501
+ if (a === Fail) return Fail
502
+ values[i] = a
503
+ }
504
+ return f(...values)
505
+ }
506
+ }
507
+
411
508
  /** Runs steps in order, for their effect on the cursor only. */
412
509
  chain(steps: ReadonlyArray<Step>): Step {
413
510
  const n = steps.length
414
511
  if (n === 0) return () => undefined
415
512
  if (n === 1) return steps[0]
416
513
  if (n === 2) {
417
- const [a, b] = steps as [Step, Step]
514
+ const a = steps[0]
515
+ const b = steps[1]
418
516
  return (s) => (a(s) === Fail ? Fail : b(s))
419
517
  }
420
518
  return (s) => {
@@ -430,20 +528,11 @@ class Compiler {
430
528
  const steps = node.parsers.map((q, i) => this.compile(q, keep(i)))
431
529
  const n = steps.length
432
530
  if (!used) return this.chain(steps)
433
- if (output === "tuple") {
434
- return (s) => {
435
- const out = new Array(n)
436
- for (let i = 0; i < n; i++) {
437
- const a = steps[i](s)
438
- if (a === Fail) return Fail
439
- out[i] = a
440
- }
441
- return out
442
- }
443
- }
531
+ if (output === "tuple") return tupleStep(steps)
444
532
  if (Predicate.isNumber(output)) {
445
533
  if (n === 2) {
446
- const [a, b] = steps as [Step, Step]
534
+ const a = steps[0]
535
+ const b = steps[1]
447
536
  return output === 0
448
537
  ? (s) => {
449
538
  const x = a(s)
@@ -479,8 +568,8 @@ class Compiler {
479
568
  /** The unit at the cursor, or -1 at the end of the input. */
480
569
  private unitReader(): (s: State<any>) => number {
481
570
  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)
571
+ ? (s: State<string>) => (s.pos < s.end ? s.input.charCodeAt(s.pos) : -1)
572
+ : (s: State<Uint8Array>) => (s.pos < s.end ? s.input[s.pos] : -1)
484
573
  }
485
574
 
486
575
  alt(node: NodeOf<"Alt">, used: boolean): Step {
@@ -533,7 +622,7 @@ class Compiler {
533
622
  const compiled = new Map<ParserLike, Step>()
534
623
  if (node.cases !== undefined) {
535
624
  for (const key of Reflect.ownKeys(node.cases)) {
536
- const q = node.cases[key as any]
625
+ const q = node.cases[key]
537
626
  if (!compiled.has(q)) compiled.set(q, this.compile(q, used))
538
627
  }
539
628
  }
@@ -557,9 +646,9 @@ class Compiler {
557
646
  return general
558
647
  }
559
648
  // 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)
649
+ const top = nodeOf(node.selector)
650
+ const peek = Predicate.isTagged(top, "Peek")
651
+ const sel = peek ? nodeOf(top.self) : top
563
652
  if (!Predicate.isTagged(sel, "Any") || !this.native(sel.encoding)) return general
564
653
  const enc = sel.encoding
565
654
  // Units below `limit` are whole tokens, so the key is known in advance
@@ -595,19 +684,19 @@ class Compiler {
595
684
 
596
685
  repeat(node: NodeOf<"Repeat"> | NodeOf<"Fold">, used: boolean): Step {
597
686
  const { max, min } = node
598
- const checkLoop = max === Infinity
687
+ // Bounded or not, the repetition checks progress below (#115)
599
688
  const isFold = Predicate.isTagged(node, "Fold")
600
- const cond = isFold ? (node as NodeOf<"Fold">).while : undefined
689
+ const cond = isFold ? node.while : undefined
601
690
  const c = this.compile(node.self, used)
602
691
  const resumable = this.resumable
603
692
  const key = new Key()
604
693
  // The accumulator: an array, the fold's value, or nothing when unused (and unneeded by `while`)
605
694
  const tracked = used || cond !== undefined
606
- const init: () => unknown = !tracked ? () => undefined : isFold ? (node as NodeOf<"Fold">).init : () => []
695
+ const init: () => unknown = !tracked ? () => undefined : isFold ? node.init : () => []
607
696
  const add: (acc: any, a: unknown) => unknown = !tracked
608
697
  ? (acc) => acc
609
698
  : isFold
610
- ? (node as NodeOf<"Fold">).f
699
+ ? node.f
611
700
  : (acc, a) => {
612
701
  acc.push(a)
613
702
  return acc
@@ -618,6 +707,7 @@ class Compiler {
618
707
  let count = 0
619
708
  let resumed = false
620
709
  if (resumable && s.resume !== undefined) {
710
+ // SAFETY: only this loop saves checkpoints under `key`: `{ acc, count, pos }` when it runs out, a `Done` when it completes
621
711
  const cp = Resume.take(s, key, entry) as Resume.Done | { acc: unknown; count: number; pos: number } | undefined
622
712
  if (cp instanceof Resume.Done) {
623
713
  s.pos = cp.pos
@@ -641,7 +731,7 @@ class Compiler {
641
731
  s.pos = start
642
732
  return resumed ? Resume.done(s, key, entry, true, acc) : acc
643
733
  }
644
- if (checkLoop && s.pos === start) {
734
+ if (s.pos === start) {
645
735
  s.mode = CUT
646
736
  return Fail
647
737
  }
@@ -657,44 +747,43 @@ class Compiler {
657
747
  */
658
748
  private entries(p: ParserLike): Step {
659
749
  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
- }
750
+ if (Predicate.isTagged(node, "Mode")) {
751
+ const c = this.entries(node.self)
752
+ const from = node.from
753
+ const to = node.to
754
+ return (s) => {
755
+ const a = c(s)
756
+ if (a === Fail && s.mode === from) s.mode = to
757
+ return a
670
758
  }
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
759
+ }
760
+ if (Predicate.isTagged(node, "Seq")) {
761
+ // SAFETY: `buildsEntries` passes through a sequence only when it keeps one output, by index
762
+ const i = node.output as number
763
+ const steps = node.parsers.map((q, j) => (j === i ? this.entries(q) : this.compile(q, false)))
764
+ return (s) => {
765
+ let out: unknown
766
+ for (let j = 0; j < steps.length; j++) {
767
+ const a = steps[j](s)
768
+ if (a === Fail) return Fail
769
+ if (j === i) out = a
682
770
  }
771
+ return out
683
772
  }
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
- }
773
+ }
774
+ if (Predicate.isTagged(node, "Repeat") || Predicate.isTagged(node, "Separated")) {
775
+ const pair = pairOf(node.self)
776
+ if (pair !== undefined) return this.entriesLoop(node, pair)
777
+ // Not reached (`buildsEntries` found the pair): build the pairs, then the object
778
+ const c = this.compile(p, true)
779
+ return (s) => {
780
+ const a = c(s)
781
+ // SAFETY: a used `repeat` or `separated` returns the array of its elements, here the pairs
782
+ return a === Fail ? Fail : fromEntries(a as Iterable<readonly [PropertyKey, unknown]>)
694
783
  }
695
- default:
696
- return this.entries((node as { readonly self: ParserLike }).self)
697
784
  }
785
+ // SAFETY: the other nodes `buildsEntries` passes through are diagnostics, which all wrap a `self`
786
+ return this.entries((node as { readonly self: ParserLike }).self)
698
787
  }
699
788
 
700
789
  private entriesLoop(node: NodeOf<"Repeat"> | NodeOf<"Separated">, pair: EntryPair): Step {
@@ -714,11 +803,18 @@ class Compiler {
714
803
  return true
715
804
  }
716
805
  const { max, min } = node
717
- const checkLoop = max === Infinity
806
+ // Bounded or not, the repetition checks progress below (#115)
807
+ // A streaming build's checkpoint holds the object built so far (see `repeat`)
808
+ const resumable = this.resumable
809
+ const site = new Key()
718
810
  if (Predicate.isTagged(node, "Repeat")) {
719
811
  return (s) => {
720
- const acc = new EntriesAcc()
721
- for (let count = 0; count < max; count++) {
812
+ const at = s.pos
813
+ const cp = resumable && s.resume !== undefined ? resumeEntries(s, site, at) : undefined
814
+ if (cp instanceof Resume.Done) return cp.value
815
+ const resumed = cp !== undefined
816
+ const acc = cp?.acc ?? new EntriesAcc()
817
+ for (let count = cp?.count ?? 0; count < max; count++) {
722
818
  const start = s.pos
723
819
  let key: unknown
724
820
  let value: unknown
@@ -733,50 +829,50 @@ class Compiler {
733
829
  else if (j === vi) value = a
734
830
  }
735
831
  if (!ok) {
736
- if (s.mode !== BT || count < min) return Fail
737
- s.pos = start
738
- return acc.result()
832
+ return stopEntries(s, site, at, resumed, acc, count, start, min)
739
833
  }
740
- if (checkLoop && s.pos === start) {
834
+ if (s.pos === start) {
741
835
  s.mode = CUT
742
836
  return Fail
743
837
  }
744
838
  acc.add(key, value)
745
839
  }
746
- return acc.result()
840
+ return entriesDone(s, site, at, resumed, acc)
747
841
  }
748
842
  }
749
843
  const sep = this.compile(node.separator, false)
750
844
  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()
845
+ const at = s.pos
846
+ const cp = resumable && s.resume !== undefined ? resumeEntries(s, site, at) : undefined
847
+ if (cp instanceof Resume.Done) return cp.value
848
+ const resumed = cp !== undefined
849
+ const acc = cp?.acc ?? new EntriesAcc()
850
+ let count = cp?.count ?? 0
851
+ if (!resumed) {
852
+ if (max === 0) return acc.result()
853
+ const start = s.pos
854
+ if (!entry(s, acc)) {
855
+ if (s.mode !== BT || min > 0) return Fail
856
+ s.pos = start
857
+ return acc.result()
858
+ }
859
+ count++
759
860
  }
760
- count++
761
861
  while (count < max) {
762
- start = s.pos
862
+ const start = s.pos
763
863
  if (sep(s) === Fail) {
764
- if (s.mode !== BT || count < min) return Fail
765
- s.pos = start
766
- return acc.result()
864
+ return stopEntries(s, site, at, resumed, acc, count, start, min)
767
865
  }
768
- if (checkLoop && s.pos === start) {
866
+ if (s.pos === start) {
769
867
  s.mode = CUT
770
868
  return Fail
771
869
  }
772
870
  if (!entry(s, acc)) {
773
- if (s.mode !== BT || count < min) return Fail
774
- s.pos = start
775
- return acc.result()
871
+ return stopEntries(s, site, at, resumed, acc, count, start, min)
776
872
  }
777
873
  count++
778
874
  }
779
- return acc.result()
875
+ return entriesDone(s, site, at, resumed, acc)
780
876
  }
781
877
  }
782
878
 
@@ -808,7 +904,7 @@ class Compiler {
808
904
 
809
905
  separated(node: NodeOf<"Separated">, used: boolean): Step {
810
906
  const { max, min } = node
811
- const checkLoop = max === Infinity
907
+ // Bounded or not, the repetition checks progress below (#115)
812
908
  const c = this.compile(node.self, used)
813
909
  const sep = this.compile(node.separator, false)
814
910
  const resumable = this.resumable
@@ -819,6 +915,7 @@ class Compiler {
819
915
  let count = 0
820
916
  let resumed = false
821
917
  if (resumable && s.resume !== undefined) {
918
+ // SAFETY: only this loop saves checkpoints under `key`: `{ acc, count, pos }` when it runs out, a `Done` when it completes
822
919
  const cp = Resume.take(s, key, entry) as
823
920
  | Resume.Done
824
921
  | { acc: Array<unknown>; count: number; pos: number }
@@ -855,7 +952,7 @@ class Compiler {
855
952
  s.pos = start
856
953
  return resumed ? Resume.done(s, key, entry, true, acc) : acc
857
954
  }
858
- if (checkLoop && s.pos === start) {
955
+ if (s.pos === start) {
859
956
  s.mode = CUT
860
957
  return Fail
861
958
  }
@@ -885,6 +982,7 @@ class Compiler {
885
982
  let acc: unknown
886
983
  let resumed = false
887
984
  if (resumable && s.resume !== undefined) {
985
+ // SAFETY: only this loop saves checkpoints under `key`: `{ acc, pos }` when it runs out, a `Done` when it completes
888
986
  const cp = Resume.take(s, key, entry) as Resume.Done | { acc: unknown; pos: number } | undefined
889
987
  if (cp instanceof Resume.Done) {
890
988
  s.pos = cp.pos
@@ -961,21 +1059,22 @@ class Compiler {
961
1059
  // ---------------------------------------------------------------------------
962
1060
 
963
1061
  token(p: ParserLike, node: TokenNode, used: boolean): Step {
1062
+ // SAFETY: `build` compiles a token node without an encoding only when `native` holds, that is when `kind` is not 2
964
1063
  const kind = this.kind as 0 | 1
965
1064
  switch (node._tag) {
966
1065
  case "Any":
967
1066
  if (kind === 1) {
968
- return (s) => {
1067
+ return (s: State<Uint8Array>) => {
969
1068
  const pos = s.pos
970
1069
  if (pos >= s.end) return fail(s)
971
1070
  s.pos = pos + 1
972
- return (s.input as Uint8Array)[pos]
1071
+ return s.input[pos]
973
1072
  }
974
1073
  }
975
1074
  return (s) => {
976
1075
  const pos = s.pos
977
1076
  if (pos >= s.end) return fail(s)
978
- const str = s.input as string
1077
+ const str: string = s.input
979
1078
  const c = str.charCodeAt(pos)
980
1079
  if (c >= 0xd800 && c <= 0xdbff && pos + 1 < str.length && (str.charCodeAt(pos + 1) & 0xfc00) === 0xdc00) {
981
1080
  s.pos = pos + 2
@@ -992,10 +1091,10 @@ class Compiler {
992
1091
  const accept = acceptTable(set, negate)
993
1092
  const test = set.test
994
1093
  if (kind === 1) {
995
- return (s) => {
1094
+ return (s: State<Uint8Array>) => {
996
1095
  const pos = s.pos
997
1096
  if (pos >= s.end) return fail(s)
998
- const b = (s.input as Uint8Array)[pos]
1097
+ const b = s.input[pos]
999
1098
  if (accept[b] === 0) return fail(s)
1000
1099
  s.pos = pos + 1
1001
1100
  return b
@@ -1004,7 +1103,7 @@ class Compiler {
1004
1103
  return (s) => {
1005
1104
  const pos = s.pos
1006
1105
  if (pos >= s.end) return fail(s)
1007
- const str = s.input as string
1106
+ const str: string = s.input
1008
1107
  const c = str.charCodeAt(pos)
1009
1108
  if (c < TableSize) {
1010
1109
  if (accept[c] === 0) return fail(s)
@@ -1025,15 +1124,15 @@ class Compiler {
1025
1124
  case "Take": {
1026
1125
  const count = node.count
1027
1126
  if (kind === 1) {
1028
- return (s) => {
1127
+ return (s: State<Uint8Array>) => {
1029
1128
  const start = s.pos
1030
1129
  if (s.end - start < count) return failAt(s, s.end)
1031
1130
  s.pos = start + count
1032
- return used ? (s.input as Uint8Array).subarray(start, start + count) : undefined
1131
+ return used ? s.input.subarray(start, start + count) : undefined
1033
1132
  }
1034
1133
  }
1035
1134
  return (s) => {
1036
- const str = s.input as string
1135
+ const str: string = s.input
1037
1136
  const start = s.pos
1038
1137
  const end = s.end
1039
1138
  let i = start
@@ -1050,12 +1149,16 @@ class Compiler {
1050
1149
  }
1051
1150
  case "TakeWhile":
1052
1151
  return this.tokenTakeWhile(node, used)
1053
- case "TakeUntil": {
1054
- const { literal, max, min } = node
1152
+ case "TakeUntil":
1153
+ case "TakeUntilAny": {
1154
+ const { max, min } = node
1055
1155
  const bounded = min > 0 || max !== Infinity
1156
+ const search: (s: State<Input>, from: number) => number = Predicate.isTagged(node, "TakeUntil")
1157
+ ? (s, from) => indexOf(s, node.literal, from)
1158
+ : (s, from) => indexOfAny(s, node.literals, from)
1056
1159
  return (s) => {
1057
1160
  const start = s.pos
1058
- const idx = indexOf(s, literal, start)
1161
+ const idx = search(s, start)
1059
1162
  if (idx < 0) return failAt(s, s.end)
1060
1163
  if (bounded) {
1061
1164
  const count = countTokens(s, start, idx)
@@ -1078,15 +1181,15 @@ class Compiler {
1078
1181
  const lit = node.literal
1079
1182
  const literalKind = Predicate.isString(lit) ? 0 : lit instanceof Uint8Array ? 1 : 2
1080
1183
  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
1184
+ if (!Predicate.isString(lit)) return this.fallback(p, node, "byte or array literal on string input")
1185
+ const str = lit
1083
1186
  const len = str.length
1084
1187
  if (node.caseless) {
1085
1188
  const lowered = Array.from({ length: len }, (_, i) => lowerAscii(str.charCodeAt(i)))
1086
1189
  return (s) => {
1087
1190
  const pos = s.pos
1088
1191
  if (s.end - pos < len) return fail(s)
1089
- const input = s.input as string
1192
+ const input: string = s.input
1090
1193
  for (let i = 0; i < len; i++) {
1091
1194
  if (lowerAscii(input.charCodeAt(pos + i)) !== lowered[i]) return fail(s)
1092
1195
  }
@@ -1096,8 +1199,8 @@ class Compiler {
1096
1199
  }
1097
1200
  return stringLiteral(str, str)
1098
1201
  }
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)
1202
+ const bytes = Predicate.isString(lit) ? new TextEncoder().encode(lit) : lit instanceof Uint8Array ? lit : undefined
1203
+ if (bytes === undefined) return this.fallback(p, node, "array literal on byte input")
1101
1204
  const len = bytes.length
1102
1205
  const sameKind = literalKind === 1 && !node.caseless
1103
1206
  const expect = node.caseless ? Array.from(bytes, lowerAscii) : Array.from(bytes)
@@ -1105,7 +1208,7 @@ class Compiler {
1105
1208
  return (s) => {
1106
1209
  const pos = s.pos
1107
1210
  if (s.end - pos < len) return fail(s)
1108
- const input = s.input as Uint8Array
1211
+ const input: Uint8Array = s.input
1109
1212
  for (let i = 0; i < len; i++) {
1110
1213
  const b = input[pos + i]
1111
1214
  if ((caseless ? lowerAscii(b) : b) !== expect[i]) return fail(s)
@@ -1116,13 +1219,14 @@ class Compiler {
1116
1219
  }
1117
1220
 
1118
1221
  tokenTakeWhile(node: NodeOf<"TakeWhile">, used: boolean): Step {
1222
+ // SAFETY: called from `token` only, whose `kind` is not 2
1119
1223
  const kind = this.kind as 0 | 1
1120
1224
  const { max, min, negate } = node
1121
1225
  const set = tokenSet(node.set, kind)
1122
1226
  const accept = acceptTable(set, negate)
1123
1227
  if (kind === 1) {
1124
1228
  return (s) => {
1125
- const bytes = s.input as Uint8Array
1229
+ const bytes: Uint8Array = s.input
1126
1230
  const start = s.pos
1127
1231
  const limit = max === Infinity ? s.end : Math.min(s.end, start + max)
1128
1232
  let i = start
@@ -1132,9 +1236,11 @@ class Compiler {
1132
1236
  return used ? bytes.subarray(start, i) : undefined
1133
1237
  }
1134
1238
  }
1239
+ const stops = negate && min <= 1 && max === Infinity ? stopChars(set) : undefined
1240
+ if (stops !== undefined) return stopTakeTill(stops, min, used)
1135
1241
  const test = set.test
1136
1242
  return (s) => {
1137
- const str = s.input as string
1243
+ const str: string = s.input
1138
1244
  const start = s.pos
1139
1245
  const end = s.end
1140
1246
  let i = start
@@ -1172,7 +1278,9 @@ class Compiler {
1172
1278
  const { fast, unitAt, width: unitWidth } = unitReader(this.kind, enc)
1173
1279
  const decode = enc.decode
1174
1280
  const width = enc.width
1175
- const toText = enc.text
1281
+ // UTF-8 decodes through the state's ASCII run (see `utf8Text`)
1282
+ const toText: (s: State<any>, start: number, end: number) => string =
1283
+ enc === utf8 ? utf8Text : (s, start, end) => enc.text(s.input, start, end)
1176
1284
  switch (node._tag) {
1177
1285
  case "Any":
1178
1286
  return (s) => {
@@ -1190,6 +1298,7 @@ class Compiler {
1190
1298
  return used ? charOf(cp) : undefined
1191
1299
  }
1192
1300
  case "Literal": {
1301
+ // SAFETY: a `Literal` with an encoding is a `Text` literal, which records the string it encodes
1193
1302
  const lit = node.literal as string
1194
1303
  if (node.caseless) {
1195
1304
  const folded = Array.from(lit, (c) => fold(firstCodePoint(c)))
@@ -1214,18 +1323,18 @@ class Compiler {
1214
1323
  if (fold(cp) !== folded[i]) return failAt(s, pos)
1215
1324
  }
1216
1325
  s.pos = pos
1217
- return used ? toText(input, start, pos) : undefined
1326
+ return used ? toText(s, start, pos) : undefined
1218
1327
  }
1219
1328
  }
1220
1329
  const encoded = enc.encode(lit)
1221
1330
  if (Predicate.isString(encoded)) return stringLiteral(encoded, lit)
1222
- const bytes = encoded as Uint8Array
1331
+ const bytes: Uint8Array = encoded
1223
1332
  const len = bytes.length
1224
1333
  if (len === 1) {
1225
1334
  const b0 = bytes[0]
1226
- return (s) => {
1335
+ return (s: State<Uint8Array>) => {
1227
1336
  const pos = s.pos
1228
- if (pos < s.end && (s.input as Uint8Array)[pos] === b0) {
1337
+ if (pos < s.end && s.input[pos] === b0) {
1229
1338
  s.pos = pos + 1
1230
1339
  return lit
1231
1340
  }
@@ -1235,7 +1344,7 @@ class Compiler {
1235
1344
  return (s) => {
1236
1345
  const pos = s.pos
1237
1346
  if (s.end - pos < len) return fail(s)
1238
- const input = s.input as Uint8Array
1347
+ const input: Uint8Array = s.input
1239
1348
  for (let i = 0; i < len; i++) if (input[pos + i] !== bytes[i]) return fail(s)
1240
1349
  s.pos = pos + len
1241
1350
  return lit
@@ -1280,7 +1389,7 @@ class Compiler {
1280
1389
  }
1281
1390
  }
1282
1391
  s.pos = pos
1283
- return used ? toText(input, start, pos) : undefined
1392
+ return used ? toText(s, start, pos) : undefined
1284
1393
  }
1285
1394
  }
1286
1395
  case "TakeWhile": {
@@ -1296,6 +1405,7 @@ class Compiler {
1296
1405
  return this.kind === 0 ? takeWhileString(loop, enc) : takeWhileBytes(loop, enc)
1297
1406
  }
1298
1407
  case "TakeUntil":
1408
+ case "TakeUntilAny":
1299
1409
  case "Rest":
1300
1410
  return this.fallback(p, node, "Text parser without a compiled form")
1301
1411
  }
@@ -1307,9 +1417,9 @@ class Compiler {
1307
1417
  if (enc === undefined) {
1308
1418
  if (!used) return c
1309
1419
  if (this.kind === 0) {
1310
- return (s) => {
1420
+ return (s: State<string>) => {
1311
1421
  const start = s.pos
1312
- return c(s) === Fail ? Fail : (s.input as string).slice(start, s.pos)
1422
+ return c(s) === Fail ? Fail : s.input.slice(start, s.pos)
1313
1423
  }
1314
1424
  }
1315
1425
  return (s) => {
@@ -1319,7 +1429,9 @@ class Compiler {
1319
1429
  }
1320
1430
  // `Text.taken` checks that the text is well formed, then decodes it
1321
1431
  const { fast, unitAt, width } = unitReader(this.kind, enc)
1322
- const toText = enc.text
1432
+ // UTF-8 decodes through the state's ASCII run (see `utf8Text`)
1433
+ const toText: (s: State<any>, start: number, end: number) => string =
1434
+ enc === utf8 ? utf8Text : (s, start, end) => enc.text(s.input, start, end)
1323
1435
  return (s) => {
1324
1436
  const start = s.pos
1325
1437
  if (c(s) === Fail) return Fail
@@ -1328,7 +1440,7 @@ class Compiler {
1328
1440
  let i = start
1329
1441
  while (i < end && unitAt(input, i, end) < fast) i += width
1330
1442
  if (i < end && validate(enc, s, i, end) < 0) return fail(s)
1331
- return used ? toText(input, start, end) : undefined
1443
+ return used ? toText(s, start, end) : undefined
1332
1444
  }
1333
1445
  }
1334
1446
 
@@ -1341,11 +1453,7 @@ class Compiler {
1341
1453
  if (!floatTexts.has(node.self) || this.kind === 2) return undefined
1342
1454
  if (!scansFloat(node.self, (enc) => this.native(enc))) return undefined
1343
1455
  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
1456
+ const scan = this.kind === 0 ? scanFloatString : scanFloatBytes
1349
1457
  const resumable = this.resumable
1350
1458
  return (s) => {
1351
1459
  const v = scan(s.input, s.pos, s.end)
@@ -1372,15 +1480,15 @@ class Compiler {
1372
1480
  if (enc === undefined || (ctrl < fastLimit(enc) && ctrl < TableSize)) {
1373
1481
  control =
1374
1482
  kind === 0
1375
- ? (s) => {
1376
- if (s.pos < s.end && (s.input as string).charCodeAt(s.pos) === ctrl) {
1483
+ ? (s: State<string>) => {
1484
+ if (s.pos < s.end && s.input.charCodeAt(s.pos) === ctrl) {
1377
1485
  s.pos++
1378
1486
  return true
1379
1487
  }
1380
1488
  return false
1381
1489
  }
1382
- : (s) => {
1383
- if (s.pos < s.end && (s.input as Uint8Array)[s.pos] === ctrl) {
1490
+ : (s: State<Uint8Array>) => {
1491
+ if (s.pos < s.end && s.input[s.pos] === ctrl) {
1384
1492
  s.pos++
1385
1493
  return true
1386
1494
  }
@@ -1407,19 +1515,20 @@ class Compiler {
1407
1515
  const resumable = this.resumable
1408
1516
  const key = new Key()
1409
1517
  /** Fails as incomplete, keeping a checkpoint in a streaming parse. */
1410
- const incomplete = (s: State<any>, start: number, checkpoint: unknown): typeof Fail => {
1518
+ const incomplete = (s: State<any>, start: number, checkpoint: EscapedCheckpoint): typeof Fail => {
1411
1519
  if (s.resume !== undefined) Resume.save(s, key, start, checkpoint)
1412
1520
  s.mode = INC
1413
1521
  return Fail
1414
1522
  }
1415
1523
  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)
1524
+ const slice: (s: State<any>, start: number) => Input =
1525
+ enc === undefined ? (s, start) => s.slice(start, s.pos) : (s, start) => textIn(enc, s, start, s.pos)
1418
1526
  return (s) => {
1419
1527
  const start = s.pos
1420
1528
  let resumed = false
1421
1529
  if (resumable && s.resume !== undefined) {
1422
- const cp = Resume.take(s, key, start) as Resume.Done | number | undefined
1530
+ // SAFETY: only this raw loop saves checkpoints under `key`: the position it stopped at, or a `Done` when it completes
1531
+ const cp = Resume.take(s, key, start) as Resume.Done | Extract<EscapedCheckpoint, number> | undefined
1423
1532
  if (cp instanceof Resume.Done) {
1424
1533
  s.pos = cp.pos
1425
1534
  return cp.value
@@ -1447,7 +1556,11 @@ class Compiler {
1447
1556
  return resumed ? Resume.done(s, key, start, true, slice(s, start)) : slice(s, start)
1448
1557
  }
1449
1558
  }
1450
- if (s.partial) return incomplete(s, start, s.pos)
1559
+ if (s.partial) {
1560
+ // The text ran out of input: `escaped`'s own failure, needing more
1561
+ s.incomplete(0)
1562
+ return incomplete(s, start, s.pos)
1563
+ }
1451
1564
  return resumed ? Resume.done(s, key, start, true, slice(s, start)) : slice(s, start)
1452
1565
  }
1453
1566
  }
@@ -1456,7 +1569,8 @@ class Compiler {
1456
1569
  let out = ""
1457
1570
  let resumed = false
1458
1571
  if (resumable && s.resume !== undefined) {
1459
- const cp = Resume.take(s, key, start) as Resume.Done | { out: string; pos: number } | undefined
1572
+ // SAFETY: only this loop saves checkpoints under `key`: `{ out, pos }` when it runs out, a `Done` when it completes
1573
+ const cp = Resume.take(s, key, start) as Resume.Done | Exclude<EscapedCheckpoint, number> | undefined
1460
1574
  if (cp instanceof Resume.Done) {
1461
1575
  s.pos = cp.pos
1462
1576
  return cp.value
@@ -1488,13 +1602,31 @@ class Compiler {
1488
1602
  return resumed ? Resume.done(s, key, start, true, out) : out
1489
1603
  }
1490
1604
  }
1491
- if (s.partial) return incomplete(s, start, { out, pos: s.pos })
1605
+ if (s.partial) {
1606
+ // The text ran out of input: `escaped`'s own failure, needing more
1607
+ s.incomplete(0)
1608
+ return incomplete(s, start, { out, pos: s.pos })
1609
+ }
1492
1610
  return resumed ? Resume.done(s, key, start, true, out) : out
1493
1611
  }
1494
1612
  }
1495
1613
  }
1496
1614
 
1497
- type TokenNode = NodeOf<"Any" | "Literal" | "OneOf" | "Take" | "TakeWhile" | "TakeUntil" | "Rest">
1615
+ /** How far past the cursor a token parser may read: its literals' length in UTF-8 at most, or `Margin`. */
1616
+ const marginOf = (node: TokenNode): number => {
1617
+ if (Predicate.isTagged(node, "Literal") || Predicate.isTagged(node, "TakeUntil")) return node.literal.length * 4
1618
+ if (Predicate.isTagged(node, "TakeUntilAny")) return Math.max(0, ...node.literals.map((lit) => lit.length)) * 4
1619
+ return Margin
1620
+ }
1621
+
1622
+ type TokenNode = NodeOf<"Any" | "Literal" | "OneOf" | "Take" | "TakeWhile" | "TakeUntil" | "TakeUntilAny" | "Rest">
1623
+
1624
+ /**
1625
+ * Where a compiled `escaped` that ran out of input resumes: the position
1626
+ * (`takeEscaped`, which slices the input), or the text unescaped so far and
1627
+ * the position after it.
1628
+ */
1629
+ type EscapedCheckpoint = number | { readonly out: string; readonly pos: number }
1498
1630
 
1499
1631
  const fail = (s: State<any>): typeof Fail => {
1500
1632
  s.mode = BT
@@ -1651,18 +1783,18 @@ const stringLiteral = (str: string, value: unknown): Step => {
1651
1783
  if (len === 0) return () => value
1652
1784
  if (len === 1) {
1653
1785
  const c0 = str.charCodeAt(0)
1654
- return (s) => {
1786
+ return (s: State<string>) => {
1655
1787
  const pos = s.pos
1656
- if (pos < s.end && (s.input as string).charCodeAt(pos) === c0) {
1788
+ if (pos < s.end && s.input.charCodeAt(pos) === c0) {
1657
1789
  s.pos = pos + 1
1658
1790
  return value
1659
1791
  }
1660
1792
  return fail(s)
1661
1793
  }
1662
1794
  }
1663
- return (s) => {
1795
+ return (s: State<string>) => {
1664
1796
  const pos = s.pos
1665
- if (s.end - pos >= len && (s.input as string).startsWith(str, pos)) {
1797
+ if (s.end - pos >= len && s.input.startsWith(str, pos)) {
1666
1798
  s.pos = pos + len
1667
1799
  return value
1668
1800
  }
@@ -1670,7 +1802,81 @@ const stringLiteral = (str: string, value: unknown): Step => {
1670
1802
  }
1671
1803
  }
1672
1804
 
1673
- /** A closure compiler for one input kind; it memoizes what it compiles. */
1805
+ /**
1806
+ * `Token.takeTill` on a string, of one to three stop characters that are
1807
+ * whole code points (see `search.ts`): every other unit, surrogates
1808
+ * included, is accepted, so the run ends at the first stop character, or
1809
+ * at the state's end.
1810
+ */
1811
+ const stopTakeTill = (stops: ReadonlyArray<number>, min: number, used: boolean): Step => {
1812
+ const head = stops.length === 1 ? SearchHead : 2 * SearchHead
1813
+ const c0 = stops[0]
1814
+ const c1 = stops.length > 1 ? stops[1] : -1
1815
+ const c2 = stops.length > 2 ? stops[2] : -1
1816
+ const one = String.fromCharCode(c0)
1817
+ const site = new StopCache(stops.map((u) => String.fromCharCode(u)))
1818
+ return (s: State<string>) => {
1819
+ const str = s.input
1820
+ const start = s.pos
1821
+ const end = s.end
1822
+ const h = start + head < end ? start + head : end
1823
+ let i = start
1824
+ let c = -1
1825
+ while (i < h && (c = str.charCodeAt(i)) !== c0 && c !== c1 && c !== c2) i++
1826
+ // A run that ends right after the head needs no search
1827
+ if (i === h && i < end && (c = str.charCodeAt(i)) !== c0 && c !== c1 && c !== c2) {
1828
+ if (stops.length === 1) {
1829
+ const at = str.indexOf(one, i)
1830
+ i = at < 0 || at > end ? end : at
1831
+ } else i = nextStop(site, s, i)
1832
+ }
1833
+ if (i - start < min) return failAt(s, i)
1834
+ s.pos = i
1835
+ return used ? str.slice(start, i) : undefined
1836
+ }
1837
+ }
1838
+
1839
+ /** A checkpoint of `entriesLoop`: the object so far, the entries parsed and where the next one starts. */
1840
+ interface EntriesCheckpoint {
1841
+ readonly acc: EntriesAcc
1842
+ readonly count: number
1843
+ readonly pos: number
1844
+ }
1845
+
1846
+ /** Takes the checkpoint `entriesLoop` saved at `at`, restoring the position of a completed one. */
1847
+ const resumeEntries = (s: State<any>, key: Key, at: number): Resume.Done | EntriesCheckpoint | undefined => {
1848
+ // SAFETY: only `entriesLoop` saves checkpoints under its `key`: an `EntriesCheckpoint` when it runs out, a `Done` when it completes
1849
+ const cp = Resume.take(s, key, at) as Resume.Done | EntriesCheckpoint | undefined
1850
+ if (cp instanceof Resume.Done) s.pos = cp.pos
1851
+ else if (cp !== undefined) s.pos = cp.pos
1852
+ return cp
1853
+ }
1854
+
1855
+ /** The object `entriesLoop` built, recorded as done when the loop resumed. */
1856
+ const entriesDone = (s: State<any>, key: Key, at: number, resumed: boolean, acc: EntriesAcc): object =>
1857
+ resumed ? Resume.done(s, key, at, true, acc.result()) : acc.result()
1858
+
1859
+ /**
1860
+ * Where `entriesLoop` stops at an entry (or separator) that failed at
1861
+ * `start`: a checkpoint when the input ran out, `Fail` unless the failure
1862
+ * backtracks after `min` entries, otherwise the object.
1863
+ */
1864
+ const stopEntries = (
1865
+ s: State<any>,
1866
+ site: Key,
1867
+ at: number,
1868
+ resumed: boolean,
1869
+ acc: EntriesAcc,
1870
+ count: number,
1871
+ start: number,
1872
+ min: number
1873
+ ): object | typeof Fail => {
1874
+ if (s.mode === INC && s.resume !== undefined) Resume.save(s, site, at, { acc, count, pos: start })
1875
+ if (s.mode !== BT || count < min) return Fail
1876
+ s.pos = start
1877
+ return entriesDone(s, site, at, resumed, acc)
1878
+ }
1879
+
1674
1880
  /** The object of `Object.fromEntries`, built an entry at a time (see `internal/entries.ts`). */
1675
1881
  class EntriesAcc {
1676
1882
  readonly o: Record<string, unknown> = {}
@@ -1721,8 +1927,20 @@ export const backend: Backend = {
1721
1927
  Result.match(
1722
1928
  Result.try(() => compileClosure(p, kind, resumable)),
1723
1929
  {
1724
- onSuccess: ({ fallbacks, step }) => ({ backend: "closure" as const, step, fallbacks, error: undefined }),
1725
- onFailure: (error) => ({ backend: undefined, step: undefined, fallbacks: [], error })
1930
+ onSuccess: ({ fallbacks, step }) => ({
1931
+ backend: "closure" as const,
1932
+ step,
1933
+ guarded: false,
1934
+ fallbacks,
1935
+ error: undefined
1936
+ }),
1937
+ onFailure: (error) => ({
1938
+ backend: "interpreted" as const,
1939
+ step: undefined,
1940
+ guarded: false,
1941
+ fallbacks: [],
1942
+ error
1943
+ })
1726
1944
  }
1727
1945
  )
1728
1946
  }