@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/Input.ts
CHANGED
|
@@ -213,8 +213,31 @@ export class State<I extends Input = Input> {
|
|
|
213
213
|
context: Context.Context<never>
|
|
214
214
|
/** Mode of the latest failure. Only meaningful after a step returned {@link Fail}. */
|
|
215
215
|
mode: ErrMode = "Backtrack"
|
|
216
|
-
/**
|
|
217
|
-
|
|
216
|
+
/**
|
|
217
|
+
* The latest failure, kept unmerged: the failure is `failureHead` merged
|
|
218
|
+
* (as `mergeFailures` does) with `failureTie1` and then `failureTie2`, all
|
|
219
|
+
* at the same offset. `Parser.alt` records its tied branch failures here
|
|
220
|
+
* with {@link failTies} instead of merging them, so that an enclosing `alt`
|
|
221
|
+
* whose next branch succeeds never pays for the merge. Reading
|
|
222
|
+
* {@link failure} performs the merge, once.
|
|
223
|
+
*
|
|
224
|
+
* @internal
|
|
225
|
+
*/
|
|
226
|
+
failureHead: Failure
|
|
227
|
+
/** @internal */
|
|
228
|
+
failureTie1: Failure | undefined = undefined
|
|
229
|
+
/** @internal */
|
|
230
|
+
failureTie2: Failure | undefined = undefined
|
|
231
|
+
/** Backs {@link deepest}. @internal */
|
|
232
|
+
deepestFailure: Failure | undefined = undefined
|
|
233
|
+
/**
|
|
234
|
+
* Whether the furthest failure is the latest failure, still unmerged, so
|
|
235
|
+
* that {@link deepest} is that failure once merged. Only set while
|
|
236
|
+
* `failureTie1` is.
|
|
237
|
+
*
|
|
238
|
+
* @internal
|
|
239
|
+
*/
|
|
240
|
+
deepestIsLatest = false
|
|
218
241
|
/**
|
|
219
242
|
* The offset of a recoverable failure that occurred without consuming
|
|
220
243
|
* input (for example the last attempt of a `repeat`). Its
|
|
@@ -233,7 +256,14 @@ export class State<I extends Input = Input> {
|
|
|
233
256
|
* recorded inside it when it commits, and `Parser.peek` and `Parser.not`
|
|
234
257
|
* forget the failures of a lookahead that succeeded.
|
|
235
258
|
*/
|
|
236
|
-
deepest: Failure | undefined
|
|
259
|
+
get deepest(): Failure | undefined {
|
|
260
|
+
if (this.deepestIsLatest) this.settleFailure()
|
|
261
|
+
return this.deepestFailure
|
|
262
|
+
}
|
|
263
|
+
set deepest(failure: Failure | undefined) {
|
|
264
|
+
this.deepestIsLatest = false
|
|
265
|
+
this.deepestFailure = failure
|
|
266
|
+
}
|
|
237
267
|
/** When defined, `Parser.resumeAfter`/`retryAfter` record failures here. */
|
|
238
268
|
recovered: Array<Failure> | undefined = undefined
|
|
239
269
|
/** When defined, `Parser.trace` reports events here. */
|
|
@@ -274,7 +304,37 @@ export class State<I extends Input = Input> {
|
|
|
274
304
|
this.partial = options?.partial ?? false
|
|
275
305
|
this.context = options?.context ?? Context.empty()
|
|
276
306
|
this.inputKind = Predicate.isString(input) ? 0 : input instanceof Uint8Array ? 1 : 2
|
|
277
|
-
this.
|
|
307
|
+
this.failureHead = initialFailure
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Details of the latest failure. Only meaningful after a step returned {@link Fail}. */
|
|
311
|
+
get failure(): Failure {
|
|
312
|
+
if (this.failureTie1 !== undefined) this.settleFailure()
|
|
313
|
+
return this.failureHead
|
|
314
|
+
}
|
|
315
|
+
set failure(failure: Failure) {
|
|
316
|
+
if (this.deepestIsLatest) this.settleFailure()
|
|
317
|
+
this.failureHead = failure
|
|
318
|
+
this.failureTie1 = undefined
|
|
319
|
+
this.failureTie2 = undefined
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Merges the latest failure's ties into it (see {@link failureHead}). */
|
|
323
|
+
private settleFailure(): void {
|
|
324
|
+
let failure = this.failureHead
|
|
325
|
+
const tie1 = this.failureTie1
|
|
326
|
+
if (tie1 !== undefined) {
|
|
327
|
+
failure = mergeTiedFailures(failure, tie1, mergeExpected(failure.expected, tie1.expected))
|
|
328
|
+
const tie2 = this.failureTie2
|
|
329
|
+
if (tie2 !== undefined) failure = mergeTiedFailures(failure, tie2, mergeExpected(failure.expected, tie2.expected))
|
|
330
|
+
this.failureHead = failure
|
|
331
|
+
this.failureTie1 = undefined
|
|
332
|
+
this.failureTie2 = undefined
|
|
333
|
+
}
|
|
334
|
+
if (this.deepestIsLatest) {
|
|
335
|
+
this.deepestFailure = failure
|
|
336
|
+
this.deepestIsLatest = false
|
|
337
|
+
}
|
|
278
338
|
}
|
|
279
339
|
|
|
280
340
|
/**
|
|
@@ -317,10 +377,13 @@ export class State<I extends Input = Input> {
|
|
|
317
377
|
this.partial = options?.partial ?? false
|
|
318
378
|
this.context = options?.context ?? Context.empty()
|
|
319
379
|
this.mode = "Backtrack"
|
|
320
|
-
this.
|
|
380
|
+
this.failureHead = initialFailure
|
|
381
|
+
this.failureTie1 = undefined
|
|
382
|
+
this.failureTie2 = undefined
|
|
321
383
|
this.hintOffset = -1
|
|
322
384
|
this.hintExpected = empty
|
|
323
|
-
this.
|
|
385
|
+
this.deepestFailure = undefined
|
|
386
|
+
this.deepestIsLatest = false
|
|
324
387
|
this.recovered = undefined
|
|
325
388
|
this.tracer = undefined
|
|
326
389
|
this.depth = 0
|
|
@@ -410,10 +473,34 @@ export class State<I extends Input = Input> {
|
|
|
410
473
|
*/
|
|
411
474
|
failWith(mode: ErrMode, failure: Failure): Fail {
|
|
412
475
|
this.mode = mode
|
|
413
|
-
this.
|
|
414
|
-
|
|
415
|
-
this.
|
|
476
|
+
if (this.deepestIsLatest) {
|
|
477
|
+
// The furthest failure is the latest one: it is replaced below, unmerged, if `failure` is as far
|
|
478
|
+
if (mode === "Backtrack" && failure.offset >= this.failureHead.offset) this.deepestIsLatest = false
|
|
479
|
+
else this.settleFailure()
|
|
416
480
|
}
|
|
481
|
+
this.failureHead = failure
|
|
482
|
+
this.failureTie1 = undefined
|
|
483
|
+
this.failureTie2 = undefined
|
|
484
|
+
if (mode === "Backtrack" && (this.deepestFailure === undefined || failure.offset >= this.deepestFailure.offset)) {
|
|
485
|
+
this.deepestFailure = failure
|
|
486
|
+
}
|
|
487
|
+
return Fail
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Records a recoverable failure made of tied failures, merged only when
|
|
492
|
+
* read: `failWith("Backtrack", f)` where `f` merges `head` with `tie1`
|
|
493
|
+
* and then `tie2` (all at the same offset), as `mergeFailures` would.
|
|
494
|
+
*
|
|
495
|
+
* @internal
|
|
496
|
+
*/
|
|
497
|
+
failTies(head: Failure, tie1: Failure, tie2: Failure | undefined): Fail {
|
|
498
|
+
this.mode = "Backtrack"
|
|
499
|
+
if (this.deepestIsLatest && head.offset < this.failureHead.offset) this.settleFailure()
|
|
500
|
+
this.failureHead = head
|
|
501
|
+
this.failureTie1 = tie1
|
|
502
|
+
this.failureTie2 = tie2
|
|
503
|
+
this.deepestIsLatest = this.deepestFailure === undefined || head.offset >= this.deepestFailure.offset
|
|
417
504
|
return Fail
|
|
418
505
|
}
|
|
419
506
|
|
|
@@ -492,7 +579,17 @@ export const mergeExpected = (a: ReadonlyArray<string>, b: ReadonlyArray<string>
|
|
|
492
579
|
export const mergeFailures = (a: Failure, b: Failure): Failure => {
|
|
493
580
|
if (a.offset > b.offset) return a
|
|
494
581
|
if (b.offset > a.offset) return b
|
|
495
|
-
|
|
582
|
+
return mergeTiedFailures(a, b, mergeExpected(a.expected, b.expected))
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* {@link mergeFailures} for two failures at the same offset, given their
|
|
587
|
+
* merged expectations (`mergeExpected(a.expected, b.expected)`, possibly
|
|
588
|
+
* memoised by the caller).
|
|
589
|
+
*
|
|
590
|
+
* @internal
|
|
591
|
+
*/
|
|
592
|
+
export const mergeTiedFailures = (a: Failure, b: Failure, expected: ReadonlyArray<string>): Failure => {
|
|
496
593
|
if (expected === b.expected) return b
|
|
497
594
|
return new Failure(
|
|
498
595
|
b.offset,
|
package/src/ParseError.ts
CHANGED
|
@@ -289,7 +289,7 @@ export const inspectToken = (u: unknown): string => {
|
|
|
289
289
|
const causeMessage = (cause: unknown): string | undefined => {
|
|
290
290
|
if (cause === undefined) return undefined
|
|
291
291
|
if (Predicate.isString(cause)) return cause
|
|
292
|
-
|
|
292
|
+
// An `Error`'s message, or any other object's string `message`
|
|
293
293
|
if (Predicate.isObjectOrArray(cause) && "message" in cause && Predicate.isString(cause.message)) {
|
|
294
294
|
return cause.message
|
|
295
295
|
}
|
package/src/Parser.ts
CHANGED
|
@@ -40,7 +40,7 @@ import { pipeArguments } from "effect/Pipeable"
|
|
|
40
40
|
import * as Result from "effect/Result"
|
|
41
41
|
import type { Contravariant, Covariant, Simplify } from "effect/Types"
|
|
42
42
|
import type { ErrMode, Input, Slice, Source, TraceEvent } from "./Input.js"
|
|
43
|
-
import { Fail, Failure, mergeExpected, mergeFailures, State } from "./Input.js"
|
|
43
|
+
import { Fail, Failure, mergeExpected, mergeFailures, mergeTiedFailures, State } from "./Input.js"
|
|
44
44
|
import type { ErrorKind } from "./ParseError.js"
|
|
45
45
|
import * as ParseError from "./ParseError.js"
|
|
46
46
|
import * as Registry from "./internal/compiler/registry.js"
|
|
@@ -48,6 +48,7 @@ import * as Entries from "./internal/entries.js"
|
|
|
48
48
|
import * as Resume from "./internal/resume.js"
|
|
49
49
|
import * as Run from "./internal/run.js"
|
|
50
50
|
import * as Stack from "./internal/stack.js"
|
|
51
|
+
import { TiedFailures } from "./internal/ties.js"
|
|
51
52
|
import type { Node } from "./internal/node.js"
|
|
52
53
|
import { bounds, NodeId } from "./internal/node.js"
|
|
53
54
|
|
|
@@ -1785,19 +1786,48 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
|
|
|
1785
1786
|
return described(
|
|
1786
1787
|
(state: State<any>) => {
|
|
1787
1788
|
const start = state.pos
|
|
1789
|
+
// The branches' failures merged left to right, as `mergeFailures` would,
|
|
1790
|
+
// except that the last two failures tied with `failure` wait in `tie1`
|
|
1791
|
+
// and `tie2` and are merged only when needed: when a third tie arrives,
|
|
1792
|
+
// when a succeeding branch stops where they failed (for the hint), or
|
|
1793
|
+
// when the merge is read from the state after `failTies`. An `alt` whose
|
|
1794
|
+
// later branch consumes input discards them unmerged.
|
|
1788
1795
|
let failure: Failure | undefined
|
|
1796
|
+
let tie1: Failure | undefined
|
|
1797
|
+
let tie2: Failure | undefined
|
|
1789
1798
|
for (let i = 0; i < n; i++) {
|
|
1790
1799
|
const a = steps[i](state)
|
|
1791
1800
|
if (a !== Fail) {
|
|
1792
|
-
if (failure !== undefined && failure.offset === state.pos)
|
|
1801
|
+
if (failure !== undefined && failure.offset === state.pos) {
|
|
1802
|
+
let expected = failure.expected
|
|
1803
|
+
if (tie1 !== undefined) expected = mergeExpected(expected, tie1.expected)
|
|
1804
|
+
if (tie2 !== undefined) expected = mergeExpected(expected, tie2.expected)
|
|
1805
|
+
state.setHint(failure.offset, expected)
|
|
1806
|
+
}
|
|
1793
1807
|
return a
|
|
1794
1808
|
}
|
|
1795
1809
|
if (state.mode !== "Backtrack") return Fail
|
|
1796
|
-
failure = failure === undefined ? state.failure : mergeFailures(failure, state.failure)
|
|
1797
1810
|
state.pos = start
|
|
1811
|
+
// The branch's failure, whose own ties (from a nested `alt`) can be taken over unmerged
|
|
1812
|
+
const head = state.failureHead
|
|
1813
|
+
if (failure === undefined || head.offset > failure.offset) {
|
|
1814
|
+
failure = head
|
|
1815
|
+
tie1 = state.failureTie1
|
|
1816
|
+
tie2 = state.failureTie2
|
|
1817
|
+
} else if (head.offset === failure.offset) {
|
|
1818
|
+
const f = state.failure
|
|
1819
|
+
if (tie1 === undefined) tie1 = f
|
|
1820
|
+
else if (tie2 === undefined) tie2 = f
|
|
1821
|
+
else {
|
|
1822
|
+
failure = mergeTiedFailures(failure, tie1, mergeExpected(failure.expected, tie1.expected))
|
|
1823
|
+
tie1 = tie2
|
|
1824
|
+
tie2 = f
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1798
1827
|
}
|
|
1799
1828
|
// `failure` is set: there are at least two alternatives, and each one that fails sets it
|
|
1800
|
-
return state.failWith("Backtrack",
|
|
1829
|
+
if (failure === undefined) return state.failWith("Backtrack", state.failure)
|
|
1830
|
+
return tie1 === undefined ? state.failWith("Backtrack", failure) : state.failTies(failure, tie1, tie2)
|
|
1801
1831
|
},
|
|
1802
1832
|
"alt",
|
|
1803
1833
|
{ _tag: "Alt", parsers }
|
|
@@ -1911,11 +1941,13 @@ export const permutation = <const Ps extends ReadonlyArray<Parser.Any>>(
|
|
|
1911
1941
|
const steps = parsers.map((p) => p.step)
|
|
1912
1942
|
const n = steps.length
|
|
1913
1943
|
return make((state: State<any>) => {
|
|
1914
|
-
|
|
1944
|
+
// A round's failures, merged only if no parser matches in it (#44)
|
|
1945
|
+
const ties = new TiedFailures()
|
|
1915
1946
|
const done = new Array<boolean>(n).fill(false)
|
|
1947
|
+
const out = new Array(n)
|
|
1916
1948
|
let remaining = n
|
|
1917
1949
|
while (remaining > 0) {
|
|
1918
|
-
|
|
1950
|
+
ties.clear()
|
|
1919
1951
|
let matched = false
|
|
1920
1952
|
for (let i = 0; i < n; i++) {
|
|
1921
1953
|
if (done[i]) continue
|
|
@@ -1929,11 +1961,11 @@ export const permutation = <const Ps extends ReadonlyArray<Parser.Any>>(
|
|
|
1929
1961
|
break
|
|
1930
1962
|
}
|
|
1931
1963
|
if (state.mode !== "Backtrack") return Fail
|
|
1932
|
-
failure = failure === undefined ? state.failure : mergeFailures(failure, state.failure)
|
|
1933
1964
|
state.pos = start
|
|
1965
|
+
ties.add(state)
|
|
1934
1966
|
}
|
|
1935
|
-
//
|
|
1936
|
-
if (!matched) return
|
|
1967
|
+
// Some parser is not done yet, and each one that fails is folded in
|
|
1968
|
+
if (!matched) return ties.fail(state)
|
|
1937
1969
|
}
|
|
1938
1970
|
return out as any
|
|
1939
1971
|
}, "permutation")
|