@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.
- package/CHANGELOG.md +202 -0
- package/README.md +56 -14
- package/dist/Ascii.d.ts +11 -4
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +12 -4
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +5 -2
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +83 -37
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +11 -4
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +74 -2
- package/dist/Compiler.d.ts.map +1 -1
- package/dist/Compiler.js +78 -1
- package/dist/Compiler.js.map +1 -1
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +86 -74
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +56 -5
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +70 -1
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +5 -15
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js.map +1 -1
- package/dist/ParseError.d.ts +69 -11
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +129 -28
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +375 -29
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +394 -37
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +2 -3
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +122 -14
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +293 -62
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +23 -5
- package/dist/Syntax.d.ts.map +1 -1
- package/dist/Syntax.js +1 -1
- package/dist/Syntax.js.map +1 -1
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +21 -15
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +65 -11
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +73 -26
- package/dist/Token.js.map +1 -1
- package/dist/internal/compiler/closure.d.ts +1 -1
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +263 -113
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +3 -1
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +3 -2
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +9 -2
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/graph.d.ts +39 -0
- package/dist/internal/compiler/graph.d.ts.map +1 -0
- package/dist/internal/compiler/graph.js +78 -0
- package/dist/internal/compiler/graph.js.map +1 -0
- package/dist/internal/compiler/jit.d.ts +5 -0
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +208 -106
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +11 -3
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js.map +1 -1
- package/dist/internal/compiler/search.d.ts +32 -0
- package/dist/internal/compiler/search.d.ts.map +1 -0
- package/dist/internal/compiler/search.js +68 -0
- package/dist/internal/compiler/search.js.map +1 -0
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +1 -1
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/compiler/wasm.d.ts +8 -0
- package/dist/internal/compiler/wasm.d.ts.map +1 -0
- package/dist/internal/compiler/wasm.js +2840 -0
- package/dist/internal/compiler/wasm.js.map +1 -0
- package/dist/internal/compiler/wasmAsm.d.ts +199 -0
- package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
- package/dist/internal/compiler/wasmAsm.js +427 -0
- package/dist/internal/compiler/wasmAsm.js.map +1 -0
- package/dist/internal/entries.d.ts +3 -1
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js.map +1 -1
- package/dist/internal/node.d.ts +77 -3
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +54 -10
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js.map +1 -1
- package/dist/internal/run.d.ts +14 -0
- package/dist/internal/run.d.ts.map +1 -1
- package/dist/internal/run.js +53 -11
- package/dist/internal/run.js.map +1 -1
- package/dist/internal/stack.d.ts +14 -0
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +24 -2
- package/dist/internal/stack.js.map +1 -1
- package/dist/internal/syntax.d.ts +4 -4
- package/dist/internal/syntax.d.ts.map +1 -1
- package/dist/internal/syntax.js +18 -10
- package/dist/internal/syntax.js.map +1 -1
- package/dist/internal/text.d.ts +32 -0
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +68 -14
- package/dist/internal/text.js.map +1 -1
- package/dist/internal/tokens.d.ts +20 -0
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +72 -39
- package/dist/internal/tokens.js.map +1 -1
- package/dist/internal/tuple.d.ts +6 -0
- package/dist/internal/tuple.d.ts.map +1 -0
- package/dist/internal/tuple.js +170 -0
- package/dist/internal/tuple.js.map +1 -0
- package/package.json +18 -10
- package/src/Ascii.ts +47 -12
- package/src/Binary.ts +174 -73
- package/src/Bits.ts +16 -6
- package/src/Compiler.ts +101 -10
- package/src/Expression.ts +94 -81
- package/src/Input.ts +99 -7
- package/src/Nom.ts +15 -5
- package/src/ParseError.ts +209 -52
- package/src/Parser.ts +684 -143
- package/src/ParserSchema.ts +1 -2
- package/src/ParserStream.ts +361 -68
- package/src/Syntax.ts +60 -36
- package/src/Text.ts +32 -27
- package/src/Token.ts +152 -34
- package/src/internal/compiler/closure.ts +371 -153
- package/src/internal/compiler/entries.ts +4 -2
- package/src/internal/compiler/first.ts +16 -0
- package/src/internal/compiler/graph.ts +96 -0
- package/src/internal/compiler/jit.ts +319 -169
- package/src/internal/compiler/registry.ts +10 -2
- package/src/internal/compiler/search.ts +68 -0
- package/src/internal/compiler/sets.ts +7 -3
- package/src/internal/compiler/wasm.ts +3276 -0
- package/src/internal/compiler/wasmAsm.ts +484 -0
- package/src/internal/entries.ts +8 -2
- package/src/internal/node.ts +103 -7
- package/src/internal/numbers.ts +3 -1
- package/src/internal/run.ts +73 -12
- package/src/internal/stack.ts +25 -1
- package/src/internal/syntax.ts +39 -18
- package/src/internal/text.ts +97 -9
- package/src/internal/tokens.ts +106 -14
- 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 {
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
1323
|
+
values[i] = a;
|
|
963
1324
|
}
|
|
964
|
-
return
|
|
965
|
-
}, "
|
|
966
|
-
_tag: "
|
|
1325
|
+
return g(...values);
|
|
1326
|
+
}, "seqWith", {
|
|
1327
|
+
_tag: "SeqWith",
|
|
967
1328
|
parsers,
|
|
968
|
-
|
|
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 =
|
|
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
|
|
1340
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
*
|
|
1439
|
-
*
|
|
1440
|
-
*
|
|
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 (
|
|
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
|
|
1933
|
-
if (step === void 0) return NotCompiled;
|
|
1934
|
-
const a =
|
|
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
|