@thomasfosterau/effect-parse 0.2.0 → 0.3.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/README.md +44 -1
- package/dist/Expression.js +17 -10
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +48 -3
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +102 -10
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +1 -1
- package/dist/Nom.d.ts.map +1 -1
- package/dist/ParseError.js +0 -1
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +32 -8
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts +198 -0
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +644 -15
- package/dist/ParserSchema.js.map +1 -1
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +11 -1
- package/dist/internal/stack.js.map +1 -1
- package/dist/internal/syntax.d.ts +1 -0
- package/dist/internal/syntax.d.ts.map +1 -1
- package/dist/internal/syntax.js +4 -0
- package/dist/internal/syntax.js.map +1 -1
- package/dist/internal/ties.d.ts +30 -0
- package/dist/internal/ties.d.ts.map +1 -0
- package/dist/internal/ties.js +67 -0
- package/dist/internal/ties.js.map +1 -0
- package/package.json +1 -1
- package/src/Expression.ts +20 -17
- package/src/Input.ts +107 -10
- package/src/ParseError.ts +1 -1
- package/src/Parser.ts +41 -9
- package/src/ParserSchema.ts +982 -23
- package/src/internal/stack.ts +6 -1
- package/src/internal/syntax.ts +5 -0
- package/src/internal/ties.ts +83 -0
package/src/internal/stack.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* engine's overflow error and report a `TooDeep` failure instead, keeping
|
|
8
8
|
* their `Result` contract on untrusted input.
|
|
9
9
|
*/
|
|
10
|
+
import * as Predicate from "effect/Predicate"
|
|
10
11
|
import type { Fail, State } from "../Input.js"
|
|
11
12
|
|
|
12
13
|
/** The message of a `TooDeep` failure. */
|
|
@@ -19,7 +20,11 @@ const tooDeepMessage = "input is nested too deeply: parsing exhausted the call s
|
|
|
19
20
|
*/
|
|
20
21
|
export const isStackOverflow = (e: unknown): boolean =>
|
|
21
22
|
(e instanceof RangeError && /call stack/i.test(e.message)) ||
|
|
22
|
-
(e
|
|
23
|
+
(Predicate.hasProperty(e, "name") &&
|
|
24
|
+
Predicate.hasProperty(e, "message") &&
|
|
25
|
+
e.name === "InternalError" &&
|
|
26
|
+
Predicate.isString(e.message) &&
|
|
27
|
+
/recursion/i.test(e.message))
|
|
23
28
|
|
|
24
29
|
/**
|
|
25
30
|
* Runs `step`, turning a call-stack overflow into an unrecoverable
|
package/src/internal/syntax.ts
CHANGED
|
@@ -223,6 +223,11 @@ export class SyntaxState<I extends Input = Input> extends State<I> implements Bu
|
|
|
223
223
|
return super.failWith(mode, failure)
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
override failTies(head: Failure, tie1: Failure, tie2: Failure | undefined): Fail {
|
|
227
|
+
if (head.offset > this.highWater) this.highWater = head.offset
|
|
228
|
+
return super.failTies(head, tie1, tie2)
|
|
229
|
+
}
|
|
230
|
+
|
|
226
231
|
override setHint(offset: number, expected: ReadonlyArray<string>): void {
|
|
227
232
|
if (offset > this.highWater) this.highWater = offset
|
|
228
233
|
super.setHint(offset, expected)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tied failures, merged only when read.
|
|
3
|
+
*
|
|
4
|
+
* A combinator that tries several parsers at the same position (an
|
|
5
|
+
* alternation, a permutation round, an operator table) reports the merge
|
|
6
|
+
* of their failures, left to right, as `mergeFailures` would. Most of the
|
|
7
|
+
* time one of the later attempts succeeds and the merge is thrown away, so
|
|
8
|
+
* {@link TiedFailures} keeps the last two failures tied with the running
|
|
9
|
+
* one unmerged, and {@link TiedFailures.fail} hands them to the state with
|
|
10
|
+
* `State.failTies`, which merges them on first read (#38, #44).
|
|
11
|
+
*
|
|
12
|
+
* `Parser.alt` does the same fold in locals, since it is the hottest
|
|
13
|
+
* combinator and cannot afford an accumulator per call.
|
|
14
|
+
*/
|
|
15
|
+
import type { Failure, Fail, Input, State } from "../Input.js"
|
|
16
|
+
import { mergeExpected, mergeTiedFailures } from "../Input.js"
|
|
17
|
+
|
|
18
|
+
/** The failures of a round of attempts, folded lazily. */
|
|
19
|
+
export class TiedFailures {
|
|
20
|
+
/** The running failure, before the pending ties are merged into it. */
|
|
21
|
+
failure: Failure | undefined = undefined
|
|
22
|
+
/** The oldest pending tie, at `failure`'s offset. */
|
|
23
|
+
tie1: Failure | undefined = undefined
|
|
24
|
+
/** The newest pending tie, at `failure`'s offset. */
|
|
25
|
+
tie2: Failure | undefined = undefined
|
|
26
|
+
|
|
27
|
+
/** Forgets every failure, for a new round. */
|
|
28
|
+
clear(): void {
|
|
29
|
+
this.failure = undefined
|
|
30
|
+
this.tie1 = undefined
|
|
31
|
+
this.tie2 = undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Folds in the failure the latest attempt left on `state`, taking over
|
|
36
|
+
* its own unmerged ties when it is the furthest.
|
|
37
|
+
*/
|
|
38
|
+
add<I extends Input>(state: State<I>): void {
|
|
39
|
+
const head = state.failureHead
|
|
40
|
+
const running = this.failure
|
|
41
|
+
if (running === undefined || head.offset > running.offset) {
|
|
42
|
+
this.failure = head
|
|
43
|
+
this.tie1 = state.failureTie1
|
|
44
|
+
this.tie2 = state.failureTie2
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
if (head.offset < running.offset) return
|
|
48
|
+
const failure = state.failure
|
|
49
|
+
const tie1 = this.tie1
|
|
50
|
+
if (tie1 === undefined) this.tie1 = failure
|
|
51
|
+
else if (this.tie2 === undefined) this.tie2 = failure
|
|
52
|
+
else {
|
|
53
|
+
// A third tie: fold the oldest pending one in, keeping the order
|
|
54
|
+
this.failure = mergeTiedFailures(running, tie1, mergeExpected(running.expected, tie1.expected))
|
|
55
|
+
this.tie1 = this.tie2
|
|
56
|
+
this.tie2 = failure
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fails `state` with the folded failure (recoverably), or with its
|
|
62
|
+
* current failure when nothing was folded.
|
|
63
|
+
*/
|
|
64
|
+
fail<I extends Input>(state: State<I>): Fail {
|
|
65
|
+
const failure = this.failure
|
|
66
|
+
if (failure === undefined) return state.failWith("Backtrack", state.failure)
|
|
67
|
+
const tie1 = this.tie1
|
|
68
|
+
return tie1 === undefined ? state.failWith("Backtrack", failure) : state.failTies(failure, tie1, this.tie2)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Records the folded expectations as a hint when the folded failure lies
|
|
73
|
+
* where `state` stopped (an attempt that fails without consuming input).
|
|
74
|
+
*/
|
|
75
|
+
hint<I extends Input>(state: State<I>): void {
|
|
76
|
+
const failure = this.failure
|
|
77
|
+
if (failure === undefined || failure.offset !== state.pos) return
|
|
78
|
+
let expected = failure.expected
|
|
79
|
+
if (this.tie1 !== undefined) expected = mergeExpected(expected, this.tie1.expected)
|
|
80
|
+
if (this.tie2 !== undefined) expected = mergeExpected(expected, this.tie2.expected)
|
|
81
|
+
state.setHint(failure.offset, expected)
|
|
82
|
+
}
|
|
83
|
+
}
|