@thomasfosterau/effect-parse 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +40 -14
  2. package/dist/Ascii.d.ts +11 -4
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +7 -1
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts.map +1 -1
  7. package/dist/Binary.js +64 -35
  8. package/dist/Binary.js.map +1 -1
  9. package/dist/Bits.d.ts.map +1 -1
  10. package/dist/Bits.js +7 -4
  11. package/dist/Bits.js.map +1 -1
  12. package/dist/Compiler.d.ts +74 -2
  13. package/dist/Compiler.d.ts.map +1 -1
  14. package/dist/Compiler.js +78 -1
  15. package/dist/Compiler.js.map +1 -1
  16. package/dist/Expression.d.ts.map +1 -1
  17. package/dist/Expression.js +86 -74
  18. package/dist/Expression.js.map +1 -1
  19. package/dist/Input.d.ts +37 -4
  20. package/dist/Input.d.ts.map +1 -1
  21. package/dist/Input.js +42 -0
  22. package/dist/Input.js.map +1 -1
  23. package/dist/Nom.d.ts +5 -15
  24. package/dist/Nom.d.ts.map +1 -1
  25. package/dist/Nom.js.map +1 -1
  26. package/dist/ParseError.d.ts +2 -2
  27. package/dist/ParseError.d.ts.map +1 -1
  28. package/dist/ParseError.js +1 -1
  29. package/dist/ParseError.js.map +1 -1
  30. package/dist/Parser.d.ts +370 -28
  31. package/dist/Parser.d.ts.map +1 -1
  32. package/dist/Parser.js +390 -19
  33. package/dist/Parser.js.map +1 -1
  34. package/dist/ParserSchema.js +2 -2
  35. package/dist/ParserSchema.js.map +1 -1
  36. package/dist/ParserStream.d.ts +3 -2
  37. package/dist/ParserStream.d.ts.map +1 -1
  38. package/dist/ParserStream.js +15 -10
  39. package/dist/ParserStream.js.map +1 -1
  40. package/dist/Syntax.d.ts +23 -5
  41. package/dist/Syntax.d.ts.map +1 -1
  42. package/dist/Syntax.js.map +1 -1
  43. package/dist/Text.d.ts.map +1 -1
  44. package/dist/Text.js +9 -9
  45. package/dist/Text.js.map +1 -1
  46. package/dist/Token.d.ts +55 -10
  47. package/dist/Token.d.ts.map +1 -1
  48. package/dist/Token.js +59 -21
  49. package/dist/Token.js.map +1 -1
  50. package/dist/internal/compiler/closure.d.ts.map +1 -1
  51. package/dist/internal/compiler/closure.js +257 -97
  52. package/dist/internal/compiler/closure.js.map +1 -1
  53. package/dist/internal/compiler/entries.d.ts +3 -1
  54. package/dist/internal/compiler/entries.d.ts.map +1 -1
  55. package/dist/internal/compiler/entries.js +3 -2
  56. package/dist/internal/compiler/entries.js.map +1 -1
  57. package/dist/internal/compiler/first.d.ts.map +1 -1
  58. package/dist/internal/compiler/first.js +7 -1
  59. package/dist/internal/compiler/first.js.map +1 -1
  60. package/dist/internal/compiler/graph.d.ts +17 -0
  61. package/dist/internal/compiler/graph.d.ts.map +1 -0
  62. package/dist/internal/compiler/graph.js +56 -0
  63. package/dist/internal/compiler/graph.js.map +1 -0
  64. package/dist/internal/compiler/jit.d.ts +5 -0
  65. package/dist/internal/compiler/jit.d.ts.map +1 -1
  66. package/dist/internal/compiler/jit.js +185 -96
  67. package/dist/internal/compiler/jit.js.map +1 -1
  68. package/dist/internal/compiler/registry.d.ts +10 -2
  69. package/dist/internal/compiler/registry.d.ts.map +1 -1
  70. package/dist/internal/compiler/registry.js.map +1 -1
  71. package/dist/internal/compiler/search.d.ts +32 -0
  72. package/dist/internal/compiler/search.d.ts.map +1 -0
  73. package/dist/internal/compiler/search.js +68 -0
  74. package/dist/internal/compiler/search.js.map +1 -0
  75. package/dist/internal/compiler/sets.d.ts.map +1 -1
  76. package/dist/internal/compiler/sets.js +1 -1
  77. package/dist/internal/compiler/sets.js.map +1 -1
  78. package/dist/internal/compiler/wasm.d.ts +8 -0
  79. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  80. package/dist/internal/compiler/wasm.js +2769 -0
  81. package/dist/internal/compiler/wasm.js.map +1 -0
  82. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  83. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  84. package/dist/internal/compiler/wasmAsm.js +427 -0
  85. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  86. package/dist/internal/entries.d.ts +3 -1
  87. package/dist/internal/entries.d.ts.map +1 -1
  88. package/dist/internal/entries.js +1 -2
  89. package/dist/internal/entries.js.map +1 -1
  90. package/dist/internal/node.d.ts +58 -2
  91. package/dist/internal/node.d.ts.map +1 -1
  92. package/dist/internal/node.js +13 -3
  93. package/dist/internal/node.js.map +1 -1
  94. package/dist/internal/numbers.d.ts.map +1 -1
  95. package/dist/internal/numbers.js.map +1 -1
  96. package/dist/internal/run.d.ts +12 -0
  97. package/dist/internal/run.d.ts.map +1 -1
  98. package/dist/internal/run.js +51 -11
  99. package/dist/internal/run.js.map +1 -1
  100. package/dist/internal/stack.d.ts +6 -0
  101. package/dist/internal/stack.d.ts.map +1 -1
  102. package/dist/internal/stack.js +8 -2
  103. package/dist/internal/stack.js.map +1 -1
  104. package/dist/internal/syntax.d.ts +4 -4
  105. package/dist/internal/syntax.d.ts.map +1 -1
  106. package/dist/internal/syntax.js +18 -10
  107. package/dist/internal/syntax.js.map +1 -1
  108. package/dist/internal/text.d.ts +32 -0
  109. package/dist/internal/text.d.ts.map +1 -1
  110. package/dist/internal/text.js +68 -14
  111. package/dist/internal/text.js.map +1 -1
  112. package/dist/internal/tokens.d.ts +13 -0
  113. package/dist/internal/tokens.d.ts.map +1 -1
  114. package/dist/internal/tokens.js +59 -39
  115. package/dist/internal/tokens.js.map +1 -1
  116. package/package.json +5 -4
  117. package/src/Ascii.ts +42 -9
  118. package/src/Binary.ts +151 -71
  119. package/src/Bits.ts +12 -6
  120. package/src/Compiler.ts +101 -10
  121. package/src/Expression.ts +94 -81
  122. package/src/Input.ts +65 -6
  123. package/src/Nom.ts +15 -5
  124. package/src/ParseError.ts +9 -4
  125. package/src/Parser.ts +682 -129
  126. package/src/ParserSchema.ts +1 -1
  127. package/src/ParserStream.ts +40 -19
  128. package/src/Syntax.ts +60 -36
  129. package/src/Text.ts +16 -15
  130. package/src/Token.ts +138 -29
  131. package/src/internal/compiler/closure.ts +362 -135
  132. package/src/internal/compiler/entries.ts +4 -2
  133. package/src/internal/compiler/first.ts +10 -0
  134. package/src/internal/compiler/graph.ts +68 -0
  135. package/src/internal/compiler/jit.ts +284 -154
  136. package/src/internal/compiler/registry.ts +10 -2
  137. package/src/internal/compiler/search.ts +68 -0
  138. package/src/internal/compiler/sets.ts +7 -3
  139. package/src/internal/compiler/wasm.ts +3203 -0
  140. package/src/internal/compiler/wasmAsm.ts +484 -0
  141. package/src/internal/entries.ts +10 -4
  142. package/src/internal/node.ts +54 -4
  143. package/src/internal/numbers.ts +3 -1
  144. package/src/internal/run.ts +71 -12
  145. package/src/internal/stack.ts +8 -1
  146. package/src/internal/syntax.ts +39 -18
  147. package/src/internal/text.ts +97 -9
  148. package/src/internal/tokens.ts +96 -14
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"
@@ -51,7 +53,7 @@ import * as Run from "./internal/run.js"
51
53
  import * as Stack from "./internal/stack.js"
52
54
  import { TiedFailures } from "./internal/ties.js"
53
55
  import type { Node } from "./internal/node.js"
54
- import { bounds, NodeId } from "./internal/node.js"
56
+ import { bounds, Defer as DeferSymbol, describedNode, NodeId } from "./internal/node.js"
55
57
 
56
58
  // -----------------------------------------------------------------------------
57
59
  // Models
@@ -83,6 +85,8 @@ export interface Parser<out A, out E = never, out R = never, in I extends Input
83
85
  /** A human-readable name, used by `toString` and tracing. */
84
86
  readonly name: string | undefined
85
87
  [Symbol.iterator](): ParserIterator<Parser<A, E, R, I>>
88
+ /** Describes the parser: `Parser(<name>)`, or `Parser` when it has no name. */
89
+ toString(): string
86
90
  }
87
91
 
88
92
  /**
@@ -144,6 +148,15 @@ export declare namespace Parser {
144
148
  : [P] extends [Parser<infer _A, infer _E, infer _R, infer I>]
145
149
  ? I
146
150
  : never
151
+ /**
152
+ * The steps {@link combinator}'s `build` receives: one per parser, in
153
+ * order, each returning its parser's output or `Fail`.
154
+ *
155
+ * @since 0.5.0
156
+ */
157
+ export type Steps<Ps extends ReadonlyArray<Any>> = {
158
+ readonly [K in keyof Ps]: (state: State<InputOf<Ps[number]>>) => Success<Ps[K]> | Fail
159
+ }
147
160
  }
148
161
 
149
162
  /**
@@ -223,7 +236,7 @@ class SingleShotGen<T> {
223
236
  }
224
237
 
225
238
  class ParserImpl<A, E, R, I extends Input> implements Parser<A, E, R, I> {
226
- readonly [TypeId] = variance as Parser.Variance<A, E, R, I>
239
+ readonly [TypeId]: Parser.Variance<A, E, R, I> = variance
227
240
  /** The description used by the compiler; `undefined` for `Opaque`. */
228
241
  readonly [NodeId]: Node | undefined
229
242
  constructor(
@@ -237,7 +250,7 @@ class ParserImpl<A, E, R, I extends Input> implements Parser<A, E, R, I> {
237
250
  return pipeArguments(this, arguments)
238
251
  }
239
252
  [Symbol.iterator](): ParserIterator<Parser<A, E, R, I>> {
240
- return new SingleShotGen(this) as any
253
+ return new SingleShotGen(this)
241
254
  }
242
255
  toString() {
243
256
  return this.name === undefined ? "Parser" : `Parser(${this.name})`
@@ -313,7 +326,61 @@ export const described = <A, E = never, R = never, I extends Input = string>(
313
326
  node: Node
314
327
  ): Parser<A, E, R, I> => new ParserImpl(step, name, node)
315
328
 
316
- const nodeOfParser = (p: Parser.Any): Node | undefined => (p as any)[NodeId]
329
+ /**
330
+ * Creates a combinator over `parsers` that compiles: `build` makes the
331
+ * combinator's step from the parsers' steps. Write combinators with it
332
+ * rather than with {@link make} when they run other parsers: a `make`
333
+ * parser is opaque to the compilers, so in a compiled grammar it runs
334
+ * interpreted, and so does every parser it runs. Interpreted, `build`
335
+ * receives the parsers' own steps; compiled (see the `Compiler` module), it
336
+ * receives their compiled steps, so the parsers inside compile with the
337
+ * grammar.
338
+ *
339
+ * Each step runs its parser on the state it is given, as `Parser.run` does,
340
+ * and returns the parser's output or `Fail`. The steps must run on the
341
+ * combinator's own state (they are compiled for its input kind): a
342
+ * combinator that runs a parser on another input, as {@link mapParser}
343
+ * does, is written with `make`. `build` may be called more than once (once
344
+ * interpreted, and once for each compiled build), so state its step shares
345
+ * between calls belongs inside `build`.
346
+ *
347
+ * @example
348
+ * ```ts
349
+ * import { Ascii, Input, Parser, Token } from "@thomasfosterau/effect-parse"
350
+ *
351
+ * // One or more `item`s separated by `sep`, returning their count
352
+ * const count = <A, B>(item: Parser.Parser<A>, sep: Parser.Parser<B>) =>
353
+ * Parser.combinator([item, sep], ([itemStep, sepStep]) => (state) => {
354
+ * if (itemStep(state) === Input.Fail) return Input.Fail
355
+ * let n = 1
356
+ * while (true) {
357
+ * const start = state.pos
358
+ * if (sepStep(state) === Input.Fail || itemStep(state) === Input.Fail) {
359
+ * if (state.mode !== "Backtrack") return Input.Fail
360
+ * state.rewind(start)
361
+ * return n
362
+ * }
363
+ * n++
364
+ * }
365
+ * })
366
+ *
367
+ * Parser.parse(count(Ascii.digit1, Token.literal(",")), "1,22,333") // Result.succeed(3)
368
+ * ```
369
+ *
370
+ * @since 0.5.0
371
+ * @category constructors
372
+ */
373
+ export const combinator = <const Ps extends ReadonlyArray<Parser.Any>, A, E = never>(
374
+ parsers: Ps,
375
+ build: (steps: Parser.Steps<Ps>) => (state: State<Parser.InputOf<Ps[number]>>) => A | Fail,
376
+ name?: string
377
+ ): Parser<A, E | Parser.Error<Ps[number]>, Parser.Services<Ps[number]>, Parser.InputOf<Ps[number]>> => {
378
+ const untyped = (steps: ReadonlyArray<(state: State<any>) => unknown>) =>
379
+ // SAFETY: `steps` holds each parser's step (or, compiled, an equivalent
380
+ // compiled step), in order, which returns that parser's output or `Fail`
381
+ build(steps as Parser.Steps<Ps>)
382
+ return described(untyped(parsers.map((p) => p.step)), name, { _tag: "Combinator", parsers, build: untyped })
383
+ }
317
384
 
318
385
  /**
319
386
  * Gives a parser a name, used by `toString`.
@@ -327,7 +394,7 @@ export const named: {
327
394
  } = /*#__PURE__*/ dual(
328
395
  2,
329
396
  <A, E, R, I extends Input>(self: Parser<A, E, R, I>, name: string): Parser<A, E, R, I> =>
330
- new ParserImpl(self.step, name, nodeOfParser(self))
397
+ new ParserImpl(self.step, name, describedNode(self))
331
398
  )
332
399
 
333
400
  /**
@@ -388,6 +455,40 @@ export const fail = <E>(error: E): Parser<never, E, never, Input> =>
388
455
 
389
456
  const failBacktrack: Node = { _tag: "Fail", mode: "Backtrack" }
390
457
 
458
+ /**
459
+ * A parser that fails (backtracks) with the given user error, reporting it
460
+ * at `offset` rather than where the parser runs. Use it for a check that
461
+ * can only run once a whole construct has parsed but should point at the
462
+ * part at fault: return it from a {@link flatMap} continuation, with an
463
+ * offset taken from {@link location} or {@link withSpan}. Like any failure,
464
+ * one at a later offset is preferred over the failures of other
465
+ * alternatives (the furthest failure wins). The offset is clamped to the
466
+ * input.
467
+ *
468
+ * @example
469
+ * ```ts
470
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
471
+ *
472
+ * const tag = (open: string) => Parser.delimited(Token.literal(open), Ascii.alpha1, Token.literal(">"))
473
+ * const element = Parser.seq(tag("<"), Parser.location, tag("</")).pipe(
474
+ * Parser.flatMap(([start, at, end]) =>
475
+ * start === end ? Parser.succeed(start) : Parser.failAt(at, `expected </${start}>`)
476
+ * )
477
+ * )
478
+ * // The error points at the end tag (offset 3), not at the start of the element
479
+ * Parser.parse(element, "<a></b>")
480
+ * ```
481
+ *
482
+ * @since 0.5.0
483
+ * @category constructors
484
+ */
485
+ export const failAt = <E>(offset: number, error: E): Parser<never, E, never, Input> =>
486
+ described<never, E, never, Input>(
487
+ (state) => state.failCause("Backtrack", "Fail", error, offset < 0 ? 0 : offset > state.end ? state.end : offset),
488
+ "failAt",
489
+ failBacktrack
490
+ )
491
+
391
492
  /**
392
493
  * A parser that always fails (backtracks) without a cause. This is winnow's
393
494
  * `fail` and nom's `fail()`.
@@ -735,6 +836,72 @@ export const tryMap: {
735
836
  }
736
837
  )
737
838
 
839
+ /**
840
+ * Transforms the output of a parser with a function that returns
841
+ * `Input.Fail` to reject it: the allocation-free form of {@link tryMap} and
842
+ * {@link filterMap}, for checks that run on nearly every token. Compiled
843
+ * code compares the result with the sentinel instead of allocating and
844
+ * unwrapping a `Result` or an `Option` on every success.
845
+ *
846
+ * On failure the parser backtracks to where it started. With `orFailWith`,
847
+ * the user error it builds (only on failure) becomes the `cause` of the
848
+ * `ParseError`, as with `tryMap`; without, the failure has no cause, as
849
+ * with `filterMap`.
850
+ *
851
+ * @example
852
+ * ```ts
853
+ * import { Ascii, Input, Parser } from "@thomasfosterau/effect-parse"
854
+ *
855
+ * const byte = Ascii.decUint.pipe(
856
+ * Parser.mapOrFail(
857
+ * (n) => (n < 256 ? n : Input.Fail),
858
+ * (n) => `${n} is not a byte`
859
+ * )
860
+ * )
861
+ * Parser.parse(byte, "300") // fails with the cause "300 is not a byte"
862
+ * ```
863
+ *
864
+ * @since 0.5.0
865
+ * @category mapping
866
+ */
867
+ export const mapOrFail: {
868
+ <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>
869
+ <A, B, E2>(
870
+ f: (a: A) => B | Fail,
871
+ orFailWith: (a: A) => E2
872
+ ): <E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<Exclude<B, Fail>, E | E2, R, I>
873
+ <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>
874
+ <A, E, R, I extends Input, B, E2>(
875
+ self: Parser<A, E, R, I>,
876
+ f: (a: A) => B | Fail,
877
+ orFailWith: (a: A) => E2
878
+ ): Parser<Exclude<B, Fail>, E | E2, R, I>
879
+ } = /*#__PURE__*/ dual(
880
+ (args) => isParser(args[0]),
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<B, E | E2, R, I> => {
886
+ const step = self.step
887
+ return described(
888
+ (state) => {
889
+ const start = state.pos
890
+ const a = step(state)
891
+ if (a === Fail) return Fail
892
+ const b = f(a)
893
+ if (b !== Fail) return b
894
+ state.rewind(start)
895
+ return orFailWith === undefined
896
+ ? state.backtrack("Verify")
897
+ : state.failCause("Backtrack", "Verify", orFailWith(a))
898
+ },
899
+ self.name,
900
+ { _tag: "MapOrFail", self, f }
901
+ )
902
+ }
903
+ )
904
+
738
905
  /**
739
906
  * Like {@link tryMap}, but the function may throw. Thrown values become the
740
907
  * `cause` of the error.
@@ -743,8 +910,8 @@ export const tryMap: {
743
910
  * @category mapping
744
911
  */
745
912
  export const tryMapUnknown: {
746
- <A, B>(f: (a: A) => B): <E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<B, E | unknown, R, I>
747
- <A, E, R, I extends Input, B>(self: Parser<A, E, R, I>, f: (a: A) => B): Parser<B, E | unknown, R, I>
913
+ <A, B>(f: (a: A) => B): <E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<B, unknown, R, I>
914
+ <A, E, R, I extends Input, B>(self: Parser<A, E, R, I>, f: (a: A) => B): Parser<B, unknown, R, I>
748
915
  } = /*#__PURE__*/ dual(
749
916
  2,
750
917
  <A, E, R, I extends Input, B>(self: Parser<A, E, R, I>, f: (a: A) => B): Parser<B, unknown, R, I> =>
@@ -950,6 +1117,51 @@ export const withSpan = <A, E, R, I extends Input>(
950
1117
  )
951
1118
  }
952
1119
 
1120
+ /**
1121
+ * Transforms the output of a parser together with the offsets it consumed,
1122
+ * `[start, end)`: the allocation-free spelling of
1123
+ * `map(withSpan(p), ([a, span]) => f(a, span.start, span.end))`, which
1124
+ * builds a tuple and a span object on every success. Compiled code passes
1125
+ * the offsets to `f` directly.
1126
+ *
1127
+ * @example
1128
+ * ```ts
1129
+ * import { Ascii, Parser } from "@thomasfosterau/effect-parse"
1130
+ *
1131
+ * const name = Ascii.alpha1.pipe(Parser.mapSpan((text, start, end) => ({ text, start, end })))
1132
+ * Parser.parse(name, "abc") // Result.succeed({ text: "abc", start: 0, end: 3 })
1133
+ * ```
1134
+ *
1135
+ * @since 0.5.0
1136
+ * @category consumed input
1137
+ */
1138
+ export const mapSpan: {
1139
+ <A, B>(
1140
+ f: (a: A, start: number, end: number) => B
1141
+ ): <E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<B, E, R, I>
1142
+ <A, E, R, I extends Input, B>(
1143
+ self: Parser<A, E, R, I>,
1144
+ f: (a: A, start: number, end: number) => B
1145
+ ): Parser<B, E, R, I>
1146
+ } = /*#__PURE__*/ dual(
1147
+ 2,
1148
+ <A, E, R, I extends Input, B>(
1149
+ self: Parser<A, E, R, I>,
1150
+ f: (a: A, start: number, end: number) => B
1151
+ ): Parser<B, E, R, I> => {
1152
+ const step = self.step
1153
+ return described(
1154
+ (state) => {
1155
+ const start = state.pos
1156
+ const a = step(state)
1157
+ return a === Fail ? Fail : f(a, start, state.pos)
1158
+ },
1159
+ self.name,
1160
+ { _tag: "MapSpan", self, f }
1161
+ )
1162
+ }
1163
+ )
1164
+
953
1165
  // -----------------------------------------------------------------------------
954
1166
  // Error management
955
1167
  // -----------------------------------------------------------------------------
@@ -1002,6 +1214,9 @@ export const cut = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser<
1002
1214
  state.mode = "Cut"
1003
1215
  return a
1004
1216
  }
1217
+ // SAFETY: `state.deepest` is declared `Failure | undefined`; the
1218
+ // assertion only undoes the narrowing to `undefined` from the
1219
+ // assignment above, which misses that `step` may have recorded a failure
1005
1220
  const inner = state.deepest as Failure | undefined
1006
1221
  if (deepest !== undefined && (inner === undefined || deepest.offset > inner.offset)) state.deepest = deepest
1007
1222
  return a
@@ -1112,6 +1327,212 @@ export const expected: {
1112
1327
  }
1113
1328
  )
1114
1329
 
1330
+ /**
1331
+ * Runs a parser without letting it add to the expectations of later errors
1332
+ * (chumsky's `quiet`). Use it on skippers, such as whitespace and comments,
1333
+ * that may appear between any two tokens: they stop by failing, and without
1334
+ * `quiet` what they would have accepted (`" "`, `"(:"`, ...) is listed in
1335
+ * every error raised after them.
1336
+ *
1337
+ * When the parser succeeds, the hints and the furthest failure it recorded
1338
+ * are forgotten, as after a lookahead that succeeded. When it fails
1339
+ * recoverably without consuming input, its failure expects nothing, so an
1340
+ * enclosing `alt` or `opt` reports only the other alternatives. A failure
1341
+ * after consuming input (an unterminated comment) or an unrecoverable one
1342
+ * is kept as it is. `Parser.expected` cannot do this: it relabels a failure,
1343
+ * and a skipper that matches zero or more times never fails.
1344
+ *
1345
+ * @example
1346
+ * ```ts
1347
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
1348
+ *
1349
+ * const comment = Parser.delimited(Token.literal("(:"), Token.takeTill(":"), Token.literal(":)"))
1350
+ * const skip = Parser.quiet(Parser.repeat(Parser.alt(Ascii.multispace1, comment)))
1351
+ * const number = Parser.preceded(skip, Ascii.digit1)
1352
+ * // Fails expecting only a digit: without `quiet`, also whitespace and "(:"
1353
+ * Parser.parse(number, " (: one :) x")
1354
+ * ```
1355
+ *
1356
+ * @since 0.5.0
1357
+ * @category error management
1358
+ */
1359
+ export const quiet = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser<A, E, R, I> => {
1360
+ const step = self.step
1361
+ return described(
1362
+ (state) => {
1363
+ const start = state.pos
1364
+ const hintOffset = state.hintOffset
1365
+ const hintExpected = state.hintExpected
1366
+ const deepest = state.deepest
1367
+ const a = step(state)
1368
+ if (a !== Fail) {
1369
+ state.hintOffset = hintOffset
1370
+ state.hintExpected = hintExpected
1371
+ state.deepest = deepest
1372
+ return a
1373
+ }
1374
+ if (state.mode === "Backtrack" && state.failure.offset === start) {
1375
+ const f = state.failure
1376
+ const expected = hintOffset === start ? hintExpected : []
1377
+ state.hintOffset = hintOffset
1378
+ state.hintExpected = hintExpected
1379
+ state.deepest = deepest
1380
+ state.failure = new Failure(f.offset, f.kind, expected, f.context, f.hasCause, f.cause, f.message, f.needed)
1381
+ }
1382
+ return a
1383
+ },
1384
+ self.name,
1385
+ { _tag: "Quiet", self }
1386
+ )
1387
+ }
1388
+
1389
+ /**
1390
+ * What a fast path (see {@link withFastPath}) returns to leave the input at
1391
+ * the current offset to its grammar.
1392
+ *
1393
+ * @since 0.5.0
1394
+ * @category fast paths
1395
+ */
1396
+ export const Defer: typeof DeferSymbol = DeferSymbol
1397
+
1398
+ /**
1399
+ * The type of {@link Defer}.
1400
+ *
1401
+ * @since 0.5.0
1402
+ * @category fast paths
1403
+ */
1404
+ export type Defer = typeof DeferSymbol
1405
+
1406
+ /**
1407
+ * Puts a hand-written fast path in front of a grammar, which stays the
1408
+ * specification: `fast` runs on the state, at the offset where the grammar
1409
+ * would, and either returns the value the grammar would return, with
1410
+ * `state.pos` where the grammar would stop, or returns {@link Defer}, and
1411
+ * the grammar runs from that offset instead. Use it where a scanner written
1412
+ * by hand (`indexOf`, a lookup table, interned names) beats compiled code on
1413
+ * the tokens that dominate an input, and let the grammar handle everything
1414
+ * else.
1415
+ *
1416
+ * The fast path runs in compiled code only (see the `Compiler` module).
1417
+ * Interpreted runs, and so every error, every `Syntax` tree and every trace,
1418
+ * come from the grammar alone, so they are exactly the grammar's. It must
1419
+ * defer wherever it would not return exactly what the grammar returns, and
1420
+ * on partial input wherever the grammar might report `Incomplete`; check it
1421
+ * against the grammar with {@link verifyFastPath}.
1422
+ *
1423
+ * @example
1424
+ * ```ts
1425
+ * import { Compiler, Parser, Token } from "@thomasfosterau/effect-parse"
1426
+ *
1427
+ * const text = Token.takeTill("<", { min: 1 })
1428
+ * // The same run, found with `indexOf`; anything else (an empty run, the end of a partial input) defers
1429
+ * const fastText = Compiler.jit(
1430
+ * Parser.withFastPath(text, (state) => {
1431
+ * const end = state.input.indexOf("<", state.pos)
1432
+ * if (end <= state.pos || end > state.end) return Parser.Defer
1433
+ * const value = state.input.slice(state.pos, end)
1434
+ * state.pos = end
1435
+ * return value
1436
+ * })
1437
+ * )
1438
+ * ```
1439
+ *
1440
+ * @since 0.5.0
1441
+ * @category fast paths
1442
+ */
1443
+ export const withFastPath: {
1444
+ <A, I extends Input>(fast: (state: State<I>) => A | Defer): <E, R>(grammar: Parser<A, E, R, I>) => Parser<A, E, R, I>
1445
+ <A, E, R, I extends Input>(grammar: Parser<A, E, R, I>, fast: (state: State<I>) => A | Defer): Parser<A, E, R, I>
1446
+ } = /*#__PURE__*/ dual(
1447
+ 2,
1448
+ <A, E, R, I extends Input>(grammar: Parser<A, E, R, I>, fast: (state: State<I>) => A | Defer): Parser<A, E, R, I> =>
1449
+ // The interpreter runs the grammar alone: it is the reference, and it builds every error
1450
+ described(grammar.step, grammar.name, { _tag: "FastPath", self: grammar, fast })
1451
+ )
1452
+
1453
+ /**
1454
+ * A place where a fast path disagrees with its grammar (see
1455
+ * {@link verifyFastPath}).
1456
+ *
1457
+ * @since 0.5.0
1458
+ * @category fast paths
1459
+ */
1460
+ export interface FastPathMismatch<A, I extends Input> {
1461
+ readonly input: I
1462
+ readonly offset: number
1463
+ readonly partial: boolean
1464
+ /** What the fast path returned, and where it stopped. */
1465
+ readonly fast: { readonly value: A; readonly end: number }
1466
+ /** What the grammar returned, and where it stopped, or how it failed. */
1467
+ readonly grammar: { readonly value: A; readonly end: number } | { readonly failed: ErrMode }
1468
+ }
1469
+
1470
+ /**
1471
+ * Checks a fast path against its grammar (see {@link withFastPath}): on
1472
+ * every input, at every offset, on complete and on partial input, wherever
1473
+ * `fast` returns a value, the grammar (run interpreted) must return an
1474
+ * equal value (by `Equal.equals`, or `options.equivalence`) and stop at the
1475
+ * same offset. Returns every place where it doesn't, so a test asserts that
1476
+ * the result is empty; generated inputs, mutated and truncated, find the
1477
+ * most. Grammars that use services are not supported.
1478
+ *
1479
+ * @example
1480
+ * ```ts
1481
+ * import { Input, Parser, Token } from "@thomasfosterau/effect-parse"
1482
+ *
1483
+ * const text = Token.takeTill("<", { min: 1 })
1484
+ * const fast = (state: Input.State<string>) => {
1485
+ * const end = state.input.indexOf("<", state.pos)
1486
+ * if (end <= state.pos || end > state.end) return Parser.Defer
1487
+ * const value = state.input.slice(state.pos, end)
1488
+ * state.pos = end
1489
+ * return value
1490
+ * }
1491
+ * Parser.verifyFastPath(text, fast, ["a<b", "<", "abc"]) // []
1492
+ * ```
1493
+ *
1494
+ * @since 0.5.0
1495
+ * @category fast paths
1496
+ */
1497
+ export const verifyFastPath = <A, E, I extends Input>(
1498
+ grammar: Parser<A, E, never, I>,
1499
+ fast: (state: State<I>) => A | Defer,
1500
+ inputs: Iterable<I>,
1501
+ options?: { readonly equivalence?: ((a: A, b: A) => boolean) | undefined }
1502
+ ): ReadonlyArray<FastPathMismatch<A, I>> => {
1503
+ const equivalence = options?.equivalence ?? Equal.equals
1504
+ const mismatches: Array<FastPathMismatch<A, I>> = []
1505
+ for (const input of inputs) {
1506
+ const length = new State(input).end
1507
+ for (let offset = 0; offset <= length; offset++) {
1508
+ for (const partial of [false, true]) {
1509
+ const mismatch = checkFastPath(grammar, fast, input, offset, partial, equivalence)
1510
+ if (mismatch !== undefined) mismatches.push(mismatch)
1511
+ }
1512
+ }
1513
+ }
1514
+ return mismatches
1515
+ }
1516
+
1517
+ /** Where `fast` disagrees with `grammar` at `offset`, if it does (see {@link verifyFastPath}). */
1518
+ const checkFastPath = <A, E, I extends Input>(
1519
+ grammar: Parser<A, E, never, I>,
1520
+ fast: (state: State<I>) => A | Defer,
1521
+ input: I,
1522
+ offset: number,
1523
+ partial: boolean,
1524
+ equivalence: (a: A, b: A) => boolean
1525
+ ): FastPathMismatch<A, I> | undefined => {
1526
+ const state = new State(input, { offset, partial })
1527
+ const a = fast(state)
1528
+ if (a === Defer) return undefined
1529
+ const expected = new State(input, { offset, partial })
1530
+ const b = Stack.run(grammar.step, expected)
1531
+ if (b !== Fail && expected.pos === state.pos && equivalence(a, b)) return undefined
1532
+ const grammarOutcome = b === Fail ? { failed: expected.mode } : { value: b, end: expected.pos }
1533
+ return { input, offset, partial, fast: { value: a, end: state.pos }, grammar: grammarOutcome }
1534
+ }
1535
+
1115
1536
  /**
1116
1537
  * Transforms the user error (cause) of a parser's failures.
1117
1538
  *
@@ -1130,6 +1551,8 @@ export const mapError: {
1130
1551
  const a = step(state)
1131
1552
  if (a === Fail && state.failure.hasCause) {
1132
1553
  const x = state.failure
1554
+ // SAFETY: a failure with `hasCause` from `self` carries a cause raised
1555
+ // by a `fail` / `mapError` inside it, whose user error type is `E`
1133
1556
  state.replaceFailure(
1134
1557
  new Failure(x.offset, x.kind, x.expected, x.context, true, f(x.cause as E), x.message, x.needed)
1135
1558
  )
@@ -1374,6 +1797,7 @@ export const seq = <const Ps extends ReadonlyArray<Parser.Any>>(
1374
1797
  Parser.Services<Ps[number]>,
1375
1798
  Parser.InputOf<Ps[number]>
1376
1799
  > => {
1800
+ type Out = { -readonly [K in keyof Ps]: Parser.Success<Ps[K]> }
1377
1801
  const steps = parsers.map((p) => p.step)
1378
1802
  const n = steps.length
1379
1803
  return described(
@@ -1384,13 +1808,58 @@ export const seq = <const Ps extends ReadonlyArray<Parser.Any>>(
1384
1808
  if (a === Fail) return Fail
1385
1809
  out[i] = a
1386
1810
  }
1387
- return out as any
1811
+ // SAFETY: `out` has one slot per parser, and slot `i` holds the
1812
+ // output of `parsers[i]`, which is `Parser.Success<Ps[i]>`
1813
+ return out as Out
1388
1814
  },
1389
1815
  "seq",
1390
1816
  { _tag: "Seq", parsers, output: "tuple" }
1391
1817
  )
1392
1818
  }
1393
1819
 
1820
+ /**
1821
+ * Runs parsers in sequence and combines their outputs with `f`, which
1822
+ * receives one argument per parser: the allocation-free spelling of
1823
+ * `map(seq(...parsers), ([a, b, ...]) => f(a, b, ...))`, which builds a
1824
+ * tuple on every success. Compiled code calls `f` with the values directly.
1825
+ * {@link zipWith} is the two-parser, pipeable form.
1826
+ *
1827
+ * @example
1828
+ * ```ts
1829
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
1830
+ *
1831
+ * const point = Parser.seqWith(
1832
+ * [Ascii.decInt, Token.literal(","), Ascii.decInt],
1833
+ * (x, _, y) => ({ x, y })
1834
+ * )
1835
+ * Parser.parse(point, "3,-4") // Result.succeed({ x: 3, y: -4 })
1836
+ * ```
1837
+ *
1838
+ * @since 0.5.0
1839
+ * @category sequencing
1840
+ */
1841
+ export const seqWith = <const Ps extends ReadonlyArray<Parser.Any>, B>(
1842
+ parsers: Ps,
1843
+ f: (...values: { -readonly [K in keyof Ps]: Parser.Success<Ps[K]> }) => B
1844
+ ): Parser<B, Parser.Error<Ps[number]>, Parser.Services<Ps[number]>, Parser.InputOf<Ps[number]>> => {
1845
+ const steps = parsers.map((p) => p.step)
1846
+ const n = steps.length
1847
+ const g: (...values: Array<any>) => B = f
1848
+ return described(
1849
+ (state: State<any>) => {
1850
+ const values = new Array<unknown>(n)
1851
+ for (let i = 0; i < n; i++) {
1852
+ const a = steps[i](state)
1853
+ if (a === Fail) return Fail
1854
+ values[i] = a
1855
+ }
1856
+ return g(...values)
1857
+ },
1858
+ "seqWith",
1859
+ { _tag: "SeqWith", parsers, f }
1860
+ )
1861
+ }
1862
+
1394
1863
  /**
1395
1864
  * Runs a record of parsers in sequence (in key order), collecting their
1396
1865
  * outputs in a record. This is the Effect counterpart to winnow's
@@ -1407,18 +1876,21 @@ export const struct = <const Fields extends Readonly<Record<string, Parser.Any>>
1407
1876
  Parser.Services<Fields[keyof Fields]>,
1408
1877
  Parser.InputOf<Fields[keyof Fields]>
1409
1878
  > => {
1410
- const keys = Object.keys(fields)
1879
+ type Out = Simplify<{ -readonly [K in keyof Fields]: Parser.Success<Fields[K]> }>
1880
+ const keys = Rec.keys(fields)
1411
1881
  const steps = keys.map((k) => fields[k].step)
1412
1882
  const n = keys.length
1413
1883
  return described(
1414
1884
  (state: State<any>) => {
1415
- const out: Record<string, unknown> = {}
1885
+ const out: Record<string, Parser.Success<Fields[keyof Fields]>> = {}
1416
1886
  for (let i = 0; i < n; i++) {
1417
1887
  const a = steps[i](state)
1418
1888
  if (a === Fail) return Fail
1419
1889
  out[keys[i]] = a
1420
1890
  }
1421
- return out as any
1891
+ // SAFETY: the loop has set every own key of `fields`, each to the
1892
+ // output of that key's parser, `Parser.Success<Fields[K]>`
1893
+ return out as Out
1422
1894
  },
1423
1895
  "struct",
1424
1896
  { _tag: "Struct", keys, parsers: keys.map((k) => fields[k]) }
@@ -1674,9 +2146,14 @@ export const gen = <Eff extends Parser.Any, AEff>(
1674
2146
  while (!next.done) {
1675
2147
  const a = next.value.step(state)
1676
2148
  if (a === Fail) {
1677
- iterator.return?.(undefined as any)
2149
+ // SAFETY: the value passed to `return` only becomes the completion value
2150
+ // of the abandoned generator, which nothing reads
2151
+ iterator.return?.(undefined as AEff)
1678
2152
  return Fail
1679
2153
  }
2154
+ // SAFETY: `a` is the output of the parser just yielded, which is what its
2155
+ // `yield*` evaluates to (typed by `ParserIterator`'s return value, not by
2156
+ // the generator's `never` next type)
1680
2157
  next = iterator.next(a as never)
1681
2158
  }
1682
2159
  return next.value
@@ -1784,7 +2261,7 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
1784
2261
  > => {
1785
2262
  const steps = parsers.map((p) => p.step)
1786
2263
  const n = steps.length
1787
- if (n === 0) return reject as any
2264
+ if (n === 0) return reject
1788
2265
  if (n === 1) return parsers[0]
1789
2266
  return described(
1790
2267
  (state: State<any>) => {
@@ -1837,6 +2314,36 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
1837
2314
  )
1838
2315
  }
1839
2316
 
2317
+ /**
2318
+ * The overloads of {@link dispatch}.
2319
+ *
2320
+ * @since 0.5.0
2321
+ * @category choice
2322
+ */
2323
+ export interface DispatchFn {
2324
+ <K, E, R, I extends Input, P extends Parser.Any>(
2325
+ selector: Parser<K, E, R, I>,
2326
+ cases: (key: K) => P | undefined
2327
+ ): Parser<Parser.Success<P>, E | Parser.Error<P>, R | Parser.Services<P>, I & Parser.InputOf<P>>
2328
+ <
2329
+ K extends PropertyKey,
2330
+ E,
2331
+ R,
2332
+ I extends Input,
2333
+ const Cases extends { readonly [key in K]?: Parser.Any },
2334
+ Otherwise extends Parser.Any = never
2335
+ >(
2336
+ selector: Parser<K, E, R, I>,
2337
+ cases: Cases,
2338
+ otherwise?: Otherwise
2339
+ ): Parser<
2340
+ Parser.Success<NonNullable<Cases[keyof Cases]> | Otherwise>,
2341
+ E | Parser.Error<NonNullable<Cases[keyof Cases]> | Otherwise>,
2342
+ R | Parser.Services<NonNullable<Cases[keyof Cases]> | Otherwise>,
2343
+ I & Parser.InputOf<NonNullable<Cases[keyof Cases]> | Otherwise>
2344
+ >
2345
+ }
2346
+
1840
2347
  /**
1841
2348
  * Selects the parser to run next based on the output of `selector`, like a
1842
2349
  * `match` over the input (winnow's `dispatch!`). If no case matches, the
@@ -1871,29 +2378,7 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
1871
2378
  * @since 0.1.0
1872
2379
  * @category choice
1873
2380
  */
1874
- export const dispatch: {
1875
- <K, E, R, I extends Input, P extends Parser.Any>(
1876
- selector: Parser<K, E, R, I>,
1877
- cases: (key: K) => P | undefined
1878
- ): Parser<Parser.Success<P>, E | Parser.Error<P>, R | Parser.Services<P>, I & Parser.InputOf<P>>
1879
- <
1880
- K extends PropertyKey,
1881
- E,
1882
- R,
1883
- I extends Input,
1884
- const Cases extends { readonly [key in K]?: Parser.Any },
1885
- Otherwise extends Parser.Any = never
1886
- >(
1887
- selector: Parser<K, E, R, I>,
1888
- cases: Cases,
1889
- otherwise?: Otherwise
1890
- ): Parser<
1891
- Parser.Success<NonNullable<Cases[keyof Cases]> | Otherwise>,
1892
- E | Parser.Error<NonNullable<Cases[keyof Cases]> | Otherwise>,
1893
- R | Parser.Services<NonNullable<Cases[keyof Cases]> | Otherwise>,
1894
- I & Parser.InputOf<NonNullable<Cases[keyof Cases]> | Otherwise>
1895
- >
1896
- } = (
2381
+ export const dispatch: DispatchFn = (
1897
2382
  selector: Parser.Any,
1898
2383
  cases: ((key: any) => Parser.Any | undefined) | Record<PropertyKey, Parser.Any>,
1899
2384
  otherwise?: Parser.Any
@@ -1941,37 +2426,44 @@ export const permutation = <const Ps extends ReadonlyArray<Parser.Any>>(
1941
2426
  Parser.Services<Ps[number]>,
1942
2427
  Parser.InputOf<Ps[number]>
1943
2428
  > => {
1944
- const steps = parsers.map((p) => p.step)
1945
- const n = steps.length
1946
- return make((state: State<any>) => {
1947
- // A round's failures, merged only if no parser matches in it (#44)
1948
- const ties = new TiedFailures()
1949
- const done = new Array<boolean>(n).fill(false)
1950
- const out = new Array(n)
1951
- let remaining = n
1952
- while (remaining > 0) {
1953
- ties.clear()
1954
- let matched = false
1955
- for (let i = 0; i < n; i++) {
1956
- if (done[i]) continue
1957
- const start = state.pos
1958
- const a = steps[i](state)
1959
- if (a !== Fail) {
1960
- out[i] = a
1961
- done[i] = true
1962
- remaining--
1963
- matched = true
1964
- break
2429
+ type Out = { -readonly [K in keyof Ps]: Parser.Success<Ps[K]> }
2430
+ const n = parsers.length
2431
+ // The parsers inside compile: the step is built from theirs (see `combinator`)
2432
+ return combinator(
2433
+ parsers,
2434
+ (steps) => (state: State<any>) => {
2435
+ // A round's failures, merged only if no parser matches in it (#44)
2436
+ const ties = new TiedFailures()
2437
+ const done = new Array<boolean>(n).fill(false)
2438
+ const out = new Array(n)
2439
+ let remaining = n
2440
+ while (remaining > 0) {
2441
+ ties.clear()
2442
+ let matched = false
2443
+ for (let i = 0; i < n; i++) {
2444
+ if (done[i]) continue
2445
+ const start = state.pos
2446
+ const a = steps[i](state)
2447
+ if (a !== Fail) {
2448
+ out[i] = a
2449
+ done[i] = true
2450
+ remaining--
2451
+ matched = true
2452
+ break
2453
+ }
2454
+ if (state.mode !== "Backtrack") return Fail
2455
+ state.rewind(start)
2456
+ ties.add(state)
1965
2457
  }
1966
- if (state.mode !== "Backtrack") return Fail
1967
- state.rewind(start)
1968
- ties.add(state)
2458
+ // Some parser is not done yet, and each one that fails is folded in
2459
+ if (!matched) return ties.fail(state)
1969
2460
  }
1970
- // Some parser is not done yet, and each one that fails is folded in
1971
- if (!matched) return ties.fail(state)
1972
- }
1973
- return out as any
1974
- }, "permutation")
2461
+ // SAFETY: every parser has matched once (`remaining` is 0), and slot
2462
+ // `i` holds the output of `parsers[i]`, which is `Parser.Success<Ps[i]>`
2463
+ return out as Out
2464
+ },
2465
+ "permutation"
2466
+ )
1975
2467
  }
1976
2468
 
1977
2469
  // -----------------------------------------------------------------------------
@@ -2019,9 +2511,13 @@ const foldImpl = <A, B, E, R, I extends Input>(
2019
2511
  const entry = state.pos
2020
2512
  let acc: B
2021
2513
  let count = 0
2514
+ // SAFETY: `key` belongs to this parser, which saves only
2515
+ // `FoldCheckpoint<B>`s under it (and a `Resume.Done`, narrowed below)
2022
2516
  const resumed = state.resume === undefined ? undefined : (Resume.take(state, key, entry) as FoldCheckpoint<B>)
2023
2517
  if (resumed instanceof Resume.Done) {
2024
2518
  state.pos = resumed.pos
2519
+ // SAFETY: a `Done` under `key` is recorded by `Resume.done` below,
2520
+ // with this fold's result, a `B`
2025
2521
  return resumed.value as B
2026
2522
  }
2027
2523
  if (resumed !== undefined) {
@@ -2260,10 +2756,14 @@ export const separated: {
2260
2756
  (state) => {
2261
2757
  const entry = state.pos
2262
2758
  let acc: Array<A>
2759
+ // SAFETY: `key` belongs to this parser, which saves only
2760
+ // `SeparatedCheckpoint<A>`s under it (and a `Resume.Done`, narrowed below)
2263
2761
  const resumed =
2264
2762
  state.resume === undefined ? undefined : (Resume.take(state, key, entry) as SeparatedCheckpoint<A>)
2265
2763
  if (resumed instanceof Resume.Done) {
2266
2764
  state.pos = resumed.pos
2765
+ // SAFETY: a `Done` under `key` is recorded by `Resume.done` below,
2766
+ // with the elements collected, an `Array<A>`
2267
2767
  return resumed.value as Array<A>
2268
2768
  }
2269
2769
  if (resumed !== undefined) {
@@ -2343,10 +2843,15 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2343
2843
  return described(
2344
2844
  (state) => {
2345
2845
  const entry = state.pos
2846
+ // SAFETY: `key` belongs to this parser, which saves only
2847
+ // `FoldCheckpoint<A>`s under it, through `saveFold` (and a
2848
+ // `Resume.Done`, narrowed below)
2346
2849
  const resumed = state.resume === undefined ? undefined : (Resume.take(state, key, entry) as FoldCheckpoint<A>)
2347
2850
  let acc: A | Fail
2348
2851
  if (resumed instanceof Resume.Done) {
2349
2852
  state.pos = resumed.pos
2853
+ // SAFETY: a `Done` under `key` is recorded by `Resume.done` below,
2854
+ // with the folded value, an `A`
2350
2855
  return resumed.value as A
2351
2856
  }
2352
2857
  if (resumed !== undefined) {
@@ -2363,7 +2868,7 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2363
2868
  if (state.mode === "Incomplete" && state.resume !== undefined) saveFold(state, key, entry, acc, start)
2364
2869
  if (state.mode !== "Backtrack") return Fail
2365
2870
  recover(state, start)
2366
- return Resume.done(state, key, entry, resumed !== undefined, acc as A)
2871
+ return Resume.done(state, key, entry, resumed !== undefined, acc)
2367
2872
  }
2368
2873
  if (state.pos === start) return assertConsumes(state, "separatedFoldl1")
2369
2874
  const a = step(state)
@@ -2371,7 +2876,7 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2371
2876
  if (state.mode === "Incomplete" && state.resume !== undefined) saveFold(state, key, entry, acc, start)
2372
2877
  if (state.mode !== "Backtrack") return Fail
2373
2878
  state.rewind(start)
2374
- return Resume.done(state, key, entry, resumed !== undefined, acc as A)
2879
+ return Resume.done(state, key, entry, resumed !== undefined, acc)
2375
2880
  }
2376
2881
  acc = op(acc, s, a)
2377
2882
  }
@@ -2421,9 +2926,9 @@ export const separatedFoldr1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2421
2926
  seps.push(s)
2422
2927
  values.push(a)
2423
2928
  }
2424
- let acc = values[values.length - 1] as A
2929
+ let acc = values[values.length - 1]
2425
2930
  for (let i = seps.length - 1; i >= 0; i--) {
2426
- acc = op(values[i] as A, seps[i] as S, acc)
2931
+ acc = op(values[i], seps[i], acc)
2427
2932
  }
2428
2933
  return acc
2429
2934
  },
@@ -2499,7 +3004,7 @@ export const provideService: {
2499
3004
  return described(
2500
3005
  (state) => {
2501
3006
  const prev = state.context
2502
- state.context = Context.add(prev, key, service) as Context.Context<never>
3007
+ state.context = Context.add(prev, key, service)
2503
3008
  try {
2504
3009
  return step(state)
2505
3010
  } finally {
@@ -2536,7 +3041,7 @@ export const provide: {
2536
3041
  return described(
2537
3042
  (state) => {
2538
3043
  const prev = state.context
2539
- state.context = Context.merge(prev, context) as Context.Context<never>
3044
+ state.context = Context.merge(prev, context)
2540
3045
  try {
2541
3046
  return step(state)
2542
3047
  } finally {
@@ -2732,18 +3237,20 @@ const NotCompiled: unique symbol = /*#__PURE__*/ Symbol("NotCompiled")
2732
3237
  * input), in which case the state must be thrown away. `resumable` selects
2733
3238
  * the build that handles partial input.
2734
3239
  */
2735
- const runCompiled = <I extends Input>(
2736
- self: Parser.Any,
3240
+ const runCompiled = <A, I extends Input>(
3241
+ self: Parser<A, unknown, unknown, I>,
2737
3242
  state: State<I>,
2738
3243
  whole: boolean,
2739
3244
  resumable = false
2740
- ): unknown => {
3245
+ ): A | Fail | typeof NotCompiled => {
2741
3246
  if (state.tracer !== undefined || state.recovered !== undefined) return NotCompiled
2742
- const step = Registry.stepFor(self, state, resumable)
2743
- if (step === undefined) return NotCompiled
2744
- const a = Stack.run(step, state)
3247
+ const compiled = Registry.compiled(self, state.inputKind, resumable)
3248
+ if (compiled?.step === undefined) return NotCompiled
3249
+ const a = Run.runCompiled(compiled.step, compiled.guarded, state)
2745
3250
  if (a === Fail || (whole && state.pos < state.end)) return Fail
2746
- return a
3251
+ // SAFETY: the compilers never change semantics (the differential suites
3252
+ // check it), so `self`'s compiled step returns what its step returns, an `A`
3253
+ return a as A
2747
3254
  }
2748
3255
 
2749
3256
  // -----------------------------------------------------------------------------
@@ -2814,7 +3321,7 @@ const parseComplete = <A, E, R, I extends Input>(
2814
3321
  const state = makeState(input, options, false)
2815
3322
  const a = runCompiled(self, state, true)
2816
3323
  if (a === NotCompiled) return runComplete(self, state)
2817
- if (a !== Fail) return Result.succeed(a as A)
3324
+ if (a !== Fail) return Result.succeed(a)
2818
3325
  // Re-run interpreted for the diagnostics
2819
3326
  return runComplete(self, makeState(input, options, false))
2820
3327
  }
@@ -2832,6 +3339,25 @@ const runComplete = <A, E, R, I extends Input>(
2832
3339
  return Result.succeed(a)
2833
3340
  }
2834
3341
 
3342
+ /**
3343
+ * The overloads of {@link parse}.
3344
+ *
3345
+ * @since 0.5.0
3346
+ * @category running
3347
+ */
3348
+ export interface ParseFn {
3349
+ <A, E, I extends Input>(
3350
+ self: Parser<A, E, never, I>,
3351
+ input: NoInfer<Source<I>>,
3352
+ options?: ParseOptions
3353
+ ): Result.Result<A, ParseError.ParseError<E>>
3354
+ <A, E, R, I extends Input>(
3355
+ self: Parser<A, E, R, I>,
3356
+ input: NoInfer<Source<I>>,
3357
+ options: ParseOptionsWithContext<R>
3358
+ ): Result.Result<A, ParseError.ParseError<E>>
3359
+ }
3360
+
2835
3361
  /**
2836
3362
  * Runs a parser against a complete input, requiring the whole input to be
2837
3363
  * consumed (winnow's `Parser::parse`).
@@ -2848,37 +3374,34 @@ const runComplete = <A, E, R, I extends Input>(
2848
3374
  * @since 0.1.0
2849
3375
  * @category running
2850
3376
  */
2851
- export const parse: {
2852
- <A, E, I extends Input>(
2853
- self: Parser<A, E, never, I>,
2854
- input: NoInfer<Source<I>>,
2855
- options?: ParseOptions
2856
- ): Result.Result<A, ParseError.ParseError<E>>
2857
- <A, E, R, I extends Input>(
2858
- self: Parser<A, E, R, I>,
2859
- input: NoInfer<Source<I>>,
2860
- options: ParseOptionsWithContext<R>
2861
- ): Result.Result<A, ParseError.ParseError<E>>
2862
- } = <A, E, R, I extends Input>(
3377
+ export const parse: ParseFn = <A, E, R, I extends Input>(
2863
3378
  self: Parser<A, E, R, I>,
2864
3379
  input: I | Source<I>,
2865
3380
  options?: ParseOptions & { readonly context?: Context.Context<R> }
2866
3381
  ): Result.Result<A, ParseError.ParseError<E>> => parseComplete(self, input, options)
2867
3382
 
2868
3383
  /**
2869
- * Like {@link parse}, but throws the `ParseError` on failure.
3384
+ * The overloads of {@link parseSync}.
2870
3385
  *
2871
- * @since 0.1.0
3386
+ * @since 0.5.0
2872
3387
  * @category running
2873
3388
  */
2874
- export const parseSync: {
3389
+ export interface ParseSyncFn {
2875
3390
  <A, E, I extends Input>(self: Parser<A, E, never, I>, input: NoInfer<Source<I>>, options?: ParseOptions): A
2876
3391
  <A, E, R, I extends Input>(
2877
3392
  self: Parser<A, E, R, I>,
2878
3393
  input: NoInfer<Source<I>>,
2879
3394
  options: ParseOptionsWithContext<R>
2880
3395
  ): A
2881
- } = <A, E, R, I extends Input>(
3396
+ }
3397
+
3398
+ /**
3399
+ * Like {@link parse}, but throws the `ParseError` on failure.
3400
+ *
3401
+ * @since 0.1.0
3402
+ * @category running
3403
+ */
3404
+ export const parseSync: ParseSyncFn = <A, E, R, I extends Input>(
2882
3405
  self: Parser<A, E, R, I>,
2883
3406
  input: I | Source<I>,
2884
3407
  options?: ParseOptions & { readonly context?: Context.Context<R> }
@@ -2889,18 +3412,12 @@ export const parseSync: {
2889
3412
  }
2890
3413
 
2891
3414
  /**
2892
- * Runs a parser against the start of an input, returning the remaining
2893
- * input along with the output, nom-style (winnow's `parse_peek`, nom's
2894
- * `IResult`).
3415
+ * The overloads of {@link parsePeek}.
2895
3416
  *
2896
- * The parser ended at offset `input.length - remaining.length`, which is
2897
- * how a caller running from an `offset` (see {@link ParseOptions}) finds
2898
- * where to continue.
2899
- *
2900
- * @since 0.1.0
3417
+ * @since 0.5.0
2901
3418
  * @category running
2902
3419
  */
2903
- export const parsePeek: {
3420
+ export interface ParsePeekFn {
2904
3421
  <A, E, I extends Input>(
2905
3422
  self: Parser<A, E, never, I>,
2906
3423
  input: NoInfer<Source<I>>,
@@ -2911,7 +3428,21 @@ export const parsePeek: {
2911
3428
  input: NoInfer<Source<I>>,
2912
3429
  options: ParseOptionsWithContext<R>
2913
3430
  ): Result.Result<[remaining: Slice<I>, output: A], ParseError.ParseError<E>>
2914
- } = <A, E, R, I extends Input>(
3431
+ }
3432
+
3433
+ /**
3434
+ * Runs a parser against the start of an input, returning the remaining
3435
+ * input along with the output, nom-style (winnow's `parse_peek`, nom's
3436
+ * `IResult`).
3437
+ *
3438
+ * The parser ended at offset `input.length - remaining.length`, which is
3439
+ * how a caller running from an `offset` (see {@link ParseOptions}) finds
3440
+ * where to continue.
3441
+ *
3442
+ * @since 0.1.0
3443
+ * @category running
3444
+ */
3445
+ export const parsePeek: ParsePeekFn = <A, E, R, I extends Input>(
2915
3446
  self: Parser<A, E, R, I>,
2916
3447
  input: I | Source<I>,
2917
3448
  options?: ParseOptions & { readonly context?: Context.Context<R> }
@@ -2919,7 +3450,7 @@ export const parsePeek: {
2919
3450
  let state = makeState(input, options, false)
2920
3451
  const compiled = runCompiled(self, state, false)
2921
3452
  if (compiled !== NotCompiled && compiled !== Fail) {
2922
- return Result.succeed([state.slice(state.pos, state.end), compiled as A])
3453
+ return Result.succeed([state.slice(state.pos, state.end), compiled])
2923
3454
  }
2924
3455
  if (compiled === Fail) state = makeState(input, options, false)
2925
3456
  const a = Stack.run(self.step, state)
@@ -2940,6 +3471,25 @@ export type PartialResult<A> =
2940
3471
  | { readonly _tag: "Done"; readonly value: A; readonly offset: number }
2941
3472
  | { readonly _tag: "Incomplete"; readonly needed: Option.Option<number> }
2942
3473
 
3474
+ /**
3475
+ * The overloads of {@link parsePartial}.
3476
+ *
3477
+ * @since 0.5.0
3478
+ * @category running
3479
+ */
3480
+ export interface ParsePartialFn {
3481
+ <A, E, I extends Input>(
3482
+ self: Parser<A, E, never, I>,
3483
+ input: NoInfer<Source<I>>,
3484
+ options?: ParseOptions
3485
+ ): Result.Result<PartialResult<A>, ParseError.ParseError<E>>
3486
+ <A, E, R, I extends Input>(
3487
+ self: Parser<A, E, R, I>,
3488
+ input: NoInfer<Source<I>>,
3489
+ options: ParseOptionsWithContext<R>
3490
+ ): Result.Result<PartialResult<A>, ParseError.ParseError<E>>
3491
+ }
3492
+
2943
3493
  /**
2944
3494
  * Runs a parser against a *partial* input, i.e. a chunk of a stream that may
2945
3495
  * be followed by more data (winnow's `Partial`, nom's `streaming` parsers).
@@ -2969,18 +3519,7 @@ export type PartialResult<A> =
2969
3519
  * @since 0.1.0
2970
3520
  * @category running
2971
3521
  */
2972
- export const parsePartial: {
2973
- <A, E, I extends Input>(
2974
- self: Parser<A, E, never, I>,
2975
- input: NoInfer<Source<I>>,
2976
- options?: ParseOptions
2977
- ): Result.Result<PartialResult<A>, ParseError.ParseError<E>>
2978
- <A, E, R, I extends Input>(
2979
- self: Parser<A, E, R, I>,
2980
- input: NoInfer<Source<I>>,
2981
- options: ParseOptionsWithContext<R>
2982
- ): Result.Result<PartialResult<A>, ParseError.ParseError<E>>
2983
- } = <A, E, R, I extends Input>(
3522
+ export const parsePartial: ParsePartialFn = <A, E, R, I extends Input>(
2984
3523
  self: Parser<A, E, R, I>,
2985
3524
  input: I | Source<I>,
2986
3525
  options?: ParseOptions & { readonly context?: Context.Context<R> }
@@ -2988,19 +3527,25 @@ export const parsePartial: {
2988
3527
  let state = makeState(input, options, true)
2989
3528
  const compiled = runCompiled(self, state, false, true)
2990
3529
  if (compiled !== NotCompiled && compiled !== Fail) {
2991
- return Result.succeed({ _tag: "Done", value: compiled as A, offset: state.pos })
3530
+ return Result.succeed({ _tag: "Done", value: compiled, offset: state.pos })
2992
3531
  }
3532
+ // Compiled code reports `Incomplete` where the interpreter does, with the same failure: the leaf that
3533
+ // ran out of input is the interpreter's own step, or records what it would. Only an error needs the re-run.
3534
+ if (compiled === Fail && state.mode === "Incomplete") return Result.succeed(incompleteOf(state))
2993
3535
  // Re-run interpreted for `needed` and the diagnostics
2994
3536
  if (compiled === Fail) state = makeState(input, options, true)
2995
3537
  const a = Stack.run(self.step, state)
2996
3538
  if (a !== Fail) return Result.succeed({ _tag: "Done", value: a, offset: state.pos })
2997
- if (state.mode === "Incomplete") {
2998
- const needed = state.failure.needed
2999
- return Result.succeed({ _tag: "Incomplete", needed: needed > 0 ? Option.some(needed) : Option.none() })
3000
- }
3539
+ if (state.mode === "Incomplete") return Result.succeed(incompleteOf(state))
3001
3540
  return Result.fail(toError(state))
3002
3541
  }
3003
3542
 
3543
+ /** The `Incomplete` result of a state whose parse ran out of partial input. */
3544
+ const incompleteOf = <A>(state: State<any>): PartialResult<A> => {
3545
+ const needed = state.failure.needed
3546
+ return { _tag: "Incomplete", needed: needed > 0 ? Option.some(needed) : Option.none() }
3547
+ }
3548
+
3004
3549
  /**
3005
3550
  * Runs a parser against a complete input as an `Effect`. Services required
3006
3551
  * by the parser are taken from the Effect's context.
@@ -3119,14 +3664,12 @@ export interface RecoverableResult<A, E> {
3119
3664
  }
3120
3665
 
3121
3666
  /**
3122
- * Runs a parser with error recovery enabled (winnow's `Recoverable`
3123
- * stream). Failures handled by {@link resumeAfter} and {@link retryAfter}
3124
- * are collected instead of aborting the parse.
3667
+ * The overloads of {@link parseRecoverable}.
3125
3668
  *
3126
- * @since 0.1.0
3669
+ * @since 0.5.0
3127
3670
  * @category running
3128
3671
  */
3129
- export const parseRecoverable: {
3672
+ export interface ParseRecoverableFn {
3130
3673
  <A, E, I extends Input>(
3131
3674
  self: Parser<A, E, never, I>,
3132
3675
  input: NoInfer<Source<I>>,
@@ -3137,7 +3680,17 @@ export const parseRecoverable: {
3137
3680
  input: NoInfer<Source<I>>,
3138
3681
  options: ParseOptionsWithContext<R>
3139
3682
  ): RecoverableResult<A, E>
3140
- } = <A, E, R, I extends Input>(
3683
+ }
3684
+
3685
+ /**
3686
+ * Runs a parser with error recovery enabled (winnow's `Recoverable`
3687
+ * stream). Failures handled by {@link resumeAfter} and {@link retryAfter}
3688
+ * are collected instead of aborting the parse.
3689
+ *
3690
+ * @since 0.1.0
3691
+ * @category running
3692
+ */
3693
+ export const parseRecoverable: ParseRecoverableFn = <A, E, R, I extends Input>(
3141
3694
  self: Parser<A, E, R, I>,
3142
3695
  input: I | Source<I>,
3143
3696
  options?: ParseOptions & { readonly context?: Context.Context<R> }