@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
package/src/Parser.ts CHANGED
@@ -32,9 +32,11 @@
32
32
  import * as Predicate from "effect/Predicate"
33
33
  import * as Context from "effect/Context"
34
34
  import * as Effect from "effect/Effect"
35
+ import * as Equal from "effect/Equal"
35
36
  import { dual } from "effect/Function"
36
37
  import { NodeInspectSymbol } from "effect/Inspectable"
37
38
  import * as Option from "effect/Option"
39
+ import * as Rec from "effect/Record"
38
40
  import type { Pipeable } from "effect/Pipeable"
39
41
  import { pipeArguments } from "effect/Pipeable"
40
42
  import * as Result from "effect/Result"
@@ -50,8 +52,9 @@ import * as Resume from "./internal/resume.js"
50
52
  import * as Run from "./internal/run.js"
51
53
  import * as Stack from "./internal/stack.js"
52
54
  import { TiedFailures } from "./internal/ties.js"
55
+ import { tupleStep } from "./internal/tuple.js"
53
56
  import type { Node } from "./internal/node.js"
54
- import { bounds, NodeId } from "./internal/node.js"
57
+ import { bounds, Defer as DeferSymbol, describedNode, NodeId } from "./internal/node.js"
55
58
 
56
59
  // -----------------------------------------------------------------------------
57
60
  // Models
@@ -146,6 +149,15 @@ export declare namespace Parser {
146
149
  : [P] extends [Parser<infer _A, infer _E, infer _R, infer I>]
147
150
  ? I
148
151
  : never
152
+ /**
153
+ * The steps {@link combinator}'s `build` receives: one per parser, in
154
+ * order, each returning its parser's output or `Fail`.
155
+ *
156
+ * @since 0.5.0
157
+ */
158
+ export type Steps<Ps extends ReadonlyArray<Any>> = {
159
+ readonly [K in keyof Ps]: (state: State<InputOf<Ps[number]>>) => Success<Ps[K]> | Fail
160
+ }
149
161
  }
150
162
 
151
163
  /**
@@ -155,6 +167,12 @@ export declare namespace Parser {
155
167
  * - `{ min, max }`: between `min` and `max` times (inclusive); `min`
156
168
  * defaults to 0 and `max` to unbounded.
157
169
  *
170
+ * A count and both bounds must be non-negative integers (`max` may be
171
+ * `Infinity`) with `min <= max`. Every combinator that takes a range
172
+ * throws a `RangeError` otherwise, when the parser is built: a range that
173
+ * comes from data (a config file, a query parameter) is checked there,
174
+ * whatever its static type says.
175
+ *
158
176
  * @since 0.1.0
159
177
  * @category models
160
178
  */
@@ -225,7 +243,7 @@ class SingleShotGen<T> {
225
243
  }
226
244
 
227
245
  class ParserImpl<A, E, R, I extends Input> implements Parser<A, E, R, I> {
228
- readonly [TypeId] = variance as Parser.Variance<A, E, R, I>
246
+ readonly [TypeId]: Parser.Variance<A, E, R, I> = variance
229
247
  /** The description used by the compiler; `undefined` for `Opaque`. */
230
248
  readonly [NodeId]: Node | undefined
231
249
  constructor(
@@ -239,7 +257,7 @@ class ParserImpl<A, E, R, I extends Input> implements Parser<A, E, R, I> {
239
257
  return pipeArguments(this, arguments)
240
258
  }
241
259
  [Symbol.iterator](): ParserIterator<Parser<A, E, R, I>> {
242
- return new SingleShotGen(this) as any
260
+ return new SingleShotGen(this)
243
261
  }
244
262
  toString() {
245
263
  return this.name === undefined ? "Parser" : `Parser(${this.name})`
@@ -315,7 +333,61 @@ export const described = <A, E = never, R = never, I extends Input = string>(
315
333
  node: Node
316
334
  ): Parser<A, E, R, I> => new ParserImpl(step, name, node)
317
335
 
318
- const nodeOfParser = (p: Parser.Any): Node | undefined => (p as any)[NodeId]
336
+ /**
337
+ * Creates a combinator over `parsers` that compiles: `build` makes the
338
+ * combinator's step from the parsers' steps. Write combinators with it
339
+ * rather than with {@link make} when they run other parsers: a `make`
340
+ * parser is opaque to the compilers, so in a compiled grammar it runs
341
+ * interpreted, and so does every parser it runs. Interpreted, `build`
342
+ * receives the parsers' own steps; compiled (see the `Compiler` module), it
343
+ * receives their compiled steps, so the parsers inside compile with the
344
+ * grammar.
345
+ *
346
+ * Each step runs its parser on the state it is given, as `Parser.run` does,
347
+ * and returns the parser's output or `Fail`. The steps must run on the
348
+ * combinator's own state (they are compiled for its input kind): a
349
+ * combinator that runs a parser on another input, as {@link mapParser}
350
+ * does, is written with `make`. `build` may be called more than once (once
351
+ * interpreted, and once for each compiled build), so state its step shares
352
+ * between calls belongs inside `build`.
353
+ *
354
+ * @example
355
+ * ```ts
356
+ * import { Ascii, Input, Parser, Token } from "@thomasfosterau/effect-parse"
357
+ *
358
+ * // One or more `item`s separated by `sep`, returning their count
359
+ * const count = <A, B>(item: Parser.Parser<A>, sep: Parser.Parser<B>) =>
360
+ * Parser.combinator([item, sep], ([itemStep, sepStep]) => (state) => {
361
+ * if (itemStep(state) === Input.Fail) return Input.Fail
362
+ * let n = 1
363
+ * while (true) {
364
+ * const start = state.pos
365
+ * if (sepStep(state) === Input.Fail || itemStep(state) === Input.Fail) {
366
+ * if (state.mode !== "Backtrack") return Input.Fail
367
+ * state.rewind(start)
368
+ * return n
369
+ * }
370
+ * n++
371
+ * }
372
+ * })
373
+ *
374
+ * Parser.parse(count(Ascii.digit1, Token.literal(",")), "1,22,333") // Result.succeed(3)
375
+ * ```
376
+ *
377
+ * @since 0.5.0
378
+ * @category constructors
379
+ */
380
+ export const combinator = <const Ps extends ReadonlyArray<Parser.Any>, A, E = never>(
381
+ parsers: Ps,
382
+ build: (steps: Parser.Steps<Ps>) => (state: State<Parser.InputOf<Ps[number]>>) => A | Fail,
383
+ name?: string
384
+ ): Parser<A, E | Parser.Error<Ps[number]>, Parser.Services<Ps[number]>, Parser.InputOf<Ps[number]>> => {
385
+ const untyped = (steps: ReadonlyArray<(state: State<any>) => unknown>) =>
386
+ // SAFETY: `steps` holds each parser's step (or, compiled, an equivalent
387
+ // compiled step), in order, which returns that parser's output or `Fail`
388
+ build(steps as Parser.Steps<Ps>)
389
+ return described(untyped(parsers.map((p) => p.step)), name, { _tag: "Combinator", parsers, build: untyped })
390
+ }
319
391
 
320
392
  /**
321
393
  * Gives a parser a name, used by `toString`.
@@ -329,7 +401,7 @@ export const named: {
329
401
  } = /*#__PURE__*/ dual(
330
402
  2,
331
403
  <A, E, R, I extends Input>(self: Parser<A, E, R, I>, name: string): Parser<A, E, R, I> =>
332
- new ParserImpl(self.step, name, nodeOfParser(self))
404
+ new ParserImpl(self.step, name, describedNode(self))
333
405
  )
334
406
 
335
407
  /**
@@ -390,6 +462,40 @@ export const fail = <E>(error: E): Parser<never, E, never, Input> =>
390
462
 
391
463
  const failBacktrack: Node = { _tag: "Fail", mode: "Backtrack" }
392
464
 
465
+ /**
466
+ * A parser that fails (backtracks) with the given user error, reporting it
467
+ * at `offset` rather than where the parser runs. Use it for a check that
468
+ * can only run once a whole construct has parsed but should point at the
469
+ * part at fault: return it from a {@link flatMap} continuation, with an
470
+ * offset taken from {@link location} or {@link withSpan}. Like any failure,
471
+ * one at a later offset is preferred over the failures of other
472
+ * alternatives (the furthest failure wins). The offset is clamped to the
473
+ * input.
474
+ *
475
+ * @example
476
+ * ```ts
477
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
478
+ *
479
+ * const tag = (open: string) => Parser.delimited(Token.literal(open), Ascii.alpha1, Token.literal(">"))
480
+ * const element = Parser.seq(tag("<"), Parser.location, tag("</")).pipe(
481
+ * Parser.flatMap(([start, at, end]) =>
482
+ * start === end ? Parser.succeed(start) : Parser.failAt(at, `expected </${start}>`)
483
+ * )
484
+ * )
485
+ * // The error points at the end tag (offset 3), not at the start of the element
486
+ * Parser.parse(element, "<a></b>")
487
+ * ```
488
+ *
489
+ * @since 0.5.0
490
+ * @category constructors
491
+ */
492
+ export const failAt = <E>(offset: number, error: E): Parser<never, E, never, Input> =>
493
+ described<never, E, never, Input>(
494
+ (state) => state.failCause("Backtrack", "Fail", error, offset < 0 ? 0 : offset > state.end ? state.end : offset),
495
+ "failAt",
496
+ failBacktrack
497
+ )
498
+
393
499
  /**
394
500
  * A parser that always fails (backtracks) without a cause. This is winnow's
395
501
  * `fail` and nom's `fail()`.
@@ -737,6 +843,72 @@ export const tryMap: {
737
843
  }
738
844
  )
739
845
 
846
+ /**
847
+ * Transforms the output of a parser with a function that returns
848
+ * `Input.Fail` to reject it: the allocation-free form of {@link tryMap} and
849
+ * {@link filterMap}, for checks that run on nearly every token. Compiled
850
+ * code compares the result with the sentinel instead of allocating and
851
+ * unwrapping a `Result` or an `Option` on every success.
852
+ *
853
+ * On failure the parser backtracks to where it started. With `orFailWith`,
854
+ * the user error it builds (only on failure) becomes the `cause` of the
855
+ * `ParseError`, as with `tryMap`; without, the failure has no cause, as
856
+ * with `filterMap`.
857
+ *
858
+ * @example
859
+ * ```ts
860
+ * import { Ascii, Input, Parser } from "@thomasfosterau/effect-parse"
861
+ *
862
+ * const byte = Ascii.decUint.pipe(
863
+ * Parser.mapOrFail(
864
+ * (n) => (n < 256 ? n : Input.Fail),
865
+ * (n) => `${n} is not a byte`
866
+ * )
867
+ * )
868
+ * Parser.parse(byte, "300") // fails with the cause "300 is not a byte"
869
+ * ```
870
+ *
871
+ * @since 0.5.0
872
+ * @category mapping
873
+ */
874
+ export const mapOrFail: {
875
+ <A, B>(f: (a: A) => B | Fail): <E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<Exclude<B, Fail>, E, R, I>
876
+ <A, B, E2>(
877
+ f: (a: A) => B | Fail,
878
+ orFailWith: (a: A) => E2
879
+ ): <E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<Exclude<B, Fail>, E | E2, R, I>
880
+ <A, E, R, I extends Input, B>(self: Parser<A, E, R, I>, f: (a: A) => B | Fail): Parser<Exclude<B, Fail>, E, R, I>
881
+ <A, E, R, I extends Input, B, E2>(
882
+ self: Parser<A, E, R, I>,
883
+ f: (a: A) => B | Fail,
884
+ orFailWith: (a: A) => E2
885
+ ): Parser<Exclude<B, Fail>, E | E2, R, I>
886
+ } = /*#__PURE__*/ dual(
887
+ (args) => isParser(args[0]),
888
+ <A, E, R, I extends Input, B, E2>(
889
+ self: Parser<A, E, R, I>,
890
+ f: (a: A) => B | Fail,
891
+ orFailWith?: (a: A) => E2
892
+ ): Parser<B, E | E2, R, I> => {
893
+ const step = self.step
894
+ return described(
895
+ (state) => {
896
+ const start = state.pos
897
+ const a = step(state)
898
+ if (a === Fail) return Fail
899
+ const b = f(a)
900
+ if (b !== Fail) return b
901
+ state.rewind(start)
902
+ return orFailWith === undefined
903
+ ? state.backtrack("Verify")
904
+ : state.failCause("Backtrack", "Verify", orFailWith(a))
905
+ },
906
+ self.name,
907
+ { _tag: "MapOrFail", self, f }
908
+ )
909
+ }
910
+ )
911
+
740
912
  /**
741
913
  * Like {@link tryMap}, but the function may throw. Thrown values become the
742
914
  * `cause` of the error.
@@ -952,6 +1124,51 @@ export const withSpan = <A, E, R, I extends Input>(
952
1124
  )
953
1125
  }
954
1126
 
1127
+ /**
1128
+ * Transforms the output of a parser together with the offsets it consumed,
1129
+ * `[start, end)`: the allocation-free spelling of
1130
+ * `map(withSpan(p), ([a, span]) => f(a, span.start, span.end))`, which
1131
+ * builds a tuple and a span object on every success. Compiled code passes
1132
+ * the offsets to `f` directly.
1133
+ *
1134
+ * @example
1135
+ * ```ts
1136
+ * import { Ascii, Parser } from "@thomasfosterau/effect-parse"
1137
+ *
1138
+ * const name = Ascii.alpha1.pipe(Parser.mapSpan((text, start, end) => ({ text, start, end })))
1139
+ * Parser.parse(name, "abc") // Result.succeed({ text: "abc", start: 0, end: 3 })
1140
+ * ```
1141
+ *
1142
+ * @since 0.5.0
1143
+ * @category consumed input
1144
+ */
1145
+ export const mapSpan: {
1146
+ <A, B>(
1147
+ f: (a: A, start: number, end: number) => B
1148
+ ): <E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<B, E, R, I>
1149
+ <A, E, R, I extends Input, B>(
1150
+ self: Parser<A, E, R, I>,
1151
+ f: (a: A, start: number, end: number) => B
1152
+ ): Parser<B, E, R, I>
1153
+ } = /*#__PURE__*/ dual(
1154
+ 2,
1155
+ <A, E, R, I extends Input, B>(
1156
+ self: Parser<A, E, R, I>,
1157
+ f: (a: A, start: number, end: number) => B
1158
+ ): Parser<B, E, R, I> => {
1159
+ const step = self.step
1160
+ return described(
1161
+ (state) => {
1162
+ const start = state.pos
1163
+ const a = step(state)
1164
+ return a === Fail ? Fail : f(a, start, state.pos)
1165
+ },
1166
+ self.name,
1167
+ { _tag: "MapSpan", self, f }
1168
+ )
1169
+ }
1170
+ )
1171
+
955
1172
  // -----------------------------------------------------------------------------
956
1173
  // Error management
957
1174
  // -----------------------------------------------------------------------------
@@ -1004,6 +1221,9 @@ export const cut = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser<
1004
1221
  state.mode = "Cut"
1005
1222
  return a
1006
1223
  }
1224
+ // SAFETY: `state.deepest` is declared `Failure | undefined`; the
1225
+ // assertion only undoes the narrowing to `undefined` from the
1226
+ // assignment above, which misses that `step` may have recorded a failure
1007
1227
  const inner = state.deepest as Failure | undefined
1008
1228
  if (deepest !== undefined && (inner === undefined || deepest.offset > inner.offset)) state.deepest = deepest
1009
1229
  return a
@@ -1114,6 +1334,212 @@ export const expected: {
1114
1334
  }
1115
1335
  )
1116
1336
 
1337
+ /**
1338
+ * Runs a parser without letting it add to the expectations of later errors
1339
+ * (chumsky's `quiet`). Use it on skippers, such as whitespace and comments,
1340
+ * that may appear between any two tokens: they stop by failing, and without
1341
+ * `quiet` what they would have accepted (`" "`, `"(:"`, ...) is listed in
1342
+ * every error raised after them.
1343
+ *
1344
+ * When the parser succeeds, the hints and the furthest failure it recorded
1345
+ * are forgotten, as after a lookahead that succeeded. When it fails
1346
+ * recoverably without consuming input, its failure expects nothing, so an
1347
+ * enclosing `alt` or `opt` reports only the other alternatives. A failure
1348
+ * after consuming input (an unterminated comment) or an unrecoverable one
1349
+ * is kept as it is. `Parser.expected` cannot do this: it relabels a failure,
1350
+ * and a skipper that matches zero or more times never fails.
1351
+ *
1352
+ * @example
1353
+ * ```ts
1354
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
1355
+ *
1356
+ * const comment = Parser.delimited(Token.literal("(:"), Token.takeTill(":"), Token.literal(":)"))
1357
+ * const skip = Parser.quiet(Parser.repeat(Parser.alt(Ascii.multispace1, comment)))
1358
+ * const number = Parser.preceded(skip, Ascii.digit1)
1359
+ * // Fails expecting only a digit: without `quiet`, also whitespace and "(:"
1360
+ * Parser.parse(number, " (: one :) x")
1361
+ * ```
1362
+ *
1363
+ * @since 0.5.0
1364
+ * @category error management
1365
+ */
1366
+ export const quiet = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser<A, E, R, I> => {
1367
+ const step = self.step
1368
+ return described(
1369
+ (state) => {
1370
+ const start = state.pos
1371
+ const hintOffset = state.hintOffset
1372
+ const hintExpected = state.hintExpected
1373
+ const deepest = state.deepest
1374
+ const a = step(state)
1375
+ if (a !== Fail) {
1376
+ state.hintOffset = hintOffset
1377
+ state.hintExpected = hintExpected
1378
+ state.deepest = deepest
1379
+ return a
1380
+ }
1381
+ if (state.mode === "Backtrack" && state.failure.offset === start) {
1382
+ const f = state.failure
1383
+ const expected = hintOffset === start ? hintExpected : []
1384
+ state.hintOffset = hintOffset
1385
+ state.hintExpected = hintExpected
1386
+ state.deepest = deepest
1387
+ state.failure = new Failure(f.offset, f.kind, expected, f.context, f.hasCause, f.cause, f.message, f.needed)
1388
+ }
1389
+ return a
1390
+ },
1391
+ self.name,
1392
+ { _tag: "Quiet", self }
1393
+ )
1394
+ }
1395
+
1396
+ /**
1397
+ * What a fast path (see {@link withFastPath}) returns to leave the input at
1398
+ * the current offset to its grammar.
1399
+ *
1400
+ * @since 0.5.0
1401
+ * @category fast paths
1402
+ */
1403
+ export const Defer: typeof DeferSymbol = DeferSymbol
1404
+
1405
+ /**
1406
+ * The type of {@link Defer}.
1407
+ *
1408
+ * @since 0.5.0
1409
+ * @category fast paths
1410
+ */
1411
+ export type Defer = typeof DeferSymbol
1412
+
1413
+ /**
1414
+ * Puts a hand-written fast path in front of a grammar, which stays the
1415
+ * specification: `fast` runs on the state, at the offset where the grammar
1416
+ * would, and either returns the value the grammar would return, with
1417
+ * `state.pos` where the grammar would stop, or returns {@link Defer}, and
1418
+ * the grammar runs from that offset instead. Use it where a scanner written
1419
+ * by hand (`indexOf`, a lookup table, interned names) beats compiled code on
1420
+ * the tokens that dominate an input, and let the grammar handle everything
1421
+ * else.
1422
+ *
1423
+ * The fast path runs in compiled code only (see the `Compiler` module).
1424
+ * Interpreted runs, and so every error, every `Syntax` tree and every trace,
1425
+ * come from the grammar alone, so they are exactly the grammar's. It must
1426
+ * defer wherever it would not return exactly what the grammar returns, and
1427
+ * on partial input wherever the grammar might report `Incomplete`; check it
1428
+ * against the grammar with {@link verifyFastPath}.
1429
+ *
1430
+ * @example
1431
+ * ```ts
1432
+ * import { Compiler, Parser, Token } from "@thomasfosterau/effect-parse"
1433
+ *
1434
+ * const text = Token.takeTill("<", { min: 1 })
1435
+ * // The same run, found with `indexOf`; anything else (an empty run, the end of a partial input) defers
1436
+ * const fastText = Compiler.jit(
1437
+ * Parser.withFastPath(text, (state) => {
1438
+ * const end = state.input.indexOf("<", state.pos)
1439
+ * if (end <= state.pos || end > state.end) return Parser.Defer
1440
+ * const value = state.input.slice(state.pos, end)
1441
+ * state.pos = end
1442
+ * return value
1443
+ * })
1444
+ * )
1445
+ * ```
1446
+ *
1447
+ * @since 0.5.0
1448
+ * @category fast paths
1449
+ */
1450
+ export const withFastPath: {
1451
+ <A, I extends Input>(fast: (state: State<I>) => A | Defer): <E, R>(grammar: Parser<A, E, R, I>) => Parser<A, E, R, I>
1452
+ <A, E, R, I extends Input>(grammar: Parser<A, E, R, I>, fast: (state: State<I>) => A | Defer): Parser<A, E, R, I>
1453
+ } = /*#__PURE__*/ dual(
1454
+ 2,
1455
+ <A, E, R, I extends Input>(grammar: Parser<A, E, R, I>, fast: (state: State<I>) => A | Defer): Parser<A, E, R, I> =>
1456
+ // The interpreter runs the grammar alone: it is the reference, and it builds every error
1457
+ described(grammar.step, grammar.name, { _tag: "FastPath", self: grammar, fast })
1458
+ )
1459
+
1460
+ /**
1461
+ * A place where a fast path disagrees with its grammar (see
1462
+ * {@link verifyFastPath}).
1463
+ *
1464
+ * @since 0.5.0
1465
+ * @category fast paths
1466
+ */
1467
+ export interface FastPathMismatch<A, I extends Input> {
1468
+ readonly input: I
1469
+ readonly offset: number
1470
+ readonly partial: boolean
1471
+ /** What the fast path returned, and where it stopped. */
1472
+ readonly fast: { readonly value: A; readonly end: number }
1473
+ /** What the grammar returned, and where it stopped, or how it failed. */
1474
+ readonly grammar: { readonly value: A; readonly end: number } | { readonly failed: ErrMode }
1475
+ }
1476
+
1477
+ /**
1478
+ * Checks a fast path against its grammar (see {@link withFastPath}): on
1479
+ * every input, at every offset, on complete and on partial input, wherever
1480
+ * `fast` returns a value, the grammar (run interpreted) must return an
1481
+ * equal value (by `Equal.equals`, or `options.equivalence`) and stop at the
1482
+ * same offset. Returns every place where it doesn't, so a test asserts that
1483
+ * the result is empty; generated inputs, mutated and truncated, find the
1484
+ * most. Grammars that use services are not supported.
1485
+ *
1486
+ * @example
1487
+ * ```ts
1488
+ * import { Input, Parser, Token } from "@thomasfosterau/effect-parse"
1489
+ *
1490
+ * const text = Token.takeTill("<", { min: 1 })
1491
+ * const fast = (state: Input.State<string>) => {
1492
+ * const end = state.input.indexOf("<", state.pos)
1493
+ * if (end <= state.pos || end > state.end) return Parser.Defer
1494
+ * const value = state.input.slice(state.pos, end)
1495
+ * state.pos = end
1496
+ * return value
1497
+ * }
1498
+ * Parser.verifyFastPath(text, fast, ["a<b", "<", "abc"]) // []
1499
+ * ```
1500
+ *
1501
+ * @since 0.5.0
1502
+ * @category fast paths
1503
+ */
1504
+ export const verifyFastPath = <A, E, I extends Input>(
1505
+ grammar: Parser<A, E, never, I>,
1506
+ fast: (state: State<I>) => A | Defer,
1507
+ inputs: Iterable<I>,
1508
+ options?: { readonly equivalence?: ((a: A, b: A) => boolean) | undefined }
1509
+ ): ReadonlyArray<FastPathMismatch<A, I>> => {
1510
+ const equivalence = options?.equivalence ?? Equal.equals
1511
+ const mismatches: Array<FastPathMismatch<A, I>> = []
1512
+ for (const input of inputs) {
1513
+ const length = new State(input).end
1514
+ for (let offset = 0; offset <= length; offset++) {
1515
+ for (const partial of [false, true]) {
1516
+ const mismatch = checkFastPath(grammar, fast, input, offset, partial, equivalence)
1517
+ if (mismatch !== undefined) mismatches.push(mismatch)
1518
+ }
1519
+ }
1520
+ }
1521
+ return mismatches
1522
+ }
1523
+
1524
+ /** Where `fast` disagrees with `grammar` at `offset`, if it does (see {@link verifyFastPath}). */
1525
+ const checkFastPath = <A, E, I extends Input>(
1526
+ grammar: Parser<A, E, never, I>,
1527
+ fast: (state: State<I>) => A | Defer,
1528
+ input: I,
1529
+ offset: number,
1530
+ partial: boolean,
1531
+ equivalence: (a: A, b: A) => boolean
1532
+ ): FastPathMismatch<A, I> | undefined => {
1533
+ const state = new State(input, { offset, partial })
1534
+ const a = fast(state)
1535
+ if (a === Defer) return undefined
1536
+ const expected = new State(input, { offset, partial })
1537
+ const b = Stack.run(grammar.step, expected)
1538
+ if (b !== Fail && expected.pos === state.pos && equivalence(a, b)) return undefined
1539
+ const grammarOutcome = b === Fail ? { failed: expected.mode } : { value: b, end: expected.pos }
1540
+ return { input, offset, partial, fast: { value: a, end: state.pos }, grammar: grammarOutcome }
1541
+ }
1542
+
1117
1543
  /**
1118
1544
  * Transforms the user error (cause) of a parser's failures.
1119
1545
  *
@@ -1132,6 +1558,8 @@ export const mapError: {
1132
1558
  const a = step(state)
1133
1559
  if (a === Fail && state.failure.hasCause) {
1134
1560
  const x = state.failure
1561
+ // SAFETY: a failure with `hasCause` from `self` carries a cause raised
1562
+ // by a `fail` / `mapError` inside it, whose user error type is `E`
1135
1563
  state.replaceFailure(
1136
1564
  new Failure(x.offset, x.kind, x.expected, x.context, true, f(x.cause as E), x.message, x.needed)
1137
1565
  )
@@ -1376,20 +1804,53 @@ export const seq = <const Ps extends ReadonlyArray<Parser.Any>>(
1376
1804
  Parser.Services<Ps[number]>,
1377
1805
  Parser.InputOf<Ps[number]>
1378
1806
  > => {
1807
+ type Out = { -readonly [K in keyof Ps]: Parser.Success<Ps[K]> }
1808
+ // SAFETY: the tuple has one slot per parser, and slot `i` holds the
1809
+ // output of `parsers[i]`, which is `Parser.Success<Ps[i]>`
1810
+ const step = tupleStep(parsers.map((p) => p.step)) as (state: State<any>) => Out | Fail
1811
+ return described(step, "seq", { _tag: "Seq", parsers, output: "tuple" })
1812
+ }
1813
+
1814
+ /**
1815
+ * Runs parsers in sequence and combines their outputs with `f`, which
1816
+ * receives one argument per parser: the allocation-free spelling of
1817
+ * `map(seq(...parsers), ([a, b, ...]) => f(a, b, ...))`, which builds a
1818
+ * tuple on every success. Compiled code calls `f` with the values directly.
1819
+ * {@link zipWith} is the two-parser, pipeable form.
1820
+ *
1821
+ * @example
1822
+ * ```ts
1823
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
1824
+ *
1825
+ * const point = Parser.seqWith(
1826
+ * [Ascii.decInt, Token.literal(","), Ascii.decInt],
1827
+ * (x, _, y) => ({ x, y })
1828
+ * )
1829
+ * Parser.parse(point, "3,-4") // Result.succeed({ x: 3, y: -4 })
1830
+ * ```
1831
+ *
1832
+ * @since 0.5.0
1833
+ * @category sequencing
1834
+ */
1835
+ export const seqWith = <const Ps extends ReadonlyArray<Parser.Any>, B>(
1836
+ parsers: Ps,
1837
+ f: (...values: { -readonly [K in keyof Ps]: Parser.Success<Ps[K]> }) => B
1838
+ ): Parser<B, Parser.Error<Ps[number]>, Parser.Services<Ps[number]>, Parser.InputOf<Ps[number]>> => {
1379
1839
  const steps = parsers.map((p) => p.step)
1380
1840
  const n = steps.length
1841
+ const g: (...values: Array<any>) => B = f
1381
1842
  return described(
1382
1843
  (state: State<any>) => {
1383
- const out = new Array(n)
1844
+ const values = new Array<unknown>(n)
1384
1845
  for (let i = 0; i < n; i++) {
1385
1846
  const a = steps[i](state)
1386
1847
  if (a === Fail) return Fail
1387
- out[i] = a
1848
+ values[i] = a
1388
1849
  }
1389
- return out as any
1850
+ return g(...values)
1390
1851
  },
1391
- "seq",
1392
- { _tag: "Seq", parsers, output: "tuple" }
1852
+ "seqWith",
1853
+ { _tag: "SeqWith", parsers, f }
1393
1854
  )
1394
1855
  }
1395
1856
 
@@ -1409,18 +1870,21 @@ export const struct = <const Fields extends Readonly<Record<string, Parser.Any>>
1409
1870
  Parser.Services<Fields[keyof Fields]>,
1410
1871
  Parser.InputOf<Fields[keyof Fields]>
1411
1872
  > => {
1412
- const keys = Object.keys(fields)
1873
+ type Out = Simplify<{ -readonly [K in keyof Fields]: Parser.Success<Fields[K]> }>
1874
+ const keys = Rec.keys(fields)
1413
1875
  const steps = keys.map((k) => fields[k].step)
1414
1876
  const n = keys.length
1415
1877
  return described(
1416
1878
  (state: State<any>) => {
1417
- const out: Record<string, unknown> = {}
1879
+ const out: Record<string, Parser.Success<Fields[keyof Fields]>> = {}
1418
1880
  for (let i = 0; i < n; i++) {
1419
1881
  const a = steps[i](state)
1420
1882
  if (a === Fail) return Fail
1421
1883
  out[keys[i]] = a
1422
1884
  }
1423
- return out as any
1885
+ // SAFETY: the loop has set every own key of `fields`, each to the
1886
+ // output of that key's parser, `Parser.Success<Fields[K]>`
1887
+ return out as Out
1424
1888
  },
1425
1889
  "struct",
1426
1890
  { _tag: "Struct", keys, parsers: keys.map((k) => fields[k]) }
@@ -1676,9 +2140,14 @@ export const gen = <Eff extends Parser.Any, AEff>(
1676
2140
  while (!next.done) {
1677
2141
  const a = next.value.step(state)
1678
2142
  if (a === Fail) {
1679
- iterator.return?.(undefined as any)
2143
+ // SAFETY: the value passed to `return` only becomes the completion value
2144
+ // of the abandoned generator, which nothing reads
2145
+ iterator.return?.(undefined as AEff)
1680
2146
  return Fail
1681
2147
  }
2148
+ // SAFETY: `a` is the output of the parser just yielded, which is what its
2149
+ // `yield*` evaluates to (typed by `ParserIterator`'s return value, not by
2150
+ // the generator's `never` next type)
1682
2151
  next = iterator.next(a as never)
1683
2152
  }
1684
2153
  return next.value
@@ -1786,7 +2255,7 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
1786
2255
  > => {
1787
2256
  const steps = parsers.map((p) => p.step)
1788
2257
  const n = steps.length
1789
- if (n === 0) return reject as any
2258
+ if (n === 0) return reject
1790
2259
  if (n === 1) return parsers[0]
1791
2260
  return described(
1792
2261
  (state: State<any>) => {
@@ -1839,6 +2308,36 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
1839
2308
  )
1840
2309
  }
1841
2310
 
2311
+ /**
2312
+ * The overloads of {@link dispatch}.
2313
+ *
2314
+ * @since 0.5.0
2315
+ * @category choice
2316
+ */
2317
+ export interface DispatchFn {
2318
+ <K, E, R, I extends Input, P extends Parser.Any>(
2319
+ selector: Parser<K, E, R, I>,
2320
+ cases: (key: K) => P | undefined
2321
+ ): Parser<Parser.Success<P>, E | Parser.Error<P>, R | Parser.Services<P>, I & Parser.InputOf<P>>
2322
+ <
2323
+ K extends PropertyKey,
2324
+ E,
2325
+ R,
2326
+ I extends Input,
2327
+ const Cases extends { readonly [key in K]?: Parser.Any },
2328
+ Otherwise extends Parser.Any = never
2329
+ >(
2330
+ selector: Parser<K, E, R, I>,
2331
+ cases: Cases,
2332
+ otherwise?: Otherwise
2333
+ ): Parser<
2334
+ Parser.Success<NonNullable<Cases[keyof Cases]> | Otherwise>,
2335
+ E | Parser.Error<NonNullable<Cases[keyof Cases]> | Otherwise>,
2336
+ R | Parser.Services<NonNullable<Cases[keyof Cases]> | Otherwise>,
2337
+ I & Parser.InputOf<NonNullable<Cases[keyof Cases]> | Otherwise>
2338
+ >
2339
+ }
2340
+
1842
2341
  /**
1843
2342
  * Selects the parser to run next based on the output of `selector`, like a
1844
2343
  * `match` over the input (winnow's `dispatch!`). If no case matches, the
@@ -1873,29 +2372,7 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
1873
2372
  * @since 0.1.0
1874
2373
  * @category choice
1875
2374
  */
1876
- export const dispatch: {
1877
- <K, E, R, I extends Input, P extends Parser.Any>(
1878
- selector: Parser<K, E, R, I>,
1879
- cases: (key: K) => P | undefined
1880
- ): Parser<Parser.Success<P>, E | Parser.Error<P>, R | Parser.Services<P>, I & Parser.InputOf<P>>
1881
- <
1882
- K extends PropertyKey,
1883
- E,
1884
- R,
1885
- I extends Input,
1886
- const Cases extends { readonly [key in K]?: Parser.Any },
1887
- Otherwise extends Parser.Any = never
1888
- >(
1889
- selector: Parser<K, E, R, I>,
1890
- cases: Cases,
1891
- otherwise?: Otherwise
1892
- ): Parser<
1893
- Parser.Success<NonNullable<Cases[keyof Cases]> | Otherwise>,
1894
- E | Parser.Error<NonNullable<Cases[keyof Cases]> | Otherwise>,
1895
- R | Parser.Services<NonNullable<Cases[keyof Cases]> | Otherwise>,
1896
- I & Parser.InputOf<NonNullable<Cases[keyof Cases]> | Otherwise>
1897
- >
1898
- } = (
2375
+ export const dispatch: DispatchFn = (
1899
2376
  selector: Parser.Any,
1900
2377
  cases: ((key: any) => Parser.Any | undefined) | Record<PropertyKey, Parser.Any>,
1901
2378
  otherwise?: Parser.Any
@@ -1943,37 +2420,44 @@ export const permutation = <const Ps extends ReadonlyArray<Parser.Any>>(
1943
2420
  Parser.Services<Ps[number]>,
1944
2421
  Parser.InputOf<Ps[number]>
1945
2422
  > => {
1946
- const steps = parsers.map((p) => p.step)
1947
- const n = steps.length
1948
- return make((state: State<any>) => {
1949
- // A round's failures, merged only if no parser matches in it (#44)
1950
- const ties = new TiedFailures()
1951
- const done = new Array<boolean>(n).fill(false)
1952
- const out = new Array(n)
1953
- let remaining = n
1954
- while (remaining > 0) {
1955
- ties.clear()
1956
- let matched = false
1957
- for (let i = 0; i < n; i++) {
1958
- if (done[i]) continue
1959
- const start = state.pos
1960
- const a = steps[i](state)
1961
- if (a !== Fail) {
1962
- out[i] = a
1963
- done[i] = true
1964
- remaining--
1965
- matched = true
1966
- break
2423
+ type Out = { -readonly [K in keyof Ps]: Parser.Success<Ps[K]> }
2424
+ const n = parsers.length
2425
+ // The parsers inside compile: the step is built from theirs (see `combinator`)
2426
+ return combinator(
2427
+ parsers,
2428
+ (steps) => (state: State<any>) => {
2429
+ // A round's failures, merged only if no parser matches in it (#44)
2430
+ const ties = new TiedFailures()
2431
+ const done = new Array<boolean>(n).fill(false)
2432
+ const out = new Array(n)
2433
+ let remaining = n
2434
+ while (remaining > 0) {
2435
+ ties.clear()
2436
+ let matched = false
2437
+ for (let i = 0; i < n; i++) {
2438
+ if (done[i]) continue
2439
+ const start = state.pos
2440
+ const a = steps[i](state)
2441
+ if (a !== Fail) {
2442
+ out[i] = a
2443
+ done[i] = true
2444
+ remaining--
2445
+ matched = true
2446
+ break
2447
+ }
2448
+ if (state.mode !== "Backtrack") return Fail
2449
+ state.rewind(start)
2450
+ ties.add(state)
1967
2451
  }
1968
- if (state.mode !== "Backtrack") return Fail
1969
- state.rewind(start)
1970
- ties.add(state)
2452
+ // Some parser is not done yet, and each one that fails is folded in
2453
+ if (!matched) return ties.fail(state)
1971
2454
  }
1972
- // Some parser is not done yet, and each one that fails is folded in
1973
- if (!matched) return ties.fail(state)
1974
- }
1975
- return out as any
1976
- }, "permutation")
2455
+ // SAFETY: every parser has matched once (`remaining` is 0), and slot
2456
+ // `i` holds the output of `parsers[i]`, which is `Parser.Success<Ps[i]>`
2457
+ return out as Out
2458
+ },
2459
+ "permutation"
2460
+ )
1977
2461
  }
1978
2462
 
1979
2463
  // -----------------------------------------------------------------------------
@@ -1981,11 +2465,7 @@ export const permutation = <const Ps extends ReadonlyArray<Parser.Any>>(
1981
2465
  // -----------------------------------------------------------------------------
1982
2466
 
1983
2467
  const normalizeRange = (range: Range | undefined): [min: number, max: number] => {
1984
- if (range === undefined) return [0, Infinity]
1985
- if (Predicate.isNumber(range)) return [range, range]
1986
- const min = range.min ?? 0
1987
- const max = range.max ?? Infinity
1988
- if (min > max) throw new RangeError(`invalid range: min (${min}) > max (${max})`)
2468
+ const { max, min } = bounds(range)
1989
2469
  return [min, max]
1990
2470
  }
1991
2471
 
@@ -2014,16 +2494,20 @@ const foldImpl = <A, B, E, R, I extends Input>(
2014
2494
  ): Parser<B, E, R, I> => {
2015
2495
  const [min, max] = normalizeRange(range)
2016
2496
  const step = self.step
2017
- const checkLoop = max === Infinity
2497
+ // Bounded or not, the repetition checks progress below (#115)
2018
2498
  const key = new Key()
2019
2499
  return described(
2020
2500
  (state) => {
2021
2501
  const entry = state.pos
2022
2502
  let acc: B
2023
2503
  let count = 0
2504
+ // SAFETY: `key` belongs to this parser, which saves only
2505
+ // `FoldCheckpoint<B>`s under it (and a `Resume.Done`, narrowed below)
2024
2506
  const resumed = state.resume === undefined ? undefined : (Resume.take(state, key, entry) as FoldCheckpoint<B>)
2025
2507
  if (resumed instanceof Resume.Done) {
2026
2508
  state.pos = resumed.pos
2509
+ // SAFETY: a `Done` under `key` is recorded by `Resume.done` below,
2510
+ // with this fold's result, a `B`
2027
2511
  return resumed.value as B
2028
2512
  }
2029
2513
  if (resumed !== undefined) {
@@ -2045,7 +2529,7 @@ const foldImpl = <A, B, E, R, I extends Input>(
2045
2529
  recover(state, start)
2046
2530
  return Resume.done(state, key, entry, resumed !== undefined, acc)
2047
2531
  }
2048
- if (checkLoop && state.pos === start) return assertConsumes(state, name)
2532
+ if (state.pos === start) return assertConsumes(state, name)
2049
2533
  acc = f(acc, a)
2050
2534
  }
2051
2535
  return Resume.done(state, key, entry, resumed !== undefined, acc)
@@ -2063,9 +2547,9 @@ const foldImpl = <A, B, E, R, I extends Input>(
2063
2547
  * exactly that many times. With a literal positive `min`, the output is
2064
2548
  * typed as a non-empty array.
2065
2549
  *
2066
- * As in winnow, an unbounded repetition of a parser that succeeds without
2067
- * consuming input fails with an unrecoverable assertion instead of looping
2068
- * forever.
2550
+ * A parser that succeeds without consuming input fails the repetition with
2551
+ * an unrecoverable `Assert` failure, whatever the range (winnow checks only
2552
+ * unbounded ones): a bound can come from the input (`Binary.lengthRepeat`).
2069
2553
  *
2070
2554
  * @example
2071
2555
  * ```ts
@@ -2256,16 +2740,20 @@ export const separated: {
2256
2740
  const [min, max] = normalizeRange(range)
2257
2741
  const step = self.step
2258
2742
  const sepStep = separator.step
2259
- const checkLoop = max === Infinity
2743
+ // Bounded or not, the repetition checks progress below (#115)
2260
2744
  const key = new Key()
2261
2745
  return described(
2262
2746
  (state) => {
2263
2747
  const entry = state.pos
2264
2748
  let acc: Array<A>
2749
+ // SAFETY: `key` belongs to this parser, which saves only
2750
+ // `SeparatedCheckpoint<A>`s under it (and a `Resume.Done`, narrowed below)
2265
2751
  const resumed =
2266
2752
  state.resume === undefined ? undefined : (Resume.take(state, key, entry) as SeparatedCheckpoint<A>)
2267
2753
  if (resumed instanceof Resume.Done) {
2268
2754
  state.pos = resumed.pos
2755
+ // SAFETY: a `Done` under `key` is recorded by `Resume.done` below,
2756
+ // with the elements collected, an `Array<A>`
2269
2757
  return resumed.value as Array<A>
2270
2758
  }
2271
2759
  if (resumed !== undefined) {
@@ -2293,7 +2781,7 @@ export const separated: {
2293
2781
  recover(state, start)
2294
2782
  return Resume.done(state, key, entry, resumed !== undefined, acc)
2295
2783
  }
2296
- if (checkLoop && state.pos === start) return assertConsumes(state, "separated")
2784
+ if (state.pos === start) return assertConsumes(state, "separated")
2297
2785
  const a = step(state)
2298
2786
  if (a === Fail) {
2299
2787
  if (state.mode === "Incomplete" && state.resume !== undefined) {
@@ -2345,10 +2833,15 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2345
2833
  return described(
2346
2834
  (state) => {
2347
2835
  const entry = state.pos
2836
+ // SAFETY: `key` belongs to this parser, which saves only
2837
+ // `FoldCheckpoint<A>`s under it, through `saveFold` (and a
2838
+ // `Resume.Done`, narrowed below)
2348
2839
  const resumed = state.resume === undefined ? undefined : (Resume.take(state, key, entry) as FoldCheckpoint<A>)
2349
2840
  let acc: A | Fail
2350
2841
  if (resumed instanceof Resume.Done) {
2351
2842
  state.pos = resumed.pos
2843
+ // SAFETY: a `Done` under `key` is recorded by `Resume.done` below,
2844
+ // with the folded value, an `A`
2352
2845
  return resumed.value as A
2353
2846
  }
2354
2847
  if (resumed !== undefined) {
@@ -2365,7 +2858,7 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2365
2858
  if (state.mode === "Incomplete" && state.resume !== undefined) saveFold(state, key, entry, acc, start)
2366
2859
  if (state.mode !== "Backtrack") return Fail
2367
2860
  recover(state, start)
2368
- return Resume.done(state, key, entry, resumed !== undefined, acc as A)
2861
+ return Resume.done(state, key, entry, resumed !== undefined, acc)
2369
2862
  }
2370
2863
  if (state.pos === start) return assertConsumes(state, "separatedFoldl1")
2371
2864
  const a = step(state)
@@ -2373,7 +2866,7 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2373
2866
  if (state.mode === "Incomplete" && state.resume !== undefined) saveFold(state, key, entry, acc, start)
2374
2867
  if (state.mode !== "Backtrack") return Fail
2375
2868
  state.rewind(start)
2376
- return Resume.done(state, key, entry, resumed !== undefined, acc as A)
2869
+ return Resume.done(state, key, entry, resumed !== undefined, acc)
2377
2870
  }
2378
2871
  acc = op(acc, s, a)
2379
2872
  }
@@ -2423,9 +2916,9 @@ export const separatedFoldr1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2423
2916
  seps.push(s)
2424
2917
  values.push(a)
2425
2918
  }
2426
- let acc = values[values.length - 1] as A
2919
+ let acc = values[values.length - 1]
2427
2920
  for (let i = seps.length - 1; i >= 0; i--) {
2428
- acc = op(values[i] as A, seps[i] as S, acc)
2921
+ acc = op(values[i], seps[i], acc)
2429
2922
  }
2430
2923
  return acc
2431
2924
  },
@@ -2501,7 +2994,7 @@ export const provideService: {
2501
2994
  return described(
2502
2995
  (state) => {
2503
2996
  const prev = state.context
2504
- state.context = Context.add(prev, key, service) as Context.Context<never>
2997
+ state.context = Context.add(prev, key, service)
2505
2998
  try {
2506
2999
  return step(state)
2507
3000
  } finally {
@@ -2538,7 +3031,7 @@ export const provide: {
2538
3031
  return described(
2539
3032
  (state) => {
2540
3033
  const prev = state.context
2541
- state.context = Context.merge(prev, context) as Context.Context<never>
3034
+ state.context = Context.merge(prev, context)
2542
3035
  try {
2543
3036
  return step(state)
2544
3037
  } finally {
@@ -2734,18 +3227,20 @@ const NotCompiled: unique symbol = /*#__PURE__*/ Symbol("NotCompiled")
2734
3227
  * input), in which case the state must be thrown away. `resumable` selects
2735
3228
  * the build that handles partial input.
2736
3229
  */
2737
- const runCompiled = <I extends Input>(
2738
- self: Parser.Any,
3230
+ const runCompiled = <A, I extends Input>(
3231
+ self: Parser<A, unknown, unknown, I>,
2739
3232
  state: State<I>,
2740
3233
  whole: boolean,
2741
3234
  resumable = false
2742
- ): unknown => {
3235
+ ): A | Fail | typeof NotCompiled => {
2743
3236
  if (state.tracer !== undefined || state.recovered !== undefined) return NotCompiled
2744
- const step = Registry.stepFor(self, state, resumable)
2745
- if (step === undefined) return NotCompiled
2746
- const a = Stack.run(step, state)
3237
+ const compiled = Registry.compiled(self, state.inputKind, resumable)
3238
+ if (compiled?.step === undefined) return NotCompiled
3239
+ const a = Run.runCompiled(compiled.step, compiled.guarded, state)
2747
3240
  if (a === Fail || (whole && state.pos < state.end)) return Fail
2748
- return a
3241
+ // SAFETY: the compilers never change semantics (the differential suites
3242
+ // check it), so `self`'s compiled step returns what its step returns, an `A`
3243
+ return a as A
2749
3244
  }
2750
3245
 
2751
3246
  // -----------------------------------------------------------------------------
@@ -2816,7 +3311,7 @@ const parseComplete = <A, E, R, I extends Input>(
2816
3311
  const state = makeState(input, options, false)
2817
3312
  const a = runCompiled(self, state, true)
2818
3313
  if (a === NotCompiled) return runComplete(self, state)
2819
- if (a !== Fail) return Result.succeed(a as A)
3314
+ if (a !== Fail) return Result.succeed(a)
2820
3315
  // Re-run interpreted for the diagnostics
2821
3316
  return runComplete(self, makeState(input, options, false))
2822
3317
  }
@@ -2834,6 +3329,25 @@ const runComplete = <A, E, R, I extends Input>(
2834
3329
  return Result.succeed(a)
2835
3330
  }
2836
3331
 
3332
+ /**
3333
+ * The overloads of {@link parse}.
3334
+ *
3335
+ * @since 0.5.0
3336
+ * @category running
3337
+ */
3338
+ export interface ParseFn {
3339
+ <A, E, I extends Input>(
3340
+ self: Parser<A, E, never, I>,
3341
+ input: NoInfer<Source<I>>,
3342
+ options?: ParseOptions
3343
+ ): Result.Result<A, ParseError.ParseError<E>>
3344
+ <A, E, R, I extends Input>(
3345
+ self: Parser<A, E, R, I>,
3346
+ input: NoInfer<Source<I>>,
3347
+ options: ParseOptionsWithContext<R>
3348
+ ): Result.Result<A, ParseError.ParseError<E>>
3349
+ }
3350
+
2837
3351
  /**
2838
3352
  * Runs a parser against a complete input, requiring the whole input to be
2839
3353
  * consumed (winnow's `Parser::parse`).
@@ -2850,37 +3364,34 @@ const runComplete = <A, E, R, I extends Input>(
2850
3364
  * @since 0.1.0
2851
3365
  * @category running
2852
3366
  */
2853
- export const parse: {
2854
- <A, E, I extends Input>(
2855
- self: Parser<A, E, never, I>,
2856
- input: NoInfer<Source<I>>,
2857
- options?: ParseOptions
2858
- ): Result.Result<A, ParseError.ParseError<E>>
2859
- <A, E, R, I extends Input>(
2860
- self: Parser<A, E, R, I>,
2861
- input: NoInfer<Source<I>>,
2862
- options: ParseOptionsWithContext<R>
2863
- ): Result.Result<A, ParseError.ParseError<E>>
2864
- } = <A, E, R, I extends Input>(
3367
+ export const parse: ParseFn = <A, E, R, I extends Input>(
2865
3368
  self: Parser<A, E, R, I>,
2866
3369
  input: I | Source<I>,
2867
3370
  options?: ParseOptions & { readonly context?: Context.Context<R> }
2868
3371
  ): Result.Result<A, ParseError.ParseError<E>> => parseComplete(self, input, options)
2869
3372
 
2870
3373
  /**
2871
- * Like {@link parse}, but throws the `ParseError` on failure.
3374
+ * The overloads of {@link parseSync}.
2872
3375
  *
2873
- * @since 0.1.0
3376
+ * @since 0.5.0
2874
3377
  * @category running
2875
3378
  */
2876
- export const parseSync: {
3379
+ export interface ParseSyncFn {
2877
3380
  <A, E, I extends Input>(self: Parser<A, E, never, I>, input: NoInfer<Source<I>>, options?: ParseOptions): A
2878
3381
  <A, E, R, I extends Input>(
2879
3382
  self: Parser<A, E, R, I>,
2880
3383
  input: NoInfer<Source<I>>,
2881
3384
  options: ParseOptionsWithContext<R>
2882
3385
  ): A
2883
- } = <A, E, R, I extends Input>(
3386
+ }
3387
+
3388
+ /**
3389
+ * Like {@link parse}, but throws the `ParseError` on failure.
3390
+ *
3391
+ * @since 0.1.0
3392
+ * @category running
3393
+ */
3394
+ export const parseSync: ParseSyncFn = <A, E, R, I extends Input>(
2884
3395
  self: Parser<A, E, R, I>,
2885
3396
  input: I | Source<I>,
2886
3397
  options?: ParseOptions & { readonly context?: Context.Context<R> }
@@ -2891,18 +3402,12 @@ export const parseSync: {
2891
3402
  }
2892
3403
 
2893
3404
  /**
2894
- * Runs a parser against the start of an input, returning the remaining
2895
- * input along with the output, nom-style (winnow's `parse_peek`, nom's
2896
- * `IResult`).
3405
+ * The overloads of {@link parsePeek}.
2897
3406
  *
2898
- * The parser ended at offset `input.length - remaining.length`, which is
2899
- * how a caller running from an `offset` (see {@link ParseOptions}) finds
2900
- * where to continue.
2901
- *
2902
- * @since 0.1.0
3407
+ * @since 0.5.0
2903
3408
  * @category running
2904
3409
  */
2905
- export const parsePeek: {
3410
+ export interface ParsePeekFn {
2906
3411
  <A, E, I extends Input>(
2907
3412
  self: Parser<A, E, never, I>,
2908
3413
  input: NoInfer<Source<I>>,
@@ -2913,7 +3418,21 @@ export const parsePeek: {
2913
3418
  input: NoInfer<Source<I>>,
2914
3419
  options: ParseOptionsWithContext<R>
2915
3420
  ): Result.Result<[remaining: Slice<I>, output: A], ParseError.ParseError<E>>
2916
- } = <A, E, R, I extends Input>(
3421
+ }
3422
+
3423
+ /**
3424
+ * Runs a parser against the start of an input, returning the remaining
3425
+ * input along with the output, nom-style (winnow's `parse_peek`, nom's
3426
+ * `IResult`).
3427
+ *
3428
+ * The parser ended at offset `input.length - remaining.length`, which is
3429
+ * how a caller running from an `offset` (see {@link ParseOptions}) finds
3430
+ * where to continue.
3431
+ *
3432
+ * @since 0.1.0
3433
+ * @category running
3434
+ */
3435
+ export const parsePeek: ParsePeekFn = <A, E, R, I extends Input>(
2917
3436
  self: Parser<A, E, R, I>,
2918
3437
  input: I | Source<I>,
2919
3438
  options?: ParseOptions & { readonly context?: Context.Context<R> }
@@ -2921,7 +3440,7 @@ export const parsePeek: {
2921
3440
  let state = makeState(input, options, false)
2922
3441
  const compiled = runCompiled(self, state, false)
2923
3442
  if (compiled !== NotCompiled && compiled !== Fail) {
2924
- return Result.succeed([state.slice(state.pos, state.end), compiled as A])
3443
+ return Result.succeed([state.slice(state.pos, state.end), compiled])
2925
3444
  }
2926
3445
  if (compiled === Fail) state = makeState(input, options, false)
2927
3446
  const a = Stack.run(self.step, state)
@@ -2942,6 +3461,25 @@ export type PartialResult<A> =
2942
3461
  | { readonly _tag: "Done"; readonly value: A; readonly offset: number }
2943
3462
  | { readonly _tag: "Incomplete"; readonly needed: Option.Option<number> }
2944
3463
 
3464
+ /**
3465
+ * The overloads of {@link parsePartial}.
3466
+ *
3467
+ * @since 0.5.0
3468
+ * @category running
3469
+ */
3470
+ export interface ParsePartialFn {
3471
+ <A, E, I extends Input>(
3472
+ self: Parser<A, E, never, I>,
3473
+ input: NoInfer<Source<I>>,
3474
+ options?: ParseOptions
3475
+ ): Result.Result<PartialResult<A>, ParseError.ParseError<E>>
3476
+ <A, E, R, I extends Input>(
3477
+ self: Parser<A, E, R, I>,
3478
+ input: NoInfer<Source<I>>,
3479
+ options: ParseOptionsWithContext<R>
3480
+ ): Result.Result<PartialResult<A>, ParseError.ParseError<E>>
3481
+ }
3482
+
2945
3483
  /**
2946
3484
  * Runs a parser against a *partial* input, i.e. a chunk of a stream that may
2947
3485
  * be followed by more data (winnow's `Partial`, nom's `streaming` parsers).
@@ -2971,18 +3509,7 @@ export type PartialResult<A> =
2971
3509
  * @since 0.1.0
2972
3510
  * @category running
2973
3511
  */
2974
- export const parsePartial: {
2975
- <A, E, I extends Input>(
2976
- self: Parser<A, E, never, I>,
2977
- input: NoInfer<Source<I>>,
2978
- options?: ParseOptions
2979
- ): Result.Result<PartialResult<A>, ParseError.ParseError<E>>
2980
- <A, E, R, I extends Input>(
2981
- self: Parser<A, E, R, I>,
2982
- input: NoInfer<Source<I>>,
2983
- options: ParseOptionsWithContext<R>
2984
- ): Result.Result<PartialResult<A>, ParseError.ParseError<E>>
2985
- } = <A, E, R, I extends Input>(
3512
+ export const parsePartial: ParsePartialFn = <A, E, R, I extends Input>(
2986
3513
  self: Parser<A, E, R, I>,
2987
3514
  input: I | Source<I>,
2988
3515
  options?: ParseOptions & { readonly context?: Context.Context<R> }
@@ -2990,19 +3517,25 @@ export const parsePartial: {
2990
3517
  let state = makeState(input, options, true)
2991
3518
  const compiled = runCompiled(self, state, false, true)
2992
3519
  if (compiled !== NotCompiled && compiled !== Fail) {
2993
- return Result.succeed({ _tag: "Done", value: compiled as A, offset: state.pos })
3520
+ return Result.succeed({ _tag: "Done", value: compiled, offset: state.pos })
2994
3521
  }
3522
+ // Compiled code reports `Incomplete` where the interpreter does, with the same failure: the leaf that
3523
+ // ran out of input is the interpreter's own step, or records what it would. Only an error needs the re-run.
3524
+ if (compiled === Fail && state.mode === "Incomplete") return Result.succeed(incompleteOf(state))
2995
3525
  // Re-run interpreted for `needed` and the diagnostics
2996
3526
  if (compiled === Fail) state = makeState(input, options, true)
2997
3527
  const a = Stack.run(self.step, state)
2998
3528
  if (a !== Fail) return Result.succeed({ _tag: "Done", value: a, offset: state.pos })
2999
- if (state.mode === "Incomplete") {
3000
- const needed = state.failure.needed
3001
- return Result.succeed({ _tag: "Incomplete", needed: needed > 0 ? Option.some(needed) : Option.none() })
3002
- }
3529
+ if (state.mode === "Incomplete") return Result.succeed(incompleteOf(state))
3003
3530
  return Result.fail(toError(state))
3004
3531
  }
3005
3532
 
3533
+ /** The `Incomplete` result of a state whose parse ran out of partial input. */
3534
+ const incompleteOf = <A>(state: State<any>): PartialResult<A> => {
3535
+ const needed = state.failure.needed
3536
+ return { _tag: "Incomplete", needed: needed > 0 ? Option.some(needed) : Option.none() }
3537
+ }
3538
+
3006
3539
  /**
3007
3540
  * Runs a parser against a complete input as an `Effect`. Services required
3008
3541
  * by the parser are taken from the Effect's context.
@@ -3121,14 +3654,12 @@ export interface RecoverableResult<A, E> {
3121
3654
  }
3122
3655
 
3123
3656
  /**
3124
- * Runs a parser with error recovery enabled (winnow's `Recoverable`
3125
- * stream). Failures handled by {@link resumeAfter} and {@link retryAfter}
3126
- * are collected instead of aborting the parse.
3657
+ * The overloads of {@link parseRecoverable}.
3127
3658
  *
3128
- * @since 0.1.0
3659
+ * @since 0.5.0
3129
3660
  * @category running
3130
3661
  */
3131
- export const parseRecoverable: {
3662
+ export interface ParseRecoverableFn {
3132
3663
  <A, E, I extends Input>(
3133
3664
  self: Parser<A, E, never, I>,
3134
3665
  input: NoInfer<Source<I>>,
@@ -3139,7 +3670,17 @@ export const parseRecoverable: {
3139
3670
  input: NoInfer<Source<I>>,
3140
3671
  options: ParseOptionsWithContext<R>
3141
3672
  ): RecoverableResult<A, E>
3142
- } = <A, E, R, I extends Input>(
3673
+ }
3674
+
3675
+ /**
3676
+ * Runs a parser with error recovery enabled (winnow's `Recoverable`
3677
+ * stream). Failures handled by {@link resumeAfter} and {@link retryAfter}
3678
+ * are collected instead of aborting the parse.
3679
+ *
3680
+ * @since 0.1.0
3681
+ * @category running
3682
+ */
3683
+ export const parseRecoverable: ParseRecoverableFn = <A, E, R, I extends Input>(
3143
3684
  self: Parser<A, E, R, I>,
3144
3685
  input: I | Source<I>,
3145
3686
  options?: ParseOptions & { readonly context?: Context.Context<R> }