@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.
- package/CHANGELOG.md +202 -0
- package/README.md +19 -1
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +5 -3
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +5 -2
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +20 -3
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +4 -0
- package/dist/Bits.js.map +1 -1
- package/dist/Input.d.ts +21 -3
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +31 -4
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts.map +1 -1
- package/dist/ParseError.d.ts +69 -11
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +129 -28
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +10 -4
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +10 -24
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +1 -2
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +119 -12
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +282 -55
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.js +1 -1
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +13 -7
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +10 -1
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +14 -5
- package/dist/Token.js.map +1 -1
- package/dist/internal/compiler/closure.d.ts +1 -1
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +7 -17
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +2 -1
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/graph.d.ts +23 -1
- package/dist/internal/compiler/graph.d.ts.map +1 -1
- package/dist/internal/compiler/graph.js +27 -5
- package/dist/internal/compiler/graph.js.map +1 -1
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +26 -13
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +1 -1
- package/dist/internal/compiler/wasm.d.ts.map +1 -1
- package/dist/internal/compiler/wasm.js +105 -34
- package/dist/internal/compiler/wasm.js.map +1 -1
- package/dist/internal/node.d.ts +19 -1
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +42 -8
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/run.d.ts +3 -1
- package/dist/internal/run.d.ts.map +1 -1
- package/dist/internal/run.js +4 -2
- package/dist/internal/run.js.map +1 -1
- package/dist/internal/stack.d.ts +8 -0
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +17 -1
- package/dist/internal/stack.js.map +1 -1
- package/dist/internal/tokens.d.ts +10 -3
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +22 -9
- package/dist/internal/tokens.js.map +1 -1
- package/dist/internal/tuple.d.ts +6 -0
- package/dist/internal/tuple.d.ts.map +1 -0
- package/dist/internal/tuple.js +170 -0
- package/dist/internal/tuple.js.map +1 -0
- package/package.json +17 -10
- package/src/Ascii.ts +5 -3
- package/src/Binary.ts +24 -3
- package/src/Bits.ts +4 -0
- package/src/Input.ts +37 -4
- package/src/ParseError.ts +206 -52
- package/src/Parser.ts +19 -29
- package/src/ParserSchema.ts +1 -2
- package/src/ParserStream.ts +336 -62
- package/src/Text.ts +16 -12
- package/src/Token.ts +14 -5
- package/src/internal/compiler/closure.ts +9 -18
- package/src/internal/compiler/first.ts +6 -0
- package/src/internal/compiler/graph.ts +32 -4
- package/src/internal/compiler/jit.ts +37 -17
- package/src/internal/compiler/wasm.ts +141 -68
- package/src/internal/node.ts +49 -3
- package/src/internal/run.ts +4 -2
- package/src/internal/stack.ts +17 -0
- package/src/internal/tokens.ts +20 -10
- 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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
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
|
|
334
|
-
let start =
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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 } =
|
|
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 } =
|
|
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
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
*
|
|
2561
|
-
*
|
|
2562
|
-
*
|
|
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
|
-
|
|
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 (
|
|
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) {
|
package/src/ParserSchema.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
)
|