@thomasfosterau/effect-parse 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +37 -13
  2. package/dist/Ascii.d.ts +11 -4
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +7 -1
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts.map +1 -1
  7. package/dist/Binary.js +64 -35
  8. package/dist/Binary.js.map +1 -1
  9. package/dist/Bits.d.ts.map +1 -1
  10. package/dist/Bits.js +7 -4
  11. package/dist/Bits.js.map +1 -1
  12. package/dist/Compiler.d.ts +74 -2
  13. package/dist/Compiler.d.ts.map +1 -1
  14. package/dist/Compiler.js +78 -1
  15. package/dist/Compiler.js.map +1 -1
  16. package/dist/Expression.d.ts.map +1 -1
  17. package/dist/Expression.js +86 -74
  18. package/dist/Expression.js.map +1 -1
  19. package/dist/Input.d.ts +37 -4
  20. package/dist/Input.d.ts.map +1 -1
  21. package/dist/Input.js +42 -0
  22. package/dist/Input.js.map +1 -1
  23. package/dist/Nom.d.ts +5 -15
  24. package/dist/Nom.d.ts.map +1 -1
  25. package/dist/Nom.js.map +1 -1
  26. package/dist/ParseError.d.ts.map +1 -1
  27. package/dist/ParseError.js.map +1 -1
  28. package/dist/Parser.d.ts +366 -26
  29. package/dist/Parser.d.ts.map +1 -1
  30. package/dist/Parser.js +390 -19
  31. package/dist/Parser.js.map +1 -1
  32. package/dist/ParserSchema.js +1 -1
  33. package/dist/ParserStream.d.ts +3 -2
  34. package/dist/ParserStream.d.ts.map +1 -1
  35. package/dist/ParserStream.js +13 -9
  36. package/dist/ParserStream.js.map +1 -1
  37. package/dist/Syntax.d.ts +23 -5
  38. package/dist/Syntax.d.ts.map +1 -1
  39. package/dist/Syntax.js.map +1 -1
  40. package/dist/Text.d.ts.map +1 -1
  41. package/dist/Text.js +9 -9
  42. package/dist/Text.js.map +1 -1
  43. package/dist/Token.d.ts +55 -10
  44. package/dist/Token.d.ts.map +1 -1
  45. package/dist/Token.js +59 -21
  46. package/dist/Token.js.map +1 -1
  47. package/dist/internal/compiler/closure.d.ts.map +1 -1
  48. package/dist/internal/compiler/closure.js +257 -97
  49. package/dist/internal/compiler/closure.js.map +1 -1
  50. package/dist/internal/compiler/entries.d.ts +3 -1
  51. package/dist/internal/compiler/entries.d.ts.map +1 -1
  52. package/dist/internal/compiler/entries.js +3 -2
  53. package/dist/internal/compiler/entries.js.map +1 -1
  54. package/dist/internal/compiler/first.d.ts.map +1 -1
  55. package/dist/internal/compiler/first.js +7 -1
  56. package/dist/internal/compiler/first.js.map +1 -1
  57. package/dist/internal/compiler/graph.d.ts +17 -0
  58. package/dist/internal/compiler/graph.d.ts.map +1 -0
  59. package/dist/internal/compiler/graph.js +56 -0
  60. package/dist/internal/compiler/graph.js.map +1 -0
  61. package/dist/internal/compiler/jit.d.ts +5 -0
  62. package/dist/internal/compiler/jit.d.ts.map +1 -1
  63. package/dist/internal/compiler/jit.js +185 -96
  64. package/dist/internal/compiler/jit.js.map +1 -1
  65. package/dist/internal/compiler/registry.d.ts +10 -2
  66. package/dist/internal/compiler/registry.d.ts.map +1 -1
  67. package/dist/internal/compiler/registry.js.map +1 -1
  68. package/dist/internal/compiler/search.d.ts +32 -0
  69. package/dist/internal/compiler/search.d.ts.map +1 -0
  70. package/dist/internal/compiler/search.js +68 -0
  71. package/dist/internal/compiler/search.js.map +1 -0
  72. package/dist/internal/compiler/sets.d.ts.map +1 -1
  73. package/dist/internal/compiler/sets.js +1 -1
  74. package/dist/internal/compiler/sets.js.map +1 -1
  75. package/dist/internal/compiler/wasm.d.ts +8 -0
  76. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  77. package/dist/internal/compiler/wasm.js +2769 -0
  78. package/dist/internal/compiler/wasm.js.map +1 -0
  79. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  80. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  81. package/dist/internal/compiler/wasmAsm.js +427 -0
  82. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  83. package/dist/internal/entries.d.ts +3 -1
  84. package/dist/internal/entries.d.ts.map +1 -1
  85. package/dist/internal/entries.js.map +1 -1
  86. package/dist/internal/node.d.ts +58 -2
  87. package/dist/internal/node.d.ts.map +1 -1
  88. package/dist/internal/node.js +13 -3
  89. package/dist/internal/node.js.map +1 -1
  90. package/dist/internal/numbers.d.ts.map +1 -1
  91. package/dist/internal/numbers.js.map +1 -1
  92. package/dist/internal/run.d.ts +12 -0
  93. package/dist/internal/run.d.ts.map +1 -1
  94. package/dist/internal/run.js +51 -11
  95. package/dist/internal/run.js.map +1 -1
  96. package/dist/internal/stack.d.ts +6 -0
  97. package/dist/internal/stack.d.ts.map +1 -1
  98. package/dist/internal/stack.js +8 -2
  99. package/dist/internal/stack.js.map +1 -1
  100. package/dist/internal/syntax.d.ts +4 -4
  101. package/dist/internal/syntax.d.ts.map +1 -1
  102. package/dist/internal/syntax.js +18 -10
  103. package/dist/internal/syntax.js.map +1 -1
  104. package/dist/internal/text.d.ts +32 -0
  105. package/dist/internal/text.d.ts.map +1 -1
  106. package/dist/internal/text.js +68 -14
  107. package/dist/internal/text.js.map +1 -1
  108. package/dist/internal/tokens.d.ts +13 -0
  109. package/dist/internal/tokens.d.ts.map +1 -1
  110. package/dist/internal/tokens.js +59 -39
  111. package/dist/internal/tokens.js.map +1 -1
  112. package/package.json +5 -4
  113. package/src/Ascii.ts +42 -9
  114. package/src/Binary.ts +151 -71
  115. package/src/Bits.ts +12 -6
  116. package/src/Compiler.ts +101 -10
  117. package/src/Expression.ts +94 -81
  118. package/src/Input.ts +65 -6
  119. package/src/Nom.ts +15 -5
  120. package/src/ParseError.ts +3 -0
  121. package/src/Parser.ts +678 -127
  122. package/src/ParserStream.ts +37 -18
  123. package/src/Syntax.ts +60 -36
  124. package/src/Text.ts +16 -15
  125. package/src/Token.ts +138 -29
  126. package/src/internal/compiler/closure.ts +362 -135
  127. package/src/internal/compiler/entries.ts +4 -2
  128. package/src/internal/compiler/first.ts +10 -0
  129. package/src/internal/compiler/graph.ts +68 -0
  130. package/src/internal/compiler/jit.ts +284 -154
  131. package/src/internal/compiler/registry.ts +10 -2
  132. package/src/internal/compiler/search.ts +68 -0
  133. package/src/internal/compiler/sets.ts +7 -3
  134. package/src/internal/compiler/wasm.ts +3203 -0
  135. package/src/internal/compiler/wasmAsm.ts +484 -0
  136. package/src/internal/entries.ts +8 -2
  137. package/src/internal/node.ts +54 -4
  138. package/src/internal/numbers.ts +3 -1
  139. package/src/internal/run.ts +71 -12
  140. package/src/internal/stack.ts +8 -1
  141. package/src/internal/syntax.ts +39 -18
  142. package/src/internal/text.ts +97 -9
  143. package/src/internal/tokens.ts +96 -14
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
@@ -146,6 +148,15 @@ export declare namespace Parser {
146
148
  : [P] extends [Parser<infer _A, infer _E, infer _R, infer I>]
147
149
  ? I
148
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
+ }
149
160
  }
150
161
 
151
162
  /**
@@ -225,7 +236,7 @@ class SingleShotGen<T> {
225
236
  }
226
237
 
227
238
  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>
239
+ readonly [TypeId]: Parser.Variance<A, E, R, I> = variance
229
240
  /** The description used by the compiler; `undefined` for `Opaque`. */
230
241
  readonly [NodeId]: Node | undefined
231
242
  constructor(
@@ -239,7 +250,7 @@ class ParserImpl<A, E, R, I extends Input> implements Parser<A, E, R, I> {
239
250
  return pipeArguments(this, arguments)
240
251
  }
241
252
  [Symbol.iterator](): ParserIterator<Parser<A, E, R, I>> {
242
- return new SingleShotGen(this) as any
253
+ return new SingleShotGen(this)
243
254
  }
244
255
  toString() {
245
256
  return this.name === undefined ? "Parser" : `Parser(${this.name})`
@@ -315,7 +326,61 @@ export const described = <A, E = never, R = never, I extends Input = string>(
315
326
  node: Node
316
327
  ): Parser<A, E, R, I> => new ParserImpl(step, name, node)
317
328
 
318
- 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
+ }
319
384
 
320
385
  /**
321
386
  * Gives a parser a name, used by `toString`.
@@ -329,7 +394,7 @@ export const named: {
329
394
  } = /*#__PURE__*/ dual(
330
395
  2,
331
396
  <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))
397
+ new ParserImpl(self.step, name, describedNode(self))
333
398
  )
334
399
 
335
400
  /**
@@ -390,6 +455,40 @@ export const fail = <E>(error: E): Parser<never, E, never, Input> =>
390
455
 
391
456
  const failBacktrack: Node = { _tag: "Fail", mode: "Backtrack" }
392
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
+
393
492
  /**
394
493
  * A parser that always fails (backtracks) without a cause. This is winnow's
395
494
  * `fail` and nom's `fail()`.
@@ -737,6 +836,72 @@ export const tryMap: {
737
836
  }
738
837
  )
739
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
+
740
905
  /**
741
906
  * Like {@link tryMap}, but the function may throw. Thrown values become the
742
907
  * `cause` of the error.
@@ -952,6 +1117,51 @@ export const withSpan = <A, E, R, I extends Input>(
952
1117
  )
953
1118
  }
954
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
+
955
1165
  // -----------------------------------------------------------------------------
956
1166
  // Error management
957
1167
  // -----------------------------------------------------------------------------
@@ -1004,6 +1214,9 @@ export const cut = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser<
1004
1214
  state.mode = "Cut"
1005
1215
  return a
1006
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
1007
1220
  const inner = state.deepest as Failure | undefined
1008
1221
  if (deepest !== undefined && (inner === undefined || deepest.offset > inner.offset)) state.deepest = deepest
1009
1222
  return a
@@ -1114,6 +1327,212 @@ export const expected: {
1114
1327
  }
1115
1328
  )
1116
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
+
1117
1536
  /**
1118
1537
  * Transforms the user error (cause) of a parser's failures.
1119
1538
  *
@@ -1132,6 +1551,8 @@ export const mapError: {
1132
1551
  const a = step(state)
1133
1552
  if (a === Fail && state.failure.hasCause) {
1134
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`
1135
1556
  state.replaceFailure(
1136
1557
  new Failure(x.offset, x.kind, x.expected, x.context, true, f(x.cause as E), x.message, x.needed)
1137
1558
  )
@@ -1376,6 +1797,7 @@ export const seq = <const Ps extends ReadonlyArray<Parser.Any>>(
1376
1797
  Parser.Services<Ps[number]>,
1377
1798
  Parser.InputOf<Ps[number]>
1378
1799
  > => {
1800
+ type Out = { -readonly [K in keyof Ps]: Parser.Success<Ps[K]> }
1379
1801
  const steps = parsers.map((p) => p.step)
1380
1802
  const n = steps.length
1381
1803
  return described(
@@ -1386,13 +1808,58 @@ export const seq = <const Ps extends ReadonlyArray<Parser.Any>>(
1386
1808
  if (a === Fail) return Fail
1387
1809
  out[i] = a
1388
1810
  }
1389
- 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
1390
1814
  },
1391
1815
  "seq",
1392
1816
  { _tag: "Seq", parsers, output: "tuple" }
1393
1817
  )
1394
1818
  }
1395
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
+
1396
1863
  /**
1397
1864
  * Runs a record of parsers in sequence (in key order), collecting their
1398
1865
  * outputs in a record. This is the Effect counterpart to winnow's
@@ -1409,18 +1876,21 @@ export const struct = <const Fields extends Readonly<Record<string, Parser.Any>>
1409
1876
  Parser.Services<Fields[keyof Fields]>,
1410
1877
  Parser.InputOf<Fields[keyof Fields]>
1411
1878
  > => {
1412
- const keys = Object.keys(fields)
1879
+ type Out = Simplify<{ -readonly [K in keyof Fields]: Parser.Success<Fields[K]> }>
1880
+ const keys = Rec.keys(fields)
1413
1881
  const steps = keys.map((k) => fields[k].step)
1414
1882
  const n = keys.length
1415
1883
  return described(
1416
1884
  (state: State<any>) => {
1417
- const out: Record<string, unknown> = {}
1885
+ const out: Record<string, Parser.Success<Fields[keyof Fields]>> = {}
1418
1886
  for (let i = 0; i < n; i++) {
1419
1887
  const a = steps[i](state)
1420
1888
  if (a === Fail) return Fail
1421
1889
  out[keys[i]] = a
1422
1890
  }
1423
- 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
1424
1894
  },
1425
1895
  "struct",
1426
1896
  { _tag: "Struct", keys, parsers: keys.map((k) => fields[k]) }
@@ -1676,9 +2146,14 @@ export const gen = <Eff extends Parser.Any, AEff>(
1676
2146
  while (!next.done) {
1677
2147
  const a = next.value.step(state)
1678
2148
  if (a === Fail) {
1679
- 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)
1680
2152
  return Fail
1681
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)
1682
2157
  next = iterator.next(a as never)
1683
2158
  }
1684
2159
  return next.value
@@ -1786,7 +2261,7 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
1786
2261
  > => {
1787
2262
  const steps = parsers.map((p) => p.step)
1788
2263
  const n = steps.length
1789
- if (n === 0) return reject as any
2264
+ if (n === 0) return reject
1790
2265
  if (n === 1) return parsers[0]
1791
2266
  return described(
1792
2267
  (state: State<any>) => {
@@ -1839,6 +2314,36 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
1839
2314
  )
1840
2315
  }
1841
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
+
1842
2347
  /**
1843
2348
  * Selects the parser to run next based on the output of `selector`, like a
1844
2349
  * `match` over the input (winnow's `dispatch!`). If no case matches, the
@@ -1873,29 +2378,7 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
1873
2378
  * @since 0.1.0
1874
2379
  * @category choice
1875
2380
  */
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
- } = (
2381
+ export const dispatch: DispatchFn = (
1899
2382
  selector: Parser.Any,
1900
2383
  cases: ((key: any) => Parser.Any | undefined) | Record<PropertyKey, Parser.Any>,
1901
2384
  otherwise?: Parser.Any
@@ -1943,37 +2426,44 @@ export const permutation = <const Ps extends ReadonlyArray<Parser.Any>>(
1943
2426
  Parser.Services<Ps[number]>,
1944
2427
  Parser.InputOf<Ps[number]>
1945
2428
  > => {
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
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)
1967
2457
  }
1968
- if (state.mode !== "Backtrack") return Fail
1969
- state.rewind(start)
1970
- 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)
1971
2460
  }
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")
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
+ )
1977
2467
  }
1978
2468
 
1979
2469
  // -----------------------------------------------------------------------------
@@ -2021,9 +2511,13 @@ const foldImpl = <A, B, E, R, I extends Input>(
2021
2511
  const entry = state.pos
2022
2512
  let acc: B
2023
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)
2024
2516
  const resumed = state.resume === undefined ? undefined : (Resume.take(state, key, entry) as FoldCheckpoint<B>)
2025
2517
  if (resumed instanceof Resume.Done) {
2026
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`
2027
2521
  return resumed.value as B
2028
2522
  }
2029
2523
  if (resumed !== undefined) {
@@ -2262,10 +2756,14 @@ export const separated: {
2262
2756
  (state) => {
2263
2757
  const entry = state.pos
2264
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)
2265
2761
  const resumed =
2266
2762
  state.resume === undefined ? undefined : (Resume.take(state, key, entry) as SeparatedCheckpoint<A>)
2267
2763
  if (resumed instanceof Resume.Done) {
2268
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>`
2269
2767
  return resumed.value as Array<A>
2270
2768
  }
2271
2769
  if (resumed !== undefined) {
@@ -2345,10 +2843,15 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2345
2843
  return described(
2346
2844
  (state) => {
2347
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)
2348
2849
  const resumed = state.resume === undefined ? undefined : (Resume.take(state, key, entry) as FoldCheckpoint<A>)
2349
2850
  let acc: A | Fail
2350
2851
  if (resumed instanceof Resume.Done) {
2351
2852
  state.pos = resumed.pos
2853
+ // SAFETY: a `Done` under `key` is recorded by `Resume.done` below,
2854
+ // with the folded value, an `A`
2352
2855
  return resumed.value as A
2353
2856
  }
2354
2857
  if (resumed !== undefined) {
@@ -2365,7 +2868,7 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2365
2868
  if (state.mode === "Incomplete" && state.resume !== undefined) saveFold(state, key, entry, acc, start)
2366
2869
  if (state.mode !== "Backtrack") return Fail
2367
2870
  recover(state, start)
2368
- return Resume.done(state, key, entry, resumed !== undefined, acc as A)
2871
+ return Resume.done(state, key, entry, resumed !== undefined, acc)
2369
2872
  }
2370
2873
  if (state.pos === start) return assertConsumes(state, "separatedFoldl1")
2371
2874
  const a = step(state)
@@ -2373,7 +2876,7 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2373
2876
  if (state.mode === "Incomplete" && state.resume !== undefined) saveFold(state, key, entry, acc, start)
2374
2877
  if (state.mode !== "Backtrack") return Fail
2375
2878
  state.rewind(start)
2376
- return Resume.done(state, key, entry, resumed !== undefined, acc as A)
2879
+ return Resume.done(state, key, entry, resumed !== undefined, acc)
2377
2880
  }
2378
2881
  acc = op(acc, s, a)
2379
2882
  }
@@ -2423,9 +2926,9 @@ export const separatedFoldr1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2423
2926
  seps.push(s)
2424
2927
  values.push(a)
2425
2928
  }
2426
- let acc = values[values.length - 1] as A
2929
+ let acc = values[values.length - 1]
2427
2930
  for (let i = seps.length - 1; i >= 0; i--) {
2428
- acc = op(values[i] as A, seps[i] as S, acc)
2931
+ acc = op(values[i], seps[i], acc)
2429
2932
  }
2430
2933
  return acc
2431
2934
  },
@@ -2501,7 +3004,7 @@ export const provideService: {
2501
3004
  return described(
2502
3005
  (state) => {
2503
3006
  const prev = state.context
2504
- state.context = Context.add(prev, key, service) as Context.Context<never>
3007
+ state.context = Context.add(prev, key, service)
2505
3008
  try {
2506
3009
  return step(state)
2507
3010
  } finally {
@@ -2538,7 +3041,7 @@ export const provide: {
2538
3041
  return described(
2539
3042
  (state) => {
2540
3043
  const prev = state.context
2541
- state.context = Context.merge(prev, context) as Context.Context<never>
3044
+ state.context = Context.merge(prev, context)
2542
3045
  try {
2543
3046
  return step(state)
2544
3047
  } finally {
@@ -2734,18 +3237,20 @@ const NotCompiled: unique symbol = /*#__PURE__*/ Symbol("NotCompiled")
2734
3237
  * input), in which case the state must be thrown away. `resumable` selects
2735
3238
  * the build that handles partial input.
2736
3239
  */
2737
- const runCompiled = <I extends Input>(
2738
- self: Parser.Any,
3240
+ const runCompiled = <A, I extends Input>(
3241
+ self: Parser<A, unknown, unknown, I>,
2739
3242
  state: State<I>,
2740
3243
  whole: boolean,
2741
3244
  resumable = false
2742
- ): unknown => {
3245
+ ): A | Fail | typeof NotCompiled => {
2743
3246
  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)
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)
2747
3250
  if (a === Fail || (whole && state.pos < state.end)) return Fail
2748
- 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
2749
3254
  }
2750
3255
 
2751
3256
  // -----------------------------------------------------------------------------
@@ -2816,7 +3321,7 @@ const parseComplete = <A, E, R, I extends Input>(
2816
3321
  const state = makeState(input, options, false)
2817
3322
  const a = runCompiled(self, state, true)
2818
3323
  if (a === NotCompiled) return runComplete(self, state)
2819
- if (a !== Fail) return Result.succeed(a as A)
3324
+ if (a !== Fail) return Result.succeed(a)
2820
3325
  // Re-run interpreted for the diagnostics
2821
3326
  return runComplete(self, makeState(input, options, false))
2822
3327
  }
@@ -2834,6 +3339,25 @@ const runComplete = <A, E, R, I extends Input>(
2834
3339
  return Result.succeed(a)
2835
3340
  }
2836
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
+
2837
3361
  /**
2838
3362
  * Runs a parser against a complete input, requiring the whole input to be
2839
3363
  * consumed (winnow's `Parser::parse`).
@@ -2850,37 +3374,34 @@ const runComplete = <A, E, R, I extends Input>(
2850
3374
  * @since 0.1.0
2851
3375
  * @category running
2852
3376
  */
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>(
3377
+ export const parse: ParseFn = <A, E, R, I extends Input>(
2865
3378
  self: Parser<A, E, R, I>,
2866
3379
  input: I | Source<I>,
2867
3380
  options?: ParseOptions & { readonly context?: Context.Context<R> }
2868
3381
  ): Result.Result<A, ParseError.ParseError<E>> => parseComplete(self, input, options)
2869
3382
 
2870
3383
  /**
2871
- * Like {@link parse}, but throws the `ParseError` on failure.
3384
+ * The overloads of {@link parseSync}.
2872
3385
  *
2873
- * @since 0.1.0
3386
+ * @since 0.5.0
2874
3387
  * @category running
2875
3388
  */
2876
- export const parseSync: {
3389
+ export interface ParseSyncFn {
2877
3390
  <A, E, I extends Input>(self: Parser<A, E, never, I>, input: NoInfer<Source<I>>, options?: ParseOptions): A
2878
3391
  <A, E, R, I extends Input>(
2879
3392
  self: Parser<A, E, R, I>,
2880
3393
  input: NoInfer<Source<I>>,
2881
3394
  options: ParseOptionsWithContext<R>
2882
3395
  ): A
2883
- } = <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>(
2884
3405
  self: Parser<A, E, R, I>,
2885
3406
  input: I | Source<I>,
2886
3407
  options?: ParseOptions & { readonly context?: Context.Context<R> }
@@ -2891,18 +3412,12 @@ export const parseSync: {
2891
3412
  }
2892
3413
 
2893
3414
  /**
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`).
3415
+ * The overloads of {@link parsePeek}.
2897
3416
  *
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
3417
+ * @since 0.5.0
2903
3418
  * @category running
2904
3419
  */
2905
- export const parsePeek: {
3420
+ export interface ParsePeekFn {
2906
3421
  <A, E, I extends Input>(
2907
3422
  self: Parser<A, E, never, I>,
2908
3423
  input: NoInfer<Source<I>>,
@@ -2913,7 +3428,21 @@ export const parsePeek: {
2913
3428
  input: NoInfer<Source<I>>,
2914
3429
  options: ParseOptionsWithContext<R>
2915
3430
  ): Result.Result<[remaining: Slice<I>, output: A], ParseError.ParseError<E>>
2916
- } = <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>(
2917
3446
  self: Parser<A, E, R, I>,
2918
3447
  input: I | Source<I>,
2919
3448
  options?: ParseOptions & { readonly context?: Context.Context<R> }
@@ -2921,7 +3450,7 @@ export const parsePeek: {
2921
3450
  let state = makeState(input, options, false)
2922
3451
  const compiled = runCompiled(self, state, false)
2923
3452
  if (compiled !== NotCompiled && compiled !== Fail) {
2924
- return Result.succeed([state.slice(state.pos, state.end), compiled as A])
3453
+ return Result.succeed([state.slice(state.pos, state.end), compiled])
2925
3454
  }
2926
3455
  if (compiled === Fail) state = makeState(input, options, false)
2927
3456
  const a = Stack.run(self.step, state)
@@ -2942,6 +3471,25 @@ export type PartialResult<A> =
2942
3471
  | { readonly _tag: "Done"; readonly value: A; readonly offset: number }
2943
3472
  | { readonly _tag: "Incomplete"; readonly needed: Option.Option<number> }
2944
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
+
2945
3493
  /**
2946
3494
  * Runs a parser against a *partial* input, i.e. a chunk of a stream that may
2947
3495
  * be followed by more data (winnow's `Partial`, nom's `streaming` parsers).
@@ -2971,18 +3519,7 @@ export type PartialResult<A> =
2971
3519
  * @since 0.1.0
2972
3520
  * @category running
2973
3521
  */
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>(
3522
+ export const parsePartial: ParsePartialFn = <A, E, R, I extends Input>(
2986
3523
  self: Parser<A, E, R, I>,
2987
3524
  input: I | Source<I>,
2988
3525
  options?: ParseOptions & { readonly context?: Context.Context<R> }
@@ -2990,19 +3527,25 @@ export const parsePartial: {
2990
3527
  let state = makeState(input, options, true)
2991
3528
  const compiled = runCompiled(self, state, false, true)
2992
3529
  if (compiled !== NotCompiled && compiled !== Fail) {
2993
- return Result.succeed({ _tag: "Done", value: compiled as A, offset: state.pos })
3530
+ return Result.succeed({ _tag: "Done", value: compiled, offset: state.pos })
2994
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))
2995
3535
  // Re-run interpreted for `needed` and the diagnostics
2996
3536
  if (compiled === Fail) state = makeState(input, options, true)
2997
3537
  const a = Stack.run(self.step, state)
2998
3538
  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
- }
3539
+ if (state.mode === "Incomplete") return Result.succeed(incompleteOf(state))
3003
3540
  return Result.fail(toError(state))
3004
3541
  }
3005
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
+
3006
3549
  /**
3007
3550
  * Runs a parser against a complete input as an `Effect`. Services required
3008
3551
  * by the parser are taken from the Effect's context.
@@ -3121,14 +3664,12 @@ export interface RecoverableResult<A, E> {
3121
3664
  }
3122
3665
 
3123
3666
  /**
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.
3667
+ * The overloads of {@link parseRecoverable}.
3127
3668
  *
3128
- * @since 0.1.0
3669
+ * @since 0.5.0
3129
3670
  * @category running
3130
3671
  */
3131
- export const parseRecoverable: {
3672
+ export interface ParseRecoverableFn {
3132
3673
  <A, E, I extends Input>(
3133
3674
  self: Parser<A, E, never, I>,
3134
3675
  input: NoInfer<Source<I>>,
@@ -3139,7 +3680,17 @@ export const parseRecoverable: {
3139
3680
  input: NoInfer<Source<I>>,
3140
3681
  options: ParseOptionsWithContext<R>
3141
3682
  ): RecoverableResult<A, E>
3142
- } = <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>(
3143
3694
  self: Parser<A, E, R, I>,
3144
3695
  input: I | Source<I>,
3145
3696
  options?: ParseOptions & { readonly context?: Context.Context<R> }