@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.
Files changed (161) hide show
  1. package/README.md +291 -231
  2. package/dist/Ascii.d.ts +32 -53
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +576 -571
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts +6 -29
  7. package/dist/Binary.d.ts.map +1 -1
  8. package/dist/Binary.js +129 -116
  9. package/dist/Binary.js.map +1 -1
  10. package/dist/Bits.d.ts +4 -2
  11. package/dist/Bits.d.ts.map +1 -1
  12. package/dist/Bits.js +121 -123
  13. package/dist/Bits.js.map +1 -1
  14. package/dist/Compiler.d.ts +39 -0
  15. package/dist/Compiler.d.ts.map +1 -0
  16. package/dist/Compiler.js +48 -0
  17. package/dist/Compiler.js.map +1 -0
  18. package/dist/Encoding.d.ts +29 -46
  19. package/dist/Encoding.d.ts.map +1 -1
  20. package/dist/Encoding.js +76 -75
  21. package/dist/Encoding.js.map +1 -1
  22. package/dist/Expression.d.ts +15 -49
  23. package/dist/Expression.d.ts.map +1 -1
  24. package/dist/Expression.js +151 -132
  25. package/dist/Expression.js.map +1 -1
  26. package/dist/Input.d.ts +251 -177
  27. package/dist/Input.d.ts.map +1 -1
  28. package/dist/Input.js +409 -299
  29. package/dist/Input.js.map +1 -1
  30. package/dist/Nom.d.ts +45 -85
  31. package/dist/Nom.d.ts.map +1 -1
  32. package/dist/Nom.js +423 -453
  33. package/dist/Nom.js.map +1 -1
  34. package/dist/ParseError.d.ts +54 -49
  35. package/dist/ParseError.d.ts.map +1 -1
  36. package/dist/ParseError.js +232 -275
  37. package/dist/ParseError.js.map +1 -1
  38. package/dist/Parser.d.ts +345 -313
  39. package/dist/Parser.d.ts.map +1 -1
  40. package/dist/Parser.js +2034 -1802
  41. package/dist/Parser.js.map +1 -1
  42. package/dist/ParserSchema.d.ts +206 -7
  43. package/dist/ParserSchema.d.ts.map +1 -1
  44. package/dist/ParserSchema.js +678 -49
  45. package/dist/ParserSchema.js.map +1 -1
  46. package/dist/ParserStream.d.ts +26 -20
  47. package/dist/ParserStream.d.ts.map +1 -1
  48. package/dist/ParserStream.js +267 -255
  49. package/dist/ParserStream.js.map +1 -1
  50. package/dist/Syntax.d.ts +401 -0
  51. package/dist/Syntax.d.ts.map +1 -0
  52. package/dist/Syntax.js +717 -0
  53. package/dist/Syntax.js.map +1 -0
  54. package/dist/Text.d.ts +98 -134
  55. package/dist/Text.d.ts.map +1 -1
  56. package/dist/Text.js +534 -496
  57. package/dist/Text.js.map +1 -1
  58. package/dist/Token.d.ts +60 -42
  59. package/dist/Token.d.ts.map +1 -1
  60. package/dist/Token.js +331 -265
  61. package/dist/Token.js.map +1 -1
  62. package/dist/index.d.ts +16 -17
  63. package/dist/index.js +16 -17
  64. package/dist/internal/compiler/closure.d.ts +14 -16
  65. package/dist/internal/compiler/closure.d.ts.map +1 -1
  66. package/dist/internal/compiler/closure.js +1458 -1637
  67. package/dist/internal/compiler/closure.js.map +1 -1
  68. package/dist/internal/compiler/entries.d.ts +11 -11
  69. package/dist/internal/compiler/entries.d.ts.map +1 -1
  70. package/dist/internal/compiler/entries.js +36 -26
  71. package/dist/internal/compiler/entries.js.map +1 -1
  72. package/dist/internal/compiler/first.d.ts +19 -19
  73. package/dist/internal/compiler/first.d.ts.map +1 -1
  74. package/dist/internal/compiler/first.js +251 -227
  75. package/dist/internal/compiler/first.js.map +1 -1
  76. package/dist/internal/compiler/jit.d.ts +6 -17
  77. package/dist/internal/compiler/jit.d.ts.map +1 -1
  78. package/dist/internal/compiler/jit.js +860 -988
  79. package/dist/internal/compiler/jit.js.map +1 -1
  80. package/dist/internal/compiler/registry.d.ts +20 -21
  81. package/dist/internal/compiler/registry.d.ts.map +1 -1
  82. package/dist/internal/compiler/registry.js +21 -41
  83. package/dist/internal/compiler/registry.js.map +1 -1
  84. package/dist/internal/compiler/sets.d.ts +9 -13
  85. package/dist/internal/compiler/sets.d.ts.map +1 -1
  86. package/dist/internal/compiler/sets.js +76 -73
  87. package/dist/internal/compiler/sets.js.map +1 -1
  88. package/dist/internal/entries.d.ts +3 -3
  89. package/dist/internal/entries.d.ts.map +1 -1
  90. package/dist/internal/entries.js +38 -30
  91. package/dist/internal/entries.js.map +1 -1
  92. package/dist/internal/node.d.ts +177 -173
  93. package/dist/internal/node.d.ts.map +1 -1
  94. package/dist/internal/node.js +77 -66
  95. package/dist/internal/node.js.map +1 -1
  96. package/dist/internal/numbers.d.ts +7 -8
  97. package/dist/internal/numbers.d.ts.map +1 -1
  98. package/dist/internal/numbers.js +246 -276
  99. package/dist/internal/numbers.js.map +1 -1
  100. package/dist/internal/resume.d.ts +6 -24
  101. package/dist/internal/resume.d.ts.map +1 -1
  102. package/dist/internal/resume.js +30 -29
  103. package/dist/internal/resume.js.map +1 -1
  104. package/dist/internal/run.d.ts +15 -0
  105. package/dist/internal/run.d.ts.map +1 -0
  106. package/dist/internal/run.js +36 -0
  107. package/dist/internal/run.js.map +1 -0
  108. package/dist/internal/stack.d.ts +16 -0
  109. package/dist/internal/stack.d.ts.map +1 -0
  110. package/dist/internal/stack.js +36 -0
  111. package/dist/internal/stack.js.map +1 -0
  112. package/dist/internal/syntax.d.ts +147 -0
  113. package/dist/internal/syntax.d.ts.map +1 -0
  114. package/dist/internal/syntax.js +309 -0
  115. package/dist/internal/syntax.js.map +1 -0
  116. package/dist/internal/text.d.ts +10 -2
  117. package/dist/internal/text.d.ts.map +1 -1
  118. package/dist/internal/text.js +295 -345
  119. package/dist/internal/text.js.map +1 -1
  120. package/dist/internal/ties.d.ts +30 -0
  121. package/dist/internal/ties.d.ts.map +1 -0
  122. package/dist/internal/ties.js +67 -0
  123. package/dist/internal/ties.js.map +1 -0
  124. package/dist/internal/tokens.d.ts +25 -30
  125. package/dist/internal/tokens.d.ts.map +1 -1
  126. package/dist/internal/tokens.js +369 -363
  127. package/dist/internal/tokens.js.map +1 -1
  128. package/package.json +42 -28
  129. package/src/Ascii.ts +278 -187
  130. package/src/Binary.ts +150 -44
  131. package/src/Bits.ts +6 -3
  132. package/src/Compiler.ts +98 -0
  133. package/src/Expression.ts +24 -16
  134. package/src/Input.ts +210 -35
  135. package/src/Nom.ts +4 -3
  136. package/src/ParseError.ts +49 -52
  137. package/src/Parser.ts +1199 -799
  138. package/src/ParserSchema.ts +982 -23
  139. package/src/ParserStream.ts +104 -80
  140. package/src/Syntax.ts +900 -0
  141. package/src/Text.ts +272 -187
  142. package/src/Token.ts +180 -87
  143. package/src/index.ts +2 -0
  144. package/src/internal/compiler/closure.ts +121 -88
  145. package/src/internal/compiler/entries.ts +21 -8
  146. package/src/internal/compiler/first.ts +17 -15
  147. package/src/internal/compiler/jit.ts +360 -157
  148. package/src/internal/compiler/registry.ts +12 -33
  149. package/src/internal/compiler/sets.ts +17 -11
  150. package/src/internal/entries.ts +4 -2
  151. package/src/internal/node.ts +51 -37
  152. package/src/internal/numbers.ts +22 -20
  153. package/src/internal/resume.ts +8 -4
  154. package/src/internal/run.ts +41 -0
  155. package/src/internal/stack.ts +41 -0
  156. package/src/internal/syntax.ts +426 -0
  157. package/src/internal/text.ts +32 -25
  158. package/src/internal/ties.ts +83 -0
  159. package/src/internal/tokens.ts +96 -51
  160. package/dist/index.d.ts.map +0 -1
  161. 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 (typeof source === "string" || source instanceof Uint8Array || Array.isArray(source)) return source
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 || (typeof SharedArrayBuffer !== "undefined" && u instanceof SharedArrayBuffer)
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 ? string
90
- : I extends Uint8Array ? number
91
- : I extends ReadonlyArray<infer T> ? T
92
- : never
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 ? string
101
- : I extends Uint8Array ? Uint8Array
102
- : I extends ReadonlyArray<infer T> ? ReadonlyArray<T>
103
- : never
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
- pos: number
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
- /** Details of the latest failure. Only meaningful after a step returned {@link Fail}. */
204
- failure: Failure
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 = 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(source: I | Source<I>, options?: {
242
- readonly partial?: boolean | undefined
243
- readonly context?: Context.Context<never> | undefined
244
- readonly offset?: number | undefined
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 = typeof input === "string" ? 0 : input instanceof Uint8Array ? 1 : 2
253
- this.failure = new Failure(0, "Fail", empty, empty, false, undefined, undefined, 0)
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.failure = failure
338
- if (mode === "Backtrack" && (this.deepest === undefined || failure.offset >= this.deepest.offset)) {
339
- this.deepest = failure
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 || d === f || d.offset <= f.offset || this.mode === "Incomplete" ||
363
- f.hasCause || f.message !== undefined
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 = d.context.length === 0 ? f.context : [...d.context, ...f.context.filter((l) => !d.context.includes(l))]
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
- const expected = mergeExpected(a.expected, b.expected)
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>(set: TokenSet<I>): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
120
- Token.takeTill<I>(set, { min: 1 })
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: "~effect-parse/ParseError" = "~effect-parse/ParseError"
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> extends Data.TaggedError("ParseError")<{
82
- /** The input that was being parsed. */
83
- readonly input: Input
84
- /** The offset of the failure in the input. */
85
- readonly offset: number
86
- /** The kind of parser that failed. */
87
- readonly kind: ErrorKind
88
- /** How the error propagated before parsing stopped. */
89
- readonly mode: ErrMode
90
- /** Descriptions of what was expected at `offset`. */
91
- readonly expected: ReadonlyArray<string>
92
- /** Context labels (`Parser.context`), innermost first. */
93
- readonly context: ReadonlyArray<string>
94
- /** The user supplied cause, if any. */
95
- readonly cause: E | undefined
96
- /** An optional free-form description. */
97
- readonly description: string | undefined
98
- /**
99
- * For byte inputs parsed by `Text` parsers, the text encoding. Errors
100
- * then show the input as text, with line and column numbers.
101
- */
102
- readonly encoding?: Encoding<Uint8Array> | undefined
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 (typeof input !== "string") return { line: 1, column: offset + 1 }
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 (typeof input === "string") {
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]!.toString(16).padStart(2, "0")}`
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 (typeof u === "string") return JSON.stringify(u)
273
- if (typeof u === "object" && u !== null) {
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 (typeof cause === "string") return cause
286
- if (cause instanceof Error) return cause.message
287
- if (typeof cause === "object" && cause !== null && "message" in cause && typeof cause.message === "string") {
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 (typeof input === "string") {
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]!.toString(16).padStart(2, "0"))
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 (typeof input === "string") {
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) {