@thomasfosterau/effect-parse 0.2.0 → 0.4.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/Ascii.d.ts +0 -2
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +6 -6
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +0 -2
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +1 -2
- package/dist/Bits.d.ts +0 -2
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +2 -3
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +0 -2
- package/dist/Compiler.d.ts.map +1 -1
- package/dist/Compiler.js +1 -2
- package/dist/Encoding.d.ts +0 -2
- package/dist/Encoding.d.ts.map +1 -1
- package/dist/Encoding.js +1 -2
- package/dist/Expression.d.ts +0 -2
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +61 -18
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +80 -7
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +149 -18
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +1 -3
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js +1 -2
- package/dist/ParseError.d.ts +0 -2
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +6 -10
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +2 -2
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +58 -32
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts +198 -2
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +646 -18
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +0 -2
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +15 -9
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +0 -2
- package/dist/Syntax.d.ts.map +1 -1
- package/dist/Syntax.js +13 -11
- package/dist/Syntax.js.map +1 -1
- package/dist/Text.d.ts +0 -2
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +1 -2
- package/dist/Token.d.ts +0 -2
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +1 -2
- package/dist/internal/compiler/closure.d.ts +5 -2
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +160 -92
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +0 -2
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +6 -7
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts +0 -2
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +10 -10
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/jit.d.ts +0 -2
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +52 -51
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +0 -2
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js +1 -2
- package/dist/internal/compiler/sets.d.ts +2 -2
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +8 -3
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/entries.d.ts +0 -2
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js +1 -2
- package/dist/internal/key.d.ts +10 -0
- package/dist/internal/key.d.ts.map +1 -0
- package/dist/internal/key.js +11 -0
- package/dist/internal/key.js.map +1 -0
- package/dist/internal/node.d.ts +11 -4
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +15 -3
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts +0 -2
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js +1 -2
- package/dist/internal/resume.d.ts +5 -6
- package/dist/internal/resume.d.ts.map +1 -1
- package/dist/internal/resume.js +1 -2
- package/dist/internal/resume.js.map +1 -1
- package/dist/internal/run.d.ts +0 -2
- package/dist/internal/run.d.ts.map +1 -1
- package/dist/internal/run.js +2 -3
- package/dist/internal/run.js.map +1 -1
- package/dist/internal/stack.d.ts +0 -2
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +12 -3
- package/dist/internal/stack.js.map +1 -1
- package/dist/internal/syntax.d.ts +30 -19
- package/dist/internal/syntax.d.ts.map +1 -1
- package/dist/internal/syntax.js +74 -63
- package/dist/internal/syntax.js.map +1 -1
- package/dist/internal/text.d.ts +5 -2
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +14 -5
- package/dist/internal/text.js.map +1 -1
- package/dist/internal/ties.d.ts +28 -0
- package/dist/internal/ties.d.ts.map +1 -0
- package/dist/internal/ties.js +66 -0
- package/dist/internal/ties.js.map +1 -0
- package/dist/internal/tokens.d.ts +10 -9
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +4 -8
- package/dist/internal/tokens.js.map +1 -1
- package/package.json +6 -6
- package/src/Ascii.ts +6 -5
- package/src/Bits.ts +1 -1
- package/src/Expression.ts +27 -23
- package/src/Input.ts +157 -21
- package/src/ParseError.ts +10 -8
- package/src/Parser.ts +67 -32
- package/src/ParserSchema.ts +982 -23
- package/src/ParserStream.ts +20 -8
- package/src/Syntax.ts +12 -9
- package/src/internal/compiler/closure.ts +202 -90
- package/src/internal/compiler/entries.ts +14 -11
- package/src/internal/compiler/first.ts +9 -8
- package/src/internal/compiler/jit.ts +95 -74
- package/src/internal/compiler/sets.ts +7 -0
- package/src/internal/key.ts +11 -0
- package/src/internal/node.ts +19 -2
- package/src/internal/resume.ts +5 -4
- package/src/internal/run.ts +1 -1
- package/src/internal/stack.ts +6 -1
- package/src/internal/syntax.ts +80 -84
- package/src/internal/text.ts +24 -4
- package/src/internal/ties.ts +83 -0
- package/src/internal/tokens.ts +20 -13
package/src/Input.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* @since 0.1.0
|
|
15
15
|
*/
|
|
16
|
+
import * as Arr from "effect/Array"
|
|
16
17
|
import * as Predicate from "effect/Predicate"
|
|
17
18
|
import * as Context from "effect/Context"
|
|
18
19
|
import type { Encoding } from "./Encoding.js"
|
|
@@ -80,7 +81,7 @@ export const fromSource: {
|
|
|
80
81
|
return source
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
const isArrayBufferLike = (u:
|
|
84
|
+
const isArrayBufferLike = (u: Input | BinaryInput): u is ArrayBufferLike =>
|
|
84
85
|
u instanceof ArrayBuffer || (globalThis.SharedArrayBuffer !== undefined && u instanceof SharedArrayBuffer)
|
|
85
86
|
|
|
86
87
|
/**
|
|
@@ -200,9 +201,11 @@ export interface TraceEvent {
|
|
|
200
201
|
export class State<I extends Input = Input> {
|
|
201
202
|
/** The full input being parsed. */
|
|
202
203
|
readonly input: I
|
|
203
|
-
/**
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
/**
|
|
205
|
+
* The current offset. Parsers advance it by assigning it; moving it back
|
|
206
|
+
* (backtracking, or after a lookahead) goes through {@link rewind}.
|
|
207
|
+
*/
|
|
208
|
+
pos: number
|
|
206
209
|
/** The end offset (the input's length; in bits inside `Bits.bits`). */
|
|
207
210
|
end: number
|
|
208
211
|
/** 0 = string, 1 = Uint8Array, 2 = array. */
|
|
@@ -213,8 +216,31 @@ export class State<I extends Input = Input> {
|
|
|
213
216
|
context: Context.Context<never>
|
|
214
217
|
/** Mode of the latest failure. Only meaningful after a step returned {@link Fail}. */
|
|
215
218
|
mode: ErrMode = "Backtrack"
|
|
216
|
-
/**
|
|
217
|
-
|
|
219
|
+
/**
|
|
220
|
+
* The latest failure, kept unmerged: the failure is `failureHead` merged
|
|
221
|
+
* (as `mergeFailures` does) with `failureTie1` and then `failureTie2`, all
|
|
222
|
+
* at the same offset. `Parser.alt` records its tied branch failures here
|
|
223
|
+
* with {@link failTies} instead of merging them, so that an enclosing `alt`
|
|
224
|
+
* whose next branch succeeds never pays for the merge. Reading
|
|
225
|
+
* {@link failure} performs the merge, once.
|
|
226
|
+
*
|
|
227
|
+
* @internal
|
|
228
|
+
*/
|
|
229
|
+
failureHead: Failure
|
|
230
|
+
/** @internal */
|
|
231
|
+
failureTie1: Failure | undefined = undefined
|
|
232
|
+
/** @internal */
|
|
233
|
+
failureTie2: Failure | undefined = undefined
|
|
234
|
+
/** Backs {@link deepest}. @internal */
|
|
235
|
+
deepestFailure: Failure | undefined = undefined
|
|
236
|
+
/**
|
|
237
|
+
* Whether the furthest failure is the latest failure, still unmerged, so
|
|
238
|
+
* that {@link deepest} is that failure once merged. Only set while
|
|
239
|
+
* `failureTie1` is.
|
|
240
|
+
*
|
|
241
|
+
* @internal
|
|
242
|
+
*/
|
|
243
|
+
deepestIsLatest = false
|
|
218
244
|
/**
|
|
219
245
|
* The offset of a recoverable failure that occurred without consuming
|
|
220
246
|
* input (for example the last attempt of a `repeat`). Its
|
|
@@ -233,7 +259,14 @@ export class State<I extends Input = Input> {
|
|
|
233
259
|
* recorded inside it when it commits, and `Parser.peek` and `Parser.not`
|
|
234
260
|
* forget the failures of a lookahead that succeeded.
|
|
235
261
|
*/
|
|
236
|
-
deepest: Failure | undefined
|
|
262
|
+
get deepest(): Failure | undefined {
|
|
263
|
+
if (this.deepestIsLatest) this.settleFailure()
|
|
264
|
+
return this.deepestFailure
|
|
265
|
+
}
|
|
266
|
+
set deepest(failure: Failure | undefined) {
|
|
267
|
+
this.deepestIsLatest = false
|
|
268
|
+
this.deepestFailure = failure
|
|
269
|
+
}
|
|
237
270
|
/** When defined, `Parser.resumeAfter`/`retryAfter` record failures here. */
|
|
238
271
|
recovered: Array<Failure> | undefined = undefined
|
|
239
272
|
/** When defined, `Parser.trace` reports events here. */
|
|
@@ -253,7 +286,8 @@ export class State<I extends Input = Input> {
|
|
|
253
286
|
*/
|
|
254
287
|
resume: Map<object, Map<number, unknown>> | undefined = undefined
|
|
255
288
|
/**
|
|
256
|
-
* The tree builder of a `Syntax` parse; `undefined` otherwise.
|
|
289
|
+
* The tree builder of a `Syntax` parse; `undefined` otherwise. A builder
|
|
290
|
+
* rather than a subclass of `State`, so that every state has one shape.
|
|
257
291
|
*
|
|
258
292
|
* @internal
|
|
259
293
|
*/
|
|
@@ -274,7 +308,37 @@ export class State<I extends Input = Input> {
|
|
|
274
308
|
this.partial = options?.partial ?? false
|
|
275
309
|
this.context = options?.context ?? Context.empty()
|
|
276
310
|
this.inputKind = Predicate.isString(input) ? 0 : input instanceof Uint8Array ? 1 : 2
|
|
277
|
-
this.
|
|
311
|
+
this.failureHead = initialFailure
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Details of the latest failure. Only meaningful after a step returned {@link Fail}. */
|
|
315
|
+
get failure(): Failure {
|
|
316
|
+
if (this.failureTie1 !== undefined) this.settleFailure()
|
|
317
|
+
return this.failureHead
|
|
318
|
+
}
|
|
319
|
+
set failure(failure: Failure) {
|
|
320
|
+
if (this.deepestIsLatest) this.settleFailure()
|
|
321
|
+
this.failureHead = failure
|
|
322
|
+
this.failureTie1 = undefined
|
|
323
|
+
this.failureTie2 = undefined
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Merges the latest failure's ties into it (see {@link failureHead}). */
|
|
327
|
+
private settleFailure(): void {
|
|
328
|
+
let failure = this.failureHead
|
|
329
|
+
const tie1 = this.failureTie1
|
|
330
|
+
if (tie1 !== undefined) {
|
|
331
|
+
failure = mergeTiedFailures(failure, tie1, mergeExpected(failure.expected, tie1.expected))
|
|
332
|
+
const tie2 = this.failureTie2
|
|
333
|
+
if (tie2 !== undefined) failure = mergeTiedFailures(failure, tie2, mergeExpected(failure.expected, tie2.expected))
|
|
334
|
+
this.failureHead = failure
|
|
335
|
+
this.failureTie1 = undefined
|
|
336
|
+
this.failureTie2 = undefined
|
|
337
|
+
}
|
|
338
|
+
if (this.deepestIsLatest) {
|
|
339
|
+
this.deepestFailure = failure
|
|
340
|
+
this.deepestIsLatest = false
|
|
341
|
+
}
|
|
278
342
|
}
|
|
279
343
|
|
|
280
344
|
/**
|
|
@@ -317,10 +381,13 @@ export class State<I extends Input = Input> {
|
|
|
317
381
|
this.partial = options?.partial ?? false
|
|
318
382
|
this.context = options?.context ?? Context.empty()
|
|
319
383
|
this.mode = "Backtrack"
|
|
320
|
-
this.
|
|
384
|
+
this.failureHead = initialFailure
|
|
385
|
+
this.failureTie1 = undefined
|
|
386
|
+
this.failureTie2 = undefined
|
|
321
387
|
this.hintOffset = -1
|
|
322
388
|
this.hintExpected = empty
|
|
323
|
-
this.
|
|
389
|
+
this.deepestFailure = undefined
|
|
390
|
+
this.deepestIsLatest = false
|
|
324
391
|
this.recovered = undefined
|
|
325
392
|
this.tracer = undefined
|
|
326
393
|
this.depth = 0
|
|
@@ -329,6 +396,37 @@ export class State<I extends Input = Input> {
|
|
|
329
396
|
return this
|
|
330
397
|
}
|
|
331
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Moves the cursor back to `offset`, abandoning what was parsed after it:
|
|
401
|
+
* how a parser backtracks, or returns to where a lookahead started. A step
|
|
402
|
+
* that moves the cursor backwards must use this rather than assigning
|
|
403
|
+
* {@link pos}, so that a `Syntax` parse drops the nodes the abandoned
|
|
404
|
+
* attempt built. Moving forward is an assignment.
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```ts
|
|
408
|
+
* import { Input, Parser } from "@thomasfosterau/effect-parse"
|
|
409
|
+
*
|
|
410
|
+
* // Like `Parser.opt`, for one parser
|
|
411
|
+
* const optional = <A>(p: Parser.Parser<A>) =>
|
|
412
|
+
* Parser.make<A | undefined>((state) => {
|
|
413
|
+
* const start = state.pos
|
|
414
|
+
* const a = p.step(state)
|
|
415
|
+
* if (a !== Input.Fail) return a
|
|
416
|
+
* if (state.mode !== "Backtrack") return Input.Fail
|
|
417
|
+
* state.rewind(start)
|
|
418
|
+
* return undefined
|
|
419
|
+
* })
|
|
420
|
+
* ```
|
|
421
|
+
*
|
|
422
|
+
* @since 0.4.0
|
|
423
|
+
*/
|
|
424
|
+
rewind(offset: number): void {
|
|
425
|
+
const syntax = this.syntax
|
|
426
|
+
if (syntax !== undefined && offset < this.pos) syntax.rewound(this.pos, offset)
|
|
427
|
+
this.pos = offset
|
|
428
|
+
}
|
|
429
|
+
|
|
332
430
|
/**
|
|
333
431
|
* The number of input units (code units, bytes or elements) remaining.
|
|
334
432
|
*/
|
|
@@ -410,10 +508,36 @@ export class State<I extends Input = Input> {
|
|
|
410
508
|
*/
|
|
411
509
|
failWith(mode: ErrMode, failure: Failure): Fail {
|
|
412
510
|
this.mode = mode
|
|
413
|
-
this.
|
|
414
|
-
|
|
415
|
-
this.
|
|
511
|
+
if (this.deepestIsLatest) {
|
|
512
|
+
// The furthest failure is the latest one: it is replaced below, unmerged, if `failure` is as far
|
|
513
|
+
if (mode === "Backtrack" && failure.offset >= this.failureHead.offset) this.deepestIsLatest = false
|
|
514
|
+
else this.settleFailure()
|
|
416
515
|
}
|
|
516
|
+
this.failureHead = failure
|
|
517
|
+
this.failureTie1 = undefined
|
|
518
|
+
this.failureTie2 = undefined
|
|
519
|
+
if (mode === "Backtrack" && (this.deepestFailure === undefined || failure.offset >= this.deepestFailure.offset)) {
|
|
520
|
+
this.deepestFailure = failure
|
|
521
|
+
}
|
|
522
|
+
if (this.syntax !== undefined) this.syntax.reach(failure.offset)
|
|
523
|
+
return Fail
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Records a recoverable failure made of tied failures, merged only when
|
|
528
|
+
* read: `failWith("Backtrack", f)` where `f` merges `head` with `tie1`
|
|
529
|
+
* and then `tie2` (all at the same offset), as `mergeFailures` would.
|
|
530
|
+
*
|
|
531
|
+
* @internal
|
|
532
|
+
*/
|
|
533
|
+
failTies(head: Failure, tie1: Failure, tie2: Failure | undefined): Fail {
|
|
534
|
+
this.mode = "Backtrack"
|
|
535
|
+
if (this.deepestIsLatest && head.offset < this.failureHead.offset) this.settleFailure()
|
|
536
|
+
this.failureHead = head
|
|
537
|
+
this.failureTie1 = tie1
|
|
538
|
+
this.failureTie2 = tie2
|
|
539
|
+
this.deepestIsLatest = this.deepestFailure === undefined || head.offset >= this.deepestFailure.offset
|
|
540
|
+
if (this.syntax !== undefined) this.syntax.reach(head.offset)
|
|
417
541
|
return Fail
|
|
418
542
|
}
|
|
419
543
|
|
|
@@ -444,8 +568,9 @@ export class State<I extends Input = Input> {
|
|
|
444
568
|
) {
|
|
445
569
|
return f
|
|
446
570
|
}
|
|
447
|
-
const context =
|
|
448
|
-
|
|
571
|
+
const context = Arr.isReadonlyArrayEmpty(d.context)
|
|
572
|
+
? f.context
|
|
573
|
+
: [...d.context, ...f.context.filter((l) => !d.context.includes(l))]
|
|
449
574
|
return new Failure(d.offset, d.kind, d.expected, context, d.hasCause, d.cause, d.message, d.needed)
|
|
450
575
|
}
|
|
451
576
|
|
|
@@ -455,6 +580,7 @@ export class State<I extends Input = Input> {
|
|
|
455
580
|
* `repeat` and `Ascii.digit1`.
|
|
456
581
|
*/
|
|
457
582
|
setHint(offset: number, expected: ReadonlyArray<string>): void {
|
|
583
|
+
if (this.syntax !== undefined) this.syntax.reach(offset)
|
|
458
584
|
if (this.hintOffset === offset) {
|
|
459
585
|
this.hintExpected = mergeExpected(this.hintExpected, expected)
|
|
460
586
|
} else {
|
|
@@ -465,7 +591,7 @@ export class State<I extends Input = Input> {
|
|
|
465
591
|
|
|
466
592
|
/** @internal */
|
|
467
593
|
makeFailure(offset: number, kind: ErrorKind, expected: ReadonlyArray<string>): Failure {
|
|
468
|
-
if (this.hintOffset === offset && this.hintExpected
|
|
594
|
+
if (this.hintOffset === offset && Arr.isReadonlyArrayNonEmpty(this.hintExpected)) {
|
|
469
595
|
expected = mergeExpected(this.hintExpected, expected)
|
|
470
596
|
}
|
|
471
597
|
return new Failure(offset, kind, expected, empty, false, undefined, undefined, 0)
|
|
@@ -474,8 +600,8 @@ export class State<I extends Input = Input> {
|
|
|
474
600
|
|
|
475
601
|
/** @internal */
|
|
476
602
|
export const mergeExpected = (a: ReadonlyArray<string>, b: ReadonlyArray<string>): ReadonlyArray<string> => {
|
|
477
|
-
if (a
|
|
478
|
-
if (b
|
|
603
|
+
if (Arr.isReadonlyArrayEmpty(a)) return b
|
|
604
|
+
if (Arr.isReadonlyArrayEmpty(b)) return a
|
|
479
605
|
const out = a.slice()
|
|
480
606
|
for (const e of b) {
|
|
481
607
|
if (!out.includes(e)) out.push(e)
|
|
@@ -492,13 +618,23 @@ export const mergeExpected = (a: ReadonlyArray<string>, b: ReadonlyArray<string>
|
|
|
492
618
|
export const mergeFailures = (a: Failure, b: Failure): Failure => {
|
|
493
619
|
if (a.offset > b.offset) return a
|
|
494
620
|
if (b.offset > a.offset) return b
|
|
495
|
-
|
|
621
|
+
return mergeTiedFailures(a, b, mergeExpected(a.expected, b.expected))
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* {@link mergeFailures} for two failures at the same offset, given their
|
|
626
|
+
* merged expectations (`mergeExpected(a.expected, b.expected)`, possibly
|
|
627
|
+
* memoised by the caller).
|
|
628
|
+
*
|
|
629
|
+
* @internal
|
|
630
|
+
*/
|
|
631
|
+
export const mergeTiedFailures = (a: Failure, b: Failure, expected: ReadonlyArray<string>): Failure => {
|
|
496
632
|
if (expected === b.expected) return b
|
|
497
633
|
return new Failure(
|
|
498
634
|
b.offset,
|
|
499
635
|
b.kind,
|
|
500
636
|
expected,
|
|
501
|
-
b.context
|
|
637
|
+
Arr.isReadonlyArrayNonEmpty(b.context) ? b.context : a.context,
|
|
502
638
|
b.hasCause || a.hasCause,
|
|
503
639
|
b.hasCause ? b.cause : a.cause,
|
|
504
640
|
b.message ?? a.message,
|
package/src/ParseError.ts
CHANGED
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
*
|
|
20
20
|
* @since 0.1.0
|
|
21
21
|
*/
|
|
22
|
+
import * as Arr from "effect/Array"
|
|
22
23
|
import * as Predicate from "effect/Predicate"
|
|
23
24
|
import * as Data from "effect/Data"
|
|
24
25
|
import * as Option from "effect/Option"
|
|
26
|
+
import * as Result from "effect/Result"
|
|
25
27
|
import type { Encoding } from "./Encoding.js"
|
|
26
28
|
import type { ErrMode, Failure, Input } from "./Input.js"
|
|
27
29
|
import * as text from "./internal/text.js"
|
|
@@ -245,7 +247,7 @@ const codePointCount = (s: string, start: number, end: number): number => {
|
|
|
245
247
|
* @category utils
|
|
246
248
|
*/
|
|
247
249
|
export const formatExpected = (expected: ReadonlyArray<string>): string => {
|
|
248
|
-
if (expected
|
|
250
|
+
if (Arr.isReadonlyArrayEmpty(expected)) return ""
|
|
249
251
|
if (expected.length === 1) return expected[0]
|
|
250
252
|
return `${expected.slice(0, -1).join(", ")} or ${expected[expected.length - 1]}`
|
|
251
253
|
}
|
|
@@ -277,11 +279,11 @@ const describeFound = (input: Input, offset: number, encoding: Encoding<any> | u
|
|
|
277
279
|
export const inspectToken = (u: unknown): string => {
|
|
278
280
|
if (Predicate.isString(u)) return JSON.stringify(u)
|
|
279
281
|
if (Predicate.isObjectOrArray(u)) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
282
|
+
// `JSON.stringify` throws on cycles and `BigInt`s
|
|
283
|
+
return Result.getOrElse(
|
|
284
|
+
Result.try(() => JSON.stringify(u)),
|
|
285
|
+
() => String(u)
|
|
286
|
+
)
|
|
285
287
|
}
|
|
286
288
|
return String(u)
|
|
287
289
|
}
|
|
@@ -289,7 +291,7 @@ export const inspectToken = (u: unknown): string => {
|
|
|
289
291
|
const causeMessage = (cause: unknown): string | undefined => {
|
|
290
292
|
if (cause === undefined) return undefined
|
|
291
293
|
if (Predicate.isString(cause)) return cause
|
|
292
|
-
|
|
294
|
+
// An `Error`'s message, or any other object's string `message`
|
|
293
295
|
if (Predicate.isObjectOrArray(cause) && "message" in cause && Predicate.isString(cause.message)) {
|
|
294
296
|
return cause.message
|
|
295
297
|
}
|
|
@@ -366,7 +368,7 @@ const render = (error: ParseError<unknown>): string => {
|
|
|
366
368
|
if (error.description !== undefined) lines.push(error.description)
|
|
367
369
|
if (error.mode === "Incomplete") {
|
|
368
370
|
lines.push("unexpected end of input (incomplete)")
|
|
369
|
-
} else if (error.expected
|
|
371
|
+
} else if (Arr.isReadonlyArrayNonEmpty(error.expected)) {
|
|
370
372
|
lines.push(`expected ${formatExpected(error.expected)}, found ${describeFound(input, offset, encoding)}`)
|
|
371
373
|
} else if (cause === undefined && error.description === undefined) {
|
|
372
374
|
lines.push(`unexpected ${describeFound(input, offset, encoding)} (${error.kind})`)
|
package/src/Parser.ts
CHANGED
|
@@ -40,14 +40,16 @@ 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"
|
|
47
47
|
import * as Entries from "./internal/entries.js"
|
|
48
|
+
import { Key } from "./internal/key.js"
|
|
48
49
|
import * as Resume from "./internal/resume.js"
|
|
49
50
|
import * as Run from "./internal/run.js"
|
|
50
51
|
import * as Stack from "./internal/stack.js"
|
|
52
|
+
import { TiedFailures } from "./internal/ties.js"
|
|
51
53
|
import type { Node } from "./internal/node.js"
|
|
52
54
|
import { bounds, NodeId } from "./internal/node.js"
|
|
53
55
|
|
|
@@ -272,6 +274,8 @@ const isTwoParserArgs = (args: IArguments) => isParser(args[0]) && isParser(args
|
|
|
272
274
|
* The step function must either return the parsed value, having advanced
|
|
273
275
|
* `state.pos` past the consumed input, or return the result of one of
|
|
274
276
|
* `state.backtrack`, `state.cut`, `state.incomplete`, ... to signal failure.
|
|
277
|
+
* A step that moves the cursor backwards (to retry, or after a lookahead)
|
|
278
|
+
* calls `state.rewind(offset)` rather than assigning `state.pos`.
|
|
275
279
|
*
|
|
276
280
|
* @example
|
|
277
281
|
* ```ts
|
|
@@ -722,7 +726,7 @@ export const tryMap: {
|
|
|
722
726
|
if (a === Fail) return Fail
|
|
723
727
|
const r = f(a)
|
|
724
728
|
if (Result.isSuccess(r)) return r.success
|
|
725
|
-
state.
|
|
729
|
+
state.rewind(start)
|
|
726
730
|
return state.failCause("Backtrack", "Verify", r.failure)
|
|
727
731
|
},
|
|
728
732
|
self.name,
|
|
@@ -776,7 +780,7 @@ export const filterMap: {
|
|
|
776
780
|
if (a === Fail) return Fail
|
|
777
781
|
const o = f(a)
|
|
778
782
|
if (Option.isSome(o)) return o.value
|
|
779
|
-
state.
|
|
783
|
+
state.rewind(start)
|
|
780
784
|
return state.backtrack("Verify")
|
|
781
785
|
},
|
|
782
786
|
self.name,
|
|
@@ -807,7 +811,7 @@ export const filter: {
|
|
|
807
811
|
const a = step(state)
|
|
808
812
|
if (a === Fail) return Fail
|
|
809
813
|
if (predicate(a)) return a
|
|
810
|
-
state.
|
|
814
|
+
state.rewind(start)
|
|
811
815
|
return state.backtrack("Verify")
|
|
812
816
|
},
|
|
813
817
|
self.name,
|
|
@@ -851,7 +855,7 @@ export const filterOrFail: {
|
|
|
851
855
|
const a = step(state)
|
|
852
856
|
if (a === Fail) return Fail
|
|
853
857
|
if (predicate(a)) return a
|
|
854
|
-
state.
|
|
858
|
+
state.rewind(start)
|
|
855
859
|
return state.failCause("Backtrack", "Verify", orFailWith(a))
|
|
856
860
|
},
|
|
857
861
|
self.name,
|
|
@@ -1031,7 +1035,7 @@ export const complete = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Pa
|
|
|
1031
1035
|
const start = state.pos
|
|
1032
1036
|
const a = step(state)
|
|
1033
1037
|
if (a === Fail && state.mode === "Incomplete") {
|
|
1034
|
-
state.
|
|
1038
|
+
state.rewind(start)
|
|
1035
1039
|
return state.backtrack("Complete")
|
|
1036
1040
|
}
|
|
1037
1041
|
return a
|
|
@@ -1222,7 +1226,7 @@ export const orElseSucceed: {
|
|
|
1222
1226
|
|
|
1223
1227
|
/** Rewinds after a recoverable failure, remembering it as a hint. */
|
|
1224
1228
|
const recover = (state: State<any>, start: number): void => {
|
|
1225
|
-
state.
|
|
1229
|
+
state.rewind(start)
|
|
1226
1230
|
const f = state.failure
|
|
1227
1231
|
if (f.offset === start) {
|
|
1228
1232
|
state.hintOffset = start
|
|
@@ -1296,7 +1300,7 @@ export const peek = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser
|
|
|
1296
1300
|
if (a !== Fail) {
|
|
1297
1301
|
// Failures inside a lookahead that succeeded are about input that was
|
|
1298
1302
|
// never consumed, so they are not reported as the furthest failure
|
|
1299
|
-
state.
|
|
1303
|
+
state.rewind(start)
|
|
1300
1304
|
state.hintOffset = hintOffset
|
|
1301
1305
|
state.hintExpected = hintExpected
|
|
1302
1306
|
state.deepest = deepest
|
|
@@ -1326,12 +1330,12 @@ export const not = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser<
|
|
|
1326
1330
|
const a = step(state)
|
|
1327
1331
|
if (a !== Fail) {
|
|
1328
1332
|
// The failures inside `self` are not this parser's failure
|
|
1329
|
-
state.
|
|
1333
|
+
state.rewind(start)
|
|
1330
1334
|
state.deepest = deepest
|
|
1331
1335
|
return state.backtrack("Not")
|
|
1332
1336
|
}
|
|
1333
1337
|
if (state.mode !== "Backtrack") return Fail
|
|
1334
|
-
state.
|
|
1338
|
+
state.rewind(start)
|
|
1335
1339
|
state.hintOffset = hintOffset
|
|
1336
1340
|
state.hintExpected = hintExpected
|
|
1337
1341
|
state.deepest = deepest
|
|
@@ -1785,19 +1789,48 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
|
|
|
1785
1789
|
return described(
|
|
1786
1790
|
(state: State<any>) => {
|
|
1787
1791
|
const start = state.pos
|
|
1792
|
+
// The branches' failures merged left to right, as `mergeFailures` would,
|
|
1793
|
+
// except that the last two failures tied with `failure` wait in `tie1`
|
|
1794
|
+
// and `tie2` and are merged only when needed: when a third tie arrives,
|
|
1795
|
+
// when a succeeding branch stops where they failed (for the hint), or
|
|
1796
|
+
// when the merge is read from the state after `failTies`. An `alt` whose
|
|
1797
|
+
// later branch consumes input discards them unmerged.
|
|
1788
1798
|
let failure: Failure | undefined
|
|
1799
|
+
let tie1: Failure | undefined
|
|
1800
|
+
let tie2: Failure | undefined
|
|
1789
1801
|
for (let i = 0; i < n; i++) {
|
|
1790
1802
|
const a = steps[i](state)
|
|
1791
1803
|
if (a !== Fail) {
|
|
1792
|
-
if (failure !== undefined && failure.offset === state.pos)
|
|
1804
|
+
if (failure !== undefined && failure.offset === state.pos) {
|
|
1805
|
+
let expected = failure.expected
|
|
1806
|
+
if (tie1 !== undefined) expected = mergeExpected(expected, tie1.expected)
|
|
1807
|
+
if (tie2 !== undefined) expected = mergeExpected(expected, tie2.expected)
|
|
1808
|
+
state.setHint(failure.offset, expected)
|
|
1809
|
+
}
|
|
1793
1810
|
return a
|
|
1794
1811
|
}
|
|
1795
1812
|
if (state.mode !== "Backtrack") return Fail
|
|
1796
|
-
|
|
1797
|
-
|
|
1813
|
+
state.rewind(start)
|
|
1814
|
+
// The branch's failure, whose own ties (from a nested `alt`) can be taken over unmerged
|
|
1815
|
+
const head = state.failureHead
|
|
1816
|
+
if (failure === undefined || head.offset > failure.offset) {
|
|
1817
|
+
failure = head
|
|
1818
|
+
tie1 = state.failureTie1
|
|
1819
|
+
tie2 = state.failureTie2
|
|
1820
|
+
} else if (head.offset === failure.offset) {
|
|
1821
|
+
const f = state.failure
|
|
1822
|
+
if (tie1 === undefined) tie1 = f
|
|
1823
|
+
else if (tie2 === undefined) tie2 = f
|
|
1824
|
+
else {
|
|
1825
|
+
failure = mergeTiedFailures(failure, tie1, mergeExpected(failure.expected, tie1.expected))
|
|
1826
|
+
tie1 = tie2
|
|
1827
|
+
tie2 = f
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1798
1830
|
}
|
|
1799
1831
|
// `failure` is set: there are at least two alternatives, and each one that fails sets it
|
|
1800
|
-
return state.failWith("Backtrack",
|
|
1832
|
+
if (failure === undefined) return state.failWith("Backtrack", state.failure)
|
|
1833
|
+
return tie1 === undefined ? state.failWith("Backtrack", failure) : state.failTies(failure, tie1, tie2)
|
|
1801
1834
|
},
|
|
1802
1835
|
"alt",
|
|
1803
1836
|
{ _tag: "Alt", parsers }
|
|
@@ -1876,7 +1909,7 @@ export const dispatch: {
|
|
|
1876
1909
|
if (key === Fail) return Fail
|
|
1877
1910
|
const parser = choose(key)
|
|
1878
1911
|
if (parser === undefined) {
|
|
1879
|
-
state.
|
|
1912
|
+
state.rewind(start)
|
|
1880
1913
|
return state.backtrack("Dispatch")
|
|
1881
1914
|
}
|
|
1882
1915
|
return parser.step(state)
|
|
@@ -1911,11 +1944,13 @@ export const permutation = <const Ps extends ReadonlyArray<Parser.Any>>(
|
|
|
1911
1944
|
const steps = parsers.map((p) => p.step)
|
|
1912
1945
|
const n = steps.length
|
|
1913
1946
|
return make((state: State<any>) => {
|
|
1914
|
-
|
|
1947
|
+
// A round's failures, merged only if no parser matches in it (#44)
|
|
1948
|
+
const ties = new TiedFailures()
|
|
1915
1949
|
const done = new Array<boolean>(n).fill(false)
|
|
1950
|
+
const out = new Array(n)
|
|
1916
1951
|
let remaining = n
|
|
1917
1952
|
while (remaining > 0) {
|
|
1918
|
-
|
|
1953
|
+
ties.clear()
|
|
1919
1954
|
let matched = false
|
|
1920
1955
|
for (let i = 0; i < n; i++) {
|
|
1921
1956
|
if (done[i]) continue
|
|
@@ -1929,11 +1964,11 @@ export const permutation = <const Ps extends ReadonlyArray<Parser.Any>>(
|
|
|
1929
1964
|
break
|
|
1930
1965
|
}
|
|
1931
1966
|
if (state.mode !== "Backtrack") return Fail
|
|
1932
|
-
|
|
1933
|
-
state
|
|
1967
|
+
state.rewind(start)
|
|
1968
|
+
ties.add(state)
|
|
1934
1969
|
}
|
|
1935
|
-
//
|
|
1936
|
-
if (!matched) return
|
|
1970
|
+
// Some parser is not done yet, and each one that fails is folded in
|
|
1971
|
+
if (!matched) return ties.fail(state)
|
|
1937
1972
|
}
|
|
1938
1973
|
return out as any
|
|
1939
1974
|
}, "permutation")
|
|
@@ -1963,7 +1998,7 @@ interface FoldCheckpoint<B> {
|
|
|
1963
1998
|
readonly pos: number
|
|
1964
1999
|
}
|
|
1965
2000
|
|
|
1966
|
-
const saveFold = <B>(state: State<any>, key:
|
|
2001
|
+
const saveFold = <B>(state: State<any>, key: Key, entry: number, acc: B, pos: number): void =>
|
|
1967
2002
|
Resume.save(state, key, entry, { acc, count: 0, pos } satisfies FoldCheckpoint<B>)
|
|
1968
2003
|
|
|
1969
2004
|
const foldImpl = <A, B, E, R, I extends Input>(
|
|
@@ -1978,7 +2013,7 @@ const foldImpl = <A, B, E, R, I extends Input>(
|
|
|
1978
2013
|
const [min, max] = normalizeRange(range)
|
|
1979
2014
|
const step = self.step
|
|
1980
2015
|
const checkLoop = max === Infinity
|
|
1981
|
-
const key =
|
|
2016
|
+
const key = new Key()
|
|
1982
2017
|
return described(
|
|
1983
2018
|
(state) => {
|
|
1984
2019
|
const entry = state.pos
|
|
@@ -2157,7 +2192,7 @@ export const repeatTill: {
|
|
|
2157
2192
|
if (b !== Fail) return [acc, b]
|
|
2158
2193
|
if (state.mode !== "Backtrack") return Fail
|
|
2159
2194
|
endFailure = state.failure
|
|
2160
|
-
state.
|
|
2195
|
+
state.rewind(start)
|
|
2161
2196
|
if (acc.length >= max) return Fail
|
|
2162
2197
|
}
|
|
2163
2198
|
const a = step(state)
|
|
@@ -2220,7 +2255,7 @@ export const separated: {
|
|
|
2220
2255
|
const step = self.step
|
|
2221
2256
|
const sepStep = separator.step
|
|
2222
2257
|
const checkLoop = max === Infinity
|
|
2223
|
-
const key =
|
|
2258
|
+
const key = new Key()
|
|
2224
2259
|
return described(
|
|
2225
2260
|
(state) => {
|
|
2226
2261
|
const entry = state.pos
|
|
@@ -2263,7 +2298,7 @@ export const separated: {
|
|
|
2263
2298
|
Resume.save(state, key, entry, { acc, pos: start } satisfies SeparatedCheckpoint<A>)
|
|
2264
2299
|
}
|
|
2265
2300
|
if (state.mode !== "Backtrack" || acc.length < min) return Fail
|
|
2266
|
-
state.
|
|
2301
|
+
state.rewind(start)
|
|
2267
2302
|
return Resume.done(state, key, entry, resumed !== undefined, acc)
|
|
2268
2303
|
}
|
|
2269
2304
|
acc.push(a)
|
|
@@ -2304,7 +2339,7 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
|
|
|
2304
2339
|
): Parser<A, E | E2, R | R2, I & I2> => {
|
|
2305
2340
|
const step = self.step
|
|
2306
2341
|
const sepStep = separator.step
|
|
2307
|
-
const key =
|
|
2342
|
+
const key = new Key()
|
|
2308
2343
|
return described(
|
|
2309
2344
|
(state) => {
|
|
2310
2345
|
const entry = state.pos
|
|
@@ -2335,7 +2370,7 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
|
|
|
2335
2370
|
if (a === Fail) {
|
|
2336
2371
|
if (state.mode === "Incomplete" && state.resume !== undefined) saveFold(state, key, entry, acc, start)
|
|
2337
2372
|
if (state.mode !== "Backtrack") return Fail
|
|
2338
|
-
state.
|
|
2373
|
+
state.rewind(start)
|
|
2339
2374
|
return Resume.done(state, key, entry, resumed !== undefined, acc as A)
|
|
2340
2375
|
}
|
|
2341
2376
|
acc = op(acc, s, a)
|
|
@@ -2380,7 +2415,7 @@ export const separatedFoldr1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
|
|
|
2380
2415
|
const a = step(state)
|
|
2381
2416
|
if (a === Fail) {
|
|
2382
2417
|
if (state.mode !== "Backtrack") return Fail
|
|
2383
|
-
state.
|
|
2418
|
+
state.rewind(start)
|
|
2384
2419
|
break
|
|
2385
2420
|
}
|
|
2386
2421
|
seps.push(s)
|
|
@@ -2552,7 +2587,7 @@ export const resumeAfter: {
|
|
|
2552
2587
|
if (state.recovered === undefined || state.mode === "Incomplete") return Fail
|
|
2553
2588
|
const failure = state.failure
|
|
2554
2589
|
const mode = state.mode
|
|
2555
|
-
state.
|
|
2590
|
+
state.rewind(start)
|
|
2556
2591
|
if (recoverStep(state) === Fail) return state.failWith(mode, failure)
|
|
2557
2592
|
state.recovered.push(failure)
|
|
2558
2593
|
state.syntax?.skipped(start, failure, mode)
|
|
@@ -2599,7 +2634,7 @@ export const retryAfter: {
|
|
|
2599
2634
|
if (state.recovered === undefined || state.mode === "Incomplete") return Fail
|
|
2600
2635
|
const failure = state.failure
|
|
2601
2636
|
const mode = state.mode
|
|
2602
|
-
state.
|
|
2637
|
+
state.rewind(start)
|
|
2603
2638
|
if (recoverStep(state) === Fail || state.pos === start) return state.failWith(mode, failure)
|
|
2604
2639
|
state.recovered.push(failure)
|
|
2605
2640
|
state.syntax?.skipped(start, failure, mode)
|
|
@@ -2676,7 +2711,7 @@ const preview = (state: State<any>, offset: number): string => {
|
|
|
2676
2711
|
*/
|
|
2677
2712
|
export const consoleTracer = (event: TraceEvent): void => {
|
|
2678
2713
|
const indent = " ".repeat(event.depth)
|
|
2679
|
-
if (event
|
|
2714
|
+
if (Predicate.isTagged(event, "Enter")) {
|
|
2680
2715
|
console.log(`${indent}> ${event.name} @${event.offset} | ${preview(event.state, event.offset)}`)
|
|
2681
2716
|
} else {
|
|
2682
2717
|
const consumed = (event.endOffset ?? event.offset) - event.offset
|