@thomasfosterau/effect-parse 0.1.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 +291 -231
- package/dist/Ascii.d.ts +32 -53
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +576 -571
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +6 -29
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +129 -116
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts +4 -2
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +121 -123
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +39 -0
- package/dist/Compiler.d.ts.map +1 -0
- package/dist/Compiler.js +48 -0
- package/dist/Compiler.js.map +1 -0
- package/dist/Encoding.d.ts +29 -46
- package/dist/Encoding.d.ts.map +1 -1
- package/dist/Encoding.js +76 -75
- package/dist/Encoding.js.map +1 -1
- package/dist/Expression.d.ts +15 -49
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +151 -132
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +251 -177
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +409 -299
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +45 -85
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js +423 -453
- package/dist/Nom.js.map +1 -1
- package/dist/ParseError.d.ts +54 -49
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +232 -275
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +345 -313
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +2034 -1802
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts +206 -7
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +678 -49
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +26 -20
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +267 -255
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +401 -0
- package/dist/Syntax.d.ts.map +1 -0
- package/dist/Syntax.js +717 -0
- package/dist/Syntax.js.map +1 -0
- package/dist/Text.d.ts +98 -134
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +534 -496
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +60 -42
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +331 -265
- package/dist/Token.js.map +1 -1
- package/dist/index.d.ts +16 -17
- package/dist/index.js +16 -17
- package/dist/internal/compiler/closure.d.ts +14 -16
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +1458 -1637
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +11 -11
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +36 -26
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts +19 -19
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +251 -227
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/jit.d.ts +6 -17
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +860 -988
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +20 -21
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js +21 -41
- package/dist/internal/compiler/registry.js.map +1 -1
- package/dist/internal/compiler/sets.d.ts +9 -13
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +76 -73
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/entries.d.ts +3 -3
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js +38 -30
- package/dist/internal/entries.js.map +1 -1
- package/dist/internal/node.d.ts +177 -173
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +77 -66
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts +7 -8
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js +246 -276
- package/dist/internal/numbers.js.map +1 -1
- package/dist/internal/resume.d.ts +6 -24
- package/dist/internal/resume.d.ts.map +1 -1
- package/dist/internal/resume.js +30 -29
- package/dist/internal/resume.js.map +1 -1
- package/dist/internal/run.d.ts +15 -0
- package/dist/internal/run.d.ts.map +1 -0
- package/dist/internal/run.js +36 -0
- package/dist/internal/run.js.map +1 -0
- package/dist/internal/stack.d.ts +16 -0
- package/dist/internal/stack.d.ts.map +1 -0
- package/dist/internal/stack.js +36 -0
- package/dist/internal/stack.js.map +1 -0
- package/dist/internal/syntax.d.ts +147 -0
- package/dist/internal/syntax.d.ts.map +1 -0
- package/dist/internal/syntax.js +309 -0
- package/dist/internal/syntax.js.map +1 -0
- package/dist/internal/text.d.ts +10 -2
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +295 -345
- package/dist/internal/text.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/dist/internal/tokens.d.ts +25 -30
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +369 -363
- package/dist/internal/tokens.js.map +1 -1
- package/package.json +42 -28
- package/src/Ascii.ts +278 -187
- package/src/Binary.ts +150 -44
- package/src/Bits.ts +6 -3
- package/src/Compiler.ts +98 -0
- package/src/Expression.ts +24 -16
- package/src/Input.ts +210 -35
- package/src/Nom.ts +4 -3
- package/src/ParseError.ts +49 -52
- package/src/Parser.ts +1199 -799
- package/src/ParserSchema.ts +982 -23
- package/src/ParserStream.ts +104 -80
- package/src/Syntax.ts +900 -0
- package/src/Text.ts +272 -187
- package/src/Token.ts +180 -87
- package/src/index.ts +2 -0
- package/src/internal/compiler/closure.ts +121 -88
- package/src/internal/compiler/entries.ts +21 -8
- package/src/internal/compiler/first.ts +17 -15
- package/src/internal/compiler/jit.ts +360 -157
- package/src/internal/compiler/registry.ts +12 -33
- package/src/internal/compiler/sets.ts +17 -11
- package/src/internal/entries.ts +4 -2
- package/src/internal/node.ts +51 -37
- package/src/internal/numbers.ts +22 -20
- package/src/internal/resume.ts +8 -4
- package/src/internal/run.ts +41 -0
- package/src/internal/stack.ts +41 -0
- package/src/internal/syntax.ts +426 -0
- package/src/internal/text.ts +32 -25
- package/src/internal/ties.ts +83 -0
- package/src/internal/tokens.ts +96 -51
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/src/Input.ts
CHANGED
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
* failure.
|
|
9
9
|
*
|
|
10
10
|
* Most users never touch `State` directly. It is exposed for writing custom
|
|
11
|
-
* low-level parsers with `Parser.make
|
|
11
|
+
* low-level parsers with `Parser.make`, and for drivers that run a parser's
|
|
12
|
+
* `step` many times over one input, reusing a state with {@link State.reset}.
|
|
12
13
|
*
|
|
13
14
|
* @since 0.1.0
|
|
14
15
|
*/
|
|
16
|
+
import * as Predicate from "effect/Predicate"
|
|
15
17
|
import * as Context from "effect/Context"
|
|
16
18
|
import type { Encoding } from "./Encoding.js"
|
|
17
19
|
import type { ErrorKind } from "./ParseError.js"
|
|
20
|
+
import type { Builder as SyntaxBuilder } from "./internal/syntax.js"
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* The kinds of input a parser can consume.
|
|
@@ -70,7 +73,7 @@ export const fromSource: {
|
|
|
70
73
|
(source: BinaryInput): Uint8Array
|
|
71
74
|
<I extends Input>(source: Source<I>): I
|
|
72
75
|
} = (source: Input | BinaryInput): any => {
|
|
73
|
-
if (
|
|
76
|
+
if (Predicate.isString(source) || source instanceof Uint8Array || Array.isArray(source)) return source
|
|
74
77
|
if (ArrayBuffer.isView(source)) return new Uint8Array(source.buffer, source.byteOffset, source.byteLength)
|
|
75
78
|
// An explicit length fixes the view's size even for a resizable buffer
|
|
76
79
|
if (isArrayBufferLike(source)) return new Uint8Array(source, 0, source.byteLength)
|
|
@@ -78,7 +81,7 @@ export const fromSource: {
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
const isArrayBufferLike = (u: unknown): u is ArrayBufferLike =>
|
|
81
|
-
u instanceof ArrayBuffer || (
|
|
84
|
+
u instanceof ArrayBuffer || (globalThis.SharedArrayBuffer !== undefined && u instanceof SharedArrayBuffer)
|
|
82
85
|
|
|
83
86
|
/**
|
|
84
87
|
* The type of a single token of an input.
|
|
@@ -86,10 +89,13 @@ const isArrayBufferLike = (u: unknown): u is ArrayBufferLike =>
|
|
|
86
89
|
* @since 0.1.0
|
|
87
90
|
* @category models
|
|
88
91
|
*/
|
|
89
|
-
export type Token<I> = I extends string
|
|
90
|
-
|
|
91
|
-
: I extends
|
|
92
|
-
|
|
92
|
+
export type Token<I> = I extends string
|
|
93
|
+
? string
|
|
94
|
+
: I extends Uint8Array
|
|
95
|
+
? number
|
|
96
|
+
: I extends ReadonlyArray<infer T>
|
|
97
|
+
? T
|
|
98
|
+
: never
|
|
93
99
|
|
|
94
100
|
/**
|
|
95
101
|
* The type of a contiguous slice of an input.
|
|
@@ -97,10 +103,13 @@ export type Token<I> = I extends string ? string
|
|
|
97
103
|
* @since 0.1.0
|
|
98
104
|
* @category models
|
|
99
105
|
*/
|
|
100
|
-
export type Slice<I> = I extends string
|
|
101
|
-
|
|
102
|
-
: I extends
|
|
103
|
-
|
|
106
|
+
export type Slice<I> = I extends string
|
|
107
|
+
? string
|
|
108
|
+
: I extends Uint8Array
|
|
109
|
+
? Uint8Array
|
|
110
|
+
: I extends ReadonlyArray<infer T>
|
|
111
|
+
? ReadonlyArray<T>
|
|
112
|
+
: never
|
|
104
113
|
|
|
105
114
|
/**
|
|
106
115
|
* Sentinel returned by a parser step to signal failure. The failure details
|
|
@@ -109,7 +118,7 @@ export type Slice<I> = I extends string ? string
|
|
|
109
118
|
* @since 0.1.0
|
|
110
119
|
* @category failure
|
|
111
120
|
*/
|
|
112
|
-
export const Fail: unique symbol = Symbol.for("effect-parse/Fail")
|
|
121
|
+
export const Fail: unique symbol = /*#__PURE__*/ Symbol.for("effect-parse/Fail")
|
|
113
122
|
|
|
114
123
|
/**
|
|
115
124
|
* @since 0.1.0
|
|
@@ -158,6 +167,9 @@ export class Failure {
|
|
|
158
167
|
|
|
159
168
|
const empty: ReadonlyArray<string> = []
|
|
160
169
|
|
|
170
|
+
/** The failure a {@link State} starts with, before any step has failed. */
|
|
171
|
+
const initialFailure = /*#__PURE__*/ new Failure(0, "Fail", empty, empty, false, undefined, undefined, 0)
|
|
172
|
+
|
|
161
173
|
/** @internal */
|
|
162
174
|
export const emptyStrings = empty
|
|
163
175
|
|
|
@@ -189,7 +201,8 @@ export class State<I extends Input = Input> {
|
|
|
189
201
|
/** The full input being parsed. */
|
|
190
202
|
readonly input: I
|
|
191
203
|
/** The current offset. Advanced by parsers, rewound on backtracking. */
|
|
192
|
-
|
|
204
|
+
// Declared, not defined, so that a subclass can make it an accessor
|
|
205
|
+
declare pos: number
|
|
193
206
|
/** The end offset (the input's length; in bits inside `Bits.bits`). */
|
|
194
207
|
end: number
|
|
195
208
|
/** 0 = string, 1 = Uint8Array, 2 = array. */
|
|
@@ -200,8 +213,31 @@ export class State<I extends Input = Input> {
|
|
|
200
213
|
context: Context.Context<never>
|
|
201
214
|
/** Mode of the latest failure. Only meaningful after a step returned {@link Fail}. */
|
|
202
215
|
mode: ErrMode = "Backtrack"
|
|
203
|
-
/**
|
|
204
|
-
|
|
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
|
|
205
241
|
/**
|
|
206
242
|
* The offset of a recoverable failure that occurred without consuming
|
|
207
243
|
* input (for example the last attempt of a `repeat`). Its
|
|
@@ -216,9 +252,18 @@ export class State<I extends Input = Input> {
|
|
|
216
252
|
* were later swallowed by backtracking (for example a trailing separator
|
|
217
253
|
* followed by an invalid element). It is reported instead of the final
|
|
218
254
|
* failure when it lies further into the input. This is the classic PEG
|
|
219
|
-
* "farthest failure" heuristic.
|
|
255
|
+
* "farthest failure" heuristic. `Parser.cut` keeps only the failures
|
|
256
|
+
* recorded inside it when it commits, and `Parser.peek` and `Parser.not`
|
|
257
|
+
* forget the failures of a lookahead that succeeded.
|
|
220
258
|
*/
|
|
221
|
-
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
|
+
}
|
|
222
267
|
/** When defined, `Parser.resumeAfter`/`retryAfter` record failures here. */
|
|
223
268
|
recovered: Array<Failure> | undefined = undefined
|
|
224
269
|
/** When defined, `Parser.trace` reports events here. */
|
|
@@ -237,20 +282,114 @@ export class State<I extends Input = Input> {
|
|
|
237
282
|
* @internal
|
|
238
283
|
*/
|
|
239
284
|
resume: Map<object, Map<number, unknown>> | undefined = undefined
|
|
285
|
+
/**
|
|
286
|
+
* The tree builder of a `Syntax` parse; `undefined` otherwise.
|
|
287
|
+
*
|
|
288
|
+
* @internal
|
|
289
|
+
*/
|
|
290
|
+
syntax: SyntaxBuilder | undefined = undefined
|
|
240
291
|
|
|
241
|
-
constructor(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
292
|
+
constructor(
|
|
293
|
+
source: I | Source<I>,
|
|
294
|
+
options?: {
|
|
295
|
+
readonly partial?: boolean | undefined
|
|
296
|
+
readonly context?: Context.Context<never> | undefined
|
|
297
|
+
readonly offset?: number | undefined
|
|
298
|
+
}
|
|
299
|
+
) {
|
|
246
300
|
const input = fromSource(source as Input) as I
|
|
247
301
|
this.input = input
|
|
248
302
|
this.end = input.length
|
|
249
303
|
this.pos = options?.offset ?? 0
|
|
250
304
|
this.partial = options?.partial ?? false
|
|
251
305
|
this.context = options?.context ?? Context.empty()
|
|
252
|
-
this.inputKind =
|
|
253
|
-
this.
|
|
306
|
+
this.inputKind = Predicate.isString(input) ? 0 : input instanceof Uint8Array ? 1 : 2
|
|
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
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Prepares the state for another run over the same input, starting at
|
|
342
|
+
* `offset`: afterwards it behaves exactly like
|
|
343
|
+
* `new State(state.input, { ...options, offset })`, without allocating a
|
|
344
|
+
* new state.
|
|
345
|
+
*
|
|
346
|
+
* Every per-run field is reset: the cursor and end, the latest failure,
|
|
347
|
+
* hints and furthest failure, recorded recoveries, the tracer and tracing
|
|
348
|
+
* depth, the recorded encoding and streaming checkpoints. Like the
|
|
349
|
+
* constructor, `partial` defaults to `false` and `context` to the empty
|
|
350
|
+
* context, so pass them again on every reset.
|
|
351
|
+
*
|
|
352
|
+
* Use it to run many small parses over one buffer, for example one per
|
|
353
|
+
* token, with `parser.step`. A state can be reused after any run,
|
|
354
|
+
* including one that failed, returned `Incomplete` or threw.
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts
|
|
358
|
+
* import { Ascii, Input } from "@thomasfosterau/effect-parse"
|
|
359
|
+
*
|
|
360
|
+
* const state = new Input.State("12 34")
|
|
361
|
+
* Ascii.decUint.step(state) // 12
|
|
362
|
+
* state.reset(3)
|
|
363
|
+
* Ascii.decUint.step(state) // 34
|
|
364
|
+
* ```
|
|
365
|
+
*
|
|
366
|
+
* @since 0.2.0
|
|
367
|
+
*/
|
|
368
|
+
reset(
|
|
369
|
+
offset: number = 0,
|
|
370
|
+
options?: {
|
|
371
|
+
readonly partial?: boolean | undefined
|
|
372
|
+
readonly context?: Context.Context<never> | undefined
|
|
373
|
+
}
|
|
374
|
+
): this {
|
|
375
|
+
this.pos = offset
|
|
376
|
+
this.end = this.input.length
|
|
377
|
+
this.partial = options?.partial ?? false
|
|
378
|
+
this.context = options?.context ?? Context.empty()
|
|
379
|
+
this.mode = "Backtrack"
|
|
380
|
+
this.failureHead = initialFailure
|
|
381
|
+
this.failureTie1 = undefined
|
|
382
|
+
this.failureTie2 = undefined
|
|
383
|
+
this.hintOffset = -1
|
|
384
|
+
this.hintExpected = empty
|
|
385
|
+
this.deepestFailure = undefined
|
|
386
|
+
this.deepestIsLatest = false
|
|
387
|
+
this.recovered = undefined
|
|
388
|
+
this.tracer = undefined
|
|
389
|
+
this.depth = 0
|
|
390
|
+
this.encoding = undefined
|
|
391
|
+
this.resume = undefined
|
|
392
|
+
return this
|
|
254
393
|
}
|
|
255
394
|
|
|
256
395
|
/**
|
|
@@ -334,13 +473,37 @@ export class State<I extends Input = Input> {
|
|
|
334
473
|
*/
|
|
335
474
|
failWith(mode: ErrMode, failure: Failure): Fail {
|
|
336
475
|
this.mode = mode
|
|
337
|
-
this.
|
|
338
|
-
|
|
339
|
-
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()
|
|
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
|
|
340
486
|
}
|
|
341
487
|
return Fail
|
|
342
488
|
}
|
|
343
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
|
|
504
|
+
return Fail
|
|
505
|
+
}
|
|
506
|
+
|
|
344
507
|
/**
|
|
345
508
|
* Replaces the details of the current failure (for example to add context),
|
|
346
509
|
* keeping the record of the furthest failure consistent.
|
|
@@ -359,12 +522,17 @@ export class State<I extends Input = Input> {
|
|
|
359
522
|
const f = this.failure
|
|
360
523
|
const d = this.deepest
|
|
361
524
|
if (
|
|
362
|
-
d === undefined ||
|
|
363
|
-
|
|
525
|
+
d === undefined ||
|
|
526
|
+
d === f ||
|
|
527
|
+
d.offset <= f.offset ||
|
|
528
|
+
this.mode === "Incomplete" ||
|
|
529
|
+
f.hasCause ||
|
|
530
|
+
f.message !== undefined
|
|
364
531
|
) {
|
|
365
532
|
return f
|
|
366
533
|
}
|
|
367
|
-
const context =
|
|
534
|
+
const context =
|
|
535
|
+
d.context.length === 0 ? f.context : [...d.context, ...f.context.filter((l) => !d.context.includes(l))]
|
|
368
536
|
return new Failure(d.offset, d.kind, d.expected, context, d.hasCause, d.cause, d.message, d.needed)
|
|
369
537
|
}
|
|
370
538
|
|
|
@@ -392,10 +560,7 @@ export class State<I extends Input = Input> {
|
|
|
392
560
|
}
|
|
393
561
|
|
|
394
562
|
/** @internal */
|
|
395
|
-
export const mergeExpected = (
|
|
396
|
-
a: ReadonlyArray<string>,
|
|
397
|
-
b: ReadonlyArray<string>
|
|
398
|
-
): ReadonlyArray<string> => {
|
|
563
|
+
export const mergeExpected = (a: ReadonlyArray<string>, b: ReadonlyArray<string>): ReadonlyArray<string> => {
|
|
399
564
|
if (a.length === 0) return b
|
|
400
565
|
if (b.length === 0) return a
|
|
401
566
|
const out = a.slice()
|
|
@@ -414,7 +579,17 @@ export const mergeExpected = (
|
|
|
414
579
|
export const mergeFailures = (a: Failure, b: Failure): Failure => {
|
|
415
580
|
if (a.offset > b.offset) return a
|
|
416
581
|
if (b.offset > a.offset) return b
|
|
417
|
-
|
|
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 => {
|
|
418
593
|
if (expected === b.expected) return b
|
|
419
594
|
return new Failure(
|
|
420
595
|
b.offset,
|
package/src/Nom.ts
CHANGED
|
@@ -116,8 +116,9 @@ export const isA = <I extends Input = string>(set: TokenSet<I>): Parser<Slice<No
|
|
|
116
116
|
* @since 0.1.0
|
|
117
117
|
* @category bytes
|
|
118
118
|
*/
|
|
119
|
-
export const isNot = <I extends Input = string>(
|
|
120
|
-
|
|
119
|
+
export const isNot = <I extends Input = string>(
|
|
120
|
+
set: TokenSet<I>
|
|
121
|
+
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> => Token.takeTill<I>(set, { min: 1 })
|
|
121
122
|
|
|
122
123
|
/**
|
|
123
124
|
* @since 0.1.0
|
|
@@ -265,7 +266,7 @@ export const double = Ascii.float
|
|
|
265
266
|
* @since 0.1.0
|
|
266
267
|
* @category number
|
|
267
268
|
*/
|
|
268
|
-
export const recognizeFloat: Parser<string> = P.taken(Ascii.float)
|
|
269
|
+
export const recognizeFloat: Parser<string> = /*#__PURE__*/ P.taken(Ascii.float)
|
|
269
270
|
|
|
270
271
|
/**
|
|
271
272
|
* nom's `hex_u32`.
|
package/src/ParseError.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* @since 0.1.0
|
|
21
21
|
*/
|
|
22
|
+
import * as Predicate from "effect/Predicate"
|
|
22
23
|
import * as Data from "effect/Data"
|
|
23
24
|
import * as Option from "effect/Option"
|
|
24
25
|
import type { Encoding } from "./Encoding.js"
|
|
@@ -29,6 +30,9 @@ import * as text from "./internal/text.js"
|
|
|
29
30
|
* The kind of parser that produced an error, in the spirit of nom's
|
|
30
31
|
* `ErrorKind`.
|
|
31
32
|
*
|
|
33
|
+
* `"TooDeep"` is not produced by a parser: the runners report it when
|
|
34
|
+
* deeply nested input exhausts the call stack (see `Parser.suspend`).
|
|
35
|
+
*
|
|
32
36
|
* @since 0.1.0
|
|
33
37
|
* @category models
|
|
34
38
|
*/
|
|
@@ -64,13 +68,14 @@ export type ErrorKind =
|
|
|
64
68
|
| "TakeUntil"
|
|
65
69
|
| "TakeWhile"
|
|
66
70
|
| "Token"
|
|
71
|
+
| "TooDeep"
|
|
67
72
|
| "Verify"
|
|
68
73
|
|
|
69
74
|
/**
|
|
70
75
|
* @since 0.1.0
|
|
71
76
|
* @category type ids
|
|
72
77
|
*/
|
|
73
|
-
export const TypeId
|
|
78
|
+
export const TypeId = "~effect-parse/ParseError" as const
|
|
74
79
|
|
|
75
80
|
/**
|
|
76
81
|
* An error produced by running a parser.
|
|
@@ -78,29 +83,31 @@ export const TypeId: "~effect-parse/ParseError" = "~effect-parse/ParseError"
|
|
|
78
83
|
* @since 0.1.0
|
|
79
84
|
* @category models
|
|
80
85
|
*/
|
|
81
|
-
export class ParseError<out E = never>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
86
|
+
export class ParseError<out E = never>
|
|
87
|
+
extends /*#__PURE__*/ Data.TaggedError("ParseError")<{
|
|
88
|
+
/** The input that was being parsed. */
|
|
89
|
+
readonly input: Input
|
|
90
|
+
/** The offset of the failure in the input. */
|
|
91
|
+
readonly offset: number
|
|
92
|
+
/** The kind of parser that failed. */
|
|
93
|
+
readonly kind: ErrorKind
|
|
94
|
+
/** How the error propagated before parsing stopped. */
|
|
95
|
+
readonly mode: ErrMode
|
|
96
|
+
/** Descriptions of what was expected at `offset`. */
|
|
97
|
+
readonly expected: ReadonlyArray<string>
|
|
98
|
+
/** Context labels (`Parser.context`), innermost first. */
|
|
99
|
+
readonly context: ReadonlyArray<string>
|
|
100
|
+
/** The user supplied cause, if any. */
|
|
101
|
+
readonly cause: E | undefined
|
|
102
|
+
/** An optional free-form description. */
|
|
103
|
+
readonly description: string | undefined
|
|
104
|
+
/**
|
|
105
|
+
* For byte inputs parsed by `Text` parsers, the text encoding. Errors
|
|
106
|
+
* then show the input as text, with line and column numbers.
|
|
107
|
+
*/
|
|
108
|
+
readonly encoding?: Encoding<Uint8Array> | undefined
|
|
109
|
+
}>
|
|
110
|
+
{
|
|
104
111
|
/**
|
|
105
112
|
* @since 0.1.0
|
|
106
113
|
*/
|
|
@@ -137,8 +144,7 @@ export class ParseError<out E = never> extends Data.TaggedError("ParseError")<{
|
|
|
137
144
|
* @since 0.1.0
|
|
138
145
|
* @category guards
|
|
139
146
|
*/
|
|
140
|
-
export const isParseError = (u: unknown): u is ParseError<unknown> =>
|
|
141
|
-
typeof u === "object" && u !== null && TypeId in u
|
|
147
|
+
export const isParseError = (u: unknown): u is ParseError<unknown> => Predicate.isObjectOrArray(u) && TypeId in u
|
|
142
148
|
|
|
143
149
|
/**
|
|
144
150
|
* Builds a `ParseError` from the failure details recorded while parsing.
|
|
@@ -159,7 +165,7 @@ export const fromFailure = <E = never>(
|
|
|
159
165
|
mode,
|
|
160
166
|
expected: failure.expected,
|
|
161
167
|
context: failure.context,
|
|
162
|
-
cause: failure.hasCause ? failure.cause as E : undefined,
|
|
168
|
+
cause: failure.hasCause ? (failure.cause as E) : undefined,
|
|
163
169
|
description: failure.message,
|
|
164
170
|
encoding: input instanceof Uint8Array ? encoding : undefined
|
|
165
171
|
})
|
|
@@ -207,7 +213,7 @@ export const lineColumn = (input: Input, offset: number, encoding?: Encoding<any
|
|
|
207
213
|
})
|
|
208
214
|
return { line, column }
|
|
209
215
|
}
|
|
210
|
-
if (
|
|
216
|
+
if (!Predicate.isString(input)) return { line: 1, column: offset + 1 }
|
|
211
217
|
let line = 1
|
|
212
218
|
let lineStart = 0
|
|
213
219
|
for (let i = 0; i < offset && i < input.length; i++) {
|
|
@@ -240,7 +246,7 @@ const codePointCount = (s: string, start: number, end: number): number => {
|
|
|
240
246
|
*/
|
|
241
247
|
export const formatExpected = (expected: ReadonlyArray<string>): string => {
|
|
242
248
|
if (expected.length === 0) return ""
|
|
243
|
-
if (expected.length === 1) return expected[0]
|
|
249
|
+
if (expected.length === 1) return expected[0]
|
|
244
250
|
return `${expected.slice(0, -1).join(", ")} or ${expected[expected.length - 1]}`
|
|
245
251
|
}
|
|
246
252
|
|
|
@@ -257,20 +263,20 @@ const describeFound = (input: Input, offset: number, encoding: Encoding<any> | u
|
|
|
257
263
|
? `incomplete ${encoding.name} sequence ${hexBytes(input.subarray(offset))}`
|
|
258
264
|
: `invalid ${encoding.name} ${hexBytes(bad)}`
|
|
259
265
|
}
|
|
260
|
-
if (
|
|
261
|
-
const cp = input.codePointAt(offset)
|
|
266
|
+
if (Predicate.isString(input)) {
|
|
267
|
+
const cp = input.codePointAt(offset) ?? Number.NaN
|
|
262
268
|
return JSON.stringify(String.fromCodePoint(cp))
|
|
263
269
|
}
|
|
264
270
|
if (input instanceof Uint8Array) {
|
|
265
|
-
return `0x${input[offset]
|
|
271
|
+
return `0x${input[offset].toString(16).padStart(2, "0")}`
|
|
266
272
|
}
|
|
267
273
|
return inspectToken(input[offset])
|
|
268
274
|
}
|
|
269
275
|
|
|
270
276
|
/** @internal */
|
|
271
277
|
export const inspectToken = (u: unknown): string => {
|
|
272
|
-
if (
|
|
273
|
-
if (
|
|
278
|
+
if (Predicate.isString(u)) return JSON.stringify(u)
|
|
279
|
+
if (Predicate.isObjectOrArray(u)) {
|
|
274
280
|
try {
|
|
275
281
|
return JSON.stringify(u)
|
|
276
282
|
} catch {
|
|
@@ -282,9 +288,9 @@ export const inspectToken = (u: unknown): string => {
|
|
|
282
288
|
|
|
283
289
|
const causeMessage = (cause: unknown): string | undefined => {
|
|
284
290
|
if (cause === undefined) return undefined
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
if (
|
|
291
|
+
if (Predicate.isString(cause)) return cause
|
|
292
|
+
// An `Error`'s message, or any other object's string `message`
|
|
293
|
+
if (Predicate.isObjectOrArray(cause) && "message" in cause && Predicate.isString(cause.message)) {
|
|
288
294
|
return cause.message
|
|
289
295
|
}
|
|
290
296
|
return inspectToken(cause)
|
|
@@ -314,12 +320,9 @@ const renderSnippet = (input: Input, offset: number, encoding: Encoding<any> | u
|
|
|
314
320
|
if (line.endsWith("\r")) line = line.slice(0, -1)
|
|
315
321
|
const gutter = String(lineColumn(input, offset, encoding).line)
|
|
316
322
|
const pad = " ".repeat(gutter.length)
|
|
317
|
-
return [
|
|
318
|
-
`${gutter} | ${line}`,
|
|
319
|
-
`${pad} | ${" ".repeat(column - 1)}^`
|
|
320
|
-
]
|
|
323
|
+
return [`${gutter} | ${line}`, `${pad} | ${" ".repeat(column - 1)}^`]
|
|
321
324
|
}
|
|
322
|
-
if (
|
|
325
|
+
if (Predicate.isString(input)) {
|
|
323
326
|
const { column, line } = lineColumn(input, offset)
|
|
324
327
|
let start = offset
|
|
325
328
|
while (start > 0 && input.charCodeAt(start - 1) !== 10) start--
|
|
@@ -329,21 +332,15 @@ const renderSnippet = (input: Input, offset: number, encoding: Encoding<any> | u
|
|
|
329
332
|
if (text.endsWith("\r")) text = text.slice(0, -1)
|
|
330
333
|
const gutter = String(line)
|
|
331
334
|
const pad = " ".repeat(gutter.length)
|
|
332
|
-
return [
|
|
333
|
-
`${gutter} | ${text}`,
|
|
334
|
-
`${pad} | ${" ".repeat(column - 1)}^`
|
|
335
|
-
]
|
|
335
|
+
return [`${gutter} | ${text}`, `${pad} | ${" ".repeat(column - 1)}^`]
|
|
336
336
|
}
|
|
337
337
|
if (input instanceof Uint8Array) {
|
|
338
338
|
const start = Math.max(0, offset - 8)
|
|
339
339
|
const end = Math.min(input.length, offset + 8)
|
|
340
340
|
const bytes: Array<string> = []
|
|
341
|
-
for (let i = start; i < end; i++) bytes.push(input[i]
|
|
341
|
+
for (let i = start; i < end; i++) bytes.push(input[i].toString(16).padStart(2, "0"))
|
|
342
342
|
const caret = (offset - start) * 3
|
|
343
|
-
return [
|
|
344
|
-
`${start.toString(16).padStart(8, "0")} | ${bytes.join(" ")}`,
|
|
345
|
-
`${" ".repeat(8)} | ${" ".repeat(caret)}^`
|
|
346
|
-
]
|
|
343
|
+
return [`${start.toString(16).padStart(8, "0")} | ${bytes.join(" ")}`, `${" ".repeat(8)} | ${" ".repeat(caret)}^`]
|
|
347
344
|
}
|
|
348
345
|
return []
|
|
349
346
|
}
|
|
@@ -351,7 +348,7 @@ const renderSnippet = (input: Input, offset: number, encoding: Encoding<any> | u
|
|
|
351
348
|
const render = (error: ParseError<unknown>): string => {
|
|
352
349
|
const { encoding, input, offset } = error
|
|
353
350
|
const lines: Array<string> = []
|
|
354
|
-
if (
|
|
351
|
+
if (Predicate.isString(input)) {
|
|
355
352
|
const { column, line } = lineColumn(input, offset)
|
|
356
353
|
lines.push(`Parse error at line ${line}, column ${column}:`)
|
|
357
354
|
} else if (encoding !== undefined) {
|