@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
@@ -1,5 +1,6 @@
1
1
  import * as Predicate from "effect/Predicate";
2
2
  import { Fail, Failure, State, fromSource } from "./Input.js";
3
+ import { isParser } from "./Parser.js";
3
4
  import * as Registry from "./internal/compiler/registry.js";
4
5
  import * as Run from "./internal/run.js";
5
6
  import { isEmptyUnits } from "./internal/text.js";
@@ -19,6 +20,12 @@ import * as Stream from "effect/Stream";
19
20
  * retried once more data arrives. When the stream ends, the leftover input
20
21
  * is parsed in complete mode.
21
22
  *
23
+ * Every runner bounds what it buffers: `maxBufferSize` (see
24
+ * {@link BufferOptions}) defaults to 64 MiB ({@link defaultMaxBufferSize}),
25
+ * and a stream that exceeds it fails with a {@link BufferLimitError}, so a
26
+ * socket or an HTTP body can be parsed without wrapping the stream first.
27
+ * Pass `Infinity` for input that is trusted to be large.
28
+ *
22
29
  * @example
23
30
  * ```ts
24
31
  * import { Effect, Stream } from "effect"
@@ -35,19 +42,149 @@ import * as Stream from "effect/Stream";
35
42
  *
36
43
  * @since 0.1.0
37
44
  */
38
- const concat = (first, b) => {
39
- const a = first;
40
- if (Predicate.isString(a)) return a + b;
41
- if (a instanceof Uint8Array) {
42
- if (a.byteLength === 0) return b;
43
- const out = new Uint8Array(a.length + b.length);
44
- out.set(a);
45
- out.set(b, a.length);
46
- return out;
45
+ /**
46
+ * The default `maxBufferSize` of {@link parseMany}, {@link parseAll},
47
+ * {@link parseReadable} and {@link parseResponse}: 64 MiB, counted in tokens
48
+ * of the input (UTF-16 code units of a string, bytes of a `Uint8Array`,
49
+ * elements of an array).
50
+ *
51
+ * @since 0.6.0
52
+ * @category constants
53
+ */
54
+ const defaultMaxBufferSize = 67108864;
55
+ /**
56
+ * A stream runner would have buffered more than its `maxBufferSize`: `size`
57
+ * tokens against a limit of `limit`. It is a different failure from a
58
+ * `ParseError`: the input is too large, not malformed (an HTTP server
59
+ * answers 413 for the one and 400 for the other), and nothing of the input
60
+ * is attached to it.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * import { Effect, Stream } from "effect"
65
+ * import { Parser, ParserStream, Token } from "@thomasfosterau/effect-parse"
66
+ *
67
+ * const line = Parser.terminated(Token.takeTill("\n"), Token.literal("\n"))
68
+ *
69
+ * Stream.make("no newline, ", "ever").pipe(
70
+ * ParserStream.parseMany(line, { maxBufferSize: 8 }),
71
+ * Stream.runCollect,
72
+ * Effect.catchTag("BufferLimitError", (e) => Effect.succeed(`too large: ${e.size} > ${e.limit}`))
73
+ * )
74
+ * ```
75
+ *
76
+ * @since 0.6.0
77
+ * @category errors
78
+ */
79
+ var BufferLimitError = class extends (/*#__PURE__*/ Data.TaggedError("BufferLimitError")) {
80
+ get message() {
81
+ return `the input is larger than maxBufferSize: ${this.size} tokens, limit ${this.limit}`;
82
+ }
83
+ };
84
+ /** The limit `options` asks for: a positive number of tokens, or `Infinity`. */
85
+ const limitOf = (options) => {
86
+ const limit = options?.maxBufferSize ?? 67108864;
87
+ if (!(limit > 0)) throw new RangeError("maxBufferSize must be a positive number of tokens, or Infinity");
88
+ return limit;
89
+ };
90
+ /**
91
+ * Above this many unconsumed tokens, {@link parseMany} stops retrying its
92
+ * unfinished value on every chunk (see {@link Pending.due}).
93
+ */
94
+ const BackoffThreshold = 65536;
95
+ /**
96
+ * The input `parseMany` has not consumed yet: what is left of the chunks
97
+ * after the values parsed from them, which is one unfinished value.
98
+ *
99
+ * Appending is amortized constant time per token: text is concatenated (a
100
+ * rope until it is parsed), bytes go into a buffer that doubles, and
101
+ * elements are pushed. Bytes that a parse has seen are never overwritten,
102
+ * since the values parsed from them may be views of the buffer
103
+ * (`Token.take` returns a `subarray`): the buffer is only ever appended to
104
+ * past its end, or replaced by a larger one.
105
+ */
106
+ var Pending = class {
107
+ kind;
108
+ text = "";
109
+ bytes = /* @__PURE__ */ new Uint8Array(0);
110
+ /** Where the unconsumed bytes start and end in `bytes`, and how far `bytes` may be written. */
111
+ start = 0;
112
+ end = 0;
113
+ capacity = 0;
114
+ items = [];
115
+ /** The unconsumed size at the last attempt that ran out of input, or 0 when none is outstanding. */
116
+ attempted = 0;
117
+ /** The number of unconsumed tokens. */
118
+ get size() {
119
+ return this.kind === 1 ? this.end - this.start : this.kind === 2 ? this.items.length : this.text.length;
120
+ }
121
+ get isEmpty() {
122
+ return this.kind === void 0;
123
+ }
124
+ append(chunk) {
125
+ if (Predicate.isString(chunk)) {
126
+ this.kind ??= 0;
127
+ this.text += chunk;
128
+ } else if (chunk instanceof Uint8Array) {
129
+ this.kind ??= 1;
130
+ this.appendBytes(chunk);
131
+ } else {
132
+ this.kind ??= 2;
133
+ for (let i = 0; i < chunk.length; i++) this.items.push(chunk[i]);
134
+ }
135
+ }
136
+ appendBytes(chunk) {
137
+ const held = this.end - this.start;
138
+ if (held === 0) {
139
+ this.bytes = chunk;
140
+ this.start = 0;
141
+ this.end = this.capacity = chunk.length;
142
+ return;
143
+ }
144
+ const needed = held + chunk.length;
145
+ if (this.end + chunk.length > this.capacity) {
146
+ const grown = new Uint8Array(Math.max(needed * 2, 4096));
147
+ grown.set(this.bytes.subarray(this.start, this.end));
148
+ this.bytes = grown;
149
+ this.start = 0;
150
+ this.end = held;
151
+ this.capacity = grown.length;
152
+ }
153
+ this.bytes.set(chunk, this.end);
154
+ this.end += chunk.length;
155
+ }
156
+ /** The unconsumed input. */
157
+ input() {
158
+ if (this.kind === 1) return this.bytes.subarray(this.start, this.end);
159
+ if (this.kind === 2) return this.items;
160
+ return this.text;
161
+ }
162
+ /** Drops the first `count` tokens, which values were parsed from. */
163
+ consume(count) {
164
+ if (count === 0) return;
165
+ if (this.kind === 1) this.start += count;
166
+ else if (this.kind === 2) this.items = this.items.slice(count);
167
+ else this.text = this.text.slice(count);
168
+ }
169
+ /** Records whether the attempt just made left an unfinished value. */
170
+ attemptedWith(unfinished) {
171
+ this.attempted = unfinished ? this.size : 0;
172
+ }
173
+ /**
174
+ * Whether to parse now. A value that is still unfinished is parsed again
175
+ * from its start, so retrying on every chunk costs the square of its
176
+ * size: a megabyte with no delimiter, in 1 KiB chunks, took seconds. Past
177
+ * {@link BackoffThreshold} tokens, the next attempt therefore waits until
178
+ * the buffer has grown by an eighth, which keeps the total work linear
179
+ * (each token is re-read at most about nine times). Smaller values are
180
+ * retried on every chunk, as cheaply as before, so a record is emitted
181
+ * as soon as its last chunk arrives.
182
+ */
183
+ due() {
184
+ const attempted = this.attempted;
185
+ return attempted <= BackoffThreshold || this.size - attempted >= attempted >>> 3;
47
186
  }
48
- return [...a, ...b];
49
187
  };
50
- const sliceFrom = (input, offset) => Predicate.isString(input) ? input.slice(offset) : input.slice(offset);
51
188
  const noProgress = (input, offset) => ParseError.fromFailure(input, new Failure(offset, "Assert", [], [], false, void 0, "`parseMany` parsers must always consume", 0), "Cut");
52
189
  /**
53
190
  * Transforms a stream of input chunks into a stream of parsed values by
@@ -63,48 +200,81 @@ const noProgress = (input, offset) => ParseError.fromFailure(input, new Failure(
63
200
  * cut short by its chunk is not: compiled code reports `Incomplete` where
64
201
  * the interpreter does.
65
202
  *
203
+ * **Untrusted streams.** The input not yet consumed (one unfinished value)
204
+ * is limited to `maxBufferSize` tokens, 64 MiB by default
205
+ * ({@link defaultMaxBufferSize}); past it the stream fails with a
206
+ * {@link BufferLimitError}, so a peer that never sends the delimiter cannot
207
+ * grow the buffer without bound. An unfinished value is parsed again from
208
+ * its start when more input arrives. Up to 64 KiB that happens on every
209
+ * chunk; a larger one is retried only once the buffer has grown by an
210
+ * eighth, so the work stays linear in its size. The cost: the last chunk of
211
+ * a value larger than 64 KiB may not complete it in the output until more
212
+ * input arrives or the stream ends, when everything left is parsed.
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * import { Effect, Stream } from "effect"
217
+ * import { Ascii, Parser, ParserStream, Token } from "@thomasfosterau/effect-parse"
218
+ *
219
+ * const line = Parser.terminated(Ascii.decInt, Token.literal("\n"))
220
+ *
221
+ * const program = Stream.make("1\n2", "3\n", "4\n").pipe(
222
+ * ParserStream.parseMany(line, { maxBufferSize: 1024 }),
223
+ * Stream.runCollect
224
+ * )
225
+ * // Effect.runSync(program) => [1, 23, 4]
226
+ * ```
227
+ *
66
228
  * @since 0.1.0
67
229
  * @category combinators
68
230
  */
69
- const parseMany = /*#__PURE__*/ dual((args) => Stream.isStream(args[0]), (self, parser, options) => Stream.suspend(() => {
70
- const step = parser.step;
71
- let buffer = options?.empty === void 0 ? void 0 : fromSource(options.empty);
72
- const drain = (context, partial) => {
73
- const out = [];
74
- if (buffer === void 0) return Effect.succeed(out);
75
- const input = buffer;
76
- const state = new State(input);
77
- let offset = 0;
78
- while (offset < input.length) {
79
- state.reset(offset, {
80
- partial,
81
- context
82
- });
83
- const a = Run.runOn(parser, step, state, false);
84
- if (a === Fail) {
85
- if (partial && state.mode === "Incomplete") break;
231
+ const parseMany = /*#__PURE__*/ dual((args) => Stream.isStream(args[0]), (self, parser, options) => {
232
+ const limit = limitOf(options);
233
+ return Stream.suspend(() => {
234
+ const step = parser.step;
235
+ const pending = new Pending();
236
+ if (options?.empty !== void 0) pending.append(fromSource(options.empty));
237
+ const drain = (context, partial) => {
238
+ const out = [];
239
+ if (pending.isEmpty) return Effect.succeed(out);
240
+ const input = pending.input();
241
+ const state = new State(input);
242
+ let offset = 0;
243
+ while (offset < input.length) {
86
244
  state.reset(offset, {
87
245
  partial,
88
246
  context
89
247
  });
90
- Run.runOn(parser, step, state);
91
- return Effect.fail(ParseError.fromFailure(input, state.reportedFailure(), state.mode, state.encoding));
248
+ const a = Run.runOn(parser, step, state, false);
249
+ if (a === Fail) {
250
+ if (partial && state.mode === "Incomplete") break;
251
+ state.reset(offset, {
252
+ partial,
253
+ context
254
+ });
255
+ Run.runOn(parser, step, state);
256
+ return Effect.fail(ParseError.fromFailure(input, state.reportedFailure(), state.mode, state.encoding));
257
+ }
258
+ if (state.pos === offset) return Effect.fail(noProgress(input, offset));
259
+ out.push(a);
260
+ offset = state.pos;
92
261
  }
93
- if (state.pos === offset) return Effect.fail(noProgress(input, offset));
94
- out.push(a);
95
- offset = state.pos;
96
- }
97
- buffer = sliceFrom(input, offset);
98
- return Effect.succeed(out);
99
- };
100
- const feed = (source) => Effect.flatMap(Effect.context(), (context) => Effect.suspend(() => {
101
- const chunk = fromSource(source);
102
- buffer = buffer === void 0 ? chunk : concat(buffer, chunk);
103
- return drain(context, true);
104
- }));
105
- const finish = Effect.flatMap(Effect.context(), (context) => Effect.suspend(() => drain(context, false)));
106
- return self.pipe(Stream.mapEffect(feed), Stream.flattenIterable, Stream.concat(Stream.unwrap(Effect.map(finish, Stream.fromIterable))));
107
- }));
262
+ pending.consume(offset);
263
+ pending.attemptedWith(offset < input.length);
264
+ const size = input.length - offset;
265
+ return size > limit ? Effect.fail(new BufferLimitError({
266
+ limit,
267
+ size
268
+ })) : Effect.succeed(out);
269
+ };
270
+ const feed = (source) => Effect.flatMap(Effect.context(), (context) => Effect.suspend(() => {
271
+ pending.append(fromSource(source));
272
+ return pending.due() || pending.size > limit ? drain(context, true) : Effect.succeed([]);
273
+ }));
274
+ const finish = Effect.flatMap(Effect.context(), (context) => Effect.suspend(() => drain(context, false)));
275
+ return self.pipe(Stream.mapEffect(feed), Stream.flattenIterable, Stream.concat(Stream.unwrap(Effect.map(finish, Stream.fromIterable))));
276
+ });
277
+ });
108
278
  /**
109
279
  * Parses a stream of chunks incrementally, as one input. Chunks are parsed
110
280
  * as they arrive, in partial mode; when the parser runs out of input, its
@@ -158,6 +328,10 @@ var Incremental = class {
158
328
  if (this.kind === 2) return this.items;
159
329
  return this.text;
160
330
  }
331
+ /** The tokens held: the whole input so far. */
332
+ get size() {
333
+ return this.length;
334
+ }
161
335
  push(chunk) {
162
336
  if (isEmptyUnits(chunk)) return;
163
337
  this.append(chunk);
@@ -228,13 +402,30 @@ var Incremental = class {
228
402
  * callbacks should be pure: the elements parsed before a checkpoint are
229
403
  * not parsed again, and on failure everything is parsed again.
230
404
  *
405
+ * The whole input is held until the stream ends, so it is limited to
406
+ * `maxBufferSize` tokens (64 MiB by default, see {@link BufferOptions}):
407
+ * a stream that would exceed it fails with a {@link BufferLimitError}
408
+ * before the chunk that crosses the limit is buffered.
409
+ *
231
410
  * @since 0.1.0
232
411
  * @category combinators
233
412
  */
234
- const parseAll = /*#__PURE__*/ dual((args) => Stream.isStream(args[0]), (self, parser, options) => Effect.flatMap(Effect.context(), (context) => Effect.suspend(() => {
235
- const incremental = new Incremental(parser, context, options?.empty === void 0 ? void 0 : fromSource(options.empty));
236
- return Effect.flatMap(Stream.runForEach(self, (chunk) => Effect.sync(() => incremental.push(fromSource(chunk)))), () => Effect.suspend(() => incremental.finish()));
237
- })));
413
+ const parseAll = /*#__PURE__*/ dual((args) => Stream.isStream(args[0]), (self, parser, options) => {
414
+ const limit = limitOf(options);
415
+ return Effect.flatMap(Effect.context(), (context) => Effect.suspend(() => {
416
+ const incremental = new Incremental(parser, context, options?.empty === void 0 ? void 0 : fromSource(options.empty));
417
+ return Effect.flatMap(Stream.runForEach(self, (source) => Effect.suspend(() => {
418
+ const chunk = fromSource(source);
419
+ const size = incremental.size + chunk.length;
420
+ if (size > limit) return Effect.fail(new BufferLimitError({
421
+ limit,
422
+ size
423
+ }));
424
+ incremental.push(chunk);
425
+ return Effect.void;
426
+ })), () => Effect.suspend(() => incremental.finish()));
427
+ }));
428
+ });
238
429
  /**
239
430
  * An error reading a `ReadableStream` given to {@link parseReadable} or
240
431
  * {@link parseResponse}.
@@ -243,11 +434,16 @@ const parseAll = /*#__PURE__*/ dual((args) => Stream.isStream(args[0]), (self, p
243
434
  * @category errors
244
435
  */
245
436
  var ReadError = class extends (/*#__PURE__*/ Data.TaggedError("ReadError")) {};
437
+ /** Whether a web-stream runner was called with its stream first: the other form starts with the parser. */
438
+ const isDataFirst = (args) => !isParser(args[0]);
246
439
  /**
247
440
  * Parses a web `ReadableStream` of bytes, such as the body of a `fetch`
248
441
  * response, incrementally (see {@link parseAll}): the bytes are parsed as
249
442
  * they arrive, without waiting for the end of the stream or decoding them
250
- * first. Use a byte parser, e.g. one built from `Text.utf8`.
443
+ * first. Use a byte parser, e.g. one built from `Text.utf8`. The stream is
444
+ * limited to `maxBufferSize` bytes (64 MiB by default, see
445
+ * {@link BufferOptions}), past which it fails with a
446
+ * {@link BufferLimitError}.
251
447
  *
252
448
  * @example
253
449
  * ```ts
@@ -264,15 +460,36 @@ var ReadError = class extends (/*#__PURE__*/ Data.TaggedError("ReadError")) {};
264
460
  * @since 0.1.0
265
461
  * @category combinators
266
462
  */
267
- const parseReadable = /*#__PURE__*/ dual(2, (self, parser) => parseAll(Stream.fromReadableStream({
463
+ const parseReadable = /*#__PURE__*/ dual(isDataFirst, (self, parser, options) => parseAll(Stream.fromReadableStream({
268
464
  evaluate: () => self,
269
465
  onError: (cause) => new ReadError({ cause })
270
- }), parser, { empty: /* @__PURE__ */ new Uint8Array(0) }));
466
+ }), parser, {
467
+ empty: /* @__PURE__ */ new Uint8Array(0),
468
+ maxBufferSize: options?.maxBufferSize
469
+ }));
470
+ /**
471
+ * The length a response declares for the body as it will be read, if it
472
+ * declares one: `Content-Length` counts the encoded body, so a response
473
+ * with a `Content-Encoding` (which `fetch` decodes) declares nothing about
474
+ * the bytes parsed.
475
+ */
476
+ const declaredLength = (response) => {
477
+ if (response.headers.has("content-encoding")) return void 0;
478
+ const header = response.headers.get("content-length");
479
+ if (header === null || !/^[0-9]+$/.test(header)) return void 0;
480
+ return Number(header);
481
+ };
271
482
  /**
272
483
  * Parses the body of a `fetch` `Response` incrementally, as it downloads
273
484
  * (see {@link parseReadable}). A response without a body is parsed as empty
274
485
  * input. The status is not checked.
275
486
  *
487
+ * The body is limited to `maxBufferSize` bytes (64 MiB by default, see
488
+ * {@link BufferOptions}). A response whose `Content-Length` already exceeds
489
+ * the limit fails with a {@link BufferLimitError} before any of the body is
490
+ * read; the limit is enforced on the bytes read as well, since the header
491
+ * may be absent or wrong.
492
+ *
276
493
  * @example
277
494
  * ```ts
278
495
  * import { Effect } from "effect"
@@ -281,13 +498,23 @@ const parseReadable = /*#__PURE__*/ dual(2, (self, parser) => parseAll(Stream.fr
281
498
  * const lines = Parser.separated(Text.utf8.tillLineEnding, Text.utf8.lineEnding)
282
499
  *
283
500
  * const response = await fetch("https://example.com/log.txt")
284
- * const value = await Effect.runPromise(ParserStream.parseResponse(response, lines))
501
+ * const value = await Effect.runPromise(
502
+ * ParserStream.parseResponse(response, lines, { maxBufferSize: 8 * 1024 * 1024 })
503
+ * )
285
504
  * ```
286
505
  *
287
506
  * @since 0.1.0
288
507
  * @category combinators
289
508
  */
290
- const parseResponse = /*#__PURE__*/ dual(2, (self, parser) => parseReadable(self.body ?? new ReadableStream({ start: (c) => c.close() }), parser));
291
- export { ReadError, parseAll, parseMany, parseReadable, parseResponse };
509
+ const parseResponse = /*#__PURE__*/ dual(isDataFirst, (self, parser, options) => Effect.suspend(() => {
510
+ const limit = limitOf(options);
511
+ const size = declaredLength(self);
512
+ if (size !== void 0 && size > limit) return Effect.fail(new BufferLimitError({
513
+ limit,
514
+ size
515
+ }));
516
+ return parseReadable(self.body ?? new ReadableStream({ start: (c) => c.close() }), parser, options);
517
+ }));
518
+ export { BufferLimitError, ReadError, defaultMaxBufferSize, parseAll, parseMany, parseReadable, parseResponse };
292
519
 
293
520
  //# sourceMappingURL=ParserStream.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ParserStream.js","names":[],"sources":["../src/ParserStream.ts"],"sourcesContent":["/**\n * Running parsers over Effect `Stream`s of input chunks.\n *\n * This is where nom's and winnow's *partial* (streaming) parsing meets\n * Effect: chunks are buffered and the parser runs in partial mode, so a\n * value split across chunk boundaries is reported as `Incomplete` and\n * retried once more data arrives. When the stream ends, the leftover input\n * is parsed in complete mode.\n *\n * @example\n * ```ts\n * import { Effect, Stream } from \"effect\"\n * import { Ascii, Parser, ParserStream, Token } from \"@thomasfosterau/effect-parse\"\n *\n * const line = Parser.terminated(Ascii.decInt, Token.literal(\"\\n\"))\n *\n * const program = Stream.make(\"1\\n2\", \"3\\n\", \"4\\n\").pipe(\n * ParserStream.parseMany(line),\n * Stream.runCollect\n * )\n * // Effect.runSync(program) => [1, 23, 4]\n * ```\n *\n * @since 0.1.0\n */\nimport * as Predicate from \"effect/Predicate\"\nimport type * as Context from \"effect/Context\"\nimport * as Data from \"effect/Data\"\nimport * as Effect from \"effect/Effect\"\nimport { dual } from \"effect/Function\"\nimport * as Stream from \"effect/Stream\"\nimport type { BinaryInput, Input, Source } from \"./Input.js\"\nimport { Fail, Failure, fromSource, State } from \"./Input.js\"\nimport * as ParseError from \"./ParseError.js\"\nimport * as Registry from \"./internal/compiler/registry.js\"\nimport type * as Resume from \"./internal/resume.js\"\nimport * as Run from \"./internal/run.js\"\nimport * as Stack from \"./internal/stack.js\"\nimport { isEmptyUnits } from \"./internal/text.js\"\nimport type { Parser } from \"./Parser.js\"\n\n/**\n * Input kinds that can be streamed in chunks.\n *\n * @since 0.1.0\n * @category models\n */\nexport type Chunkable = string | Uint8Array | ReadonlyArray<unknown>\n\nconst concat = <I extends Chunkable>(first: I, b: I): I => {\n // Widened so the checks below narrow it (a type parameter does not narrow).\n const a: Chunkable = first\n if (Predicate.isString(a)) {\n // SAFETY: both are chunks of one stream of `I`, so `b` is a string when `a` is, and so is their concatenation\n return (a + (b as string)) as I\n }\n if (a instanceof Uint8Array) {\n if (a.byteLength === 0) return b\n // SAFETY: both are chunks of one stream of `I`, so `b` is a `Uint8Array` when `a` is\n const out = new Uint8Array(a.length + (b as Uint8Array).length)\n out.set(a)\n // SAFETY: both are chunks of one stream of `I`, so `b` is a `Uint8Array` when `a` is\n out.set(b as Uint8Array, a.length)\n // SAFETY: `I` is `a`'s type, a `Uint8Array` here, and `out` is a new `Uint8Array`\n return out as I\n }\n // SAFETY: both are chunks of one stream of `I`, so `b` is an array when `a` is\n const joined: Chunkable = [...a, ...(b as ReadonlyArray<unknown>)]\n // SAFETY: `I` is `a`'s type, an array here, and `joined` is a new array of both chunks' elements\n return joined as I\n}\n\nconst sliceFrom = <I extends Chunkable>(input: I, offset: number): I =>\n // SAFETY: slicing a string, `Uint8Array` or array returns a value of the same kind\n (Predicate.isString(input) ? input.slice(offset) : input.slice(offset)) as I\n\nconst noProgress = (input: Input, offset: number) =>\n ParseError.fromFailure<never>(\n input,\n new Failure(offset, \"Assert\", [], [], false, undefined, \"`parseMany` parsers must always consume\", 0),\n \"Cut\"\n )\n\n/**\n * Options for {@link parseMany}.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface ParseManyOptions<I> {\n /** The empty input, used to initialise the buffer (inferred from the first chunk if omitted). */\n readonly empty?: Source<I> | undefined\n}\n\n/**\n * Transforms a stream of input chunks into a stream of parsed values by\n * applying `parser` repeatedly. Each application must consume input.\n *\n * Services required by the parser are taken from the stream's context.\n * Error offsets are relative to the internal buffer of unconsumed input,\n * which is also the `input` of the reported `ParseError`.\n *\n * A parser compiled with the `Compiler` module runs compiled, with the\n * build that handles partial input (see `Parser.run`); a value that fails\n * is parsed again interpreted, so errors are the interpreter's. A value\n * cut short by its chunk is not: compiled code reports `Incomplete` where\n * the interpreter does.\n *\n * @since 0.1.0\n * @category combinators\n */\nexport const parseMany: {\n <A, E, R, I extends Chunkable>(\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): <E0, R0>(self: Stream.Stream<Source<I>, E0, R0>) => Stream.Stream<A, E0 | ParseError.ParseError<E>, R0 | R>\n <I extends Chunkable, E0, R0, A, E, R>(\n self: Stream.Stream<Source<NoInfer<I>>, E0, R0>,\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): Stream.Stream<A, E0 | ParseError.ParseError<E>, R0 | R>\n} = /*#__PURE__*/ dual(\n (args) => Stream.isStream(args[0]),\n <I extends Chunkable, E0, R0, A, E, R>(\n self: Stream.Stream<Source<I>, E0, R0>,\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): Stream.Stream<A, E0 | ParseError.ParseError<E>, R0 | R> =>\n Stream.suspend(() => {\n const step = parser.step\n let buffer: I | undefined = options?.empty === undefined ? undefined : fromSource<I>(options.empty)\n\n const drain = (context: any, partial: boolean): Effect.Effect<Array<A>, ParseError.ParseError<E>> => {\n const out: Array<A> = []\n if (buffer === undefined) return Effect.succeed(out)\n const input = buffer\n // One state for the whole buffer, reset before each value\n const state = new State(input)\n let offset = 0\n while (offset < input.length) {\n state.reset(offset, { partial, context })\n // A compiled `Incomplete` is the interpreter's: only an error needs the interpreted run\n const a = Run.runOn(parser, step, state, false)\n if (a === Fail) {\n if (partial && state.mode === \"Incomplete\") break\n state.reset(offset, { partial, context })\n Run.runOn(parser, step, state)\n return Effect.fail(ParseError.fromFailure<E>(input, state.reportedFailure(), state.mode, state.encoding))\n }\n if (state.pos === offset) return Effect.fail(noProgress(input, offset))\n out.push(a)\n offset = state.pos\n }\n buffer = sliceFrom(input, offset)\n return Effect.succeed(out)\n }\n\n const feed = (source: Source<I>) =>\n Effect.flatMap(Effect.context<R>(), (context) =>\n Effect.suspend(() => {\n const chunk = fromSource<I>(source)\n buffer = buffer === undefined ? chunk : concat(buffer, chunk)\n return drain(context, true)\n })\n )\n\n const finish = Effect.flatMap(Effect.context<R>(), (context) => Effect.suspend(() => drain(context, false)))\n\n return self.pipe(\n Stream.mapEffect(feed),\n Stream.flattenIterable,\n Stream.concat(Stream.unwrap(Effect.map(finish, Stream.fromIterable)))\n )\n })\n)\n\n/**\n * Parses a stream of chunks incrementally, as one input. Chunks are parsed\n * as they arrive, in partial mode; when the parser runs out of input, its\n * repetitions keep checkpoints (see `internal/resume.ts`), so the next\n * attempt carries on from where the last one stopped instead of starting\n * over.\n */\nclass Incremental<A, E, I extends Chunkable> {\n private kind: 0 | 1 | 2 | undefined\n private text = \"\"\n private bytes = new Uint8Array(0)\n private items: Array<unknown> = []\n private length = 0\n private readonly resume: Resume.Checkpoints = new Map()\n /** The input length at the last attempt, and where that attempt ran out of input. */\n private attempted = 0\n private restart = 0\n private pushed = 0\n /** Set once an attempt settles: a success (with its end offset) or a definite failure. */\n private settled: { readonly value: A; readonly pos: number } | \"failed\" | undefined\n\n constructor(\n private readonly parser: Parser<A, E, any, I>,\n private readonly context: Context.Context<never>,\n empty: I | undefined\n ) {\n if (empty !== undefined) this.append(empty)\n }\n\n private append(chunk: I): void {\n if (Predicate.isString(chunk)) {\n this.kind ??= 0\n this.text += chunk\n this.length += chunk.length\n } else if (chunk instanceof Uint8Array) {\n this.kind ??= 1\n const needed = this.length + chunk.length\n if (needed > this.bytes.length) {\n // Grow geometrically, so appending costs amortized constant time per byte\n const grown = new Uint8Array(Math.max(needed, this.bytes.length * 2, 4096))\n grown.set(this.bytes.subarray(0, this.length))\n this.bytes = grown\n }\n this.bytes.set(chunk, this.length)\n this.length = needed\n } else {\n this.kind ??= 2\n for (let i = 0; i < chunk.length; i++) this.items.push(chunk[i])\n this.length = this.items.length\n }\n }\n\n private input(): I {\n if (this.kind === 1) {\n // SAFETY: `kind` 1 is set by the first chunk being a `Uint8Array`, so `I` is `Uint8Array`\n return this.bytes.subarray(0, this.length) as I\n }\n if (this.kind === 2) {\n // Items are only appended between attempts, after each state has read its `end`\n const items: Chunkable = this.items\n // SAFETY: `kind` 2 is set by the first chunk being an array, so `I` is an array of the items' type\n return items as I\n }\n // SAFETY: `kind` 0 means the first chunk was a string, so `I` is `string`; while `kind` is unset (no\n // chunk arrived) `I` is unknown and the empty string stands for the empty input\n return this.text as I\n }\n\n push(chunk: I): void {\n if (isEmptyUnits(chunk)) return\n this.append(chunk)\n if (this.settled !== undefined) return\n // The first attempt waits for a second chunk: an input that arrives in\n // one chunk (most small responses) is parsed once, by `finish`, instead\n // of once in partial mode and again in complete mode, since a repetition\n // that completes without resuming keeps no checkpoint\n if (++this.pushed === 1) return\n // An attempt re-reads what the last one could not finish, from `restart`;\n // waiting until at least half as much new input has arrived keeps the\n // total work linear in the input. Strings are also copied when they are\n // flattened, so they wait for a fraction of the whole input too.\n const added = this.length - this.attempted\n if (added < (this.attempted - this.restart) / 2) return\n if (this.kind === 0 && added < this.attempted / 8) return\n this.attempt()\n }\n\n /**\n * The step attempts run: the parser's compiled code if it was compiled\n * (the resumable build, unless `resumable` is false), else the interpreter.\n */\n private step(state: State<I>, resumable = true): A | Fail {\n // SAFETY: a compiled step (typed `any` by the compilers) returns what the parser's own step returns\n return Stack.run(Registry.stepFor(this.parser, state, resumable) ?? this.parser.step, state) as A | Fail\n }\n\n private attempt(): void {\n const state = new State(this.input(), { partial: true, context: this.context })\n state.resume = this.resume\n const a = this.step(state)\n this.attempted = this.length\n if (a !== Fail) {\n this.settled = { value: a, pos: state.pos }\n } else if (state.mode === \"Incomplete\") {\n this.restart = state.failure.offset\n } else {\n this.settled = \"failed\"\n }\n }\n\n finish(): Effect.Effect<A, ParseError.ParseError<E>> {\n const settled = this.settled\n if (settled !== undefined && settled !== \"failed\") {\n if (settled.pos === this.length) return Effect.succeed(settled.value)\n } else if (settled === undefined) {\n // The last attempt, in complete mode, resumes from the checkpoints too;\n // with no attempt before it, it is an ordinary parse\n const state = new State(this.input(), { context: this.context })\n const resumable = this.attempted > 0\n if (resumable) state.resume = this.resume\n const a = this.step(state, resumable)\n if (a !== Fail && state.pos === state.end) return Effect.succeed(a)\n }\n // The error comes from an ordinary parse of the whole input, so it is the\n // same as if the input had been parsed in one go\n const all = this.input()\n const state = new State(all, { context: this.context })\n const a = Stack.run(this.parser.step, state)\n if (a !== Fail && state.pos === state.end) return Effect.succeed(a)\n if (a !== Fail) state.backtrack(\"Eof\", [\"end of input\"])\n return Effect.fail(ParseError.fromFailure<E>(all, state.reportedFailure(), state.mode, state.encoding))\n }\n}\n\n/**\n * Parses an entire stream of chunks as a single complete input.\n *\n * Parsing does not wait for the stream to end: each chunk is parsed as it\n * arrives, in partial mode, and when the parser runs out of input its\n * repetitions (`repeat`, `separated`, `fold`, `escaped`, ...) remember how\n * far they got, so that the next attempt resumes there rather than starting\n * over. A large document, such as the body of a `fetch` response, is\n * therefore parsed while it downloads, and is ready soon after its last\n * byte. Use {@link parseReadable} or {@link parseResponse} for web streams.\n *\n * The result and any `ParseError` are those of parsing the whole input at\n * once: errors are reported once the stream has ended, by an ordinary\n * interpreted parse of the whole input. A parser compiled with the\n * `Compiler` module streams compiled too (with a build that handles partial\n * input), which is several times faster. As with compiled parsers,\n * callbacks should be pure: the elements parsed before a checkpoint are\n * not parsed again, and on failure everything is parsed again.\n *\n * @since 0.1.0\n * @category combinators\n */\nexport const parseAll: {\n <A, E, R, I extends Chunkable>(\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): <E0, R0>(self: Stream.Stream<Source<I>, E0, R0>) => Effect.Effect<A, E0 | ParseError.ParseError<E>, R0 | R>\n <I extends Chunkable, E0, R0, A, E, R>(\n self: Stream.Stream<Source<NoInfer<I>>, E0, R0>,\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): Effect.Effect<A, E0 | ParseError.ParseError<E>, R0 | R>\n} = /*#__PURE__*/ dual(\n (args) => Stream.isStream(args[0]),\n <I extends Chunkable, E0, R0, A, E, R>(\n self: Stream.Stream<Source<I>, E0, R0>,\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): Effect.Effect<A, E0 | ParseError.ParseError<E>, R0 | R> =>\n Effect.flatMap(Effect.context<R>(), (context) =>\n Effect.suspend(() => {\n const empty = options?.empty === undefined ? undefined : fromSource<I>(options.empty)\n const incremental = new Incremental(parser, context, empty)\n return Effect.flatMap(\n Stream.runForEach(self, (chunk) => Effect.sync(() => incremental.push(fromSource<I>(chunk)))),\n () => Effect.suspend(() => incremental.finish())\n )\n })\n )\n)\n\n/**\n * An error reading a `ReadableStream` given to {@link parseReadable} or\n * {@link parseResponse}.\n *\n * @since 0.1.0\n * @category errors\n */\nexport class ReadError extends /*#__PURE__*/ Data.TaggedError(\"ReadError\")<{ readonly cause: unknown }> {}\n\n/**\n * Parses a web `ReadableStream` of bytes, such as the body of a `fetch`\n * response, incrementally (see {@link parseAll}): the bytes are parsed as\n * they arrive, without waiting for the end of the stream or decoding them\n * first. Use a byte parser, e.g. one built from `Text.utf8`.\n *\n * @example\n * ```ts\n * import { Effect } from \"effect\"\n * import { Parser, ParserStream, Text } from \"@thomasfosterau/effect-parse\"\n *\n * const csvLine = Parser.separated(Text.utf8.takeTill(\",\\n\"), Text.utf8.literal(\",\"))\n * const csv = Parser.separated(csvLine, Text.utf8.literal(\"\\n\"))\n *\n * const response = await fetch(\"https://example.com/data.csv\")\n * const rows = await Effect.runPromise(ParserStream.parseReadable(response.body!, csv))\n * ```\n *\n * @since 0.1.0\n * @category combinators\n */\nexport const parseReadable: {\n <A, E, R>(\n parser: Parser<A, E, R, Uint8Array>\n ): (self: ReadableStream<BinaryInput>) => Effect.Effect<A, ParseError.ParseError<E> | ReadError, R>\n <A, E, R>(\n self: ReadableStream<BinaryInput>,\n parser: Parser<A, E, R, Uint8Array>\n ): Effect.Effect<A, ParseError.ParseError<E> | ReadError, R>\n} = /*#__PURE__*/ dual(\n 2,\n <A, E, R>(\n self: ReadableStream<BinaryInput>,\n parser: Parser<A, E, R, Uint8Array>\n ): Effect.Effect<A, ParseError.ParseError<E> | ReadError, R> =>\n parseAll(\n Stream.fromReadableStream({ evaluate: () => self, onError: (cause) => new ReadError({ cause }) }),\n parser,\n { empty: new Uint8Array(0) }\n )\n)\n\n/**\n * Parses the body of a `fetch` `Response` incrementally, as it downloads\n * (see {@link parseReadable}). A response without a body is parsed as empty\n * input. The status is not checked.\n *\n * @example\n * ```ts\n * import { Effect } from \"effect\"\n * import { Parser, ParserStream, Text } from \"@thomasfosterau/effect-parse\"\n *\n * const lines = Parser.separated(Text.utf8.tillLineEnding, Text.utf8.lineEnding)\n *\n * const response = await fetch(\"https://example.com/log.txt\")\n * const value = await Effect.runPromise(ParserStream.parseResponse(response, lines))\n * ```\n *\n * @since 0.1.0\n * @category combinators\n */\nexport const parseResponse: {\n <A, E, R>(\n parser: Parser<A, E, R, Uint8Array>\n ): (self: Response) => Effect.Effect<A, ParseError.ParseError<E> | ReadError, R>\n <A, E, R>(\n self: Response,\n parser: Parser<A, E, R, Uint8Array>\n ): Effect.Effect<A, ParseError.ParseError<E> | ReadError, R>\n} = /*#__PURE__*/ dual(\n 2,\n <A, E, R>(\n self: Response,\n parser: Parser<A, E, R, Uint8Array>\n ): Effect.Effect<A, ParseError.ParseError<E> | ReadError, R> =>\n parseReadable(self.body ?? new ReadableStream<Uint8Array>({ start: (c) => c.close() }), parser)\n)\n"],"mappings":"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"}
1
+ {"version":3,"file":"ParserStream.js","names":[],"sources":["../src/ParserStream.ts"],"sourcesContent":["/**\n * Running parsers over Effect `Stream`s of input chunks.\n *\n * This is where nom's and winnow's *partial* (streaming) parsing meets\n * Effect: chunks are buffered and the parser runs in partial mode, so a\n * value split across chunk boundaries is reported as `Incomplete` and\n * retried once more data arrives. When the stream ends, the leftover input\n * is parsed in complete mode.\n *\n * Every runner bounds what it buffers: `maxBufferSize` (see\n * {@link BufferOptions}) defaults to 64 MiB ({@link defaultMaxBufferSize}),\n * and a stream that exceeds it fails with a {@link BufferLimitError}, so a\n * socket or an HTTP body can be parsed without wrapping the stream first.\n * Pass `Infinity` for input that is trusted to be large.\n *\n * @example\n * ```ts\n * import { Effect, Stream } from \"effect\"\n * import { Ascii, Parser, ParserStream, Token } from \"@thomasfosterau/effect-parse\"\n *\n * const line = Parser.terminated(Ascii.decInt, Token.literal(\"\\n\"))\n *\n * const program = Stream.make(\"1\\n2\", \"3\\n\", \"4\\n\").pipe(\n * ParserStream.parseMany(line),\n * Stream.runCollect\n * )\n * // Effect.runSync(program) => [1, 23, 4]\n * ```\n *\n * @since 0.1.0\n */\nimport * as Predicate from \"effect/Predicate\"\nimport type * as Context from \"effect/Context\"\nimport * as Data from \"effect/Data\"\nimport * as Effect from \"effect/Effect\"\nimport { dual } from \"effect/Function\"\nimport * as Stream from \"effect/Stream\"\nimport type { BinaryInput, Input, Source } from \"./Input.js\"\nimport { Fail, Failure, fromSource, State } from \"./Input.js\"\nimport * as ParseError from \"./ParseError.js\"\nimport * as Registry from \"./internal/compiler/registry.js\"\nimport type * as Resume from \"./internal/resume.js\"\nimport * as Run from \"./internal/run.js\"\nimport * as Stack from \"./internal/stack.js\"\nimport { isEmptyUnits } from \"./internal/text.js\"\nimport type { Parser } from \"./Parser.js\"\nimport { isParser } from \"./Parser.js\"\n\n/**\n * Input kinds that can be streamed in chunks.\n *\n * @since 0.1.0\n * @category models\n */\nexport type Chunkable = string | Uint8Array | ReadonlyArray<unknown>\n\n/**\n * The default `maxBufferSize` of {@link parseMany}, {@link parseAll},\n * {@link parseReadable} and {@link parseResponse}: 64 MiB, counted in tokens\n * of the input (UTF-16 code units of a string, bytes of a `Uint8Array`,\n * elements of an array).\n *\n * @since 0.6.0\n * @category constants\n */\nexport const defaultMaxBufferSize: number = 64 * 1024 * 1024\n\n/**\n * A stream runner would have buffered more than its `maxBufferSize`: `size`\n * tokens against a limit of `limit`. It is a different failure from a\n * `ParseError`: the input is too large, not malformed (an HTTP server\n * answers 413 for the one and 400 for the other), and nothing of the input\n * is attached to it.\n *\n * @example\n * ```ts\n * import { Effect, Stream } from \"effect\"\n * import { Parser, ParserStream, Token } from \"@thomasfosterau/effect-parse\"\n *\n * const line = Parser.terminated(Token.takeTill(\"\\n\"), Token.literal(\"\\n\"))\n *\n * Stream.make(\"no newline, \", \"ever\").pipe(\n * ParserStream.parseMany(line, { maxBufferSize: 8 }),\n * Stream.runCollect,\n * Effect.catchTag(\"BufferLimitError\", (e) => Effect.succeed(`too large: ${e.size} > ${e.limit}`))\n * )\n * ```\n *\n * @since 0.6.0\n * @category errors\n */\nexport class BufferLimitError\n extends /*#__PURE__*/ Data.TaggedError(\"BufferLimitError\")<{\n /** The `maxBufferSize` in force, in tokens. */\n readonly limit: number\n /** The tokens the runner held, or was about to hold (for `parseResponse`, the declared `Content-Length`). */\n readonly size: number\n }>\n{\n override get message(): string {\n return `the input is larger than maxBufferSize: ${this.size} tokens, limit ${this.limit}`\n }\n}\n\n/**\n * The limit on buffered input that the stream runners share.\n *\n * @since 0.6.0\n * @category models\n */\nexport interface BufferOptions {\n /**\n * The most tokens a runner buffers (UTF-16 code units of a string, bytes\n * of a `Uint8Array`, elements of an array) before it fails with a\n * {@link BufferLimitError}. Defaults to {@link defaultMaxBufferSize}\n * (64 MiB); `Infinity` turns the limit off.\n *\n * {@link parseMany} counts the input it has not consumed yet, which is one\n * unfinished value: complete values in a chunk are parsed and emitted\n * first, whatever the chunk's size. {@link parseAll},\n * {@link parseReadable} and {@link parseResponse} hold the whole input, so\n * they count all of it.\n */\n readonly maxBufferSize?: number | undefined\n}\n\n/** The limit `options` asks for: a positive number of tokens, or `Infinity`. */\nconst limitOf = (options: BufferOptions | undefined): number => {\n const limit = options?.maxBufferSize ?? defaultMaxBufferSize\n if (!(limit > 0)) throw new RangeError(\"maxBufferSize must be a positive number of tokens, or Infinity\")\n return limit\n}\n\n/**\n * Above this many unconsumed tokens, {@link parseMany} stops retrying its\n * unfinished value on every chunk (see {@link Pending.due}).\n */\nconst BackoffThreshold = 64 * 1024\n\n/**\n * The input `parseMany` has not consumed yet: what is left of the chunks\n * after the values parsed from them, which is one unfinished value.\n *\n * Appending is amortized constant time per token: text is concatenated (a\n * rope until it is parsed), bytes go into a buffer that doubles, and\n * elements are pushed. Bytes that a parse has seen are never overwritten,\n * since the values parsed from them may be views of the buffer\n * (`Token.take` returns a `subarray`): the buffer is only ever appended to\n * past its end, or replaced by a larger one.\n */\nclass Pending<I extends Chunkable> {\n private kind: 0 | 1 | 2 | undefined\n private text = \"\"\n private bytes: Uint8Array = new Uint8Array(0)\n /** Where the unconsumed bytes start and end in `bytes`, and how far `bytes` may be written. */\n private start = 0\n private end = 0\n private capacity = 0\n private items: Array<unknown> = []\n /** The unconsumed size at the last attempt that ran out of input, or 0 when none is outstanding. */\n private attempted = 0\n\n /** The number of unconsumed tokens. */\n get size(): number {\n return this.kind === 1 ? this.end - this.start : this.kind === 2 ? this.items.length : this.text.length\n }\n\n get isEmpty(): boolean {\n return this.kind === undefined\n }\n\n append(chunk: I): void {\n if (Predicate.isString(chunk)) {\n this.kind ??= 0\n this.text += chunk\n } else if (chunk instanceof Uint8Array) {\n this.kind ??= 1\n this.appendBytes(chunk)\n } else {\n this.kind ??= 2\n for (let i = 0; i < chunk.length; i++) this.items.push(chunk[i])\n }\n }\n\n private appendBytes(chunk: Uint8Array): void {\n const held = this.end - this.start\n if (held === 0) {\n // Nothing is pending: the chunk is the buffer, uncopied. It is the\n // caller's, so it is never written to (`capacity` is its end)\n this.bytes = chunk\n this.start = 0\n this.end = this.capacity = chunk.length\n return\n }\n const needed = held + chunk.length\n if (this.end + chunk.length > this.capacity) {\n // Twice what is needed, so appending costs amortized constant time per byte\n const grown = new Uint8Array(Math.max(needed * 2, 4096))\n grown.set(this.bytes.subarray(this.start, this.end))\n this.bytes = grown\n this.start = 0\n this.end = held\n this.capacity = grown.length\n }\n this.bytes.set(chunk, this.end)\n this.end += chunk.length\n }\n\n /** The unconsumed input. */\n input(): I {\n if (this.kind === 1) {\n // SAFETY: `kind` 1 is set by the first chunk being a `Uint8Array`, so `I` is `Uint8Array`\n return this.bytes.subarray(this.start, this.end) as I\n }\n if (this.kind === 2) {\n const items: Chunkable = this.items\n // SAFETY: `kind` 2 is set by the first chunk being an array, so `I` is an array of the items' type\n return items as I\n }\n // SAFETY: `kind` 0 is set by the first chunk being a string, so `I` is `string`\n return this.text as I\n }\n\n /** Drops the first `count` tokens, which values were parsed from. */\n consume(count: number): void {\n if (count === 0) return\n if (this.kind === 1) this.start += count\n // A new array: the one parsed is the `input` of the state that read it\n else if (this.kind === 2) this.items = this.items.slice(count)\n else this.text = this.text.slice(count)\n }\n\n /** Records whether the attempt just made left an unfinished value. */\n attemptedWith(unfinished: boolean): void {\n this.attempted = unfinished ? this.size : 0\n }\n\n /**\n * Whether to parse now. A value that is still unfinished is parsed again\n * from its start, so retrying on every chunk costs the square of its\n * size: a megabyte with no delimiter, in 1 KiB chunks, took seconds. Past\n * {@link BackoffThreshold} tokens, the next attempt therefore waits until\n * the buffer has grown by an eighth, which keeps the total work linear\n * (each token is re-read at most about nine times). Smaller values are\n * retried on every chunk, as cheaply as before, so a record is emitted\n * as soon as its last chunk arrives.\n */\n due(): boolean {\n const attempted = this.attempted\n return attempted <= BackoffThreshold || this.size - attempted >= attempted >>> 3\n }\n}\n\nconst noProgress = (input: Input, offset: number) =>\n ParseError.fromFailure<never>(\n input,\n new Failure(offset, \"Assert\", [], [], false, undefined, \"`parseMany` parsers must always consume\", 0),\n \"Cut\"\n )\n\n/**\n * Options for {@link parseMany} and {@link parseAll}.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface ParseManyOptions<I> extends BufferOptions {\n /** The empty input, used to initialise the buffer (inferred from the first chunk if omitted). */\n readonly empty?: Source<I> | undefined\n}\n\n/**\n * Transforms a stream of input chunks into a stream of parsed values by\n * applying `parser` repeatedly. Each application must consume input.\n *\n * Services required by the parser are taken from the stream's context.\n * Error offsets are relative to the internal buffer of unconsumed input,\n * which is also the `input` of the reported `ParseError`.\n *\n * A parser compiled with the `Compiler` module runs compiled, with the\n * build that handles partial input (see `Parser.run`); a value that fails\n * is parsed again interpreted, so errors are the interpreter's. A value\n * cut short by its chunk is not: compiled code reports `Incomplete` where\n * the interpreter does.\n *\n * **Untrusted streams.** The input not yet consumed (one unfinished value)\n * is limited to `maxBufferSize` tokens, 64 MiB by default\n * ({@link defaultMaxBufferSize}); past it the stream fails with a\n * {@link BufferLimitError}, so a peer that never sends the delimiter cannot\n * grow the buffer without bound. An unfinished value is parsed again from\n * its start when more input arrives. Up to 64 KiB that happens on every\n * chunk; a larger one is retried only once the buffer has grown by an\n * eighth, so the work stays linear in its size. The cost: the last chunk of\n * a value larger than 64 KiB may not complete it in the output until more\n * input arrives or the stream ends, when everything left is parsed.\n *\n * @example\n * ```ts\n * import { Effect, Stream } from \"effect\"\n * import { Ascii, Parser, ParserStream, Token } from \"@thomasfosterau/effect-parse\"\n *\n * const line = Parser.terminated(Ascii.decInt, Token.literal(\"\\n\"))\n *\n * const program = Stream.make(\"1\\n2\", \"3\\n\", \"4\\n\").pipe(\n * ParserStream.parseMany(line, { maxBufferSize: 1024 }),\n * Stream.runCollect\n * )\n * // Effect.runSync(program) => [1, 23, 4]\n * ```\n *\n * @since 0.1.0\n * @category combinators\n */\nexport const parseMany: {\n <A, E, R, I extends Chunkable>(\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): <E0, R0>(\n self: Stream.Stream<Source<I>, E0, R0>\n ) => Stream.Stream<A, E0 | ParseError.ParseError<E> | BufferLimitError, R0 | R>\n <I extends Chunkable, E0, R0, A, E, R>(\n self: Stream.Stream<Source<NoInfer<I>>, E0, R0>,\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): Stream.Stream<A, E0 | ParseError.ParseError<E> | BufferLimitError, R0 | R>\n} = /*#__PURE__*/ dual(\n (args) => Stream.isStream(args[0]),\n <I extends Chunkable, E0, R0, A, E, R>(\n self: Stream.Stream<Source<I>, E0, R0>,\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): Stream.Stream<A, E0 | ParseError.ParseError<E> | BufferLimitError, R0 | R> => {\n const limit = limitOf(options)\n return Stream.suspend(() => {\n const step = parser.step\n const pending = new Pending<I>()\n if (options?.empty !== undefined) pending.append(fromSource<I>(options.empty))\n\n const drain = (\n context: any,\n partial: boolean\n ): Effect.Effect<Array<A>, ParseError.ParseError<E> | BufferLimitError> => {\n const out: Array<A> = []\n if (pending.isEmpty) return Effect.succeed(out)\n const input = pending.input()\n // One state for the whole buffer, reset before each value\n const state = new State(input)\n let offset = 0\n while (offset < input.length) {\n state.reset(offset, { partial, context })\n // A compiled `Incomplete` is the interpreter's: only an error needs the interpreted run\n const a = Run.runOn(parser, step, state, false)\n if (a === Fail) {\n if (partial && state.mode === \"Incomplete\") break\n state.reset(offset, { partial, context })\n Run.runOn(parser, step, state)\n return Effect.fail(ParseError.fromFailure<E>(input, state.reportedFailure(), state.mode, state.encoding))\n }\n if (state.pos === offset) return Effect.fail(noProgress(input, offset))\n out.push(a)\n offset = state.pos\n }\n pending.consume(offset)\n pending.attemptedWith(offset < input.length)\n // Only what no value consumed counts: an unfinished value larger than the limit\n const size = input.length - offset\n return size > limit ? Effect.fail(new BufferLimitError({ limit, size })) : Effect.succeed(out)\n }\n\n const feed = (source: Source<I>) =>\n Effect.flatMap(Effect.context<R>(), (context) =>\n Effect.suspend((): Effect.Effect<Array<A>, ParseError.ParseError<E> | BufferLimitError> => {\n pending.append(fromSource<I>(source))\n // Over the limit, parse whatever the back-off says, so that complete values are not counted\n return pending.due() || pending.size > limit ? drain(context, true) : Effect.succeed([])\n })\n )\n\n const finish = Effect.flatMap(Effect.context<R>(), (context) => Effect.suspend(() => drain(context, false)))\n\n return self.pipe(\n Stream.mapEffect(feed),\n Stream.flattenIterable,\n Stream.concat(Stream.unwrap(Effect.map(finish, Stream.fromIterable)))\n )\n })\n }\n)\n\n/**\n * Parses a stream of chunks incrementally, as one input. Chunks are parsed\n * as they arrive, in partial mode; when the parser runs out of input, its\n * repetitions keep checkpoints (see `internal/resume.ts`), so the next\n * attempt carries on from where the last one stopped instead of starting\n * over.\n */\nclass Incremental<A, E, I extends Chunkable> {\n private kind: 0 | 1 | 2 | undefined\n private text = \"\"\n private bytes = new Uint8Array(0)\n private items: Array<unknown> = []\n private length = 0\n private readonly resume: Resume.Checkpoints = new Map()\n /** The input length at the last attempt, and where that attempt ran out of input. */\n private attempted = 0\n private restart = 0\n private pushed = 0\n /** Set once an attempt settles: a success (with its end offset) or a definite failure. */\n private settled: { readonly value: A; readonly pos: number } | \"failed\" | undefined\n\n constructor(\n private readonly parser: Parser<A, E, any, I>,\n private readonly context: Context.Context<never>,\n empty: I | undefined\n ) {\n if (empty !== undefined) this.append(empty)\n }\n\n private append(chunk: I): void {\n if (Predicate.isString(chunk)) {\n this.kind ??= 0\n this.text += chunk\n this.length += chunk.length\n } else if (chunk instanceof Uint8Array) {\n this.kind ??= 1\n const needed = this.length + chunk.length\n if (needed > this.bytes.length) {\n // Grow geometrically, so appending costs amortized constant time per byte\n const grown = new Uint8Array(Math.max(needed, this.bytes.length * 2, 4096))\n grown.set(this.bytes.subarray(0, this.length))\n this.bytes = grown\n }\n this.bytes.set(chunk, this.length)\n this.length = needed\n } else {\n this.kind ??= 2\n for (let i = 0; i < chunk.length; i++) this.items.push(chunk[i])\n this.length = this.items.length\n }\n }\n\n private input(): I {\n if (this.kind === 1) {\n // SAFETY: `kind` 1 is set by the first chunk being a `Uint8Array`, so `I` is `Uint8Array`\n return this.bytes.subarray(0, this.length) as I\n }\n if (this.kind === 2) {\n // Items are only appended between attempts, after each state has read its `end`\n const items: Chunkable = this.items\n // SAFETY: `kind` 2 is set by the first chunk being an array, so `I` is an array of the items' type\n return items as I\n }\n // SAFETY: `kind` 0 means the first chunk was a string, so `I` is `string`; while `kind` is unset (no\n // chunk arrived) `I` is unknown and the empty string stands for the empty input\n return this.text as I\n }\n\n /** The tokens held: the whole input so far. */\n get size(): number {\n return this.length\n }\n\n push(chunk: I): void {\n if (isEmptyUnits(chunk)) return\n this.append(chunk)\n if (this.settled !== undefined) return\n // The first attempt waits for a second chunk: an input that arrives in\n // one chunk (most small responses) is parsed once, by `finish`, instead\n // of once in partial mode and again in complete mode, since a repetition\n // that completes without resuming keeps no checkpoint\n if (++this.pushed === 1) return\n // An attempt re-reads what the last one could not finish, from `restart`;\n // waiting until at least half as much new input has arrived keeps the\n // total work linear in the input. Strings are also copied when they are\n // flattened, so they wait for a fraction of the whole input too.\n const added = this.length - this.attempted\n if (added < (this.attempted - this.restart) / 2) return\n if (this.kind === 0 && added < this.attempted / 8) return\n this.attempt()\n }\n\n /**\n * The step attempts run: the parser's compiled code if it was compiled\n * (the resumable build, unless `resumable` is false), else the interpreter.\n */\n private step(state: State<I>, resumable = true): A | Fail {\n // SAFETY: a compiled step (typed `any` by the compilers) returns what the parser's own step returns\n return Stack.run(Registry.stepFor(this.parser, state, resumable) ?? this.parser.step, state) as A | Fail\n }\n\n private attempt(): void {\n const state = new State(this.input(), { partial: true, context: this.context })\n state.resume = this.resume\n const a = this.step(state)\n this.attempted = this.length\n if (a !== Fail) {\n this.settled = { value: a, pos: state.pos }\n } else if (state.mode === \"Incomplete\") {\n this.restart = state.failure.offset\n } else {\n this.settled = \"failed\"\n }\n }\n\n finish(): Effect.Effect<A, ParseError.ParseError<E>> {\n const settled = this.settled\n if (settled !== undefined && settled !== \"failed\") {\n if (settled.pos === this.length) return Effect.succeed(settled.value)\n } else if (settled === undefined) {\n // The last attempt, in complete mode, resumes from the checkpoints too;\n // with no attempt before it, it is an ordinary parse\n const state = new State(this.input(), { context: this.context })\n const resumable = this.attempted > 0\n if (resumable) state.resume = this.resume\n const a = this.step(state, resumable)\n if (a !== Fail && state.pos === state.end) return Effect.succeed(a)\n }\n // The error comes from an ordinary parse of the whole input, so it is the\n // same as if the input had been parsed in one go\n const all = this.input()\n const state = new State(all, { context: this.context })\n const a = Stack.run(this.parser.step, state)\n if (a !== Fail && state.pos === state.end) return Effect.succeed(a)\n if (a !== Fail) state.backtrack(\"Eof\", [\"end of input\"])\n return Effect.fail(ParseError.fromFailure<E>(all, state.reportedFailure(), state.mode, state.encoding))\n }\n}\n\n/**\n * Parses an entire stream of chunks as a single complete input.\n *\n * Parsing does not wait for the stream to end: each chunk is parsed as it\n * arrives, in partial mode, and when the parser runs out of input its\n * repetitions (`repeat`, `separated`, `fold`, `escaped`, ...) remember how\n * far they got, so that the next attempt resumes there rather than starting\n * over. A large document, such as the body of a `fetch` response, is\n * therefore parsed while it downloads, and is ready soon after its last\n * byte. Use {@link parseReadable} or {@link parseResponse} for web streams.\n *\n * The result and any `ParseError` are those of parsing the whole input at\n * once: errors are reported once the stream has ended, by an ordinary\n * interpreted parse of the whole input. A parser compiled with the\n * `Compiler` module streams compiled too (with a build that handles partial\n * input), which is several times faster. As with compiled parsers,\n * callbacks should be pure: the elements parsed before a checkpoint are\n * not parsed again, and on failure everything is parsed again.\n *\n * The whole input is held until the stream ends, so it is limited to\n * `maxBufferSize` tokens (64 MiB by default, see {@link BufferOptions}):\n * a stream that would exceed it fails with a {@link BufferLimitError}\n * before the chunk that crosses the limit is buffered.\n *\n * @since 0.1.0\n * @category combinators\n */\nexport const parseAll: {\n <A, E, R, I extends Chunkable>(\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): <E0, R0>(\n self: Stream.Stream<Source<I>, E0, R0>\n ) => Effect.Effect<A, E0 | ParseError.ParseError<E> | BufferLimitError, R0 | R>\n <I extends Chunkable, E0, R0, A, E, R>(\n self: Stream.Stream<Source<NoInfer<I>>, E0, R0>,\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): Effect.Effect<A, E0 | ParseError.ParseError<E> | BufferLimitError, R0 | R>\n} = /*#__PURE__*/ dual(\n (args) => Stream.isStream(args[0]),\n <I extends Chunkable, E0, R0, A, E, R>(\n self: Stream.Stream<Source<I>, E0, R0>,\n parser: Parser<A, E, R, I>,\n options?: ParseManyOptions<I>\n ): Effect.Effect<A, E0 | ParseError.ParseError<E> | BufferLimitError, R0 | R> => {\n const limit = limitOf(options)\n return Effect.flatMap(Effect.context<R>(), (context) =>\n Effect.suspend(() => {\n const empty = options?.empty === undefined ? undefined : fromSource<I>(options.empty)\n const incremental = new Incremental(parser, context, empty)\n return Effect.flatMap(\n Stream.runForEach(self, (source) =>\n Effect.suspend(() => {\n const chunk = fromSource<I>(source)\n // Checked before the chunk is copied in: the whole input is held, so all of it counts\n const size = incremental.size + chunk.length\n if (size > limit) return Effect.fail(new BufferLimitError({ limit, size }))\n incremental.push(chunk)\n return Effect.void\n })\n ),\n () => Effect.suspend(() => incremental.finish())\n )\n })\n )\n }\n)\n\n/**\n * An error reading a `ReadableStream` given to {@link parseReadable} or\n * {@link parseResponse}.\n *\n * @since 0.1.0\n * @category errors\n */\nexport class ReadError extends /*#__PURE__*/ Data.TaggedError(\"ReadError\")<{ readonly cause: unknown }> {}\n\n/** Whether a web-stream runner was called with its stream first: the other form starts with the parser. */\nconst isDataFirst = (args: IArguments): boolean => !isParser(args[0])\n\n/**\n * Parses a web `ReadableStream` of bytes, such as the body of a `fetch`\n * response, incrementally (see {@link parseAll}): the bytes are parsed as\n * they arrive, without waiting for the end of the stream or decoding them\n * first. Use a byte parser, e.g. one built from `Text.utf8`. The stream is\n * limited to `maxBufferSize` bytes (64 MiB by default, see\n * {@link BufferOptions}), past which it fails with a\n * {@link BufferLimitError}.\n *\n * @example\n * ```ts\n * import { Effect } from \"effect\"\n * import { Parser, ParserStream, Text } from \"@thomasfosterau/effect-parse\"\n *\n * const csvLine = Parser.separated(Text.utf8.takeTill(\",\\n\"), Text.utf8.literal(\",\"))\n * const csv = Parser.separated(csvLine, Text.utf8.literal(\"\\n\"))\n *\n * const response = await fetch(\"https://example.com/data.csv\")\n * const rows = await Effect.runPromise(ParserStream.parseReadable(response.body!, csv))\n * ```\n *\n * @since 0.1.0\n * @category combinators\n */\nexport const parseReadable: {\n <A, E, R>(\n parser: Parser<A, E, R, Uint8Array>,\n options?: BufferOptions\n ): (self: ReadableStream<BinaryInput>) => Effect.Effect<A, ParseError.ParseError<E> | ReadError | BufferLimitError, R>\n <A, E, R>(\n self: ReadableStream<BinaryInput>,\n parser: Parser<A, E, R, Uint8Array>,\n options?: BufferOptions\n ): Effect.Effect<A, ParseError.ParseError<E> | ReadError | BufferLimitError, R>\n} = /*#__PURE__*/ dual(\n isDataFirst,\n <A, E, R>(\n self: ReadableStream<BinaryInput>,\n parser: Parser<A, E, R, Uint8Array>,\n options?: BufferOptions\n ): Effect.Effect<A, ParseError.ParseError<E> | ReadError | BufferLimitError, R> =>\n parseAll(\n Stream.fromReadableStream({ evaluate: () => self, onError: (cause) => new ReadError({ cause }) }),\n parser,\n { empty: new Uint8Array(0), maxBufferSize: options?.maxBufferSize }\n )\n)\n\n/**\n * The length a response declares for the body as it will be read, if it\n * declares one: `Content-Length` counts the encoded body, so a response\n * with a `Content-Encoding` (which `fetch` decodes) declares nothing about\n * the bytes parsed.\n */\nconst declaredLength = (response: Response): number | undefined => {\n if (response.headers.has(\"content-encoding\")) return undefined\n const header = response.headers.get(\"content-length\")\n if (header === null || !/^[0-9]+$/.test(header)) return undefined\n return Number(header)\n}\n\n/**\n * Parses the body of a `fetch` `Response` incrementally, as it downloads\n * (see {@link parseReadable}). A response without a body is parsed as empty\n * input. The status is not checked.\n *\n * The body is limited to `maxBufferSize` bytes (64 MiB by default, see\n * {@link BufferOptions}). A response whose `Content-Length` already exceeds\n * the limit fails with a {@link BufferLimitError} before any of the body is\n * read; the limit is enforced on the bytes read as well, since the header\n * may be absent or wrong.\n *\n * @example\n * ```ts\n * import { Effect } from \"effect\"\n * import { Parser, ParserStream, Text } from \"@thomasfosterau/effect-parse\"\n *\n * const lines = Parser.separated(Text.utf8.tillLineEnding, Text.utf8.lineEnding)\n *\n * const response = await fetch(\"https://example.com/log.txt\")\n * const value = await Effect.runPromise(\n * ParserStream.parseResponse(response, lines, { maxBufferSize: 8 * 1024 * 1024 })\n * )\n * ```\n *\n * @since 0.1.0\n * @category combinators\n */\nexport const parseResponse: {\n <A, E, R>(\n parser: Parser<A, E, R, Uint8Array>,\n options?: BufferOptions\n ): (self: Response) => Effect.Effect<A, ParseError.ParseError<E> | ReadError | BufferLimitError, R>\n <A, E, R>(\n self: Response,\n parser: Parser<A, E, R, Uint8Array>,\n options?: BufferOptions\n ): Effect.Effect<A, ParseError.ParseError<E> | ReadError | BufferLimitError, R>\n} = /*#__PURE__*/ dual(\n isDataFirst,\n <A, E, R>(\n self: Response,\n parser: Parser<A, E, R, Uint8Array>,\n options?: BufferOptions\n ): Effect.Effect<A, ParseError.ParseError<E> | ReadError | BufferLimitError, R> =>\n Effect.suspend(() => {\n const limit = limitOf(options)\n const size = declaredLength(self)\n if (size !== undefined && size > limit) return Effect.fail(new BufferLimitError({ limit, size }))\n return parseReadable(self.body ?? new ReadableStream<Uint8Array>({ start: (c) => c.close() }), parser, options)\n })\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA,MAAa,uBAA+B;;;;;;;;;;;;;;;;;;;;;;;;;AA0B5C,IAAa,mBAAb,eACwB,mBAAK,YAAY,kBAAkB,EAAA,CAM3D;CACE,IAAa,UAAkB;EAC7B,OAAO,2CAA2C,KAAK,KAAK,iBAAiB,KAAK;CACpF;AACF;;AAyBA,MAAM,WAAW,YAA+C;CAC9D,MAAM,QAAQ,SAAS,iBAAA;CACvB,IAAI,EAAE,QAAQ,IAAI,MAAM,IAAI,WAAW,gEAAgE;CACvG,OAAO;AACT;;;;;AAMA,MAAM,mBAAmB;;;;;;;;;;;;AAazB,IAAM,UAAN,MAAmC;CACjC;CACA,OAAe;CACf,wBAA4B,IAAI,WAAW,CAAC;;CAE5C,QAAgB;CAChB,MAAc;CACd,WAAmB;CACnB,QAAgC,CAAC;;CAEjC,YAAoB;;CAGpB,IAAI,OAAe;EACjB,OAAO,KAAK,SAAS,IAAI,KAAK,MAAM,KAAK,QAAQ,KAAK,SAAS,IAAI,KAAK,MAAM,SAAS,KAAK,KAAK;CACnG;CAEA,IAAI,UAAmB;EACrB,OAAO,KAAK,SAAS,KAAA;CACvB;CAEA,OAAO,OAAgB;EACrB,IAAI,UAAU,SAAS,KAAK,GAAG;GAC7B,KAAK,SAAS;GACd,KAAK,QAAQ;EACf,OAAO,IAAI,iBAAiB,YAAY;GACtC,KAAK,SAAS;GACd,KAAK,YAAY,KAAK;EACxB,OAAO;GACL,KAAK,SAAS;GACd,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,KAAK,MAAM,EAAE;EACjE;CACF;CAEA,YAAoB,OAAyB;EAC3C,MAAM,OAAO,KAAK,MAAM,KAAK;EAC7B,IAAI,SAAS,GAAG;GAGd,KAAK,QAAQ;GACb,KAAK,QAAQ;GACb,KAAK,MAAM,KAAK,WAAW,MAAM;GACjC;EACF;EACA,MAAM,SAAS,OAAO,MAAM;EAC5B,IAAI,KAAK,MAAM,MAAM,SAAS,KAAK,UAAU;GAE3C,MAAM,QAAQ,IAAI,WAAW,KAAK,IAAI,SAAS,GAAG,IAAI,CAAC;GACvD,MAAM,IAAI,KAAK,MAAM,SAAS,KAAK,OAAO,KAAK,GAAG,CAAC;GACnD,KAAK,QAAQ;GACb,KAAK,QAAQ;GACb,KAAK,MAAM;GACX,KAAK,WAAW,MAAM;EACxB;EACA,KAAK,MAAM,IAAI,OAAO,KAAK,GAAG;EAC9B,KAAK,OAAO,MAAM;CACpB;;CAGA,QAAW;EACT,IAAI,KAAK,SAAS,GAEhB,OAAO,KAAK,MAAM,SAAS,KAAK,OAAO,KAAK,GAAG;EAEjD,IAAI,KAAK,SAAS,GAGhB,OAFyB,KAAK;EAKhC,OAAO,KAAK;CACd;;CAGA,QAAQ,OAAqB;EAC3B,IAAI,UAAU,GAAG;EACjB,IAAI,KAAK,SAAS,GAAG,KAAK,SAAS;OAE9B,IAAI,KAAK,SAAS,GAAG,KAAK,QAAQ,KAAK,MAAM,MAAM,KAAK;OACxD,KAAK,OAAO,KAAK,KAAK,MAAM,KAAK;CACxC;;CAGA,cAAc,YAA2B;EACvC,KAAK,YAAY,aAAa,KAAK,OAAO;CAC5C;;;;;;;;;;;CAYA,MAAe;EACb,MAAM,YAAY,KAAK;EACvB,OAAO,aAAa,oBAAoB,KAAK,OAAO,aAAa,cAAc;CACjF;AACF;AAEA,MAAM,cAAc,OAAc,WAChC,WAAW,YACT,OACA,IAAI,QAAQ,QAAQ,UAAU,CAAC,GAAG,CAAC,GAAG,OAAO,KAAA,GAAW,2CAA2C,CAAC,GACpG,KACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDF,MAAa,YAYK,oBACf,SAAS,OAAO,SAAS,KAAK,EAAE,IAE/B,MACA,QACA,YAC+E;CAC/E,MAAM,QAAQ,QAAQ,OAAO;CAC7B,OAAO,OAAO,cAAc;EAC1B,MAAM,OAAO,OAAO;EACpB,MAAM,UAAU,IAAI,QAAW;EAC/B,IAAI,SAAS,UAAU,KAAA,GAAW,QAAQ,OAAO,WAAc,QAAQ,KAAK,CAAC;EAE7E,MAAM,SACJ,SACA,YACyE;GACzE,MAAM,MAAgB,CAAC;GACvB,IAAI,QAAQ,SAAS,OAAO,OAAO,QAAQ,GAAG;GAC9C,MAAM,QAAQ,QAAQ,MAAM;GAE5B,MAAM,QAAQ,IAAI,MAAM,KAAK;GAC7B,IAAI,SAAS;GACb,OAAO,SAAS,MAAM,QAAQ;IAC5B,MAAM,MAAM,QAAQ;KAAE;KAAS;IAAQ,CAAC;IAExC,MAAM,IAAI,IAAI,MAAM,QAAQ,MAAM,OAAO,KAAK;IAC9C,IAAI,MAAM,MAAM;KACd,IAAI,WAAW,MAAM,SAAS,cAAc;KAC5C,MAAM,MAAM,QAAQ;MAAE;MAAS;KAAQ,CAAC;KACxC,IAAI,MAAM,QAAQ,MAAM,KAAK;KAC7B,OAAO,OAAO,KAAK,WAAW,YAAe,OAAO,MAAM,gBAAgB,GAAG,MAAM,MAAM,MAAM,QAAQ,CAAC;IAC1G;IACA,IAAI,MAAM,QAAQ,QAAQ,OAAO,OAAO,KAAK,WAAW,OAAO,MAAM,CAAC;IACtE,IAAI,KAAK,CAAC;IACV,SAAS,MAAM;GACjB;GACA,QAAQ,QAAQ,MAAM;GACtB,QAAQ,cAAc,SAAS,MAAM,MAAM;GAE3C,MAAM,OAAO,MAAM,SAAS;GAC5B,OAAO,OAAO,QAAQ,OAAO,KAAK,IAAI,iBAAiB;IAAE;IAAO;GAAK,CAAC,CAAC,IAAI,OAAO,QAAQ,GAAG;EAC/F;EAEA,MAAM,QAAQ,WACZ,OAAO,QAAQ,OAAO,QAAW,IAAI,YACnC,OAAO,cAAoF;GACzF,QAAQ,OAAO,WAAc,MAAM,CAAC;GAEpC,OAAO,QAAQ,IAAI,KAAK,QAAQ,OAAO,QAAQ,MAAM,SAAS,IAAI,IAAI,OAAO,QAAQ,CAAC,CAAC;EACzF,CAAC,CACH;EAEF,MAAM,SAAS,OAAO,QAAQ,OAAO,QAAW,IAAI,YAAY,OAAO,cAAc,MAAM,SAAS,KAAK,CAAC,CAAC;EAE3G,OAAO,KAAK,KACV,OAAO,UAAU,IAAI,GACrB,OAAO,iBACP,OAAO,OAAO,OAAO,OAAO,OAAO,IAAI,QAAQ,OAAO,YAAY,CAAC,CAAC,CACtE;CACF,CAAC;AACH,CACF;;;;;;;;AASA,IAAM,cAAN,MAA6C;CAexB;CACA;CAfnB;CACA,OAAe;CACf,wBAAgB,IAAI,WAAW,CAAC;CAChC,QAAgC,CAAC;CACjC,SAAiB;CACjB,yBAA8C,IAAI,IAAI;;CAEtD,YAAoB;CACpB,UAAkB;CAClB,SAAiB;;CAEjB;CAEA,YACE,QACA,SACA,OACA;EAHiB,KAAA,SAAA;EACA,KAAA,UAAA;EAGjB,IAAI,UAAU,KAAA,GAAW,KAAK,OAAO,KAAK;CAC5C;CAEA,OAAe,OAAgB;EAC7B,IAAI,UAAU,SAAS,KAAK,GAAG;GAC7B,KAAK,SAAS;GACd,KAAK,QAAQ;GACb,KAAK,UAAU,MAAM;EACvB,OAAO,IAAI,iBAAiB,YAAY;GACtC,KAAK,SAAS;GACd,MAAM,SAAS,KAAK,SAAS,MAAM;GACnC,IAAI,SAAS,KAAK,MAAM,QAAQ;IAE9B,MAAM,QAAQ,IAAI,WAAW,KAAK,IAAI,QAAQ,KAAK,MAAM,SAAS,GAAG,IAAI,CAAC;IAC1E,MAAM,IAAI,KAAK,MAAM,SAAS,GAAG,KAAK,MAAM,CAAC;IAC7C,KAAK,QAAQ;GACf;GACA,KAAK,MAAM,IAAI,OAAO,KAAK,MAAM;GACjC,KAAK,SAAS;EAChB,OAAO;GACL,KAAK,SAAS;GACd,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,KAAK,MAAM,EAAE;GAC/D,KAAK,SAAS,KAAK,MAAM;EAC3B;CACF;CAEA,QAAmB;EACjB,IAAI,KAAK,SAAS,GAEhB,OAAO,KAAK,MAAM,SAAS,GAAG,KAAK,MAAM;EAE3C,IAAI,KAAK,SAAS,GAIhB,OAFyB,KAAK;EAMhC,OAAO,KAAK;CACd;;CAGA,IAAI,OAAe;EACjB,OAAO,KAAK;CACd;CAEA,KAAK,OAAgB;EACnB,IAAI,aAAa,KAAK,GAAG;EACzB,KAAK,OAAO,KAAK;EACjB,IAAI,KAAK,YAAY,KAAA,GAAW;EAKhC,IAAI,EAAE,KAAK,WAAW,GAAG;EAKzB,MAAM,QAAQ,KAAK,SAAS,KAAK;EACjC,IAAI,SAAS,KAAK,YAAY,KAAK,WAAW,GAAG;EACjD,IAAI,KAAK,SAAS,KAAK,QAAQ,KAAK,YAAY,GAAG;EACnD,KAAK,QAAQ;CACf;;;;;CAMA,KAAa,OAAiB,YAAY,MAAgB;EAExD,OAAO,MAAM,IAAI,SAAS,QAAQ,KAAK,QAAQ,OAAO,SAAS,KAAK,KAAK,OAAO,MAAM,KAAK;CAC7F;CAEA,UAAwB;EACtB,MAAM,QAAQ,IAAI,MAAM,KAAK,MAAM,GAAG;GAAE,SAAS;GAAM,SAAS,KAAK;EAAQ,CAAC;EAC9E,MAAM,SAAS,KAAK;EACpB,MAAM,IAAI,KAAK,KAAK,KAAK;EACzB,KAAK,YAAY,KAAK;EACtB,IAAI,MAAM,MACR,KAAK,UAAU;GAAE,OAAO;GAAG,KAAK,MAAM;EAAI;OACrC,IAAI,MAAM,SAAS,cACxB,KAAK,UAAU,MAAM,QAAQ;OAE7B,KAAK,UAAU;CAEnB;CAEA,SAAqD;EACnD,MAAM,UAAU,KAAK;EACrB,IAAI,YAAY,KAAA,KAAa,YAAY,UACnC;OAAA,QAAQ,QAAQ,KAAK,QAAQ,OAAO,OAAO,QAAQ,QAAQ,KAAK;EAAA,OAC/D,IAAI,YAAY,KAAA,GAAW;GAGhC,MAAM,QAAQ,IAAI,MAAM,KAAK,MAAM,GAAG,EAAE,SAAS,KAAK,QAAQ,CAAC;GAC/D,MAAM,YAAY,KAAK,YAAY;GACnC,IAAI,WAAW,MAAM,SAAS,KAAK;GACnC,MAAM,IAAI,KAAK,KAAK,OAAO,SAAS;GACpC,IAAI,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,QAAQ,CAAC;EACpE;EAGA,MAAM,MAAM,KAAK,MAAM;EACvB,MAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,SAAS,KAAK,QAAQ,CAAC;EACtD,MAAM,IAAI,MAAM,IAAI,KAAK,OAAO,MAAM,KAAK;EAC3C,IAAI,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,QAAQ,CAAC;EAClE,IAAI,MAAM,MAAM,MAAM,UAAU,OAAO,CAAC,cAAc,CAAC;EACvD,OAAO,OAAO,KAAK,WAAW,YAAe,KAAK,MAAM,gBAAgB,GAAG,MAAM,MAAM,MAAM,QAAQ,CAAC;CACxG;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,WAYK,oBACf,SAAS,OAAO,SAAS,KAAK,EAAE,IAE/B,MACA,QACA,YAC+E;CAC/E,MAAM,QAAQ,QAAQ,OAAO;CAC7B,OAAO,OAAO,QAAQ,OAAO,QAAW,IAAI,YAC1C,OAAO,cAAc;EAEnB,MAAM,cAAc,IAAI,YAAY,QAAQ,SAD9B,SAAS,UAAU,KAAA,IAAY,KAAA,IAAY,WAAc,QAAQ,KAAK,CAC1B;EAC1D,OAAO,OAAO,QACZ,OAAO,WAAW,OAAO,WACvB,OAAO,cAAc;GACnB,MAAM,QAAQ,WAAc,MAAM;GAElC,MAAM,OAAO,YAAY,OAAO,MAAM;GACtC,IAAI,OAAO,OAAO,OAAO,OAAO,KAAK,IAAI,iBAAiB;IAAE;IAAO;GAAK,CAAC,CAAC;GAC1E,YAAY,KAAK,KAAK;GACtB,OAAO,OAAO;EAChB,CAAC,CACH,SACM,OAAO,cAAc,YAAY,OAAO,CAAC,CACjD;CACF,CAAC,CACH;AACF,CACF;;;;;;;;AASA,IAAa,YAAb,eAA6C,mBAAK,YAAY,WAAW,EAAA,CAA+B,CAAC;;AAGzG,MAAM,eAAe,SAA8B,CAAC,SAAS,KAAK,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;AA0BpE,MAAa,gBAUK,mBAChB,cAEE,MACA,QACA,YAEA,SACE,OAAO,mBAAmB;CAAE,gBAAgB;CAAM,UAAU,UAAU,IAAI,UAAU,EAAE,MAAM,CAAC;AAAE,CAAC,GAChG,QACA;CAAE,uBAAO,IAAI,WAAW,CAAC;CAAG,eAAe,SAAS;AAAc,CACpE,CACJ;;;;;;;AAQA,MAAM,kBAAkB,aAA2C;CACjE,IAAI,SAAS,QAAQ,IAAI,kBAAkB,GAAG,OAAO,KAAA;CACrD,MAAM,SAAS,SAAS,QAAQ,IAAI,gBAAgB;CACpD,IAAI,WAAW,QAAQ,CAAC,WAAW,KAAK,MAAM,GAAG,OAAO,KAAA;CACxD,OAAO,OAAO,MAAM;AACtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,gBAUK,mBAChB,cAEE,MACA,QACA,YAEA,OAAO,cAAc;CACnB,MAAM,QAAQ,QAAQ,OAAO;CAC7B,MAAM,OAAO,eAAe,IAAI;CAChC,IAAI,SAAS,KAAA,KAAa,OAAO,OAAO,OAAO,OAAO,KAAK,IAAI,iBAAiB;EAAE;EAAO;CAAK,CAAC,CAAC;CAChG,OAAO,cAAc,KAAK,QAAQ,IAAI,eAA2B,EAAE,QAAQ,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,QAAQ,OAAO;AAChH,CAAC,CACL"}
package/dist/Syntax.js CHANGED
@@ -2,11 +2,11 @@ import * as Predicate from "effect/Predicate";
2
2
  import { Fail, State, fromSource } from "./Input.js";
3
3
  import { described, isParser } from "./Parser.js";
4
4
  import { Key } from "./internal/key.js";
5
+ import { nodeOf } from "./internal/node.js";
5
6
  import * as Option from "effect/Option";
6
7
  import { dual } from "effect/Function";
7
8
  import * as ParseError from "./ParseError.js";
8
9
  import * as Stack from "./internal/stack.js";
9
- import { nodeOf } from "./internal/node.js";
10
10
  import * as Str from "effect/String";
11
11
  import { SyntaxBuilder, absolute, errorKind } from "./internal/syntax.js";
12
12
  // src/Syntax.ts
@@ -1 +1 @@
1
- {"version":3,"file":"Text.d.ts","names":[],"sources":["../src/Text.ts"],"mappings":";;;;;;;;;;YAgEY,iBAAiB;;;;;;;;;;;;;;;;;qBAsChB,WAAY,iBAAe;;;;;;;;qBAS3B,cAAc;;;;;;;qBAQd,UAAU;;;;;;;qBAQV,aAAa;;;;;;;qBAQb,aAAa;;;;;;;;qBASb,gBAAgB;;;;;;;;qBAShB,WAAW;;;;;;;qBAQX,gBAAgB;;;;;;;;qBAShB,cAAc;;;;;;;;qBASd,SAAS;;;;;;;;qBAYT,WAAW;;;;;;;;qBASX,cAAc;;;;;;;qBAQd,eAAe;;;;;;;qBAQf,WAAW;;;;;;;;;;;;iBAiBP,YAAY,mBAAmB;;WAErC,UAAU,SAAS;;WAKnB,KAAK,6BAA6B;;;;;WAKlC,gBAAgB,kBAAkB,SAAS,MAAM,OAAO,iBAAiB;;;;;;WAMzE,WAAW,oBAAoB,6BAA6B;;WAE5D,QAAQ,KAAK,mBAAmB,6BAA6B;;WAE7D,SAAS,KAAK,mBAAmB,6BAA6B;;WAE9D,KAAK,8BAA8B;;WAKnC,OAAO,kBAAkB,6BAA6B;;WAEtD,YAAY,KAAK,gBAAgB,QAAQ,UAAU,6BAA6B;;WAEhF,WAAW,KAAK,gBAAgB,QAAQ,UAAU,6BAA6B;;WAE/E,YAAY,iBAAiB,QAAQ,UAAU,6BAA6B;;WAE5E,MAAM,6BAA6B;;;;;WAKnC,QAAQ,GAAG,GAAG,GAAG,MAAM,OAAO,GAAG,GAAG,GAAG,OAAO,eAAe,GAAG,GAAG;;WAKnE,QAAQ,6BAA6B;;WAErC,QAAQ,6BAA6B;;WAErC,QAAQ,6BAA6B;;WAErC,QAAQ,6BAA6B;;WAErC,eAAe,6BAA6B;;WAE5C,eAAe,6BAA6B;;WAE5C,QAAQ,6BAA6B;;WAErC,QAAQ,6BAA6B;;WAErC,aAAa,6BAA6B;;WAE1C,aAAa,6BAA6B;;;;;WAK1C,YAAY,6BAA6B;;WAKzC,SAAS,2BAA2B;;WAEpC,MAAM,6BAA6B;;WAEnC,KAAK,2BAA2B;;WAEhC,YAAY,oCAAoC;;WAEhD,gBAAgB,6BAA6B;;WAK7C,SAAS,8BAA8B;;WAEvC,QAAQ,8BAA8B;;WAEtC,YAAY,6BAA6B;;WAEzC,WAAW,6BAA6B;;WAExC,SAAS,8BAA8B;;WAEvC,OAAO,6BAA6B;;;;;WAQpC,cAAc,GAAG,GAAG,GAAG,GAAG,IAAI,IACrC,QAAQ,OAAO,GAAG,GAAG,GAAG,IACxB,iBACA,WAAW,OAAO,GAAG,IAAI,IAAI,OAC1B,eAAe,IAAI,IAAI,IAAI,IAAI;;;;;WAK3B,UAAU,GAAG,GAAG,IAAI,IAC3B,QAAQ,eAAe,GAAG,GAAG,IAC7B,iBACA,QAAQ,eAAe,IAAI,IAAI,OAC5B,eAAe,IAAI,IAAI,IAAI,IAAI;;;;;;;;;;;;;;;;qBAyCzB,OAAQ,mBAAmB,YAAY,UAAU,SAAS,OAAK,YAAY;;;;;;;qBAwZ3E,QAAQ;;;;;;;;;;;;;;;;qBAiBR,MAAM,YAAY;;;;;;;qBAQlB,SAAS,YAAY;;;;;;;qBAQrB,SAAS,YAAY;;;;;;;qBAQrB,QAAQ,YAAY"}
1
+ {"version":3,"file":"Text.d.ts","names":[],"sources":["../src/Text.ts"],"mappings":";;;;;;;;;;YAiEY,iBAAiB;;;;;;;;;;;;;;;;;qBAsChB,WAAY,iBAAe;;;;;;;;qBAS3B,cAAc;;;;;;;qBAQd,UAAU;;;;;;;qBAQV,aAAa;;;;;;;qBAQb,aAAa;;;;;;;;qBASb,gBAAgB;;;;;;;;qBAShB,WAAW;;;;;;;qBAQX,gBAAgB;;;;;;;;qBAShB,cAAc;;;;;;;;qBASd,SAAS;;;;;;;;qBAYT,WAAW;;;;;;;;qBASX,cAAc;;;;;;;qBAQd,eAAe;;;;;;;qBAQf,WAAW;;;;;;;;;;;;iBAiBP,YAAY,mBAAmB;;WAErC,UAAU,SAAS;;WAKnB,KAAK,6BAA6B;;;;;WAKlC,gBAAgB,kBAAkB,SAAS,MAAM,OAAO,iBAAiB;;;;;;WAMzE,WAAW,oBAAoB,6BAA6B;;WAE5D,QAAQ,KAAK,mBAAmB,6BAA6B;;WAE7D,SAAS,KAAK,mBAAmB,6BAA6B;;WAE9D,KAAK,8BAA8B;;WAKnC,OAAO,kBAAkB,6BAA6B;;WAEtD,YAAY,KAAK,gBAAgB,QAAQ,UAAU,6BAA6B;;WAEhF,WAAW,KAAK,gBAAgB,QAAQ,UAAU,6BAA6B;;WAE/E,YAAY,iBAAiB,QAAQ,UAAU,6BAA6B;;WAE5E,MAAM,6BAA6B;;;;;WAKnC,QAAQ,GAAG,GAAG,GAAG,MAAM,OAAO,GAAG,GAAG,GAAG,OAAO,eAAe,GAAG,GAAG;;WAKnE,QAAQ,6BAA6B;;WAErC,QAAQ,6BAA6B;;WAErC,QAAQ,6BAA6B;;WAErC,QAAQ,6BAA6B;;WAErC,eAAe,6BAA6B;;WAE5C,eAAe,6BAA6B;;WAE5C,QAAQ,6BAA6B;;WAErC,QAAQ,6BAA6B;;WAErC,aAAa,6BAA6B;;WAE1C,aAAa,6BAA6B;;;;;WAK1C,YAAY,6BAA6B;;WAKzC,SAAS,2BAA2B;;WAEpC,MAAM,6BAA6B;;WAEnC,KAAK,2BAA2B;;WAEhC,YAAY,oCAAoC;;WAEhD,gBAAgB,6BAA6B;;WAK7C,SAAS,8BAA8B;;WAEvC,QAAQ,8BAA8B;;WAEtC,YAAY,6BAA6B;;WAEzC,WAAW,6BAA6B;;WAExC,SAAS,8BAA8B;;WAEvC,OAAO,6BAA6B;;;;;WAQpC,cAAc,GAAG,GAAG,GAAG,GAAG,IAAI,IACrC,QAAQ,OAAO,GAAG,GAAG,GAAG,IACxB,iBACA,WAAW,OAAO,GAAG,IAAI,IAAI,OAC1B,eAAe,IAAI,IAAI,IAAI,IAAI;;;;;WAK3B,UAAU,GAAG,GAAG,IAAI,IAC3B,QAAQ,eAAe,GAAG,GAAG,IAC7B,iBACA,QAAQ,eAAe,IAAI,IAAI,OAC5B,eAAe,IAAI,IAAI,IAAI,IAAI;;;;;;;;;;;;;;;;qBAwCzB,OAAQ,mBAAmB,YAAY,UAAU,SAAS,OAAK,YAAY;;;;;;;qBA4Z3E,QAAQ;;;;;;;;;;;;;;;;qBAiBR,MAAM,YAAY;;;;;;;qBAQlB,SAAS,YAAY;;;;;;;qBAQrB,SAAS,YAAY;;;;;;;qBAQrB,QAAQ,YAAY"}