@thomasfosterau/effect-parse 0.5.0 → 0.6.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 (99) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README.md +19 -1
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +5 -3
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts +5 -2
  7. package/dist/Binary.d.ts.map +1 -1
  8. package/dist/Binary.js +20 -3
  9. package/dist/Binary.js.map +1 -1
  10. package/dist/Bits.d.ts.map +1 -1
  11. package/dist/Bits.js +4 -0
  12. package/dist/Bits.js.map +1 -1
  13. package/dist/Input.d.ts +21 -3
  14. package/dist/Input.d.ts.map +1 -1
  15. package/dist/Input.js +31 -4
  16. package/dist/Input.js.map +1 -1
  17. package/dist/Nom.d.ts.map +1 -1
  18. package/dist/ParseError.d.ts +69 -11
  19. package/dist/ParseError.d.ts.map +1 -1
  20. package/dist/ParseError.js +129 -28
  21. package/dist/ParseError.js.map +1 -1
  22. package/dist/Parser.d.ts +10 -4
  23. package/dist/Parser.d.ts.map +1 -1
  24. package/dist/Parser.js +10 -24
  25. package/dist/Parser.js.map +1 -1
  26. package/dist/ParserSchema.d.ts.map +1 -1
  27. package/dist/ParserSchema.js +1 -2
  28. package/dist/ParserSchema.js.map +1 -1
  29. package/dist/ParserStream.d.ts +119 -12
  30. package/dist/ParserStream.d.ts.map +1 -1
  31. package/dist/ParserStream.js +282 -55
  32. package/dist/ParserStream.js.map +1 -1
  33. package/dist/Syntax.js +1 -1
  34. package/dist/Text.d.ts.map +1 -1
  35. package/dist/Text.js +13 -7
  36. package/dist/Text.js.map +1 -1
  37. package/dist/Token.d.ts +10 -1
  38. package/dist/Token.d.ts.map +1 -1
  39. package/dist/Token.js +14 -5
  40. package/dist/Token.js.map +1 -1
  41. package/dist/internal/compiler/closure.d.ts +1 -1
  42. package/dist/internal/compiler/closure.d.ts.map +1 -1
  43. package/dist/internal/compiler/closure.js +7 -17
  44. package/dist/internal/compiler/closure.js.map +1 -1
  45. package/dist/internal/compiler/first.d.ts.map +1 -1
  46. package/dist/internal/compiler/first.js +2 -1
  47. package/dist/internal/compiler/first.js.map +1 -1
  48. package/dist/internal/compiler/graph.d.ts +23 -1
  49. package/dist/internal/compiler/graph.d.ts.map +1 -1
  50. package/dist/internal/compiler/graph.js +27 -5
  51. package/dist/internal/compiler/graph.js.map +1 -1
  52. package/dist/internal/compiler/jit.d.ts.map +1 -1
  53. package/dist/internal/compiler/jit.js +26 -13
  54. package/dist/internal/compiler/jit.js.map +1 -1
  55. package/dist/internal/compiler/registry.d.ts +1 -1
  56. package/dist/internal/compiler/wasm.d.ts.map +1 -1
  57. package/dist/internal/compiler/wasm.js +105 -34
  58. package/dist/internal/compiler/wasm.js.map +1 -1
  59. package/dist/internal/node.d.ts +19 -1
  60. package/dist/internal/node.d.ts.map +1 -1
  61. package/dist/internal/node.js +42 -8
  62. package/dist/internal/node.js.map +1 -1
  63. package/dist/internal/run.d.ts +3 -1
  64. package/dist/internal/run.d.ts.map +1 -1
  65. package/dist/internal/run.js +4 -2
  66. package/dist/internal/run.js.map +1 -1
  67. package/dist/internal/stack.d.ts +8 -0
  68. package/dist/internal/stack.d.ts.map +1 -1
  69. package/dist/internal/stack.js +17 -1
  70. package/dist/internal/stack.js.map +1 -1
  71. package/dist/internal/tokens.d.ts +10 -3
  72. package/dist/internal/tokens.d.ts.map +1 -1
  73. package/dist/internal/tokens.js +22 -9
  74. package/dist/internal/tokens.js.map +1 -1
  75. package/dist/internal/tuple.d.ts +6 -0
  76. package/dist/internal/tuple.d.ts.map +1 -0
  77. package/dist/internal/tuple.js +170 -0
  78. package/dist/internal/tuple.js.map +1 -0
  79. package/package.json +17 -10
  80. package/src/Ascii.ts +5 -3
  81. package/src/Binary.ts +24 -3
  82. package/src/Bits.ts +4 -0
  83. package/src/Input.ts +37 -4
  84. package/src/ParseError.ts +206 -52
  85. package/src/Parser.ts +19 -29
  86. package/src/ParserSchema.ts +1 -2
  87. package/src/ParserStream.ts +336 -62
  88. package/src/Text.ts +16 -12
  89. package/src/Token.ts +14 -5
  90. package/src/internal/compiler/closure.ts +9 -18
  91. package/src/internal/compiler/first.ts +6 -0
  92. package/src/internal/compiler/graph.ts +32 -4
  93. package/src/internal/compiler/jit.ts +37 -17
  94. package/src/internal/compiler/wasm.ts +141 -68
  95. package/src/internal/node.ts +49 -3
  96. package/src/internal/run.ts +4 -2
  97. package/src/internal/stack.ts +17 -0
  98. package/src/internal/tokens.ts +20 -10
  99. package/src/internal/tuple.ts +141 -0
package/src/ParseError.ts CHANGED
@@ -79,37 +79,140 @@ export type ErrorKind =
79
79
  */
80
80
  export const TypeId = "~effect-parse/ParseError" as const
81
81
 
82
+ /** What a {@link ParseError} is built from. */
83
+ interface Fields<E> {
84
+ /** The input that was being parsed. */
85
+ readonly input: Input
86
+ /** The offset of the failure in the input. */
87
+ readonly offset: number
88
+ /** The kind of parser that failed. */
89
+ readonly kind: ErrorKind
90
+ /** How the error propagated before parsing stopped. */
91
+ readonly mode: ErrMode
92
+ /** Descriptions of what was expected at `offset`. */
93
+ readonly expected: ReadonlyArray<string>
94
+ /** Context labels (`Parser.context`), innermost first. */
95
+ readonly context: ReadonlyArray<string>
96
+ /** The user supplied cause, if any. */
97
+ readonly cause: E | undefined
98
+ /** An optional free-form description. */
99
+ readonly description: string | undefined
100
+ /**
101
+ * For byte inputs parsed by `Text` parsers, the text encoding. Errors
102
+ * then show the input as text, with line and column numbers.
103
+ */
104
+ readonly encoding?: Encoding<Uint8Array> | undefined
105
+ }
106
+
107
+ /**
108
+ * What a {@link ParseError} serialises as (`toJSON`): everything but the
109
+ * input it was parsing and the encoding it was read in.
110
+ *
111
+ * @since 0.6.0
112
+ * @category models
113
+ */
114
+ export interface ParseErrorJson<out E = never> {
115
+ readonly _tag: "ParseError"
116
+ /** The offset of the failure in the input. */
117
+ readonly offset: number
118
+ /** The 1-based line and column of the failure. */
119
+ readonly position: LineColumn
120
+ readonly kind: ErrorKind
121
+ readonly mode: ErrMode
122
+ readonly expected: ReadonlyArray<string>
123
+ readonly context: ReadonlyArray<string>
124
+ readonly cause: E | undefined
125
+ readonly description: string | undefined
126
+ /** The rendered message, which shows a bounded window of the input around the failure. */
127
+ readonly message: string
128
+ }
129
+
82
130
  /**
83
131
  * An error produced by running a parser.
84
132
  *
133
+ * The error keeps the whole input alive for as long as it is retained
134
+ * (`input`), but does not serialise it: `input` and `encoding` are
135
+ * accessors, not own properties, `toJSON` (which `JSON.stringify`,
136
+ * Effect's `Cause` rendering and Node's `inspect` use) leaves them out, and
137
+ * `message` shows a bounded window around the failure, never a whole line.
138
+ *
85
139
  * @since 0.1.0
86
140
  * @category models
87
141
  */
88
142
  export class ParseError<out E = never>
89
- extends /*#__PURE__*/ Data.TaggedError("ParseError")<{
90
- /** The input that was being parsed. */
91
- readonly input: Input
92
- /** The offset of the failure in the input. */
93
- readonly offset: number
94
- /** The kind of parser that failed. */
95
- readonly kind: ErrorKind
96
- /** How the error propagated before parsing stopped. */
97
- readonly mode: ErrMode
98
- /** Descriptions of what was expected at `offset`. */
99
- readonly expected: ReadonlyArray<string>
100
- /** Context labels (`Parser.context`), innermost first. */
101
- readonly context: ReadonlyArray<string>
102
- /** The user supplied cause, if any. */
103
- readonly cause: E | undefined
104
- /** An optional free-form description. */
105
- readonly description: string | undefined
106
- /**
107
- * For byte inputs parsed by `Text` parsers, the text encoding. Errors
108
- * then show the input as text, with line and column numbers.
109
- */
110
- readonly encoding?: Encoding<Uint8Array> | undefined
111
- }>
143
+ extends /*#__PURE__*/ Data.TaggedError("ParseError")<Omit<Fields<unknown>, "input" | "encoding">>
112
144
  {
145
+ /** The user supplied cause, if any. */
146
+ declare readonly cause: E | undefined
147
+
148
+ // Private fields, not properties: nothing that walks the error (its own
149
+ // keys, enumerable or not, or the arguments `Data.TaggedError` keeps for
150
+ // `toJSON` and structural equality) can reach the input (#117)
151
+ readonly #input: Input
152
+ readonly #encoding: Encoding<Uint8Array> | undefined
153
+ // Both are functions of the fields above, which never change, and both
154
+ // scan the input up to the failure: computed on first use, then kept
155
+ #position: LineColumn | undefined
156
+ #message: string | undefined
157
+
158
+ constructor(fields: Fields<E>) {
159
+ super({
160
+ offset: fields.offset,
161
+ kind: fields.kind,
162
+ mode: fields.mode,
163
+ expected: fields.expected,
164
+ context: fields.context,
165
+ cause: fields.cause,
166
+ description: fields.description
167
+ })
168
+ this.#input = fields.input
169
+ this.#encoding = fields.encoding
170
+ }
171
+
172
+ /**
173
+ * The input that was being parsed. The error holds it (for
174
+ * {@link ParseError.position} and the message) and keeps it alive, but it
175
+ * is not one of the error's own properties, so `JSON.stringify`, object
176
+ * spread, `Object.keys`, `toJSON` and loggers do not see it.
177
+ *
178
+ * @since 0.1.0
179
+ */
180
+ get input(): Input {
181
+ return this.#input
182
+ }
183
+
184
+ /**
185
+ * For byte inputs parsed by `Text` parsers, the text encoding. Errors
186
+ * then show the input as text, with line and column numbers. Like
187
+ * `input`, it is not an own property.
188
+ *
189
+ * @since 0.1.0
190
+ */
191
+ get encoding(): Encoding<Uint8Array> | undefined {
192
+ return this.#encoding
193
+ }
194
+
195
+ /**
196
+ * The error without its `input` and `encoding`: what `JSON.stringify`,
197
+ * Effect's `Cause` rendering and Node's `inspect` show.
198
+ *
199
+ * @since 0.6.0
200
+ */
201
+ override toJSON(): ParseErrorJson<E> {
202
+ return {
203
+ _tag: this._tag,
204
+ offset: this.offset,
205
+ position: this.position,
206
+ kind: this.kind,
207
+ mode: this.mode,
208
+ expected: this.expected,
209
+ context: this.context,
210
+ cause: this.cause,
211
+ description: this.description,
212
+ message: this.message
213
+ }
214
+ }
215
+
113
216
  /**
114
217
  * @since 0.1.0
115
218
  */
@@ -122,7 +225,7 @@ export class ParseError<out E = never>
122
225
  * @since 0.1.0
123
226
  */
124
227
  get position(): LineColumn {
125
- return lineColumn(this.input, this.offset, this.encoding)
228
+ return (this.#position ??= lineColumn(this.#input, this.offset, this.#encoding))
126
229
  }
127
230
 
128
231
  /**
@@ -131,7 +234,7 @@ export class ParseError<out E = never>
131
234
  * @since 0.1.0
132
235
  */
133
236
  override get message(): string {
134
- return render(this)
237
+ return (this.#message ??= render(this))
135
238
  }
136
239
 
137
240
  /**
@@ -303,43 +406,94 @@ const causeMessage = (cause: unknown): string | undefined => {
303
406
  return inspectToken(cause)
304
407
  }
305
408
 
306
- const renderSnippet = (input: Input, offset: number, encoding: Encoding<any> | undefined): Array<string> => {
409
+ /**
410
+ * How many characters of the failing line a message shows on each side of
411
+ * the failure. A line has no length limit (minified JSON is one line), and
412
+ * the input's sender chooses its length, so the message shows a window
413
+ * and marks what it left out with `…`.
414
+ */
415
+ const SnippetWindow = 60
416
+
417
+ const Ellipsis = "…"
418
+
419
+ /** The two lines of a text snippet: the window of the line, and a caret under the failure. */
420
+ const snippetLines = (line: number, window: string, before: number, cutStart: boolean, cutEnd: boolean) => {
421
+ const gutter = String(line)
422
+ const pad = " ".repeat(gutter.length)
423
+ const shown = window.endsWith("\r") && !cutEnd ? window.slice(0, -1) : window
424
+ return [
425
+ `${gutter} | ${cutStart ? Ellipsis : ""}${shown}${cutEnd ? Ellipsis : ""}`,
426
+ `${pad} | ${" ".repeat(before + (cutStart ? 1 : 0))}^`
427
+ ]
428
+ }
429
+
430
+ const isHighSurrogate = (c: number): boolean => c >= 0xd800 && c <= 0xdbff
431
+ const isLowSurrogate = (c: number): boolean => c >= 0xdc00 && c <= 0xdfff
432
+
433
+ const renderSnippet = (
434
+ input: Input,
435
+ offset: number,
436
+ encoding: Encoding<any> | undefined,
437
+ line: number
438
+ ): Array<string> => {
307
439
  if (encoding !== undefined && input instanceof Uint8Array) {
308
- // Find the line containing `offset`, then decode it
440
+ // The window is found in one walk: where the last `SnippetWindow`
441
+ // characters before the failure start (a ring of their offsets), and
442
+ // where the line or the window ends after it
443
+ const ring = new Array<number>(SnippetWindow)
444
+ let before = 0
309
445
  let lineStart = 0
446
+ let after = 0
310
447
  let end = input.length
311
- let column = 1
448
+ let cutEnd = false
312
449
  text.walk(encoding, input, 0, input.length, (cp, i, w) => {
313
- if (i >= offset && cp === 10) {
314
- end = i
315
- return false
316
- }
317
450
  if (i < offset) {
318
451
  if (cp === 10) {
452
+ before = 0
319
453
  lineStart = i + w
320
- column = 1
321
454
  } else {
322
- column++
455
+ ring[before % SnippetWindow] = i
456
+ before++
323
457
  }
458
+ return
324
459
  }
460
+ if (cp === 10) {
461
+ end = i
462
+ return false
463
+ }
464
+ if (after === SnippetWindow) {
465
+ end = i
466
+ cutEnd = true
467
+ return false
468
+ }
469
+ after++
325
470
  })
326
- let line = text.lenientText(encoding, input, lineStart, end)
327
- if (line.endsWith("\r")) line = line.slice(0, -1)
328
- const gutter = String(lineColumn(input, offset, encoding).line)
329
- const pad = " ".repeat(gutter.length)
330
- return [`${gutter} | ${line}`, `${pad} | ${" ".repeat(column - 1)}^`]
471
+ const cutStart = before > SnippetWindow
472
+ const start = cutStart ? ring[before % SnippetWindow] : lineStart
473
+ const shown = Math.min(before, SnippetWindow)
474
+ return snippetLines(line, text.lenientText(encoding, input, start, end), shown, cutStart, cutEnd)
331
475
  }
332
476
  if (Predicate.isString(input)) {
333
- const { column, line } = lineColumn(input, offset)
334
- let start = offset
335
- while (start > 0 && input.charCodeAt(start - 1) !== 10) start--
336
- let end = offset
337
- while (end < input.length && input.charCodeAt(end) !== 10) end++
338
- let text = input.slice(start, end)
339
- if (text.endsWith("\r")) text = text.slice(0, -1)
340
- const gutter = String(line)
341
- const pad = " ".repeat(gutter.length)
342
- return [`${gutter} | ${text}`, `${pad} | ${" ".repeat(column - 1)}^`]
477
+ const at = Math.min(offset, input.length)
478
+ let start = at
479
+ let before = 0
480
+ while (start > 0 && input.charCodeAt(start - 1) !== 10 && before < SnippetWindow) {
481
+ start--
482
+ // A surrogate pair is one character of the window, and is never split
483
+ if (start > 0 && isLowSurrogate(input.charCodeAt(start)) && isHighSurrogate(input.charCodeAt(start - 1))) start--
484
+ before++
485
+ }
486
+ const cutStart = start > 0 && input.charCodeAt(start - 1) !== 10
487
+ let end = at
488
+ let after = 0
489
+ while (end < input.length && input.charCodeAt(end) !== 10 && after < SnippetWindow) {
490
+ if (isHighSurrogate(input.charCodeAt(end)) && end + 1 < input.length && isLowSurrogate(input.charCodeAt(end + 1)))
491
+ end++
492
+ end++
493
+ after++
494
+ }
495
+ const cutEnd = end < input.length && input.charCodeAt(end) !== 10
496
+ return snippetLines(line, input.slice(start, end), before, cutStart, cutEnd)
343
497
  }
344
498
  if (input instanceof Uint8Array) {
345
499
  const start = Math.max(0, offset - 8)
@@ -356,15 +510,15 @@ const render = (error: ParseError<unknown>): string => {
356
510
  const { encoding, input, offset } = error
357
511
  const lines: Array<string> = []
358
512
  if (Predicate.isString(input)) {
359
- const { column, line } = lineColumn(input, offset)
513
+ const { column, line } = error.position
360
514
  lines.push(`Parse error at line ${line}, column ${column}:`)
361
515
  } else if (encoding !== undefined) {
362
- const { column, line } = lineColumn(input, offset, encoding)
516
+ const { column, line } = error.position
363
517
  lines.push(`Parse error at line ${line}, column ${column} (byte ${offset}):`)
364
518
  } else {
365
519
  lines.push(`Parse error at offset ${offset}:`)
366
520
  }
367
- lines.push(...renderSnippet(input, offset, encoding))
521
+ lines.push(...renderSnippet(input, offset, encoding, error.position.line))
368
522
  for (let i = error.context.length - 1; i >= 0; i--) {
369
523
  lines.push(`while parsing ${error.context[i]}`)
370
524
  }
package/src/Parser.ts CHANGED
@@ -52,6 +52,7 @@ import * as Resume from "./internal/resume.js"
52
52
  import * as Run from "./internal/run.js"
53
53
  import * as Stack from "./internal/stack.js"
54
54
  import { TiedFailures } from "./internal/ties.js"
55
+ import { tupleStep } from "./internal/tuple.js"
55
56
  import type { Node } from "./internal/node.js"
56
57
  import { bounds, Defer as DeferSymbol, describedNode, NodeId } from "./internal/node.js"
57
58
 
@@ -166,6 +167,12 @@ export declare namespace Parser {
166
167
  * - `{ min, max }`: between `min` and `max` times (inclusive); `min`
167
168
  * defaults to 0 and `max` to unbounded.
168
169
  *
170
+ * A count and both bounds must be non-negative integers (`max` may be
171
+ * `Infinity`) with `min <= max`. Every combinator that takes a range
172
+ * throws a `RangeError` otherwise, when the parser is built: a range that
173
+ * comes from data (a config file, a query parameter) is checked there,
174
+ * whatever its static type says.
175
+ *
169
176
  * @since 0.1.0
170
177
  * @category models
171
178
  */
@@ -1798,23 +1805,10 @@ export const seq = <const Ps extends ReadonlyArray<Parser.Any>>(
1798
1805
  Parser.InputOf<Ps[number]>
1799
1806
  > => {
1800
1807
  type Out = { -readonly [K in keyof Ps]: Parser.Success<Ps[K]> }
1801
- const steps = parsers.map((p) => p.step)
1802
- const n = steps.length
1803
- return described(
1804
- (state: State<any>) => {
1805
- const out = new Array(n)
1806
- for (let i = 0; i < n; i++) {
1807
- const a = steps[i](state)
1808
- if (a === Fail) return Fail
1809
- out[i] = a
1810
- }
1811
- // SAFETY: `out` has one slot per parser, and slot `i` holds the
1812
- // output of `parsers[i]`, which is `Parser.Success<Ps[i]>`
1813
- return out as Out
1814
- },
1815
- "seq",
1816
- { _tag: "Seq", parsers, output: "tuple" }
1817
- )
1808
+ // SAFETY: the tuple has one slot per parser, and slot `i` holds the
1809
+ // output of `parsers[i]`, which is `Parser.Success<Ps[i]>`
1810
+ const step = tupleStep(parsers.map((p) => p.step)) as (state: State<any>) => Out | Fail
1811
+ return described(step, "seq", { _tag: "Seq", parsers, output: "tuple" })
1818
1812
  }
1819
1813
 
1820
1814
  /**
@@ -2471,11 +2465,7 @@ export const permutation = <const Ps extends ReadonlyArray<Parser.Any>>(
2471
2465
  // -----------------------------------------------------------------------------
2472
2466
 
2473
2467
  const normalizeRange = (range: Range | undefined): [min: number, max: number] => {
2474
- if (range === undefined) return [0, Infinity]
2475
- if (Predicate.isNumber(range)) return [range, range]
2476
- const min = range.min ?? 0
2477
- const max = range.max ?? Infinity
2478
- if (min > max) throw new RangeError(`invalid range: min (${min}) > max (${max})`)
2468
+ const { max, min } = bounds(range)
2479
2469
  return [min, max]
2480
2470
  }
2481
2471
 
@@ -2504,7 +2494,7 @@ const foldImpl = <A, B, E, R, I extends Input>(
2504
2494
  ): Parser<B, E, R, I> => {
2505
2495
  const [min, max] = normalizeRange(range)
2506
2496
  const step = self.step
2507
- const checkLoop = max === Infinity
2497
+ // Bounded or not, the repetition checks progress below (#115)
2508
2498
  const key = new Key()
2509
2499
  return described(
2510
2500
  (state) => {
@@ -2539,7 +2529,7 @@ const foldImpl = <A, B, E, R, I extends Input>(
2539
2529
  recover(state, start)
2540
2530
  return Resume.done(state, key, entry, resumed !== undefined, acc)
2541
2531
  }
2542
- if (checkLoop && state.pos === start) return assertConsumes(state, name)
2532
+ if (state.pos === start) return assertConsumes(state, name)
2543
2533
  acc = f(acc, a)
2544
2534
  }
2545
2535
  return Resume.done(state, key, entry, resumed !== undefined, acc)
@@ -2557,9 +2547,9 @@ const foldImpl = <A, B, E, R, I extends Input>(
2557
2547
  * exactly that many times. With a literal positive `min`, the output is
2558
2548
  * typed as a non-empty array.
2559
2549
  *
2560
- * As in winnow, an unbounded repetition of a parser that succeeds without
2561
- * consuming input fails with an unrecoverable assertion instead of looping
2562
- * forever.
2550
+ * A parser that succeeds without consuming input fails the repetition with
2551
+ * an unrecoverable `Assert` failure, whatever the range (winnow checks only
2552
+ * unbounded ones): a bound can come from the input (`Binary.lengthRepeat`).
2563
2553
  *
2564
2554
  * @example
2565
2555
  * ```ts
@@ -2750,7 +2740,7 @@ export const separated: {
2750
2740
  const [min, max] = normalizeRange(range)
2751
2741
  const step = self.step
2752
2742
  const sepStep = separator.step
2753
- const checkLoop = max === Infinity
2743
+ // Bounded or not, the repetition checks progress below (#115)
2754
2744
  const key = new Key()
2755
2745
  return described(
2756
2746
  (state) => {
@@ -2791,7 +2781,7 @@ export const separated: {
2791
2781
  recover(state, start)
2792
2782
  return Resume.done(state, key, entry, resumed !== undefined, acc)
2793
2783
  }
2794
- if (checkLoop && state.pos === start) return assertConsumes(state, "separated")
2784
+ if (state.pos === start) return assertConsumes(state, "separated")
2795
2785
  const a = step(state)
2796
2786
  if (a === Fail) {
2797
2787
  if (state.mode === "Incomplete" && state.resume !== undefined) {
@@ -120,8 +120,7 @@ const decodeWith = <T extends Schema.Top>(
120
120
  if (encode === undefined) {
121
121
  return Effect.fail(new SchemaIssue.Forbidden({ message: "Encoding is not supported" }, value, parseOptions))
122
122
  }
123
- const result = encode(value)
124
- return Result.isSuccess(result) ? Effect.succeed(result.success) : Effect.fail(result.failure)
123
+ return Effect.fromResult(encode(value))
125
124
  }
126
125
  })
127
126
  )