@thomasfosterau/effect-parse 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README.md +56 -14
  3. package/dist/Ascii.d.ts +11 -4
  4. package/dist/Ascii.d.ts.map +1 -1
  5. package/dist/Ascii.js +12 -4
  6. package/dist/Ascii.js.map +1 -1
  7. package/dist/Binary.d.ts +5 -2
  8. package/dist/Binary.d.ts.map +1 -1
  9. package/dist/Binary.js +83 -37
  10. package/dist/Binary.js.map +1 -1
  11. package/dist/Bits.d.ts.map +1 -1
  12. package/dist/Bits.js +11 -4
  13. package/dist/Bits.js.map +1 -1
  14. package/dist/Compiler.d.ts +74 -2
  15. package/dist/Compiler.d.ts.map +1 -1
  16. package/dist/Compiler.js +78 -1
  17. package/dist/Compiler.js.map +1 -1
  18. package/dist/Expression.d.ts.map +1 -1
  19. package/dist/Expression.js +86 -74
  20. package/dist/Expression.js.map +1 -1
  21. package/dist/Input.d.ts +56 -5
  22. package/dist/Input.d.ts.map +1 -1
  23. package/dist/Input.js +70 -1
  24. package/dist/Input.js.map +1 -1
  25. package/dist/Nom.d.ts +5 -15
  26. package/dist/Nom.d.ts.map +1 -1
  27. package/dist/Nom.js.map +1 -1
  28. package/dist/ParseError.d.ts +69 -11
  29. package/dist/ParseError.d.ts.map +1 -1
  30. package/dist/ParseError.js +129 -28
  31. package/dist/ParseError.js.map +1 -1
  32. package/dist/Parser.d.ts +375 -29
  33. package/dist/Parser.d.ts.map +1 -1
  34. package/dist/Parser.js +394 -37
  35. package/dist/Parser.js.map +1 -1
  36. package/dist/ParserSchema.d.ts.map +1 -1
  37. package/dist/ParserSchema.js +2 -3
  38. package/dist/ParserSchema.js.map +1 -1
  39. package/dist/ParserStream.d.ts +122 -14
  40. package/dist/ParserStream.d.ts.map +1 -1
  41. package/dist/ParserStream.js +293 -62
  42. package/dist/ParserStream.js.map +1 -1
  43. package/dist/Syntax.d.ts +23 -5
  44. package/dist/Syntax.d.ts.map +1 -1
  45. package/dist/Syntax.js +1 -1
  46. package/dist/Syntax.js.map +1 -1
  47. package/dist/Text.d.ts.map +1 -1
  48. package/dist/Text.js +21 -15
  49. package/dist/Text.js.map +1 -1
  50. package/dist/Token.d.ts +65 -11
  51. package/dist/Token.d.ts.map +1 -1
  52. package/dist/Token.js +73 -26
  53. package/dist/Token.js.map +1 -1
  54. package/dist/internal/compiler/closure.d.ts +1 -1
  55. package/dist/internal/compiler/closure.d.ts.map +1 -1
  56. package/dist/internal/compiler/closure.js +263 -113
  57. package/dist/internal/compiler/closure.js.map +1 -1
  58. package/dist/internal/compiler/entries.d.ts +3 -1
  59. package/dist/internal/compiler/entries.d.ts.map +1 -1
  60. package/dist/internal/compiler/entries.js +3 -2
  61. package/dist/internal/compiler/entries.js.map +1 -1
  62. package/dist/internal/compiler/first.d.ts.map +1 -1
  63. package/dist/internal/compiler/first.js +9 -2
  64. package/dist/internal/compiler/first.js.map +1 -1
  65. package/dist/internal/compiler/graph.d.ts +39 -0
  66. package/dist/internal/compiler/graph.d.ts.map +1 -0
  67. package/dist/internal/compiler/graph.js +78 -0
  68. package/dist/internal/compiler/graph.js.map +1 -0
  69. package/dist/internal/compiler/jit.d.ts +5 -0
  70. package/dist/internal/compiler/jit.d.ts.map +1 -1
  71. package/dist/internal/compiler/jit.js +208 -106
  72. package/dist/internal/compiler/jit.js.map +1 -1
  73. package/dist/internal/compiler/registry.d.ts +11 -3
  74. package/dist/internal/compiler/registry.d.ts.map +1 -1
  75. package/dist/internal/compiler/registry.js.map +1 -1
  76. package/dist/internal/compiler/search.d.ts +32 -0
  77. package/dist/internal/compiler/search.d.ts.map +1 -0
  78. package/dist/internal/compiler/search.js +68 -0
  79. package/dist/internal/compiler/search.js.map +1 -0
  80. package/dist/internal/compiler/sets.d.ts.map +1 -1
  81. package/dist/internal/compiler/sets.js +1 -1
  82. package/dist/internal/compiler/sets.js.map +1 -1
  83. package/dist/internal/compiler/wasm.d.ts +8 -0
  84. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  85. package/dist/internal/compiler/wasm.js +2840 -0
  86. package/dist/internal/compiler/wasm.js.map +1 -0
  87. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  88. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  89. package/dist/internal/compiler/wasmAsm.js +427 -0
  90. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  91. package/dist/internal/entries.d.ts +3 -1
  92. package/dist/internal/entries.d.ts.map +1 -1
  93. package/dist/internal/entries.js.map +1 -1
  94. package/dist/internal/node.d.ts +77 -3
  95. package/dist/internal/node.d.ts.map +1 -1
  96. package/dist/internal/node.js +54 -10
  97. package/dist/internal/node.js.map +1 -1
  98. package/dist/internal/numbers.d.ts.map +1 -1
  99. package/dist/internal/numbers.js.map +1 -1
  100. package/dist/internal/run.d.ts +14 -0
  101. package/dist/internal/run.d.ts.map +1 -1
  102. package/dist/internal/run.js +53 -11
  103. package/dist/internal/run.js.map +1 -1
  104. package/dist/internal/stack.d.ts +14 -0
  105. package/dist/internal/stack.d.ts.map +1 -1
  106. package/dist/internal/stack.js +24 -2
  107. package/dist/internal/stack.js.map +1 -1
  108. package/dist/internal/syntax.d.ts +4 -4
  109. package/dist/internal/syntax.d.ts.map +1 -1
  110. package/dist/internal/syntax.js +18 -10
  111. package/dist/internal/syntax.js.map +1 -1
  112. package/dist/internal/text.d.ts +32 -0
  113. package/dist/internal/text.d.ts.map +1 -1
  114. package/dist/internal/text.js +68 -14
  115. package/dist/internal/text.js.map +1 -1
  116. package/dist/internal/tokens.d.ts +20 -0
  117. package/dist/internal/tokens.d.ts.map +1 -1
  118. package/dist/internal/tokens.js +72 -39
  119. package/dist/internal/tokens.js.map +1 -1
  120. package/dist/internal/tuple.d.ts +6 -0
  121. package/dist/internal/tuple.d.ts.map +1 -0
  122. package/dist/internal/tuple.js +170 -0
  123. package/dist/internal/tuple.js.map +1 -0
  124. package/package.json +18 -10
  125. package/src/Ascii.ts +47 -12
  126. package/src/Binary.ts +174 -73
  127. package/src/Bits.ts +16 -6
  128. package/src/Compiler.ts +101 -10
  129. package/src/Expression.ts +94 -81
  130. package/src/Input.ts +99 -7
  131. package/src/Nom.ts +15 -5
  132. package/src/ParseError.ts +209 -52
  133. package/src/Parser.ts +684 -143
  134. package/src/ParserSchema.ts +1 -2
  135. package/src/ParserStream.ts +361 -68
  136. package/src/Syntax.ts +60 -36
  137. package/src/Text.ts +32 -27
  138. package/src/Token.ts +152 -34
  139. package/src/internal/compiler/closure.ts +371 -153
  140. package/src/internal/compiler/entries.ts +4 -2
  141. package/src/internal/compiler/first.ts +16 -0
  142. package/src/internal/compiler/graph.ts +96 -0
  143. package/src/internal/compiler/jit.ts +319 -169
  144. package/src/internal/compiler/registry.ts +10 -2
  145. package/src/internal/compiler/search.ts +68 -0
  146. package/src/internal/compiler/sets.ts +7 -3
  147. package/src/internal/compiler/wasm.ts +3276 -0
  148. package/src/internal/compiler/wasmAsm.ts +484 -0
  149. package/src/internal/entries.ts +8 -2
  150. package/src/internal/node.ts +103 -7
  151. package/src/internal/numbers.ts +3 -1
  152. package/src/internal/run.ts +73 -12
  153. package/src/internal/stack.ts +25 -1
  154. package/src/internal/syntax.ts +39 -18
  155. package/src/internal/text.ts +97 -9
  156. package/src/internal/tokens.ts +106 -14
  157. package/src/internal/tuple.ts +141 -0
package/dist/Parser.js CHANGED
@@ -2,20 +2,23 @@ import * as Predicate from "effect/Predicate";
2
2
  import * as Result from "effect/Result";
3
3
  import { Fail, Failure, State, mergeExpected, mergeFailures, mergeTiedFailures } from "./Input.js";
4
4
  import { Key } from "./internal/key.js";
5
+ import { Defer as Defer$1, NodeId, bounds, describedNode } from "./internal/node.js";
5
6
  import * as Resume from "./internal/resume.js";
6
7
  import * as Registry from "./internal/compiler/registry.js";
8
+ import * as Run from "./internal/run.js";
7
9
  import * as Option from "effect/Option";
8
10
  import { TiedFailures } from "./internal/ties.js";
9
11
  import * as Context from "effect/Context";
10
12
  import * as Effect from "effect/Effect";
13
+ import * as Equal from "effect/Equal";
11
14
  import { dual } from "effect/Function";
12
15
  import { NodeInspectSymbol } from "effect/Inspectable";
16
+ import * as Rec from "effect/Record";
13
17
  import { pipeArguments } from "effect/Pipeable";
14
18
  import * as ParseError from "./ParseError.js";
15
19
  import * as Entries from "./internal/entries.js";
16
- import * as Run from "./internal/run.js";
17
20
  import * as Stack from "./internal/stack.js";
18
- import { NodeId, bounds } from "./internal/node.js";
21
+ import { tupleStep } from "./internal/tuple.js";
19
22
  // src/Parser.ts
20
23
  /**
21
24
  * The `Parser` data type and its general-purpose combinators.
@@ -166,14 +169,65 @@ const make = (step, name) => new ParserImpl(step, name, void 0);
166
169
  * @internal
167
170
  */
168
171
  const described = (step, name, node) => new ParserImpl(step, name, node);
169
- const nodeOfParser = (p) => p[NodeId];
172
+ /**
173
+ * Creates a combinator over `parsers` that compiles: `build` makes the
174
+ * combinator's step from the parsers' steps. Write combinators with it
175
+ * rather than with {@link make} when they run other parsers: a `make`
176
+ * parser is opaque to the compilers, so in a compiled grammar it runs
177
+ * interpreted, and so does every parser it runs. Interpreted, `build`
178
+ * receives the parsers' own steps; compiled (see the `Compiler` module), it
179
+ * receives their compiled steps, so the parsers inside compile with the
180
+ * grammar.
181
+ *
182
+ * Each step runs its parser on the state it is given, as `Parser.run` does,
183
+ * and returns the parser's output or `Fail`. The steps must run on the
184
+ * combinator's own state (they are compiled for its input kind): a
185
+ * combinator that runs a parser on another input, as {@link mapParser}
186
+ * does, is written with `make`. `build` may be called more than once (once
187
+ * interpreted, and once for each compiled build), so state its step shares
188
+ * between calls belongs inside `build`.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * import { Ascii, Input, Parser, Token } from "@thomasfosterau/effect-parse"
193
+ *
194
+ * // One or more `item`s separated by `sep`, returning their count
195
+ * const count = <A, B>(item: Parser.Parser<A>, sep: Parser.Parser<B>) =>
196
+ * Parser.combinator([item, sep], ([itemStep, sepStep]) => (state) => {
197
+ * if (itemStep(state) === Input.Fail) return Input.Fail
198
+ * let n = 1
199
+ * while (true) {
200
+ * const start = state.pos
201
+ * if (sepStep(state) === Input.Fail || itemStep(state) === Input.Fail) {
202
+ * if (state.mode !== "Backtrack") return Input.Fail
203
+ * state.rewind(start)
204
+ * return n
205
+ * }
206
+ * n++
207
+ * }
208
+ * })
209
+ *
210
+ * Parser.parse(count(Ascii.digit1, Token.literal(",")), "1,22,333") // Result.succeed(3)
211
+ * ```
212
+ *
213
+ * @since 0.5.0
214
+ * @category constructors
215
+ */
216
+ const combinator = (parsers, build, name) => {
217
+ const untyped = (steps) => build(steps);
218
+ return described(untyped(parsers.map((p) => p.step)), name, {
219
+ _tag: "Combinator",
220
+ parsers,
221
+ build: untyped
222
+ });
223
+ };
170
224
  /**
171
225
  * Gives a parser a name, used by `toString`.
172
226
  *
173
227
  * @since 0.1.0
174
228
  * @category utils
175
229
  */
176
- const named = /*#__PURE__*/ dual(2, (self, name) => new ParserImpl(self.step, name, nodeOfParser(self)));
230
+ const named = /*#__PURE__*/ dual(2, (self, name) => new ParserImpl(self.step, name, describedNode(self)));
177
231
  /**
178
232
  * A parser that consumes nothing and succeeds with the given value.
179
233
  *
@@ -225,6 +279,34 @@ const failBacktrack = {
225
279
  mode: "Backtrack"
226
280
  };
227
281
  /**
282
+ * A parser that fails (backtracks) with the given user error, reporting it
283
+ * at `offset` rather than where the parser runs. Use it for a check that
284
+ * can only run once a whole construct has parsed but should point at the
285
+ * part at fault: return it from a {@link flatMap} continuation, with an
286
+ * offset taken from {@link location} or {@link withSpan}. Like any failure,
287
+ * one at a later offset is preferred over the failures of other
288
+ * alternatives (the furthest failure wins). The offset is clamped to the
289
+ * input.
290
+ *
291
+ * @example
292
+ * ```ts
293
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
294
+ *
295
+ * const tag = (open: string) => Parser.delimited(Token.literal(open), Ascii.alpha1, Token.literal(">"))
296
+ * const element = Parser.seq(tag("<"), Parser.location, tag("</")).pipe(
297
+ * Parser.flatMap(([start, at, end]) =>
298
+ * start === end ? Parser.succeed(start) : Parser.failAt(at, `expected </${start}>`)
299
+ * )
300
+ * )
301
+ * // The error points at the end tag (offset 3), not at the start of the element
302
+ * Parser.parse(element, "<a></b>")
303
+ * ```
304
+ *
305
+ * @since 0.5.0
306
+ * @category constructors
307
+ */
308
+ const failAt = (offset, error) => described((state) => state.failCause("Backtrack", "Fail", error, offset < 0 ? 0 : offset > state.end ? state.end : offset), "failAt", failBacktrack);
309
+ /**
228
310
  * A parser that always fails (backtracks) without a cause. This is winnow's
229
311
  * `fail` and nom's `fail()`.
230
312
  *
@@ -473,6 +555,50 @@ const tryMap = /*#__PURE__*/ dual(2, (self, f) => {
473
555
  });
474
556
  });
475
557
  /**
558
+ * Transforms the output of a parser with a function that returns
559
+ * `Input.Fail` to reject it: the allocation-free form of {@link tryMap} and
560
+ * {@link filterMap}, for checks that run on nearly every token. Compiled
561
+ * code compares the result with the sentinel instead of allocating and
562
+ * unwrapping a `Result` or an `Option` on every success.
563
+ *
564
+ * On failure the parser backtracks to where it started. With `orFailWith`,
565
+ * the user error it builds (only on failure) becomes the `cause` of the
566
+ * `ParseError`, as with `tryMap`; without, the failure has no cause, as
567
+ * with `filterMap`.
568
+ *
569
+ * @example
570
+ * ```ts
571
+ * import { Ascii, Input, Parser } from "@thomasfosterau/effect-parse"
572
+ *
573
+ * const byte = Ascii.decUint.pipe(
574
+ * Parser.mapOrFail(
575
+ * (n) => (n < 256 ? n : Input.Fail),
576
+ * (n) => `${n} is not a byte`
577
+ * )
578
+ * )
579
+ * Parser.parse(byte, "300") // fails with the cause "300 is not a byte"
580
+ * ```
581
+ *
582
+ * @since 0.5.0
583
+ * @category mapping
584
+ */
585
+ const mapOrFail = /*#__PURE__*/ dual((args) => isParser(args[0]), (self, f, orFailWith) => {
586
+ const step = self.step;
587
+ return described((state) => {
588
+ const start = state.pos;
589
+ const a = step(state);
590
+ if (a === Fail) return Fail;
591
+ const b = f(a);
592
+ if (b !== Fail) return b;
593
+ state.rewind(start);
594
+ return orFailWith === void 0 ? state.backtrack("Verify") : state.failCause("Backtrack", "Verify", orFailWith(a));
595
+ }, self.name, {
596
+ _tag: "MapOrFail",
597
+ self,
598
+ f
599
+ });
600
+ });
601
+ /**
476
602
  * Like {@link tryMap}, but the function may throw. Thrown values become the
477
603
  * `cause` of the error.
478
604
  *
@@ -632,6 +758,36 @@ const withSpan = (self) => {
632
758
  self
633
759
  });
634
760
  };
761
+ /**
762
+ * Transforms the output of a parser together with the offsets it consumed,
763
+ * `[start, end)`: the allocation-free spelling of
764
+ * `map(withSpan(p), ([a, span]) => f(a, span.start, span.end))`, which
765
+ * builds a tuple and a span object on every success. Compiled code passes
766
+ * the offsets to `f` directly.
767
+ *
768
+ * @example
769
+ * ```ts
770
+ * import { Ascii, Parser } from "@thomasfosterau/effect-parse"
771
+ *
772
+ * const name = Ascii.alpha1.pipe(Parser.mapSpan((text, start, end) => ({ text, start, end })))
773
+ * Parser.parse(name, "abc") // Result.succeed({ text: "abc", start: 0, end: 3 })
774
+ * ```
775
+ *
776
+ * @since 0.5.0
777
+ * @category consumed input
778
+ */
779
+ const mapSpan = /*#__PURE__*/ dual(2, (self, f) => {
780
+ const step = self.step;
781
+ return described((state) => {
782
+ const start = state.pos;
783
+ const a = step(state);
784
+ return a === Fail ? Fail : f(a, start, state.pos);
785
+ }, self.name, {
786
+ _tag: "MapSpan",
787
+ self,
788
+ f
789
+ });
790
+ });
635
791
  const changeMode = (self, from, to) => {
636
792
  const step = self.step;
637
793
  return described((state) => {
@@ -766,6 +922,181 @@ const expected = /*#__PURE__*/ dual(2, (self, description) => {
766
922
  });
767
923
  });
768
924
  /**
925
+ * Runs a parser without letting it add to the expectations of later errors
926
+ * (chumsky's `quiet`). Use it on skippers, such as whitespace and comments,
927
+ * that may appear between any two tokens: they stop by failing, and without
928
+ * `quiet` what they would have accepted (`" "`, `"(:"`, ...) is listed in
929
+ * every error raised after them.
930
+ *
931
+ * When the parser succeeds, the hints and the furthest failure it recorded
932
+ * are forgotten, as after a lookahead that succeeded. When it fails
933
+ * recoverably without consuming input, its failure expects nothing, so an
934
+ * enclosing `alt` or `opt` reports only the other alternatives. A failure
935
+ * after consuming input (an unterminated comment) or an unrecoverable one
936
+ * is kept as it is. `Parser.expected` cannot do this: it relabels a failure,
937
+ * and a skipper that matches zero or more times never fails.
938
+ *
939
+ * @example
940
+ * ```ts
941
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
942
+ *
943
+ * const comment = Parser.delimited(Token.literal("(:"), Token.takeTill(":"), Token.literal(":)"))
944
+ * const skip = Parser.quiet(Parser.repeat(Parser.alt(Ascii.multispace1, comment)))
945
+ * const number = Parser.preceded(skip, Ascii.digit1)
946
+ * // Fails expecting only a digit: without `quiet`, also whitespace and "(:"
947
+ * Parser.parse(number, " (: one :) x")
948
+ * ```
949
+ *
950
+ * @since 0.5.0
951
+ * @category error management
952
+ */
953
+ const quiet = (self) => {
954
+ const step = self.step;
955
+ return described((state) => {
956
+ const start = state.pos;
957
+ const hintOffset = state.hintOffset;
958
+ const hintExpected = state.hintExpected;
959
+ const deepest = state.deepest;
960
+ const a = step(state);
961
+ if (a !== Fail) {
962
+ state.hintOffset = hintOffset;
963
+ state.hintExpected = hintExpected;
964
+ state.deepest = deepest;
965
+ return a;
966
+ }
967
+ if (state.mode === "Backtrack" && state.failure.offset === start) {
968
+ const f = state.failure;
969
+ const expected = hintOffset === start ? hintExpected : [];
970
+ state.hintOffset = hintOffset;
971
+ state.hintExpected = hintExpected;
972
+ state.deepest = deepest;
973
+ state.failure = new Failure(f.offset, f.kind, expected, f.context, f.hasCause, f.cause, f.message, f.needed);
974
+ }
975
+ return a;
976
+ }, self.name, {
977
+ _tag: "Quiet",
978
+ self
979
+ });
980
+ };
981
+ /**
982
+ * What a fast path (see {@link withFastPath}) returns to leave the input at
983
+ * the current offset to its grammar.
984
+ *
985
+ * @since 0.5.0
986
+ * @category fast paths
987
+ */
988
+ const Defer = Defer$1;
989
+ /**
990
+ * Puts a hand-written fast path in front of a grammar, which stays the
991
+ * specification: `fast` runs on the state, at the offset where the grammar
992
+ * would, and either returns the value the grammar would return, with
993
+ * `state.pos` where the grammar would stop, or returns {@link Defer}, and
994
+ * the grammar runs from that offset instead. Use it where a scanner written
995
+ * by hand (`indexOf`, a lookup table, interned names) beats compiled code on
996
+ * the tokens that dominate an input, and let the grammar handle everything
997
+ * else.
998
+ *
999
+ * The fast path runs in compiled code only (see the `Compiler` module).
1000
+ * Interpreted runs, and so every error, every `Syntax` tree and every trace,
1001
+ * come from the grammar alone, so they are exactly the grammar's. It must
1002
+ * defer wherever it would not return exactly what the grammar returns, and
1003
+ * on partial input wherever the grammar might report `Incomplete`; check it
1004
+ * against the grammar with {@link verifyFastPath}.
1005
+ *
1006
+ * @example
1007
+ * ```ts
1008
+ * import { Compiler, Parser, Token } from "@thomasfosterau/effect-parse"
1009
+ *
1010
+ * const text = Token.takeTill("<", { min: 1 })
1011
+ * // The same run, found with `indexOf`; anything else (an empty run, the end of a partial input) defers
1012
+ * const fastText = Compiler.jit(
1013
+ * Parser.withFastPath(text, (state) => {
1014
+ * const end = state.input.indexOf("<", state.pos)
1015
+ * if (end <= state.pos || end > state.end) return Parser.Defer
1016
+ * const value = state.input.slice(state.pos, end)
1017
+ * state.pos = end
1018
+ * return value
1019
+ * })
1020
+ * )
1021
+ * ```
1022
+ *
1023
+ * @since 0.5.0
1024
+ * @category fast paths
1025
+ */
1026
+ const withFastPath = /*#__PURE__*/ dual(2, (grammar, fast) => described(grammar.step, grammar.name, {
1027
+ _tag: "FastPath",
1028
+ self: grammar,
1029
+ fast
1030
+ }));
1031
+ /**
1032
+ * Checks a fast path against its grammar (see {@link withFastPath}): on
1033
+ * every input, at every offset, on complete and on partial input, wherever
1034
+ * `fast` returns a value, the grammar (run interpreted) must return an
1035
+ * equal value (by `Equal.equals`, or `options.equivalence`) and stop at the
1036
+ * same offset. Returns every place where it doesn't, so a test asserts that
1037
+ * the result is empty; generated inputs, mutated and truncated, find the
1038
+ * most. Grammars that use services are not supported.
1039
+ *
1040
+ * @example
1041
+ * ```ts
1042
+ * import { Input, Parser, Token } from "@thomasfosterau/effect-parse"
1043
+ *
1044
+ * const text = Token.takeTill("<", { min: 1 })
1045
+ * const fast = (state: Input.State<string>) => {
1046
+ * const end = state.input.indexOf("<", state.pos)
1047
+ * if (end <= state.pos || end > state.end) return Parser.Defer
1048
+ * const value = state.input.slice(state.pos, end)
1049
+ * state.pos = end
1050
+ * return value
1051
+ * }
1052
+ * Parser.verifyFastPath(text, fast, ["a<b", "<", "abc"]) // []
1053
+ * ```
1054
+ *
1055
+ * @since 0.5.0
1056
+ * @category fast paths
1057
+ */
1058
+ const verifyFastPath = (grammar, fast, inputs, options) => {
1059
+ const equivalence = options?.equivalence ?? Equal.equals;
1060
+ const mismatches = [];
1061
+ for (const input of inputs) {
1062
+ const length = new State(input).end;
1063
+ for (let offset = 0; offset <= length; offset++) for (const partial of [false, true]) {
1064
+ const mismatch = checkFastPath(grammar, fast, input, offset, partial, equivalence);
1065
+ if (mismatch !== void 0) mismatches.push(mismatch);
1066
+ }
1067
+ }
1068
+ return mismatches;
1069
+ };
1070
+ /** Where `fast` disagrees with `grammar` at `offset`, if it does (see {@link verifyFastPath}). */
1071
+ const checkFastPath = (grammar, fast, input, offset, partial, equivalence) => {
1072
+ const state = new State(input, {
1073
+ offset,
1074
+ partial
1075
+ });
1076
+ const a = fast(state);
1077
+ if (a === Defer) return void 0;
1078
+ const expected = new State(input, {
1079
+ offset,
1080
+ partial
1081
+ });
1082
+ const b = Stack.run(grammar.step, expected);
1083
+ if (b !== Fail && expected.pos === state.pos && equivalence(a, b)) return void 0;
1084
+ const grammarOutcome = b === Fail ? { failed: expected.mode } : {
1085
+ value: b,
1086
+ end: expected.pos
1087
+ };
1088
+ return {
1089
+ input,
1090
+ offset,
1091
+ partial,
1092
+ fast: {
1093
+ value: a,
1094
+ end: state.pos
1095
+ },
1096
+ grammar: grammarOutcome
1097
+ };
1098
+ };
1099
+ /**
769
1100
  * Transforms the user error (cause) of a parser's failures.
770
1101
  *
771
1102
  * @since 0.1.0
@@ -952,20 +1283,50 @@ const allConsuming = (self) => terminated(self, eof);
952
1283
  * @category sequencing
953
1284
  */
954
1285
  const seq = (...parsers) => {
1286
+ const step = tupleStep(parsers.map((p) => p.step));
1287
+ return described(step, "seq", {
1288
+ _tag: "Seq",
1289
+ parsers,
1290
+ output: "tuple"
1291
+ });
1292
+ };
1293
+ /**
1294
+ * Runs parsers in sequence and combines their outputs with `f`, which
1295
+ * receives one argument per parser: the allocation-free spelling of
1296
+ * `map(seq(...parsers), ([a, b, ...]) => f(a, b, ...))`, which builds a
1297
+ * tuple on every success. Compiled code calls `f` with the values directly.
1298
+ * {@link zipWith} is the two-parser, pipeable form.
1299
+ *
1300
+ * @example
1301
+ * ```ts
1302
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
1303
+ *
1304
+ * const point = Parser.seqWith(
1305
+ * [Ascii.decInt, Token.literal(","), Ascii.decInt],
1306
+ * (x, _, y) => ({ x, y })
1307
+ * )
1308
+ * Parser.parse(point, "3,-4") // Result.succeed({ x: 3, y: -4 })
1309
+ * ```
1310
+ *
1311
+ * @since 0.5.0
1312
+ * @category sequencing
1313
+ */
1314
+ const seqWith = (parsers, f) => {
955
1315
  const steps = parsers.map((p) => p.step);
956
1316
  const n = steps.length;
1317
+ const g = f;
957
1318
  return described((state) => {
958
- const out = new Array(n);
1319
+ const values = new Array(n);
959
1320
  for (let i = 0; i < n; i++) {
960
1321
  const a = steps[i](state);
961
1322
  if (a === Fail) return Fail;
962
- out[i] = a;
1323
+ values[i] = a;
963
1324
  }
964
- return out;
965
- }, "seq", {
966
- _tag: "Seq",
1325
+ return g(...values);
1326
+ }, "seqWith", {
1327
+ _tag: "SeqWith",
967
1328
  parsers,
968
- output: "tuple"
1329
+ f
969
1330
  });
970
1331
  };
971
1332
  /**
@@ -977,7 +1338,7 @@ const seq = (...parsers) => {
977
1338
  * @category sequencing
978
1339
  */
979
1340
  const struct = (fields) => {
980
- const keys = Object.keys(fields);
1341
+ const keys = Rec.keys(fields);
981
1342
  const steps = keys.map((k) => fields[k].step);
982
1343
  const n = keys.length;
983
1344
  return described((state) => {
@@ -1336,9 +1697,8 @@ const dispatch = (selector, cases, otherwise) => {
1336
1697
  * @category choice
1337
1698
  */
1338
1699
  const permutation = (...parsers) => {
1339
- const steps = parsers.map((p) => p.step);
1340
- const n = steps.length;
1341
- return make((state) => {
1700
+ const n = parsers.length;
1701
+ return combinator(parsers, (steps) => (state) => {
1342
1702
  const ties = new TiedFailures();
1343
1703
  const done = new Array(n).fill(false);
1344
1704
  const out = new Array(n);
@@ -1367,11 +1727,7 @@ const permutation = (...parsers) => {
1367
1727
  }, "permutation");
1368
1728
  };
1369
1729
  const normalizeRange = (range) => {
1370
- if (range === void 0) return [0, Infinity];
1371
- if (Predicate.isNumber(range)) return [range, range];
1372
- const min = range.min ?? 0;
1373
- const max = range.max ?? Infinity;
1374
- if (min > max) throw new RangeError(`invalid range: min (${min}) > max (${max})`);
1730
+ const { max, min } = bounds(range);
1375
1731
  return [min, max];
1376
1732
  };
1377
1733
  const assertConsumes = (state, name) => state.failMessage("Cut", "Assert", `\`${name}\` parsers must always consume`);
@@ -1383,7 +1739,6 @@ const saveFold = (state, key, entry, acc, pos) => Resume.save(state, key, entry,
1383
1739
  const foldImpl = (self, range, init, f, name, node, cond) => {
1384
1740
  const [min, max] = normalizeRange(range);
1385
1741
  const step = self.step;
1386
- const checkLoop = max === Infinity;
1387
1742
  const key = new Key();
1388
1743
  return described((state) => {
1389
1744
  const entry = state.pos;
@@ -1413,7 +1768,7 @@ const foldImpl = (self, range, init, f, name, node, cond) => {
1413
1768
  recover(state, start);
1414
1769
  return Resume.done(state, key, entry, resumed !== void 0, acc);
1415
1770
  }
1416
- if (checkLoop && state.pos === start) return assertConsumes(state, name);
1771
+ if (state.pos === start) return assertConsumes(state, name);
1417
1772
  acc = f(acc, a);
1418
1773
  }
1419
1774
  return Resume.done(state, key, entry, resumed !== void 0, acc);
@@ -1435,9 +1790,9 @@ const foldImpl = (self, range, init, f, name, node, cond) => {
1435
1790
  * exactly that many times. With a literal positive `min`, the output is
1436
1791
  * typed as a non-empty array.
1437
1792
  *
1438
- * As in winnow, an unbounded repetition of a parser that succeeds without
1439
- * consuming input fails with an unrecoverable assertion instead of looping
1440
- * forever.
1793
+ * A parser that succeeds without consuming input fails the repetition with
1794
+ * an unrecoverable `Assert` failure, whatever the range (winnow checks only
1795
+ * unbounded ones): a bound can come from the input (`Binary.lengthRepeat`).
1441
1796
  *
1442
1797
  * @example
1443
1798
  * ```ts
@@ -1545,7 +1900,6 @@ const separated = /*#__PURE__*/ dual(isTwoParserArgs, (self, separator, range) =
1545
1900
  const [min, max] = normalizeRange(range);
1546
1901
  const step = self.step;
1547
1902
  const sepStep = separator.step;
1548
- const checkLoop = max === Infinity;
1549
1903
  const key = new Key();
1550
1904
  return described((state) => {
1551
1905
  const entry = state.pos;
@@ -1581,7 +1935,7 @@ const separated = /*#__PURE__*/ dual(isTwoParserArgs, (self, separator, range) =
1581
1935
  recover(state, start);
1582
1936
  return Resume.done(state, key, entry, resumed !== void 0, acc);
1583
1937
  }
1584
- if (checkLoop && state.pos === start) return assertConsumes(state, "separated");
1938
+ if (state.pos === start) return assertConsumes(state, "separated");
1585
1939
  const a = step(state);
1586
1940
  if (a === Fail) {
1587
1941
  if (state.mode === "Incomplete" && state.resume !== void 0) Resume.save(state, key, entry, {
@@ -1929,9 +2283,9 @@ const NotCompiled = /*#__PURE__*/ Symbol("NotCompiled");
1929
2283
  */
1930
2284
  const runCompiled = (self, state, whole, resumable = false) => {
1931
2285
  if (state.tracer !== void 0 || state.recovered !== void 0) return NotCompiled;
1932
- const step = Registry.stepFor(self, state, resumable);
1933
- if (step === void 0) return NotCompiled;
1934
- const a = Stack.run(step, state);
2286
+ const compiled = Registry.compiled(self, state.inputKind, resumable);
2287
+ if (compiled?.step === void 0) return NotCompiled;
2288
+ const a = Run.runCompiled(compiled.step, compiled.guarded, state);
1935
2289
  if (a === Fail || whole && state.pos < state.end) return Fail;
1936
2290
  return a;
1937
2291
  };
@@ -2051,6 +2405,7 @@ const parsePartial = (self, input, options) => {
2051
2405
  value: compiled,
2052
2406
  offset: state.pos
2053
2407
  });
2408
+ if (compiled === Fail && state.mode === "Incomplete") return Result.succeed(incompleteOf(state));
2054
2409
  if (compiled === Fail) state = makeState(input, options, true);
2055
2410
  const a = Stack.run(self.step, state);
2056
2411
  if (a !== Fail) return Result.succeed({
@@ -2058,15 +2413,17 @@ const parsePartial = (self, input, options) => {
2058
2413
  value: a,
2059
2414
  offset: state.pos
2060
2415
  });
2061
- if (state.mode === "Incomplete") {
2062
- const needed = state.failure.needed;
2063
- return Result.succeed({
2064
- _tag: "Incomplete",
2065
- needed: needed > 0 ? Option.some(needed) : Option.none()
2066
- });
2067
- }
2416
+ if (state.mode === "Incomplete") return Result.succeed(incompleteOf(state));
2068
2417
  return Result.fail(toError(state));
2069
2418
  };
2419
+ /** The `Incomplete` result of a state whose parse ran out of partial input. */
2420
+ const incompleteOf = (state) => {
2421
+ const needed = state.failure.needed;
2422
+ return {
2423
+ _tag: "Incomplete",
2424
+ needed: needed > 0 ? Option.some(needed) : Option.none()
2425
+ };
2426
+ };
2070
2427
  /**
2071
2428
  * Runs a parser against a complete input as an `Effect`. Services required
2072
2429
  * by the parser are taken from the Effect's context.
@@ -2165,6 +2522,6 @@ const parseRecoverable = (self, input, options) => {
2165
2522
  errors
2166
2523
  };
2167
2524
  };
2168
- export { Do, TypeId, allConsuming, alt, as, asSome, asVoid, backtrack, bind, bindTo, complete, cond, consoleTracer, context, cut, delimited, described, dispatch, empty, eof, expected, fail, failMessage, filter, filterMap, filterOrFail, flatMap, fold, fromEntries, gen, isParser, let_ as let, location, make, map, mapError, mapParser, named, not, opt, optional, orElse, orElseSucceed, orFail, parse, parseEffect, parsePartial, parsePeek, parseRecoverable, parseSync, peek, permutation, preceded, provide, provideService, reject, repeat, repeatTill, resumeAfter, retryAfter, run, separated, separatedFoldl1, separatedFoldr1, separatedPair, seq, service, span, struct, succeed, suspend, sync, taken, tap, terminated, todo, trace, tryMap, tryMapUnknown, void_ as void, void_, withSpan, withTaken, zip, zipLeft, zipRight, zipWith };
2525
+ export { Defer, Do, TypeId, allConsuming, alt, as, asSome, asVoid, backtrack, bind, bindTo, combinator, complete, cond, consoleTracer, context, cut, delimited, described, dispatch, empty, eof, expected, fail, failAt, failMessage, filter, filterMap, filterOrFail, flatMap, fold, fromEntries, gen, isParser, let_ as let, location, make, map, mapError, mapOrFail, mapParser, mapSpan, named, not, opt, optional, orElse, orElseSucceed, orFail, parse, parseEffect, parsePartial, parsePeek, parseRecoverable, parseSync, peek, permutation, preceded, provide, provideService, quiet, reject, repeat, repeatTill, resumeAfter, retryAfter, run, separated, separatedFoldl1, separatedFoldr1, separatedPair, seq, seqWith, service, span, struct, succeed, suspend, sync, taken, tap, terminated, todo, trace, tryMap, tryMapUnknown, verifyFastPath, void_ as void, void_, withFastPath, withSpan, withTaken, zip, zipLeft, zipRight, zipWith };
2169
2526
 
2170
2527
  //# sourceMappingURL=Parser.js.map